Skip to content

Commit eebf687

Browse files
committed
enhancement(transformer): Refactor GetMethodDescriptor implementation and utilize TypeScript's MethodSignature type
1 parent d4f3a41 commit eebf687

File tree

7 files changed

+82
-173
lines changed

7 files changed

+82
-173
lines changed
Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
11
import * as ts from 'typescript';
22
import { Scope } from '../../scope/scope';
3+
import { TypescriptCreator } from '../../helper/creator';
34
import { PropertySignatureCache } from '../property/cache';
4-
import { GetReturnTypeFromBodyDescriptor } from './bodyReturnType';
5+
import { GetReturnNodeFromBody } from './bodyReturnType';
56
import { GetMethodDescriptor } from './method';
67

78
type functionAssignment = ts.ArrowFunction | ts.FunctionExpression;
89

910
export function GetFunctionAssignmentDescriptor(node: functionAssignment, scope: Scope): ts.Expression {
1011
const property: ts.PropertyName = PropertySignatureCache.instance.get();
11-
const returnValue: ts.Expression = GetReturnTypeFromBodyDescriptor(node, scope);
12+
const returnValue: ts.Expression = GetReturnNodeFromBody(node);
1213

13-
return GetMethodDescriptor(property, [{ returnValue }]);
14+
const returnType: ts.TypeNode = ts.createLiteralTypeNode(returnValue as ts.LiteralExpression);
15+
16+
return GetMethodDescriptor(
17+
property,
18+
[
19+
TypescriptCreator.createMethodSignature(
20+
undefined,
21+
returnType,
22+
),
23+
],
24+
scope,
25+
);
1426
}
Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as ts from 'typescript';
22
import { Scope } from '../../scope/scope';
3-
import { GetDescriptor } from '../descriptor';
3+
import { TypescriptCreator } from '../../helper/creator';
44
import { PropertySignatureCache } from '../property/cache';
55
import { GetMethodDescriptor } from './method';
66

@@ -11,7 +11,14 @@ export function GetFunctionTypeDescriptor(node: ts.FunctionTypeNode | ts.CallSig
1111
throw new Error(`No type was declared for ${node.getText()}.`);
1212
}
1313

14-
const returnValue: ts.Expression = GetDescriptor(node.type, scope);
15-
16-
return GetMethodDescriptor(property, [{ returnValue }]);
14+
return GetMethodDescriptor(
15+
property,
16+
[
17+
TypescriptCreator.createMethodSignature(
18+
node.parameters.map((p: ts.ParameterDeclaration) => p.type),
19+
node.type,
20+
),
21+
],
22+
scope,
23+
);
1724
}
Lines changed: 16 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,14 @@
11
import ts from 'typescript';
2-
import { GetTsAutoMockOverloadOptions, TsAutoMockOverloadOptions } from '../../../options/overload';
3-
import { TypescriptCreator } from '../../helper/creator';
2+
import { MethodSignature, TypescriptCreator } from '../../helper/creator';
43
import { MockDefiner } from '../../mockDefiner/mockDefiner';
54
import { ModuleName } from '../../mockDefiner/modules/moduleName';
5+
import { Scope } from '../../scope/scope';
6+
import { ResolveSignatureElseBranch } from '../helper/branching';
67
import { TypescriptHelper } from '../helper/helper';
78

8-
export interface MethodSignature {
9-
parameters?: ts.ParameterDeclaration[];
10-
returnValue: ts.Expression;
11-
}
12-
13-
export function GetMethodDescriptor(propertyName: ts.PropertyName, methodSignatures: MethodSignature[]): ts.CallExpression {
9+
export function GetMethodDescriptor(_propertyName: ts.PropertyName, methodSignatures: MethodSignature[], scope: Scope): ts.CallExpression {
1410
const providerGetMethod: ts.PropertyAccessExpression = CreateProviderGetMethod();
1511

16-
const propertyNameString: string = TypescriptHelper.GetStringPropertyName(propertyName);
17-
const propertyNameStringLiteral: ts.StringLiteral = ts.createStringLiteral(propertyNameString);
18-
1912
const signatureWithMostParameters: MethodSignature = methodSignatures.reduce(
2013
(acc: MethodSignature, signature: MethodSignature) => {
2114
const longestParametersLength: number = (acc.parameters || []).length;
@@ -25,23 +18,21 @@ export function GetMethodDescriptor(propertyName: ts.PropertyName, methodSignatu
2518
},
2619
);
2720

28-
const longestParameterList: ts.ParameterDeclaration[] = signatureWithMostParameters.parameters || [];
29-
30-
const declarationVariableMap: Map<ts.ParameterDeclaration, ts.Identifier> = new Map<ts.ParameterDeclaration, ts.Identifier>();
21+
const declarationVariableMap: Map<ts.TypeNode, ts.Identifier> = new Map<ts.TypeNode, ts.Identifier>();
3122

3223
let i: number = 0;
3324
const declarationVariables: ts.VariableDeclaration[] = methodSignatures.reduce(
34-
(variables: ts.VariableDeclaration[], { parameters = [] }: MethodSignature) => {
25+
(variables: ts.VariableDeclaration[], { parameters }: MethodSignature) => {
3526
for (const parameter of parameters) {
36-
if (declarationVariableMap.has(parameter)) {
27+
if (declarationVariableMap.has(parameter.type)) {
3728
continue;
3829
}
3930

4031
const declarationType: ts.TypeNode | undefined = parameter.type;
4132
if (declarationType && ts.isTypeReferenceNode(declarationType)) {
42-
const variableIdentifier: ts.Identifier = ts.createIdentifier(`__${i++}`);
33+
const variableIdentifier: ts.Identifier = ts.createIdentifier(`___${i++}`);
4334

44-
declarationVariableMap.set(parameter, variableIdentifier);
35+
declarationVariableMap.set(parameter.type, variableIdentifier);
4536

4637
const declaration: ts.Declaration = TypescriptHelper.GetDeclarationFromNode(declarationType.typeName);
4738

@@ -63,131 +54,20 @@ export function GetMethodDescriptor(propertyName: ts.PropertyName, methodSignatu
6354
statements.push(TypescriptCreator.createVariableStatement(declarationVariables));
6455
}
6556

66-
statements.push(ResolveSignatureElseBranch(declarationVariableMap, methodSignatures, longestParameterList));
57+
statements.push(ResolveSignatureElseBranch(declarationVariableMap, methodSignatures, signatureWithMostParameters, scope));
6758

6859
const block: ts.Block = ts.createBlock(statements, true);
6960

7061
const propertyValueFunction: ts.ArrowFunction = TypescriptCreator.createArrowFunction(
7162
block,
72-
longestParameterList,
73-
);
74-
75-
return TypescriptCreator.createCall(providerGetMethod, [propertyNameStringLiteral, propertyValueFunction]);
76-
}
77-
78-
function CreateTypeEquality(signatureType: ts.Identifier | ts.TypeNode | undefined, primaryDeclaration: ts.ParameterDeclaration): ts.Expression {
79-
const identifier: ts.Identifier = ts.createIdentifier(primaryDeclaration.name.getText());
80-
81-
if (!signatureType) {
82-
return ts.createPrefix(
83-
ts.SyntaxKind.ExclamationToken,
84-
ts.createPrefix(
85-
ts.SyntaxKind.ExclamationToken,
86-
identifier,
87-
),
88-
);
89-
}
90-
91-
if (TypescriptHelper.IsLiteralOrPrimitive(signatureType)) {
92-
return ts.createStrictEquality(
93-
ts.createTypeOf(identifier),
94-
signatureType ? ts.createStringLiteral(signatureType.getText()) : ts.createVoidZero(),
95-
);
96-
}
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'));
106-
}
107-
108-
function CreateUnionTypeOfEquality(signatureType: ts.Identifier | ts.TypeNode | undefined, primaryDeclaration: ts.ParameterDeclaration): ts.Expression {
109-
const typeNodesAndVariableReferences: Array<ts.TypeNode | ts.Identifier> = [];
110-
111-
if (signatureType) {
112-
if (ts.isTypeNode(signatureType) && ts.isUnionTypeNode(signatureType)) {
113-
typeNodesAndVariableReferences.push(...signatureType.types);
114-
} else {
115-
typeNodesAndVariableReferences.push(signatureType);
116-
}
117-
}
118-
119-
const [firstType, ...remainingTypes]: Array<ts.TypeNode | ts.Identifier> = typeNodesAndVariableReferences;
120-
121-
return remainingTypes.reduce(
122-
(prevStatement: ts.Expression, typeNode: ts.TypeNode) =>
123-
ts.createLogicalOr(
124-
prevStatement,
125-
CreateTypeEquality(typeNode, primaryDeclaration),
126-
),
127-
CreateTypeEquality(firstType, primaryDeclaration),
63+
signatureWithMostParameters.parameters,
12864
);
129-
}
13065

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 {
138-
const [firstDeclaration, ...remainingDeclarations]: Array<ts.ParameterDeclaration | undefined> = declarations;
139-
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.
161-
const condition: ts.Expression = remainingDeclarations.reduce(
162-
(prevStatement: ts.Expression, declaration: ts.ParameterDeclaration, index: number) =>
163-
ts.createLogicalAnd(
164-
prevStatement,
165-
CreateUnionTypeOfEquality(variableReferenceOrType(declaration), allDeclarations[index + 1]),
166-
),
167-
CreateUnionTypeOfEquality(variableReferenceOrType(firstDeclaration), allDeclarations[0]),
66+
const propName: ts.StringLiteral = ts.createStringLiteral(
67+
TypescriptCreator.createSignatureHash(methodSignatures),
16868
);
16969

170-
return ts.createIf(condition, ts.createReturn(returnValue), elseBranch);
171-
}
172-
173-
export function ResolveSignatureElseBranch(
174-
declarationVariableMap: Map<ts.ParameterDeclaration, ts.Identifier>,
175-
signatures: MethodSignature[],
176-
longestParameterList: ts.ParameterDeclaration[],
177-
): ts.Statement {
178-
const transformOverloadsOption: TsAutoMockOverloadOptions = GetTsAutoMockOverloadOptions();
179-
180-
const [signature, ...remainingSignatures]: MethodSignature[] = signatures.filter((_: unknown, notFirst: number) => transformOverloadsOption || !notFirst);
181-
182-
const indistinctSignatures: boolean = signatures.every((sig: MethodSignature) => !sig.parameters?.length);
183-
if (!remainingSignatures.length || indistinctSignatures) {
184-
return ts.createReturn(signature.returnValue);
185-
}
186-
187-
const elseBranch: ts.Statement = ResolveSignatureElseBranch(declarationVariableMap, remainingSignatures, longestParameterList);
188-
189-
const currentParameters: ts.ParameterDeclaration[] = signature.parameters || [];
190-
return ResolveParameterBranch(declarationVariableMap, currentParameters, longestParameterList, signature.returnValue, elseBranch);
70+
return TypescriptCreator.createCall(providerGetMethod, [propName, propertyValueFunction]);
19171
}
19272

19373
function CreateProviderGetMethod(): ts.PropertyAccessExpression {
@@ -198,5 +78,6 @@ function CreateProviderGetMethod(): ts.PropertyAccessExpression {
19878
ts.createIdentifier('Provider'),
19979
),
20080
ts.createIdentifier('instance')),
201-
ts.createIdentifier('getMethod'));
81+
ts.createIdentifier('getMethod'),
82+
);
20283
}
Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import * as ts from 'typescript';
2+
import { MethodSignature, TypescriptCreator } from '../../helper/creator';
23
import { Scope } from '../../scope/scope';
34
import { TypeChecker } from '../../typeChecker/typeChecker';
4-
import { GetDescriptor } from '../descriptor';
5-
import { GetFunctionReturnType } from './functionReturnType';
6-
import { GetMethodDescriptor, MethodSignature } from './method';
5+
import { GetReturnNodeFromBody } from './bodyReturnType';
6+
import { GetMethodDescriptor } from './method';
77

88
export function GetMethodDeclarationDescriptor(node: ts.MethodDeclaration | ts.FunctionDeclaration, scope: Scope): ts.Expression {
99
const declarationType: ts.Type | undefined = TypeChecker().getTypeAtLocation(node);
@@ -17,22 +17,24 @@ export function GetMethodDeclarationDescriptor(node: ts.MethodDeclaration | ts.F
1717
methodDeclarations.push(node);
1818
}
1919

20-
const methodSignatures: MethodSignature[] = methodDeclarations.map((declaration: ts.MethodDeclaration | ts.FunctionDeclaration) => ReshapeCallableDeclaration(declaration, scope));
20+
const methodSignatures: MethodSignature[] = methodDeclarations.map((signature: ts.MethodDeclaration | ts.FunctionDeclaration) => {
21+
let signatureType: ts.TypeNode | undefined = signature.type;
22+
23+
if (!signatureType) {
24+
signatureType = ts.createLiteralTypeNode(GetReturnNodeFromBody(signature) as ts.LiteralExpression);
25+
}
26+
27+
return TypescriptCreator.createMethodSignature(
28+
signature.parameters.map((p: ts.ParameterDeclaration) => p.type),
29+
signatureType,
30+
);
31+
});
2132

2233
if (!node.name) {
2334
throw new Error(
2435
`The transformer couldn't determine the name of ${node.getText()}. Please report this incident.`,
2536
);
2637
}
2738

28-
return GetMethodDescriptor(node.name, methodSignatures);
29-
}
30-
31-
export function ReshapeCallableDeclaration(declaration: ts.SignatureDeclaration, scope: Scope): MethodSignature {
32-
const returnTypeNode: ts.Node = GetFunctionReturnType(declaration);
33-
34-
return {
35-
parameters: declaration.parameters.map((parameter: ts.ParameterDeclaration) => parameter),
36-
returnValue: GetDescriptor(returnTypeNode, scope),
37-
};
39+
return GetMethodDescriptor(node.name, methodSignatures, scope);
3840
}
Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import * as ts from 'typescript';
22
import { Scope } from '../../scope/scope';
33
import { GetDescriptor } from '../descriptor';
4+
import { TypescriptCreator } from '../../helper/creator';
45
import { GetNullDescriptor } from '../null/null';
56
import { GetMethodDescriptor } from './method';
67

78
export function GetMethodSignatureDescriptor(node: ts.MethodSignature, scope: Scope): ts.Expression {
8-
let returnValue: ts.Expression;
9-
10-
if (node.type) {
11-
returnValue = GetDescriptor(node.type, scope);
12-
} else {
13-
returnValue = GetNullDescriptor();
14-
}
15-
16-
return GetMethodDescriptor(node.name, [{ returnValue }]);
9+
return GetMethodDescriptor(
10+
node.name,
11+
[
12+
TypescriptCreator.createMethodSignature(
13+
node.parameters.map((p: ts.ParameterDeclaration) => p.type),
14+
node.type,
15+
),
16+
],
17+
scope,
18+
);
1719
}

src/transformer/descriptor/mock/mockCall.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import * as ts from 'typescript';
2-
import { TypescriptCreator } from '../../helper/creator';
2+
import { TypescriptCreator, MethodSignature } from '../../helper/creator';
3+
import { Scope } from '../../scope/scope';
34
import { MockIdentifierInternalValues, MockIdentifierObjectReturnValue } from '../../mockIdentifier/mockIdentifier';
4-
import { GetMethodDescriptor, MethodSignature } from '../method/method';
5+
import { GetMethodDescriptor } from '../method/method';
56
import { GetMockMarkerProperty, Property } from './mockMarker';
67
import { PropertyAssignments } from './mockPropertiesAssignments';
78

8-
export function GetMockCall(properties: PropertyAssignments, signatures: MethodSignature[]): ts.CallExpression {
9+
export function GetMockCall(properties: PropertyAssignments, signatures: MethodSignature[], scope: Scope): ts.CallExpression {
910
const mockObjectReturnValueName: ts.Identifier = MockIdentifierObjectReturnValue;
1011

1112
const variableStatements: ts.VariableDeclaration[] = [
@@ -18,7 +19,7 @@ export function GetMockCall(properties: PropertyAssignments, signatures: MethodS
1819
// FIXME: It'd probably be wise to extract the name of the callable
1920
// signature and only fallback to `new` if there is none (or something
2021
// shorter).
21-
const callableEntry: ts.CallExpression = GetMethodDescriptor(ts.createStringLiteral('new'), signatures);
22+
const callableEntry: ts.CallExpression = GetMethodDescriptor(ts.createStringLiteral('new'), signatures, scope);
2223

2324
variableStatements.push(
2425
TypescriptCreator.createVariableDeclaration(mockObjectReturnValueName, callableEntry),

src/transformer/descriptor/mock/mockProperties.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import * as ts from 'typescript';
2+
import { MethodSignature, TypescriptCreator } from '../../helper/creator';
23
import { Scope } from '../../scope/scope';
34
import { IsTypescriptType } from '../tsLibs/typecriptLibs';
4-
import { MethodSignature } from '../method/method';
5-
import { ReshapeCallableDeclaration } from '../method/methodDeclaration';
65
import { GetMockCall } from './mockCall';
76
import { GetMockPropertiesAssignments, PropertyAssignments } from './mockPropertiesAssignments';
87
import { PropertyLike } from './propertyLike';
@@ -35,7 +34,12 @@ export function GetMockPropertiesFromDeclarations(list: ReadonlyArray<PropertyLi
3534

3635
const accessorDeclaration: PropertyAssignments = GetMockPropertiesAssignments(propertiesFilter, scope);
3736

38-
const methodSignatures: MethodSignature[] = signatures.map((declaration: ts.CallSignatureDeclaration | ts.ConstructSignatureDeclaration) => ReshapeCallableDeclaration(declaration, scope));
37+
const methodSignatures: MethodSignature[] = signatures.map((signature: SignatureLike) =>
38+
TypescriptCreator.createMethodSignature(
39+
signature.parameters.map((p: ts.ParameterDeclaration) => p.type),
40+
signature.type,
41+
),
42+
);
3943

40-
return GetMockCall(accessorDeclaration, methodSignatures);
44+
return GetMockCall(accessorDeclaration, methodSignatures, scope);
4145
}

0 commit comments

Comments
 (0)