Skip to content

Commit 9d8360d

Browse files
committed
enhancement(transformer): Support non-primitive function arguments
1 parent d69e0b6 commit 9d8360d

File tree

3 files changed

+108
-40
lines changed

3 files changed

+108
-40
lines changed

src/transformer/descriptor/method/method.ts

Lines changed: 99 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { TypescriptCreator } from '../../helper/creator';
44
import { MockDefiner } from '../../mockDefiner/mockDefiner';
55
import { ModuleName } from '../../mockDefiner/modules/moduleName';
66
import { TypescriptHelper } from '../helper/helper';
7-
import { TransformerLogger } from '../../logger/transformerLogger';
87

98
export interface MethodSignature {
109
parameters?: ts.ParameterDeclaration[];
@@ -17,21 +16,56 @@ export function GetMethodDescriptor(propertyName: ts.PropertyName, methodSignatu
1716
const propertyNameString: string = TypescriptHelper.GetStringPropertyName(propertyName);
1817
const propertyNameStringLiteral: ts.StringLiteral = ts.createStringLiteral(propertyNameString);
1918

20-
const [signatureWithMostParameters]: MethodSignature[] = [...methodSignatures].sort(
21-
(
22-
{ parameters: leftParameters = [] }: MethodSignature,
23-
{ parameters: rightParameters = [] }: MethodSignature,
24-
) => rightParameters.length - leftParameters.length,
19+
const signatureWithMostParameters: MethodSignature = methodSignatures.reduce(
20+
(acc: MethodSignature, signature: MethodSignature) => {
21+
const longestParametersLength: number = (acc.parameters || []).length;
22+
const parametersLength: number = (signature.parameters || []).length;
23+
24+
return parametersLength < longestParametersLength ? acc : signature;
25+
},
2526
);
2627

2728
const longestParameterList: ts.ParameterDeclaration[] = signatureWithMostParameters.parameters || [];
2829

29-
const block: ts.Block = ts.createBlock(
30-
[
31-
ResolveSignatureElseBranch(methodSignatures, longestParameterList),
32-
],
33-
true,
34-
);
30+
const declarationVariableMap: Map<ts.ParameterDeclaration, ts.Identifier> = new Map<ts.ParameterDeclaration, ts.Identifier>();
31+
32+
const declarationVariables: ts.VariableDeclaration[] = methodSignatures.reduce(
33+
(variables: ts.VariableDeclaration[], { parameters = [] }: MethodSignature) => {
34+
let i: number = 0;
35+
for (const parameter of parameters) {
36+
if (declarationVariableMap.has(parameter)) {
37+
continue;
38+
}
39+
40+
const declarationType: ts.TypeNode | undefined = parameter.type;
41+
if (declarationType && ts.isTypeReferenceNode(declarationType)) {
42+
const variableIdentifier: ts.Identifier = ts.createIdentifier(`__${i++}`);
43+
44+
declarationVariableMap.set(parameter, variableIdentifier);
45+
46+
const declaration: ts.Declaration = TypescriptHelper.GetDeclarationFromNode(declarationType.typeName);
47+
48+
variables.push(
49+
TypescriptCreator.createVariableDeclaration(
50+
variableIdentifier,
51+
ts.createStringLiteral(MockDefiner.instance.getDeclarationKeyMap(declaration)),
52+
),
53+
);
54+
}
55+
}
56+
57+
return variables;
58+
}, [] as ts.VariableDeclaration[]);
59+
60+
const statements: ts.Statement[] = [];
61+
62+
if (declarationVariables.length) {
63+
statements.push(TypescriptCreator.createVariableStatement(declarationVariables));
64+
}
65+
66+
statements.push(ResolveSignatureElseBranch(declarationVariableMap, methodSignatures, longestParameterList));
67+
68+
const block: ts.Block = ts.createBlock(statements, true);
3569

3670
const propertyValueFunction: ts.ArrowFunction = TypescriptCreator.createArrowFunction(
3771
block,
@@ -41,7 +75,7 @@ export function GetMethodDescriptor(propertyName: ts.PropertyName, methodSignatu
4175
return TypescriptCreator.createCall(providerGetMethod, [propertyNameStringLiteral, propertyValueFunction]);
4276
}
4377

44-
function CreateTypeEquality(signatureType: ts.TypeNode | undefined, primaryDeclaration: ts.ParameterDeclaration): ts.Expression {
78+
function CreateTypeEquality(signatureType: ts.Identifier | ts.TypeNode | undefined, primaryDeclaration: ts.ParameterDeclaration): ts.Expression {
4579
const identifier: ts.Identifier = ts.createIdentifier(primaryDeclaration.name.getText());
4680

4781
if (!signatureType) {
@@ -59,25 +93,30 @@ function CreateTypeEquality(signatureType: ts.TypeNode | undefined, primaryDecla
5993
ts.createTypeOf(identifier),
6094
signatureType ? ts.createStringLiteral(signatureType.getText()) : ts.createVoidZero(),
6195
);
62-
} else {
63-
// FIXME: Support `instanceof Class`, falls back to Object for now. The fallback causes undefined behavior!
64-
TransformerLogger().overloadNonLiteralParameterNotSupported(signatureType.getText());
65-
return ts.createBinary(identifier, ts.SyntaxKind.InstanceOfKeyword, ts.createIdentifier('Object'));
6696
}
97+
98+
if (ts.isIdentifier(signatureType)) {
99+
return ts.createStrictEquality(
100+
ts.createPropertyAccess(identifier, '__factory'),
101+
signatureType,
102+
);
103+
}
104+
105+
return ts.createBinary(identifier, ts.SyntaxKind.InstanceOfKeyword, ts.createIdentifier('Object'));
67106
}
68107

69-
function CreateUnionTypeOfEquality(signatureType: ts.TypeNode | undefined, primaryDeclaration: ts.ParameterDeclaration): ts.Expression {
70-
const typeNodes: ts.TypeNode[] = [];
108+
function CreateUnionTypeOfEquality(signatureType: ts.Identifier | ts.TypeNode | undefined, primaryDeclaration: ts.ParameterDeclaration): ts.Expression {
109+
const typeNodesAndVariableReferences: Array<ts.TypeNode | ts.Identifier> = [];
71110

72111
if (signatureType) {
73-
if (ts.isUnionTypeNode(signatureType)) {
74-
typeNodes.push(...signatureType.types);
112+
if (ts.isTypeNode(signatureType) && ts.isUnionTypeNode(signatureType)) {
113+
typeNodesAndVariableReferences.push(...signatureType.types);
75114
} else {
76-
typeNodes.push(signatureType);
115+
typeNodesAndVariableReferences.push(signatureType);
77116
}
78117
}
79118

80-
const [firstType, ...remainingTypes]: ts.TypeNode[] = typeNodes;
119+
const [firstType, ...remainingTypes]: Array<ts.TypeNode | ts.Identifier> = typeNodesAndVariableReferences;
81120

82121
return remainingTypes.reduce(
83122
(prevStatement: ts.Expression, typeNode: ts.TypeNode) =>
@@ -89,22 +128,53 @@ function CreateUnionTypeOfEquality(signatureType: ts.TypeNode | undefined, prima
89128
);
90129
}
91130

92-
function ResolveParameterBranch(declarations: ts.ParameterDeclaration[], allDeclarations: ts.ParameterDeclaration[], returnValue: ts.Expression, elseBranch: ts.Statement): ts.Statement {
131+
function ResolveParameterBranch(
132+
declarationVariableMap: Map<ts.ParameterDeclaration, ts.Identifier>,
133+
declarations: ts.ParameterDeclaration[],
134+
allDeclarations: ts.ParameterDeclaration[],
135+
returnValue: ts.Expression,
136+
elseBranch: ts.Statement,
137+
): ts.Statement {
93138
const [firstDeclaration, ...remainingDeclarations]: Array<ts.ParameterDeclaration | undefined> = declarations;
94139

140+
const variableReferenceOrType: (declaration: ts.ParameterDeclaration) => ts.Identifier | ts.TypeNode | undefined =
141+
(declaration: ts.ParameterDeclaration) => {
142+
if (declarationVariableMap.has(declaration)) {
143+
return declarationVariableMap.get(declaration);
144+
} else {
145+
return declaration.type;
146+
}
147+
};
148+
149+
// TODO: These conditions quickly grow in size, but it should be possible to
150+
// squeeze things together and optimize it with something like:
151+
//
152+
// const typeOf = function (left, right) { return typeof left === right; }
153+
// const evaluate = (function(left, right) { return this._ = this._ || typeOf(left, right); }).bind({})
154+
//
155+
// if (evaluate(firstArg, 'boolean') && evaluate(secondArg, 'number') && ...) {
156+
// ...
157+
// }
158+
//
159+
// `this._' acts as a cache, since the control flow may evaluate the same
160+
// conditions multiple times.
95161
const condition: ts.Expression = remainingDeclarations.reduce(
96162
(prevStatement: ts.Expression, declaration: ts.ParameterDeclaration, index: number) =>
97163
ts.createLogicalAnd(
98164
prevStatement,
99-
CreateUnionTypeOfEquality(declaration.type, allDeclarations[index + 1]),
165+
CreateUnionTypeOfEquality(variableReferenceOrType(declaration), allDeclarations[index + 1]),
100166
),
101-
CreateUnionTypeOfEquality(firstDeclaration?.type, allDeclarations[0]),
167+
CreateUnionTypeOfEquality(variableReferenceOrType(firstDeclaration), allDeclarations[0]),
102168
);
103169

104170
return ts.createIf(condition, ts.createReturn(returnValue), elseBranch);
105171
}
106172

107-
export function ResolveSignatureElseBranch(signatures: MethodSignature[], longestParameterList: ts.ParameterDeclaration[]): ts.Statement {
173+
export function ResolveSignatureElseBranch(
174+
declarationVariableMap: Map<ts.ParameterDeclaration, ts.Identifier>,
175+
signatures: MethodSignature[],
176+
longestParameterList: ts.ParameterDeclaration[],
177+
): ts.Statement {
108178
const transformOverloadsOption: TsAutoMockOverloadOptions = GetTsAutoMockOverloadOptions();
109179

110180
const [signature, ...remainingSignatures]: MethodSignature[] = signatures.filter((_: unknown, notFirst: number) => transformOverloadsOption || !notFirst);
@@ -114,10 +184,10 @@ export function ResolveSignatureElseBranch(signatures: MethodSignature[], longes
114184
return ts.createReturn(signature.returnValue);
115185
}
116186

117-
const elseBranch: ts.Statement = ResolveSignatureElseBranch(remainingSignatures, longestParameterList);
187+
const elseBranch: ts.Statement = ResolveSignatureElseBranch(declarationVariableMap, remainingSignatures, longestParameterList);
118188

119189
const currentParameters: ts.ParameterDeclaration[] = signature.parameters || [];
120-
return ResolveParameterBranch(currentParameters, longestParameterList, signature.returnValue, elseBranch);
190+
return ResolveParameterBranch(declarationVariableMap, currentParameters, longestParameterList, signature.returnValue, elseBranch);
121191
}
122192

123193
function CreateProviderGetMethod(): ts.PropertyAccessExpression {

test/transformer/descriptor/methods/overloads.test.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { createMock } from 'ts-auto-mock';
22

33
import {
44
exportedDeclaredOverloadedFunction,
5-
// ExportedDeclaredClass,
5+
ExportedDeclaredClass,
66
} from '../utils/typeQuery/typeQueryUtils';
77

88
describe('for overloads', () => {
@@ -32,14 +32,13 @@ describe('for overloads', () => {
3232
}
3333
});
3434

35-
// FIXME: Support more than just literals
36-
// it('should assign the correct function mock for mockable inputs', () => {
37-
// const classMock: typeof ExportedDeclaredClass = createMock<typeof ExportedDeclaredClass>();
35+
it('should assign the correct function mock for mockable inputs', () => {
36+
const classMock: typeof ExportedDeclaredClass = createMock<typeof ExportedDeclaredClass>();
3837

39-
// const functionMock: typeof exportedDeclaredOverloadedFunction = createMock<typeof exportedDeclaredOverloadedFunction>();
38+
const functionMock: typeof exportedDeclaredOverloadedFunction = createMock<typeof exportedDeclaredOverloadedFunction>();
4039

41-
// expect(functionMock(new classMock())).toBeInstanceOf(ExportedDeclaredClass);
42-
// });
40+
expect(functionMock(new classMock()).prop).toBe(0);
41+
});
4342

4443
});
4544

test/transformer/descriptor/utils/typeQuery/typeQueryUtils.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,10 @@ export declare function exportedDeclaredOverloadedFunction(a: number, b: string,
2727
export declare function exportedDeclaredOverloadedFunction(a: number, b: boolean, c: number): number;
2828
export declare function exportedDeclaredOverloadedFunction(a: boolean, b: number, c: boolean): boolean;
2929
export declare function exportedDeclaredOverloadedFunction(a: boolean, b: string, c: boolean): boolean;
30+
3031
export declare function exportedDeclaredOverloadedFunction(a: string | number | boolean, b: string | number | boolean, c: string | number | boolean): string | number | boolean;
31-
// TODO: ExportedClass may need to be mocked and it is not imported as of this
32-
// writing. The transformation does take `a instanceof ExportedClass` into
33-
// consideration though.
34-
// export declare function exportedDeclaredOverloadedFunction(a: ExportedClass): ExportedClass;
32+
33+
export declare function exportedDeclaredOverloadedFunction(a: ExportedDeclaredClass): ExportedClass;
3534
export declare function exportedDeclaredOverloadedFunction(a: boolean): boolean;
3635
export declare function exportedDeclaredOverloadedFunction(a: number): number;
3736
export declare function exportedDeclaredOverloadedFunction(a: string): string;

0 commit comments

Comments
 (0)