Skip to content

Commit a21e012

Browse files
authored
feat(functionliteraltypes): add some compatibility for functions without type definitions! (#889)
1 parent 6f3a0c5 commit a21e012

File tree

6 files changed

+308
-0
lines changed

6 files changed

+308
-0
lines changed

src/transformer/descriptor/descriptor.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,25 @@ import { GetUndefinedDescriptor } from './undefined/undefined';
4242
import { GetUnionDescriptor } from './union/union';
4343
import { GetTypeOperatorDescriptor } from './typeOperator/typeOperator';
4444
import { GetTupleDescriptor } from './tuple/tuple';
45+
import { GetShorthandPropertyAssignmentDescriptor } from './shorthandPropertyAssignment/shorthandPropertyAssignment';
46+
import { GetParameterDescriptor } from './parameter/parameter';
47+
import { GetVariableDeclarationDescriptor } from './variable/variable';
4548

4649
export function GetDescriptor(node: ts.Node, scope: Scope): ts.Expression {
4750
switch (node.kind) {
51+
case core.ts.SyntaxKind.ShorthandPropertyAssignment:
52+
return GetShorthandPropertyAssignmentDescriptor(
53+
node as ts.ShorthandPropertyAssignment,
54+
scope
55+
);
56+
57+
case core.ts.SyntaxKind.VariableDeclaration:
58+
return GetVariableDeclarationDescriptor(
59+
node as ts.VariableDeclaration,
60+
scope
61+
);
62+
case core.ts.SyntaxKind?.Parameter:
63+
return GetParameterDescriptor(node as ts.ParameterDeclaration, scope);
4864
case core.ts.SyntaxKind.TypeAliasDeclaration:
4965
return GetTypeAliasDescriptor(node as ts.TypeAliasDeclaration, scope);
5066
case core.ts.SyntaxKind.TypeReference:
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import * as ts from 'typescript';
2+
import { Scope } from '../../scope/scope';
3+
import { GetNullDescriptor } from '../null/null';
4+
import { GetDescriptor } from '../descriptor';
5+
6+
export const GetParameterDescriptor: (
7+
node: ts.ParameterDeclaration,
8+
scope: Scope
9+
) => ts.Expression = (node: ts.ParameterDeclaration, scope: Scope) => {
10+
if (node.type) {
11+
return GetDescriptor(node.type, scope);
12+
}
13+
14+
if (node.initializer) {
15+
return GetDescriptor(node.initializer, scope);
16+
}
17+
18+
return GetNullDescriptor();
19+
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as ts from 'typescript';
2+
import { Scope } from '../../scope/scope';
3+
import { core } from '../../core/core';
4+
import { TypescriptHelper } from '../helper/helper';
5+
import { GetDescriptor } from '../descriptor';
6+
7+
export const GetShorthandPropertyAssignmentDescriptor: (
8+
node: ts.ShorthandPropertyAssignment,
9+
scope: Scope
10+
) => ts.Expression = (node: ts.ShorthandPropertyAssignment, scope) => {
11+
const typeChecker: ts.TypeChecker = core.typeChecker;
12+
13+
const symbol: ts.Symbol | undefined =
14+
typeChecker.getShorthandAssignmentValueSymbol(node);
15+
16+
if (!symbol) {
17+
throw new Error(
18+
`The type checker failed to look up a symbol for \`${node.getText()}'.
19+
Perhaps, the checker was searching an outdated source.`
20+
);
21+
}
22+
23+
const declaration: ts.Declaration =
24+
TypescriptHelper.GetDeclarationFromSymbol(symbol);
25+
26+
return GetDescriptor(declaration, scope);
27+
};
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import * as ts from 'typescript';
2+
import { Scope } from '../../scope/scope';
3+
import { core } from '../../core/core';
4+
import { GetMockPropertiesFromSymbol } from '../mock/mockProperties';
5+
import { GetNullDescriptor } from '../null/null';
6+
import { GetDescriptor } from '../descriptor';
7+
8+
export const GetVariableDeclarationDescriptor: (
9+
node: ts.VariableDeclaration,
10+
scope: Scope
11+
) => ts.Expression = (node: ts.VariableDeclaration, scope: Scope) => {
12+
const typeChecker: ts.TypeChecker = core.typeChecker;
13+
const coreTs: typeof core.ts = core.ts;
14+
if (node.type) {
15+
return GetDescriptor(node.type, scope);
16+
}
17+
18+
const symbol: ts.Symbol | undefined = typeChecker.getSymbolAtLocation(
19+
node.name
20+
);
21+
22+
if (!symbol) {
23+
throw new Error(
24+
`The type checker failed to look up a symbol for \`${node.getText()}'.
25+
Perhaps, the checker was searching an outdated source.`
26+
);
27+
}
28+
29+
const type: ts.Type = typeChecker.getTypeOfSymbolAtLocation(symbol, node);
30+
const typeToNode: ts.TypeNode | undefined = typeChecker.typeToTypeNode(
31+
type,
32+
undefined,
33+
undefined
34+
);
35+
36+
if (!typeToNode) {
37+
throw new Error(
38+
`The type checker failed to look up a node for \`${node.getText()}'.
39+
Perhaps, the checker was searching an outdated source.`
40+
);
41+
}
42+
43+
if (coreTs.isTypeLiteralNode(typeToNode)) {
44+
const properties: ts.Symbol[] = typeChecker.getPropertiesOfType(type);
45+
return GetMockPropertiesFromSymbol(properties, [], scope);
46+
}
47+
48+
if (coreTs.isLiteralTypeNode(typeToNode)) {
49+
return GetDescriptor(typeToNode.literal, scope);
50+
}
51+
return GetNullDescriptor();
52+
};
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import { createMock } from 'ts-auto-mock';
2+
import { anImportedObject } from '../utils/object/object';
3+
4+
describe('functions without types defined', () => {
5+
it('should infer basic boolean object literal types', () => {
6+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
7+
function functionToMock() {
8+
// eslint-disable-next-line @typescript-eslint/typedef
9+
const primitiveValue = false;
10+
11+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
12+
function whateverFunction() {
13+
return true;
14+
}
15+
16+
return { primitiveValue, whateverFunction };
17+
}
18+
19+
const type: typeof functionToMock = createMock<typeof functionToMock>();
20+
expect(type().primitiveValue).toBe(false);
21+
expect(type().whateverFunction()).toBe(true);
22+
});
23+
24+
it('should infer basic string object literal types', () => {
25+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
26+
function functionToMock() {
27+
// eslint-disable-next-line @typescript-eslint/typedef
28+
const primitiveValue = 'Hello world';
29+
30+
return { primitiveValue };
31+
}
32+
33+
const type: typeof functionToMock = createMock<typeof functionToMock>();
34+
expect(type().primitiveValue).toBe('Hello world');
35+
});
36+
37+
it('should infer basic object literal types', () => {
38+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
39+
function functionToMock() {
40+
// eslint-disable-next-line @typescript-eslint/typedef
41+
const primitiveValue = {
42+
test: 'hello',
43+
};
44+
45+
return { primitiveValue };
46+
}
47+
48+
const type: typeof functionToMock = createMock<typeof functionToMock>();
49+
expect(type().primitiveValue).toEqual({
50+
test: 'hello',
51+
});
52+
});
53+
54+
it('should use the default behaviour for variables with a type defined', () => {
55+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
56+
function functionToMock() {
57+
const primitiveValue: boolean = true;
58+
59+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
60+
function whateverFunction() {
61+
return true;
62+
}
63+
64+
return { primitiveValue, whateverFunction };
65+
}
66+
67+
const type: typeof functionToMock = createMock<typeof functionToMock>();
68+
expect(type().primitiveValue).toBe(false);
69+
});
70+
71+
it('should use the default behaviour for internal function declarations with a type defined', () => {
72+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
73+
function functionToMock() {
74+
const primitiveValue: boolean = true;
75+
76+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
77+
function whateverFunction(): boolean {
78+
return true;
79+
}
80+
81+
return { primitiveValue, whateverFunction };
82+
}
83+
84+
const type: typeof functionToMock = createMock<typeof functionToMock>();
85+
expect(type().whateverFunction()).toBe(false);
86+
});
87+
88+
it('should use the default behaviour for internal function declarations with a type defined', () => {
89+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
90+
function functionToMock() {
91+
const primitiveValue: boolean = true;
92+
93+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
94+
function whateverFunction(): boolean {
95+
return true;
96+
}
97+
98+
return { primitiveValue, whateverFunction };
99+
}
100+
101+
const type: typeof functionToMock = createMock<typeof functionToMock>();
102+
expect(type().whateverFunction()).toBe(false);
103+
});
104+
105+
it('should infer object literal return types', () => {
106+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
107+
function functionToMock() {
108+
return { a: 'hello world', b: 123 };
109+
}
110+
111+
const type: typeof functionToMock = createMock<typeof functionToMock>();
112+
expect(type()).toEqual({
113+
a: 'hello world',
114+
b: 123,
115+
});
116+
});
117+
118+
it('should infer variables outside the function', () => {
119+
// eslint-disable-next-line @typescript-eslint/typedef
120+
const anObject = { a: 'hello world', b: 123 };
121+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
122+
function functionToMock() {
123+
return anObject;
124+
}
125+
126+
const type: typeof functionToMock = createMock<typeof functionToMock>();
127+
expect(type()).toEqual({
128+
a: 'hello world',
129+
b: 123,
130+
});
131+
});
132+
133+
it('should infer variables from a different file', () => {
134+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
135+
function functionToMock() {
136+
return anImportedObject;
137+
}
138+
139+
const type: typeof functionToMock = createMock<typeof functionToMock>();
140+
expect(type()).toEqual({
141+
a: 'hello world',
142+
b: 123,
143+
});
144+
});
145+
146+
it('should infer a spread object', () => {
147+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
148+
function functionToMock() {
149+
return {
150+
...anImportedObject,
151+
};
152+
}
153+
154+
const type: typeof functionToMock = createMock<typeof functionToMock>();
155+
expect(type()).toEqual({
156+
a: 'hello world',
157+
b: 123,
158+
});
159+
});
160+
161+
it('should set null when a parameter has not type', () => {
162+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
163+
function functionToMock(param) {
164+
return {
165+
param,
166+
};
167+
}
168+
169+
const type: typeof functionToMock = createMock<typeof functionToMock>();
170+
expect(
171+
type({
172+
test: 'hello',
173+
})
174+
).toEqual({
175+
param: null,
176+
});
177+
});
178+
179+
it('should be able to infer parameter types', () => {
180+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
181+
function functionToMock(param: string) {
182+
return {
183+
param,
184+
};
185+
}
186+
187+
const type: typeof functionToMock = createMock<typeof functionToMock>();
188+
expect(type('hello')).toEqual({
189+
param: '',
190+
});
191+
});
192+
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// eslint-disable-next-line @typescript-eslint/typedef
2+
export const anImportedObject = { a: 'hello world', b: 123 };

0 commit comments

Comments
 (0)