From 805a980264465947b8a59f9505538f184aed85ab Mon Sep 17 00:00:00 2001 From: Pmyl Date: Sat, 8 Feb 2020 18:44:36 +0000 Subject: [PATCH 1/5] #225 add Map support as a type Also changed a linting rule for indentation, remove some code regarding typescript types, fixed a bug when using typeof variable without type and function call initializer and removed descriptor 'case' for function call --- .eslintrc | 3 +- config/modules/transformer/webpack.js | 1 + src/transformer/descriptor/descriptor.ts | 196 +++++++++--------- .../descriptor/indexedAccess/indexedAccess.ts | 34 +-- .../descriptor/method/bodyReturnType.ts | 15 +- .../descriptor/method/functionAssignment.ts | 4 +- .../descriptor/method/methodDeclaration.ts | 4 +- src/transformer/descriptor/module/module.ts | 23 +- .../descriptor/tsLibs/typecriptLibs.ts | 43 +++- .../tsLibs/typescriptLibsTypeAdapter.ts | 41 ---- .../descriptor/tsLibs/typescriptLibsTypes.ts | 1 + src/transformer/descriptor/type/type.ts | 11 +- .../descriptor/typeQuery/typeQuery.ts | 76 +++---- .../descriptor/typeReference/typeReference.ts | 4 +- src/transformer/helper/creator.ts | 4 +- .../descriptor/tsLibs/tsLibs.test.ts | 26 ++- .../descriptor/typeQuery/typeQuery.test.ts | 25 ++- ui/src/views/types-supported.mdx | 5 +- 18 files changed, 280 insertions(+), 236 deletions(-) delete mode 100644 src/transformer/descriptor/tsLibs/typescriptLibsTypeAdapter.ts diff --git a/.eslintrc b/.eslintrc index 356384baa..3f9f8f1c8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -64,7 +64,8 @@ }, "FunctionExpression": { "parameters": "first" - } + }, + "SwitchCase": 1 } ], "@typescript-eslint/interface-name-prefix": "off", diff --git a/config/modules/transformer/webpack.js b/config/modules/transformer/webpack.js index 352935e8b..7488c2151 100644 --- a/config/modules/transformer/webpack.js +++ b/config/modules/transformer/webpack.js @@ -7,6 +7,7 @@ const base = require("../base/webpack.base"); module.exports = merge(base({ tsConfigFile: 'config/modules/transformer/tsconfig.json' }), { + devtool: 'cheap-module-eval-source-map', target: "node", node: { __dirname: false diff --git a/src/transformer/descriptor/descriptor.ts b/src/transformer/descriptor/descriptor.ts index c39dc7c85..72d05cbe3 100644 --- a/src/transformer/descriptor/descriptor.ts +++ b/src/transformer/descriptor/descriptor.ts @@ -40,104 +40,102 @@ import { GetUnionDescriptor } from './union/union'; export function GetDescriptor(node: ts.Node, scope: Scope): ts.Expression { switch (node.kind) { - case ts.SyntaxKind.TypeAliasDeclaration: - return GetTypeAliasDescriptor(node as ts.TypeAliasDeclaration, scope); - case ts.SyntaxKind.TypeReference: - return GetTypeReferenceDescriptor(node as ts.TypeReferenceNode, scope); - case ts.SyntaxKind.TypeLiteral: - return GetTypeLiteralDescriptor(node as ts.TypeLiteralNode, scope); - case ts.SyntaxKind.InterfaceDeclaration: - return GetInterfaceDeclarationDescriptor(node as ts.InterfaceDeclaration, scope); - case ts.SyntaxKind.ClassDeclaration: - return GetClassDeclarationDescriptor(node as ts.ClassDeclaration, scope); - case ts.SyntaxKind.PropertySignature: - case ts.SyntaxKind.PropertyAssignment: - return GetPropertyDescriptor(node as ts.PropertySignature, scope); - case ts.SyntaxKind.PropertyDeclaration: - return GetPropertyDescriptor(node as ts.PropertyDeclaration, scope); - case ts.SyntaxKind.LiteralType: - return GetLiteralDescriptor(node as ts.LiteralTypeNode, scope); - case ts.SyntaxKind.ExpressionWithTypeArguments: - return GetExpressionWithTypeArgumentsDescriptor(node as ts.ExpressionWithTypeArguments, scope); - case ts.SyntaxKind.Identifier: - return GetIdentifierDescriptor(node as ts.Identifier, scope); - case ts.SyntaxKind.ThisType: - return GetMockFactoryCallForThis(scope.currentMockKey); - case ts.SyntaxKind.ImportSpecifier: - return GetImportDescriptor(node as ts.ImportSpecifier, scope); - case ts.SyntaxKind.TypeParameter: - return GetTypeParameterDescriptor(node as ts.TypeParameterDeclaration, scope); - case ts.SyntaxKind.ImportClause: - return GetImportDescriptor(node as ts.ImportClause, scope); - case ts.SyntaxKind.MethodSignature: - return GetMethodSignatureDescriptor(node as ts.MethodSignature, scope); - case ts.SyntaxKind.FunctionDeclaration: - return GetMethodDeclarationDescriptor(node as ts.FunctionDeclaration, scope); - case ts.SyntaxKind.MethodDeclaration: - return GetMethodDeclarationDescriptor(node as ts.MethodDeclaration, scope); - case ts.SyntaxKind.FunctionType: - return GetFunctionTypeDescriptor(node as ts.FunctionTypeNode, scope); - case ts.SyntaxKind.ConstructSignature: - return GetFunctionTypeDescriptor(node as ts.ConstructSignatureDeclaration, scope); - case ts.SyntaxKind.CallSignature: - return GetFunctionTypeDescriptor(node as ts.CallSignatureDeclaration, scope); - case ts.SyntaxKind.ArrowFunction: - case ts.SyntaxKind.FunctionExpression: - return GetFunctionAssignmentDescriptor(node as ts.ArrowFunction, scope); - case ts.SyntaxKind.ConstructorType: - return GetConstructorTypeDescriptor(node as ts.ConstructorTypeNode, scope); - case ts.SyntaxKind.TypeQuery: - return GetTypeQueryDescriptor(node as ts.TypeQueryNode, scope); - case ts.SyntaxKind.UnionType: - return GetUnionDescriptor(node as ts.UnionTypeNode, scope); - case ts.SyntaxKind.IntersectionType: - return GetIntersectionDescriptor(node as ts.IntersectionTypeNode, scope); - case ts.SyntaxKind.EnumDeclaration: - return GetEnumDeclarationDescriptor(node as ts.EnumDeclaration); - case ts.SyntaxKind.MappedType: - return GetMappedDescriptor(node as ts.MappedTypeNode, scope); - case ts.SyntaxKind.ParenthesizedType: - return GetParenthesizedDescriptor(node as ts.ParenthesizedTypeNode, scope); - case ts.SyntaxKind.ArrayType: - case ts.SyntaxKind.TupleType: - return GetArrayDescriptor(); - case ts.SyntaxKind.StringKeyword: - return GetStringDescriptor(); - case ts.SyntaxKind.NumberKeyword: - return GetNumberDescriptor(); - case ts.SyntaxKind.TrueKeyword: - return GetBooleanTrueDescriptor(); - case ts.SyntaxKind.FalseKeyword: - return GetBooleanFalseDescriptor(); - case ts.SyntaxKind.NumericLiteral: - case ts.SyntaxKind.StringLiteral: - return GetLiteralDescriptor(node as ts.LiteralTypeNode, scope); - case ts.SyntaxKind.ObjectLiteralExpression: - return GetObjectLiteralDescriptor(node as ts.ObjectLiteralExpression, scope); - case ts.SyntaxKind.IndexedAccessType: - return GetIndexedAccessTypeDescriptor(node as ts.IndexedAccessTypeNode, scope); - case ts.SyntaxKind.BooleanKeyword: - case ts.SyntaxKind.TypePredicate: - case ts.SyntaxKind.FirstTypeNode: - return GetBooleanDescriptor(); - case ts.SyntaxKind.ObjectKeyword: - return GetMockPropertiesFromSymbol([], [], scope); - case ts.SyntaxKind.NullKeyword: - return GetNullDescriptor(); - case ts.SyntaxKind.ImportEqualsDeclaration: - return GetImportEqualsDescriptor(node as ts.ImportEqualsDeclaration, scope); - case ts.SyntaxKind.BigIntKeyword: - return GetBigIntDescriptor(); - case ts.SyntaxKind.AnyKeyword: - case ts.SyntaxKind.NeverKeyword: - case ts.SyntaxKind.UnknownKeyword: - case ts.SyntaxKind.UndefinedKeyword: - case ts.SyntaxKind.VoidKeyword: - return GetUndefinedDescriptor(); - case ts.SyntaxKind.CallExpression: - return node as ts.CallExpression; - default: - TransformerLogger().typeNotSupported(ts.SyntaxKind[node.kind]); - return GetNullDescriptor(); + case ts.SyntaxKind.TypeAliasDeclaration: + return GetTypeAliasDescriptor(node as ts.TypeAliasDeclaration, scope); + case ts.SyntaxKind.TypeReference: + return GetTypeReferenceDescriptor(node as ts.TypeReferenceNode, scope); + case ts.SyntaxKind.TypeLiteral: + return GetTypeLiteralDescriptor(node as ts.TypeLiteralNode, scope); + case ts.SyntaxKind.InterfaceDeclaration: + return GetInterfaceDeclarationDescriptor(node as ts.InterfaceDeclaration, scope); + case ts.SyntaxKind.ClassDeclaration: + return GetClassDeclarationDescriptor(node as ts.ClassDeclaration, scope); + case ts.SyntaxKind.PropertySignature: + case ts.SyntaxKind.PropertyAssignment: + return GetPropertyDescriptor(node as ts.PropertySignature, scope); + case ts.SyntaxKind.PropertyDeclaration: + return GetPropertyDescriptor(node as ts.PropertyDeclaration, scope); + case ts.SyntaxKind.LiteralType: + return GetLiteralDescriptor(node as ts.LiteralTypeNode, scope); + case ts.SyntaxKind.ExpressionWithTypeArguments: + return GetExpressionWithTypeArgumentsDescriptor(node as ts.ExpressionWithTypeArguments, scope); + case ts.SyntaxKind.Identifier: + return GetIdentifierDescriptor(node as ts.Identifier, scope); + case ts.SyntaxKind.ThisType: + return GetMockFactoryCallForThis(scope.currentMockKey); + case ts.SyntaxKind.ImportSpecifier: + return GetImportDescriptor(node as ts.ImportSpecifier, scope); + case ts.SyntaxKind.TypeParameter: + return GetTypeParameterDescriptor(node as ts.TypeParameterDeclaration, scope); + case ts.SyntaxKind.ImportClause: + return GetImportDescriptor(node as ts.ImportClause, scope); + case ts.SyntaxKind.MethodSignature: + return GetMethodSignatureDescriptor(node as ts.MethodSignature, scope); + case ts.SyntaxKind.FunctionDeclaration: + return GetMethodDeclarationDescriptor(node as ts.FunctionDeclaration, scope); + case ts.SyntaxKind.MethodDeclaration: + return GetMethodDeclarationDescriptor(node as ts.MethodDeclaration, scope); + case ts.SyntaxKind.FunctionType: + return GetFunctionTypeDescriptor(node as ts.FunctionTypeNode, scope); + case ts.SyntaxKind.ConstructSignature: + return GetFunctionTypeDescriptor(node as ts.ConstructSignatureDeclaration, scope); + case ts.SyntaxKind.CallSignature: + return GetFunctionTypeDescriptor(node as ts.CallSignatureDeclaration, scope); + case ts.SyntaxKind.ArrowFunction: + case ts.SyntaxKind.FunctionExpression: + return GetFunctionAssignmentDescriptor(node as ts.ArrowFunction, scope); + case ts.SyntaxKind.ConstructorType: + return GetConstructorTypeDescriptor(node as ts.ConstructorTypeNode, scope); + case ts.SyntaxKind.TypeQuery: + return GetTypeQueryDescriptor(node as ts.TypeQueryNode, scope); + case ts.SyntaxKind.UnionType: + return GetUnionDescriptor(node as ts.UnionTypeNode, scope); + case ts.SyntaxKind.IntersectionType: + return GetIntersectionDescriptor(node as ts.IntersectionTypeNode, scope); + case ts.SyntaxKind.EnumDeclaration: + return GetEnumDeclarationDescriptor(node as ts.EnumDeclaration); + case ts.SyntaxKind.MappedType: + return GetMappedDescriptor(node as ts.MappedTypeNode, scope); + case ts.SyntaxKind.ParenthesizedType: + return GetParenthesizedDescriptor(node as ts.ParenthesizedTypeNode, scope); + case ts.SyntaxKind.ArrayType: + case ts.SyntaxKind.TupleType: + return GetArrayDescriptor(); + case ts.SyntaxKind.StringKeyword: + return GetStringDescriptor(); + case ts.SyntaxKind.NumberKeyword: + return GetNumberDescriptor(); + case ts.SyntaxKind.TrueKeyword: + return GetBooleanTrueDescriptor(); + case ts.SyntaxKind.FalseKeyword: + return GetBooleanFalseDescriptor(); + case ts.SyntaxKind.NumericLiteral: + case ts.SyntaxKind.StringLiteral: + return GetLiteralDescriptor(node as ts.LiteralTypeNode, scope); + case ts.SyntaxKind.ObjectLiteralExpression: + return GetObjectLiteralDescriptor(node as ts.ObjectLiteralExpression, scope); + case ts.SyntaxKind.IndexedAccessType: + return GetIndexedAccessTypeDescriptor(node as ts.IndexedAccessTypeNode, scope); + case ts.SyntaxKind.BooleanKeyword: + case ts.SyntaxKind.TypePredicate: + case ts.SyntaxKind.FirstTypeNode: + return GetBooleanDescriptor(); + case ts.SyntaxKind.ObjectKeyword: + return GetMockPropertiesFromSymbol([], [], scope); + case ts.SyntaxKind.NullKeyword: + return GetNullDescriptor(); + case ts.SyntaxKind.ImportEqualsDeclaration: + return GetImportEqualsDescriptor(node as ts.ImportEqualsDeclaration, scope); + case ts.SyntaxKind.BigIntKeyword: + return GetBigIntDescriptor(); + case ts.SyntaxKind.AnyKeyword: + case ts.SyntaxKind.NeverKeyword: + case ts.SyntaxKind.UnknownKeyword: + case ts.SyntaxKind.UndefinedKeyword: + case ts.SyntaxKind.VoidKeyword: + return GetUndefinedDescriptor(); + default: + TransformerLogger().typeNotSupported(ts.SyntaxKind[node.kind]); + return GetNullDescriptor(); } } diff --git a/src/transformer/descriptor/indexedAccess/indexedAccess.ts b/src/transformer/descriptor/indexedAccess/indexedAccess.ts index 5ad5016d9..ea1096b13 100644 --- a/src/transformer/descriptor/indexedAccess/indexedAccess.ts +++ b/src/transformer/descriptor/indexedAccess/indexedAccess.ts @@ -12,28 +12,28 @@ export function GetIndexedAccessTypeDescriptor(node: ts.IndexedAccessTypeNode, s let propertyName: string | null = null; switch (node.indexType.kind) { - case ts.SyntaxKind.TypeReference: - const declaration: ts.Declaration = TypescriptHelper.GetDeclarationFromNode((node.indexType as ts.TypeReferenceNode).typeName); + case ts.SyntaxKind.TypeReference: + const declaration: ts.Declaration = TypescriptHelper.GetDeclarationFromNode((node.indexType as ts.TypeReferenceNode).typeName); - switch (declaration.kind) { - case ts.SyntaxKind.TypeParameter: - const propertyNameIdentifier: ts.PropertyName = PropertySignatureCache.instance.get(); - propertyName = (propertyNameIdentifier as ts.Identifier).escapedText as string; + switch (declaration.kind) { + case ts.SyntaxKind.TypeParameter: + const propertyNameIdentifier: ts.PropertyName = PropertySignatureCache.instance.get(); + propertyName = (propertyNameIdentifier as ts.Identifier).escapedText as string; + break; + case ts.SyntaxKind.TypeAliasDeclaration: + propertyName = (((declaration as ts.TypeAliasDeclaration).type as ts.LiteralTypeNode).literal as ts.StringLiteral).text; + break; + default: + TransformerLogger().typeNotSupported('IndexedAccess of TypeReference of ' + ts.SyntaxKind[declaration.kind]); + break; + } break; - case ts.SyntaxKind.TypeAliasDeclaration: - propertyName = (((declaration as ts.TypeAliasDeclaration).type as ts.LiteralTypeNode).literal as ts.StringLiteral).text; + case ts.SyntaxKind.LiteralType: + propertyName = ((node.indexType as ts.LiteralTypeNode).literal as ts.StringLiteral).text; break; default: - TransformerLogger().typeNotSupported('IndexedAccess of TypeReference of ' + ts.SyntaxKind[declaration.kind]); + TransformerLogger().typeNotSupported('IndexedAccess of ' + ts.SyntaxKind[node.indexType.kind]); break; - } - break; - case ts.SyntaxKind.LiteralType: - propertyName = ((node.indexType as ts.LiteralTypeNode).literal as ts.StringLiteral).text; - break; - default: - TransformerLogger().typeNotSupported('IndexedAccess of ' + ts.SyntaxKind[node.indexType.kind]); - break; } if (propertyName !== null) { diff --git a/src/transformer/descriptor/method/bodyReturnType.ts b/src/transformer/descriptor/method/bodyReturnType.ts index fd88eea90..a03d78360 100644 --- a/src/transformer/descriptor/method/bodyReturnType.ts +++ b/src/transformer/descriptor/method/bodyReturnType.ts @@ -1,10 +1,13 @@ import * as ts from 'typescript'; import { Scope } from '../../scope/scope'; import { GetDescriptor } from '../descriptor'; -import { GetNullDescriptor } from '../null/null'; -export function GetReturnTypeFromBody(node: ts.ArrowFunction | ts.FunctionExpression | ts.MethodDeclaration | ts.FunctionDeclaration, scope: Scope): ts.Expression { - let returnValue: ts.Expression; +export function GetReturnTypeFromBodyDescriptor(node: ts.ArrowFunction | ts.FunctionExpression | ts.MethodDeclaration | ts.FunctionDeclaration, scope: Scope): ts.Expression { + return GetDescriptor(GetReturnNodeFromBody(node), scope); +} + +export function GetReturnNodeFromBody(node: ts.ArrowFunction | ts.FunctionExpression | ts.MethodDeclaration | ts.FunctionDeclaration): ts.Node { + let returnValue: ts.Node; const functionBody: ts.FunctionBody = node.body as ts.FunctionBody; @@ -12,12 +15,12 @@ export function GetReturnTypeFromBody(node: ts.ArrowFunction | ts.FunctionExpres const returnStatement: ts.ReturnStatement = GetReturnStatement(functionBody); if (returnStatement) { - returnValue = GetDescriptor(returnStatement.expression, scope); + returnValue = returnStatement.expression; } else { - returnValue = GetNullDescriptor(); + returnValue = ts.createNull(); } } else { - returnValue = GetDescriptor(node.body, scope); + returnValue = node.body; } return returnValue; diff --git a/src/transformer/descriptor/method/functionAssignment.ts b/src/transformer/descriptor/method/functionAssignment.ts index 012d27ffd..316da73cd 100644 --- a/src/transformer/descriptor/method/functionAssignment.ts +++ b/src/transformer/descriptor/method/functionAssignment.ts @@ -1,14 +1,14 @@ import * as ts from 'typescript'; import { Scope } from '../../scope/scope'; import { PropertySignatureCache } from '../property/cache'; -import { GetReturnTypeFromBody } from './bodyReturnType'; +import { GetReturnTypeFromBodyDescriptor } from './bodyReturnType'; import { GetMethodDescriptor } from './method'; type functionAssignment = ts.ArrowFunction | ts.FunctionExpression; export function GetFunctionAssignmentDescriptor(node: functionAssignment, scope: Scope): ts.Expression { const property: ts.PropertyName = PropertySignatureCache.instance.get(); - const returnValue: ts.Expression = GetReturnTypeFromBody(node, scope); + const returnValue: ts.Expression = GetReturnTypeFromBodyDescriptor(node, scope); return GetMethodDescriptor(property, returnValue); } diff --git a/src/transformer/descriptor/method/methodDeclaration.ts b/src/transformer/descriptor/method/methodDeclaration.ts index 530a0ab3c..dcde77833 100644 --- a/src/transformer/descriptor/method/methodDeclaration.ts +++ b/src/transformer/descriptor/method/methodDeclaration.ts @@ -1,7 +1,7 @@ import * as ts from 'typescript'; import { Scope } from '../../scope/scope'; import { GetDescriptor } from '../descriptor'; -import { GetReturnTypeFromBody } from './bodyReturnType'; +import { GetReturnTypeFromBodyDescriptor } from './bodyReturnType'; import { GetMethodDescriptor } from './method'; export function GetMethodDeclarationDescriptor(node: ts.MethodDeclaration | ts.FunctionDeclaration, scope: Scope): ts.Expression { @@ -10,7 +10,7 @@ export function GetMethodDeclarationDescriptor(node: ts.MethodDeclaration | ts.F if (node.type) { returnType = GetDescriptor(node.type, scope); } else { - returnType = GetReturnTypeFromBody(node, scope); + returnType = GetReturnTypeFromBodyDescriptor(node, scope); } return GetMethodDescriptor(node.name, returnType); diff --git a/src/transformer/descriptor/module/module.ts b/src/transformer/descriptor/module/module.ts index 593ec496a..b7e705596 100644 --- a/src/transformer/descriptor/module/module.ts +++ b/src/transformer/descriptor/module/module.ts @@ -4,8 +4,8 @@ import { Scope } from '../../scope/scope'; import { TypeChecker } from '../../typeChecker/typeChecker'; import { TypescriptHelper } from '../helper/helper'; import { GetMockPropertiesFromDeclarations } from '../mock/mockProperties'; -import { PropertyLike } from '../mock/propertyLike'; import { GetTypeQueryDescriptorFromDeclaration } from '../typeQuery/typeQuery'; + type ExternalSource = ts.SourceFile | ts.ModuleDeclaration; export function GetModuleDescriptor(node: ts.NamedDeclaration, scope: Scope): ts.Expression { @@ -16,7 +16,7 @@ export function GetModuleDescriptor(node: ts.NamedDeclaration, scope: Scope): ts const externalModuleDeclaration: ts.NamedDeclaration = symbol.declarations[0]; if (isExternalSource(externalModuleDeclaration)) { - return GetPropertiesFromSourceFileOrModuleDeclaration(externalModuleDeclaration, symbol, scope); + return GetPropertiesFromSourceFileOrModuleDeclarationDescriptor(externalModuleDeclaration, symbol, scope); } return GetTypeQueryDescriptorFromDeclaration(externalModuleDeclaration, scope); @@ -26,29 +26,32 @@ function isExternalSource(declaration: ts.Node): declaration is ExternalSource { return ts.isSourceFile(declaration) || ts.isModuleDeclaration(declaration); } -function GetPropertiesFromSourceFileOrModuleDeclaration(sourceFile: ExternalSource, symbol: ts.Symbol, scope: Scope): ts.Expression { +function GetPropertiesFromSourceFileOrModuleDeclarationDescriptor(sourceFile: ExternalSource, symbol: ts.Symbol, scope: Scope): ts.Expression { + return GetMockPropertiesFromDeclarations(GetPropertiesFromSourceFileOrModuleDeclaration(sourceFile, symbol, scope), [], scope); +} + +function GetPropertiesFromSourceFileOrModuleDeclaration(sourceFile: ExternalSource, symbol: ts.Symbol, scope: Scope): ts.PropertySignature[] { const typeChecker: ts.TypeChecker = TypeChecker(); const moduleExports: ts.Symbol[] = typeChecker.getExportsOfModule(symbol); - const properties: PropertyLike[] = moduleExports.map((prop: ts.Symbol): PropertyLike => { + return moduleExports.map((prop: ts.Symbol): ts.PropertySignature => { const originalSymbol: ts.Symbol = TypescriptHelper.GetAliasedSymbolSafe(prop); const originalDeclaration: ts.NamedDeclaration = originalSymbol.declarations[0]; const declaration: ts.Declaration = prop.declarations[0]; if (ts.isExportAssignment(declaration)) { - return TypescriptCreator.createProperty('default', ts.createTypeQueryNode(originalDeclaration.name as ts.Identifier)); + return TypescriptCreator.createPropertySignature('default', ts.createTypeQueryNode(originalDeclaration.name as ts.Identifier)); } if (ts.isExportSpecifier(declaration) && ts.isSourceFile(originalDeclaration)) { const exportSpecifierSymbol: ts.Symbol = typeChecker.getSymbolAtLocation(declaration.name); const exportSpecifierAliasSymbol: ts.Symbol = typeChecker.getAliasedSymbol(exportSpecifierSymbol); - const exportSpecifierProperties: ts.Expression = GetPropertiesFromSourceFileOrModuleDeclaration(originalDeclaration, exportSpecifierAliasSymbol, scope); + const exportSpecifierProperties: ts.PropertySignature[] = GetPropertiesFromSourceFileOrModuleDeclaration(originalDeclaration, exportSpecifierAliasSymbol, scope); + const propertyType: ts.TypeNode = ts.createTypeLiteralNode(exportSpecifierProperties); - return TypescriptCreator.createPropertyWitInitializer(declaration.name, exportSpecifierProperties); + return TypescriptCreator.createPropertySignature(declaration.name, propertyType); } - return TypescriptCreator.createProperty(originalDeclaration.name as ts.Identifier, ts.createTypeQueryNode(originalDeclaration.name as ts.Identifier)); + return TypescriptCreator.createPropertySignature(originalDeclaration.name as ts.Identifier, ts.createTypeQueryNode(originalDeclaration.name as ts.Identifier)); }); - - return GetMockPropertiesFromDeclarations(properties, [], scope); } diff --git a/src/transformer/descriptor/tsLibs/typecriptLibs.ts b/src/transformer/descriptor/tsLibs/typecriptLibs.ts index 91cd44f15..b6b437a07 100644 --- a/src/transformer/descriptor/tsLibs/typecriptLibs.ts +++ b/src/transformer/descriptor/tsLibs/typecriptLibs.ts @@ -1,7 +1,9 @@ import * as ts from 'typescript'; import { Scope } from '../../scope/scope'; -import { TypescriptLibsTypeAdapter } from './typescriptLibsTypeAdapter'; -import { TypescriptLibsTypesFolder } from './typescriptLibsTypes'; +import { TypeChecker } from '../../typeChecker/typeChecker'; +import { GetDescriptor } from '../descriptor'; +import { GetUndefinedDescriptor } from '../undefined/undefined'; +import { TypescriptLibsTypes, TypescriptLibsTypesFolder } from './typescriptLibsTypes'; export function IsTypescriptType(node: ts.Node): boolean { const nodeFile: ts.SourceFile = node.getSourceFile(); @@ -14,6 +16,39 @@ export function IsTypescriptType(node: ts.Node): boolean { return false; } -export function GetTypescriptType(node: ts.TypeReferenceNode, scope: Scope): ts.Node { - return TypescriptLibsTypeAdapter(node, scope); +export function GetTypescriptTypeDescriptor(node: ts.TypeReferenceNode, scope: Scope): ts.Expression { + const typeChecker: ts.TypeChecker = TypeChecker(); + const symbol: ts.Symbol = typeChecker.getSymbolAtLocation(node.typeName); + const typeScriptType: TypescriptLibsTypes = TypescriptLibsTypes[symbol.name]; + + switch (typeScriptType) { + case(TypescriptLibsTypes.Array): + case(TypescriptLibsTypes.ReadonlyArray): + return GetDescriptor(ts.createNode(ts.SyntaxKind.ArrayType), scope); + case(TypescriptLibsTypes.Number): + return GetDescriptor(ts.createNode(ts.SyntaxKind.NumberKeyword), scope); + case(TypescriptLibsTypes.String): + return GetDescriptor(ts.createNode(ts.SyntaxKind.StringKeyword), scope); + case(TypescriptLibsTypes.Boolean): + return GetDescriptor(ts.createNode(ts.SyntaxKind.BooleanKeyword), scope); + case(TypescriptLibsTypes.Object): + return GetDescriptor(ts.createNode(ts.SyntaxKind.ObjectKeyword), scope); + case(TypescriptLibsTypes.Function): + const functionNode: ts.Node = ts.createNode(ts.SyntaxKind.VoidKeyword); + return GetDescriptor(ts.createFunctionTypeNode([], [], functionNode as ts.TypeNode), scope); + case(TypescriptLibsTypes.Promise): + const dataResolved: ts.Expression = node.typeArguments && node.typeArguments[0] ? GetDescriptor(node.typeArguments[0], scope) : GetUndefinedDescriptor(); + + const promiseAccess: ts.PropertyAccessExpression = ts.createPropertyAccess(ts.createIdentifier('Promise'), ts.createIdentifier('resolve')); + + return ts.createCall( + promiseAccess, + [], + [dataResolved], + ); + case(TypescriptLibsTypes.Map): + return ts.createNew(ts.createIdentifier('Map'), undefined, undefined); + default: + return GetDescriptor(ts.createNode(ts.SyntaxKind.UndefinedKeyword), scope); + } } diff --git a/src/transformer/descriptor/tsLibs/typescriptLibsTypeAdapter.ts b/src/transformer/descriptor/tsLibs/typescriptLibsTypeAdapter.ts deleted file mode 100644 index a59d3a569..000000000 --- a/src/transformer/descriptor/tsLibs/typescriptLibsTypeAdapter.ts +++ /dev/null @@ -1,41 +0,0 @@ -import * as ts from 'typescript'; -import { Scope } from '../../scope/scope'; -import { TypeChecker } from '../../typeChecker/typeChecker'; -import { GetDescriptor } from '../descriptor'; -import { GetUndefinedDescriptor } from '../undefined/undefined'; -import { TypescriptLibsTypes } from './typescriptLibsTypes'; - -export function TypescriptLibsTypeAdapter(node: ts.TypeReferenceNode, scope: Scope): ts.Node { - const typeChecker: ts.TypeChecker = TypeChecker(); - const symbol: ts.Symbol = typeChecker.getSymbolAtLocation(node.typeName); - const typeScriptType: TypescriptLibsTypes = TypescriptLibsTypes[symbol.name]; - - switch (typeScriptType) { - case(TypescriptLibsTypes.Array): - case(TypescriptLibsTypes.ReadonlyArray): - return ts.createNode(ts.SyntaxKind.ArrayType); - case(TypescriptLibsTypes.Number): - return ts.createNode(ts.SyntaxKind.NumberKeyword); - case(TypescriptLibsTypes.String): - return ts.createNode(ts.SyntaxKind.StringKeyword); - case(TypescriptLibsTypes.Boolean): - return ts.createNode(ts.SyntaxKind.BooleanKeyword); - case(TypescriptLibsTypes.Object): - return ts.createNode(ts.SyntaxKind.ObjectKeyword); - case(TypescriptLibsTypes.Function): - const functionNode: ts.Node = ts.createNode(ts.SyntaxKind.VoidKeyword); - return ts.createFunctionTypeNode([], [], functionNode as ts.TypeNode); - case(TypescriptLibsTypes.Promise): - const dataResolved: ts.Expression = node.typeArguments && node.typeArguments[0] ? GetDescriptor(node.typeArguments[0], scope) : GetUndefinedDescriptor(); - - const promiseAccess: ts.PropertyAccessExpression = ts.createPropertyAccess(ts.createIdentifier('Promise'), ts.createIdentifier('resolve')); - - return ts.createCall( - promiseAccess, - [], - [dataResolved], - ); - default: - return ts.createNode(ts.SyntaxKind.UndefinedKeyword); - } -} diff --git a/src/transformer/descriptor/tsLibs/typescriptLibsTypes.ts b/src/transformer/descriptor/tsLibs/typescriptLibsTypes.ts index c6e0008e6..ce36ac5d9 100644 --- a/src/transformer/descriptor/tsLibs/typescriptLibsTypes.ts +++ b/src/transformer/descriptor/tsLibs/typescriptLibsTypes.ts @@ -7,6 +7,7 @@ export enum TypescriptLibsTypes { Object = 'Object', Function = 'Function', Promise = 'Promise', + Map = 'Map', } export const TypescriptLibsTypesFolder: string = 'node_modules/typescript/lib'; diff --git a/src/transformer/descriptor/type/type.ts b/src/transformer/descriptor/type/type.ts index bf616dec6..bb6da3454 100644 --- a/src/transformer/descriptor/type/type.ts +++ b/src/transformer/descriptor/type/type.ts @@ -1,7 +1,7 @@ import * as ts from 'typescript'; import { Scope } from '../../scope/scope'; import { TypescriptHelper } from '../helper/helper'; -import { GetTypescriptType, IsTypescriptType } from '../tsLibs/typecriptLibs'; +import { GetReturnNodeFromBody } from '../method/bodyReturnType'; import { GetTypeImport } from './typeImport'; export function GetTypes(nodes: ts.NodeArray, scope: Scope): ts.Node[] { @@ -33,10 +33,6 @@ export function GetType(node: ts.Node, scope: Scope): ts.Node { if (ts.isTypeReferenceNode(node)) { const declaration: ts.Declaration = TypescriptHelper.GetDeclarationFromNode(node.typeName); - if (IsTypescriptType(declaration)) { - return GetTypescriptType(node, scope); - } - return GetType(declaration, scope); } @@ -62,5 +58,10 @@ export function GetType(node: ts.Node, scope: Scope): ts.Node { return GetType(node.type, scope); } + if (ts.isCallExpression(node)) { + const declaration: ts.Declaration = TypescriptHelper.GetDeclarationFromNode(node.expression); + return GetType(GetReturnNodeFromBody(declaration as ts.FunctionDeclaration), scope); + } + return node; } diff --git a/src/transformer/descriptor/typeQuery/typeQuery.ts b/src/transformer/descriptor/typeQuery/typeQuery.ts index e024d1206..95f31face 100644 --- a/src/transformer/descriptor/typeQuery/typeQuery.ts +++ b/src/transformer/descriptor/typeQuery/typeQuery.ts @@ -21,50 +21,50 @@ export function GetTypeQueryDescriptorFromDeclaration(declaration: ts.NamedDecla const typeChecker: ts.TypeChecker = TypeChecker(); switch (declaration.kind) { - case ts.SyntaxKind.ClassDeclaration: - return TypescriptCreator.createFunctionExpressionReturn( - GetTypeReferenceDescriptor( + case ts.SyntaxKind.ClassDeclaration: + return TypescriptCreator.createFunctionExpressionReturn( + GetTypeReferenceDescriptor( + ts.createTypeReferenceNode(declaration.name as ts.Identifier, undefined), + scope, + ), + ); + case ts.SyntaxKind.TypeAliasDeclaration: + case ts.SyntaxKind.InterfaceDeclaration: + return GetTypeReferenceDescriptor( ts.createTypeReferenceNode(declaration.name as ts.Identifier, undefined), scope, - ), - ); - case ts.SyntaxKind.TypeAliasDeclaration: - case ts.SyntaxKind.InterfaceDeclaration: - return GetTypeReferenceDescriptor( - ts.createTypeReferenceNode(declaration.name as ts.Identifier, undefined), - scope, - ); - case ts.SyntaxKind.NamespaceImport: - case ts.SyntaxKind.ImportEqualsDeclaration: - return GetModuleDescriptor(declaration, scope); - case ts.SyntaxKind.EnumDeclaration: - // TODO: Use following two lines when issue #17552 on typescript github is resolved (https://github.com/microsoft/TypeScript/issues/17552) - // TheNewEmitResolver.ensureEmitOf(GetImportDeclarationOf(node.eprName as ts.Identifier); - // return node.exprName as ts.Identifier; - return GetMockFactoryCallTypeofEnum(declaration as ts.EnumDeclaration); - case ts.SyntaxKind.FunctionDeclaration: - case ts.SyntaxKind.MethodSignature: - return GetMethodDeclarationDescriptor(declaration as ts.FunctionDeclaration, scope); - case ts.SyntaxKind.VariableDeclaration: - const variable: ts.VariableDeclaration = declaration as ts.VariableDeclaration; + ); + case ts.SyntaxKind.NamespaceImport: + case ts.SyntaxKind.ImportEqualsDeclaration: + return GetModuleDescriptor(declaration, scope); + case ts.SyntaxKind.EnumDeclaration: + // TODO: Use following two lines when issue #17552 on typescript github is resolved (https://github.com/microsoft/TypeScript/issues/17552) + // TheNewEmitResolver.ensureEmitOf(GetImportDeclarationOf(node.eprName as ts.Identifier); + // return node.exprName as ts.Identifier; + return GetMockFactoryCallTypeofEnum(declaration as ts.EnumDeclaration); + case ts.SyntaxKind.FunctionDeclaration: + case ts.SyntaxKind.MethodSignature: + return GetMethodDeclarationDescriptor(declaration as ts.FunctionDeclaration, scope); + case ts.SyntaxKind.VariableDeclaration: + const variable: ts.VariableDeclaration = declaration as ts.VariableDeclaration; - if (variable.type) { - return GetDescriptor(variable.type, scope); - } + if (variable.type) { + return GetDescriptor(variable.type, scope); + } - const inferredType: ts.Node = GetType(variable.initializer, scope); - const symbol: ts.Symbol = typeChecker.getSymbolAtLocation(inferredType); + const inferredType: ts.Node = GetType(variable.initializer, scope); + const symbol: ts.Symbol = typeChecker.getSymbolAtLocation(inferredType); - if (symbol) { - const inferredTypeDeclaration: ts.NamedDeclaration = getTypeQueryDeclarationFromSymbol(symbol); + if (symbol) { + const inferredTypeDeclaration: ts.NamedDeclaration = getTypeQueryDeclarationFromSymbol(symbol); - return GetTypeQueryDescriptorFromDeclaration(inferredTypeDeclaration, scope); - } else { - return GetDescriptor(inferredType, scope); - } - default: - TransformerLogger().typeNotSupported(`TypeQuery of ${ts.SyntaxKind[declaration.kind]}`); - return GetNullDescriptor(); + return GetTypeQueryDescriptorFromDeclaration(inferredTypeDeclaration, scope); + } else { + return GetDescriptor(inferredType, scope); + } + default: + TransformerLogger().typeNotSupported(`TypeQuery of ${ts.SyntaxKind[declaration.kind]}`); + return GetNullDescriptor(); } } diff --git a/src/transformer/descriptor/typeReference/typeReference.ts b/src/transformer/descriptor/typeReference/typeReference.ts index 40fe63a60..120ca5b68 100644 --- a/src/transformer/descriptor/typeReference/typeReference.ts +++ b/src/transformer/descriptor/typeReference/typeReference.ts @@ -5,7 +5,7 @@ import { Scope } from '../../scope/scope'; import { isTypeReferenceReusable } from '../../typeReferenceReusable/typeReferenceReusable'; import { GetDescriptor } from '../descriptor'; import { TypescriptHelper } from '../helper/helper'; -import { GetTypescriptType, IsTypescriptType } from '../tsLibs/typecriptLibs'; +import { GetTypescriptTypeDescriptor, IsTypescriptType } from '../tsLibs/typecriptLibs'; export function GetTypeReferenceDescriptor(node: ts.TypeReferenceNode, scope: Scope): ts.Expression { const declaration: ts.Declaration = TypescriptHelper.GetDeclarationFromNode(node.typeName); @@ -15,7 +15,7 @@ export function GetTypeReferenceDescriptor(node: ts.TypeReferenceNode, scope: Sc } if (IsTypescriptType(declaration)) { - return GetDescriptor(GetTypescriptType(node, scope), scope); + return GetTypescriptTypeDescriptor(node, scope); } if (isTypeReferenceReusable(declaration)) { diff --git a/src/transformer/helper/creator.ts b/src/transformer/helper/creator.ts index 57e0a4f31..6e32e1631 100644 --- a/src/transformer/helper/creator.ts +++ b/src/transformer/helper/creator.ts @@ -40,8 +40,8 @@ export namespace TypescriptCreator { return ts.createProperty([], [], propertyName, undefined, type, undefined); } - export function createPropertyWitInitializer(propertyName: string | PropertyName, initializer: ts.Expression): ts.PropertyDeclaration { - return ts.createProperty([], [], propertyName, undefined, undefined, initializer); + export function createPropertySignature(propertyName: string | PropertyName, type: ts.TypeNode): ts.PropertySignature { + return ts.createPropertySignature([], propertyName, undefined, type, undefined); } export function createParameter(parameterName: string): ts.ParameterDeclaration { diff --git a/test/transformer/descriptor/tsLibs/tsLibs.test.ts b/test/transformer/descriptor/tsLibs/tsLibs.test.ts index 4df3ace79..9513a6a07 100644 --- a/test/transformer/descriptor/tsLibs/tsLibs.test.ts +++ b/test/transformer/descriptor/tsLibs/tsLibs.test.ts @@ -203,10 +203,28 @@ describe('typescript lib', () => { }); it('should set a promise resolved for a type mocked directly', async () => { - type S = Promise; - const properties: S = createMock>(); - const result: string = await properties; - expect(result).toBe(''); + type S = Promise; + const properties: S = createMock>(); + const result: string = await properties; + expect(result).toBe(''); + }); + + it('should create a new Map for a Map', () => { + type S = Map; + type AType = { + map: Map; + }; + interface AnInterface { + map: Map; + } + const mapMock: S = createMock>(); + const aType: AType = createMock(); + const anInterface: AnInterface = createMock(); + const realMap: Map = new Map(); + + expect(mapMock.constructor).toBe(realMap.constructor); + expect(aType.map.constructor).toBe(realMap.constructor); + expect(anInterface.map.constructor).toBe(realMap.constructor); }); it('should set undefined for a not recognized type alias declaration', () => { diff --git a/test/transformer/descriptor/typeQuery/typeQuery.test.ts b/test/transformer/descriptor/typeQuery/typeQuery.test.ts index 639772ff8..a31b44a61 100644 --- a/test/transformer/descriptor/typeQuery/typeQuery.test.ts +++ b/test/transformer/descriptor/typeQuery/typeQuery.test.ts @@ -283,7 +283,30 @@ describe('typeQuery', () => { const mock: typeof aVariable = createMock(); - expect(mock.prop).toEqual('asd'); + expect(mock.second).toEqual(7); + }); + + it('should not call the real function to get the type for function call', () => { + let count: number = 0; + + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + function test(value: boolean) { + count++; + + if (value) { + return {prop: 'asd'}; + } + + return {second: 7}; + } + + // eslint-disable-next-line @typescript-eslint/typedef + const aVariable = test(true); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const mock: typeof aVariable = createMock(); + + expect(count).toEqual(1); }); it('should work for module', () => { diff --git a/ui/src/views/types-supported.mdx b/ui/src/views/types-supported.mdx index c72957af0..335298304 100644 --- a/ui/src/views/types-supported.mdx +++ b/ui/src/views/types-supported.mdx @@ -169,8 +169,9 @@ This is a special case. The library try to convert to the most useful type. ```ts class MyClass { array: Array; // [] - number: Number; // 0; - promise: Promise// a promise that will resolve an empty string Promise.resolve("") + number: Number; // 0 + promise: Promise; // a promise that will resolve an empty string Promise.resolve("") + map: Map; // new Map() } ``` From e4e73532fd7205d9dc5e9100fdfc8b7d25763c9c Mon Sep 17 00:00:00 2001 From: Pmyl Date: Sat, 8 Feb 2020 18:48:49 +0000 Subject: [PATCH 2/5] remove extra webpack configuration --- config/modules/transformer/webpack.js | 1 - 1 file changed, 1 deletion(-) diff --git a/config/modules/transformer/webpack.js b/config/modules/transformer/webpack.js index 7488c2151..352935e8b 100644 --- a/config/modules/transformer/webpack.js +++ b/config/modules/transformer/webpack.js @@ -7,7 +7,6 @@ const base = require("../base/webpack.base"); module.exports = merge(base({ tsConfigFile: 'config/modules/transformer/tsconfig.json' }), { - devtool: 'cheap-module-eval-source-map', target: "node", node: { __dirname: false From d8dd0fc65542789534da51c098d847b17bffbfb3 Mon Sep 17 00:00:00 2001 From: Pmyl Date: Sat, 8 Feb 2020 18:54:06 +0000 Subject: [PATCH 3/5] use custom function to get null descriptor --- src/transformer/descriptor/method/bodyReturnType.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/transformer/descriptor/method/bodyReturnType.ts b/src/transformer/descriptor/method/bodyReturnType.ts index a03d78360..69bd78d08 100644 --- a/src/transformer/descriptor/method/bodyReturnType.ts +++ b/src/transformer/descriptor/method/bodyReturnType.ts @@ -1,6 +1,7 @@ import * as ts from 'typescript'; import { Scope } from '../../scope/scope'; import { GetDescriptor } from '../descriptor'; +import { GetNullDescriptor } from '../null/null'; export function GetReturnTypeFromBodyDescriptor(node: ts.ArrowFunction | ts.FunctionExpression | ts.MethodDeclaration | ts.FunctionDeclaration, scope: Scope): ts.Expression { return GetDescriptor(GetReturnNodeFromBody(node), scope); @@ -17,7 +18,7 @@ export function GetReturnNodeFromBody(node: ts.ArrowFunction | ts.FunctionExpres if (returnStatement) { returnValue = returnStatement.expression; } else { - returnValue = ts.createNull(); + returnValue = GetNullDescriptor(); } } else { returnValue = node.body; From 597d2bfcd23004ca0468c6372260cc20f381c369 Mon Sep 17 00:00:00 2001 From: Pmyl Date: Sat, 8 Feb 2020 19:03:08 +0000 Subject: [PATCH 4/5] remove casting, use typescript function to narrow type --- src/transformer/descriptor/method/bodyReturnType.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/transformer/descriptor/method/bodyReturnType.ts b/src/transformer/descriptor/method/bodyReturnType.ts index 69bd78d08..7648968f5 100644 --- a/src/transformer/descriptor/method/bodyReturnType.ts +++ b/src/transformer/descriptor/method/bodyReturnType.ts @@ -10,9 +10,9 @@ export function GetReturnTypeFromBodyDescriptor(node: ts.ArrowFunction | ts.Func export function GetReturnNodeFromBody(node: ts.ArrowFunction | ts.FunctionExpression | ts.MethodDeclaration | ts.FunctionDeclaration): ts.Node { let returnValue: ts.Node; - const functionBody: ts.FunctionBody = node.body as ts.FunctionBody; + const functionBody: ts.ConciseBody = node.body; - if (functionBody.statements) { + if (ts.isBlock(functionBody)) { const returnStatement: ts.ReturnStatement = GetReturnStatement(functionBody); if (returnStatement) { From 42a85a4ec93143ecc986fbe98c3401425ceeeb85 Mon Sep 17 00:00:00 2001 From: Pmyl Date: Sat, 8 Feb 2020 20:36:46 +0000 Subject: [PATCH 5/5] handle callExpression in descriptor, handle calls of variables --- .../callExpression/callExpression.ts | 36 ++++++++++ src/transformer/descriptor/descriptor.ts | 3 + .../descriptor/method/bodyReturnType.ts | 2 +- .../descriptor/method/functionReturnType.ts | 14 ++++ .../descriptor/method/methodDeclaration.ts | 11 +-- src/transformer/descriptor/type/type.ts | 5 +- src/transformer/logger/transformerLogger.ts | 4 ++ src/transformer/printNode.ts | 13 ++-- .../descriptor/properties/inferred.test.ts | 40 +++++++++++ .../descriptor/typeQuery/typeQuery.test.ts | 71 +++++++++++++++++++ 10 files changed, 182 insertions(+), 17 deletions(-) create mode 100644 src/transformer/descriptor/callExpression/callExpression.ts create mode 100644 src/transformer/descriptor/method/functionReturnType.ts create mode 100644 test/transformer/descriptor/properties/inferred.test.ts diff --git a/src/transformer/descriptor/callExpression/callExpression.ts b/src/transformer/descriptor/callExpression/callExpression.ts new file mode 100644 index 000000000..4ff8ff31f --- /dev/null +++ b/src/transformer/descriptor/callExpression/callExpression.ts @@ -0,0 +1,36 @@ +import { FunctionLikeDeclaration } from 'typescript'; +import * as ts from 'typescript'; +import { TransformerLogger } from '../../logger/transformerLogger'; +import { NodeToString } from '../../printNode'; +import { Scope } from '../../scope/scope'; +import { GetDescriptor } from '../descriptor'; +import { TypescriptHelper } from '../helper/helper'; +import { GetFunctionReturnType } from '../method/functionReturnType'; +import { GetNullDescriptor } from '../null/null'; + +export function GetCallExpressionDescriptor(node: ts.CallExpression, scope: Scope): ts.Expression { + return GetDescriptor(GetCallExpressionType(node), scope); +} + +export function GetCallExpressionType(node: ts.CallExpression): ts.Node { + const declaration: ts.Declaration = TypescriptHelper.GetDeclarationFromNode(node.expression); + + return GetFinalFunctionTypeFromDeclaration(node, declaration); +} + +function GetFinalFunctionTypeFromDeclaration(initialNode: ts.Node, node: ts.Node): ts.Node { + if (ts.isFunctionLike(node)) { + return GetFunctionReturnType(node as FunctionLikeDeclaration); + } else if (ts.isVariableDeclaration(node)) { + if (node.type) { + if (ts.isFunctionTypeNode(node.type)) { + return node.type.type; + } + } else if (node.initializer) { + return GetFinalFunctionTypeFromDeclaration(initialNode, node.initializer); + } + } + + TransformerLogger().typeOfFunctionCallNotFound(NodeToString(initialNode)); + return GetNullDescriptor(); +} diff --git a/src/transformer/descriptor/descriptor.ts b/src/transformer/descriptor/descriptor.ts index 72d05cbe3..211e50312 100644 --- a/src/transformer/descriptor/descriptor.ts +++ b/src/transformer/descriptor/descriptor.ts @@ -7,6 +7,7 @@ import { GetBigIntDescriptor } from './bigint/bigint'; import { GetBooleanDescriptor } from './boolean/boolean'; import { GetBooleanFalseDescriptor } from './boolean/booleanFalse'; import { GetBooleanTrueDescriptor } from './boolean/booleanTrue'; +import { GetCallExpressionDescriptor } from './callExpression/callExpression'; import { GetClassDeclarationDescriptor } from './class/classDeclaration'; import { GetConstructorTypeDescriptor } from './constructor/constructorType'; import { GetEnumDeclarationDescriptor } from './enum/enumDeclaration'; @@ -134,6 +135,8 @@ export function GetDescriptor(node: ts.Node, scope: Scope): ts.Expression { case ts.SyntaxKind.UndefinedKeyword: case ts.SyntaxKind.VoidKeyword: return GetUndefinedDescriptor(); + case ts.SyntaxKind.CallExpression: + return GetCallExpressionDescriptor(node as ts.CallExpression, scope); default: TransformerLogger().typeNotSupported(ts.SyntaxKind[node.kind]); return GetNullDescriptor(); diff --git a/src/transformer/descriptor/method/bodyReturnType.ts b/src/transformer/descriptor/method/bodyReturnType.ts index 7648968f5..63c202e67 100644 --- a/src/transformer/descriptor/method/bodyReturnType.ts +++ b/src/transformer/descriptor/method/bodyReturnType.ts @@ -7,7 +7,7 @@ export function GetReturnTypeFromBodyDescriptor(node: ts.ArrowFunction | ts.Func return GetDescriptor(GetReturnNodeFromBody(node), scope); } -export function GetReturnNodeFromBody(node: ts.ArrowFunction | ts.FunctionExpression | ts.MethodDeclaration | ts.FunctionDeclaration): ts.Node { +export function GetReturnNodeFromBody(node: ts.FunctionLikeDeclaration): ts.Node { let returnValue: ts.Node; const functionBody: ts.ConciseBody = node.body; diff --git a/src/transformer/descriptor/method/functionReturnType.ts b/src/transformer/descriptor/method/functionReturnType.ts new file mode 100644 index 000000000..8bc4cb634 --- /dev/null +++ b/src/transformer/descriptor/method/functionReturnType.ts @@ -0,0 +1,14 @@ +import * as ts from 'typescript'; +import { GetReturnNodeFromBody } from './bodyReturnType'; + +export function GetFunctionReturnType(node: ts.FunctionLikeDeclaration): ts.Node { + let returnType: ts.Node; + + if (node.type) { + returnType = node.type; + } else { + returnType = GetReturnNodeFromBody(node); + } + + return returnType; +} diff --git a/src/transformer/descriptor/method/methodDeclaration.ts b/src/transformer/descriptor/method/methodDeclaration.ts index dcde77833..e27b66076 100644 --- a/src/transformer/descriptor/method/methodDeclaration.ts +++ b/src/transformer/descriptor/method/methodDeclaration.ts @@ -1,17 +1,12 @@ import * as ts from 'typescript'; import { Scope } from '../../scope/scope'; import { GetDescriptor } from '../descriptor'; -import { GetReturnTypeFromBodyDescriptor } from './bodyReturnType'; +import { GetFunctionReturnType } from './functionReturnType'; import { GetMethodDescriptor } from './method'; export function GetMethodDeclarationDescriptor(node: ts.MethodDeclaration | ts.FunctionDeclaration, scope: Scope): ts.Expression { - let returnType: ts.Expression; - - if (node.type) { - returnType = GetDescriptor(node.type, scope); - } else { - returnType = GetReturnTypeFromBodyDescriptor(node, scope); - } + const returnTypeNode: ts.Node = GetFunctionReturnType(node); + const returnType: ts.Expression = GetDescriptor(returnTypeNode, scope); return GetMethodDescriptor(node.name, returnType); } diff --git a/src/transformer/descriptor/type/type.ts b/src/transformer/descriptor/type/type.ts index bb6da3454..8382032a9 100644 --- a/src/transformer/descriptor/type/type.ts +++ b/src/transformer/descriptor/type/type.ts @@ -1,7 +1,7 @@ import * as ts from 'typescript'; import { Scope } from '../../scope/scope'; +import { GetCallExpressionType } from '../callExpression/callExpression'; import { TypescriptHelper } from '../helper/helper'; -import { GetReturnNodeFromBody } from '../method/bodyReturnType'; import { GetTypeImport } from './typeImport'; export function GetTypes(nodes: ts.NodeArray, scope: Scope): ts.Node[] { @@ -59,8 +59,7 @@ export function GetType(node: ts.Node, scope: Scope): ts.Node { } if (ts.isCallExpression(node)) { - const declaration: ts.Declaration = TypescriptHelper.GetDeclarationFromNode(node.expression); - return GetType(GetReturnNodeFromBody(declaration as ts.FunctionDeclaration), scope); + return GetType(GetCallExpressionType(node), scope); } return node; diff --git a/src/transformer/logger/transformerLogger.ts b/src/transformer/logger/transformerLogger.ts index e675da3cb..2fd12c693 100644 --- a/src/transformer/logger/transformerLogger.ts +++ b/src/transformer/logger/transformerLogger.ts @@ -6,6 +6,7 @@ let logger: ILogger; export interface TransformerLogger { unexpectedCreateMock(mockFileName: string, expectedFileName: string): void; typeNotSupported(type: string): void; + typeOfFunctionCallNotFound(node: string): void; } export function TransformerLogger(): TransformerLogger { @@ -20,5 +21,8 @@ export function TransformerLogger(): TransformerLogger { typeNotSupported(type: string): void { logger.warning(`Not supported type: ${type} - it will convert to null`); }, + typeOfFunctionCallNotFound(node: string): void { + logger.warning(`Cannot find type of function call: ${node} - it will convert to null`); + }, }; } diff --git a/src/transformer/printNode.ts b/src/transformer/printNode.ts index 891fcd89c..60e3a0367 100644 --- a/src/transformer/printNode.ts +++ b/src/transformer/printNode.ts @@ -2,15 +2,18 @@ import * as ts from 'typescript'; import { Logger } from '../logger/logger'; import { ILogger } from '../logger/logger.interface'; -export function PrintNode(node: ts.Node): void { - const PrintNodeLogger: ILogger = Logger('PrintNode'); - +export function NodeToString(node: ts.Node): string { const resultFile: ts.SourceFile = ts.createSourceFile('someFileName.ts', '', ts.ScriptTarget.Latest, /* setParentNodes*/ false, ts.ScriptKind.TS); const printer: ts.Printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + return printer.printNode(ts.EmitHint.Unspecified, node, resultFile); +} + +export function PrintNode(node: ts.Node): void { + const PrintNodeLogger: ILogger = Logger('PrintNode'); + try { - const result: string = printer.printNode(ts.EmitHint.Unspecified, node, resultFile); - PrintNodeLogger.info(result); + PrintNodeLogger.info(NodeToString(node)); } catch (e) { PrintNodeLogger.warning('There was an error printing the node'); } diff --git a/test/transformer/descriptor/properties/inferred.test.ts b/test/transformer/descriptor/properties/inferred.test.ts new file mode 100644 index 000000000..b02d21905 --- /dev/null +++ b/test/transformer/descriptor/properties/inferred.test.ts @@ -0,0 +1,40 @@ +import { createMock } from 'ts-auto-mock'; + + +describe('properties inferred type', () => { + function funcReturningNumber(): number { + return 3; + } + + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + function funcReturningNumberNoTypeDef() { + return 3; + } + + class Test { + // eslint-disable-next-line @typescript-eslint/typedef + public test = 2; + // eslint-disable-next-line @typescript-eslint/typedef + public testFunctionCall = funcReturningNumber(); + // eslint-disable-next-line @typescript-eslint/typedef + public testFunctionCallNoTypeDef = funcReturningNumberNoTypeDef(); + } + + it('should infer the correct type for numbers', () => { + const mock: Test = createMock(); + + expect(mock.test).toBe(2); + }); + + it('should infer the correct type for function calls of functions with type def', () => { + const mock: Test = createMock(); + + expect(mock.testFunctionCall).toBe(0); + }); + + it('should infer the correct type for function calls of functions with no type def', () => { + const mock: Test = createMock(); + + expect(mock.testFunctionCallNoTypeDef).toBe(3); + }); +}); diff --git a/test/transformer/descriptor/typeQuery/typeQuery.test.ts b/test/transformer/descriptor/typeQuery/typeQuery.test.ts index a31b44a61..2a61db5e5 100644 --- a/test/transformer/descriptor/typeQuery/typeQuery.test.ts +++ b/test/transformer/descriptor/typeQuery/typeQuery.test.ts @@ -268,7 +268,78 @@ describe('typeQuery', () => { expect(mock.A).toEqual(0); }); + it('should work for function call of a variable with no explicit return type of function', () => { + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type,@typescript-eslint/typedef + const testExplicitType: (value: boolean) => {prop: string} = (value: boolean) => { + if (value) { + return {prop: 'asd'}; + } + + return {prop: 'wooo'}; + }; + + // eslint-disable-next-line @typescript-eslint/typedef + const aVariable = testExplicitType(true); + + const mock: typeof aVariable = createMock(); + + expect(mock.prop).toEqual(''); + }); + + it('should work for function call of a variable with no explicit type', () => { + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type,@typescript-eslint/typedef + const test = (value: boolean): {prop: string} => { + if (value) { + return {prop: 'asd'}; + } + + return {prop: 'wooo'}; + }; + + // eslint-disable-next-line @typescript-eslint/typedef + const aVariable = test(true); + + const mock: typeof aVariable = createMock(); + + expect(mock.prop).toEqual(''); + }); + + it('should work for function call of a variable with no explicit type and no explicit return type of function', () => { + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type,@typescript-eslint/typedef + const test = (value: boolean) => { + if (value) { + return {prop: 'asd'}; + } + + return {prop: 'wooo'}; + }; + + // eslint-disable-next-line @typescript-eslint/typedef + const aVariable = test(true); + + const mock: typeof aVariable = createMock(); + + expect(mock.prop).toEqual('wooo'); + }); + it('should work for function call', () => { + function test(value: boolean): { prop: string } { + if (value) { + return {prop: 'asd'}; + } + + return {prop: 'wooo'}; + } + + // eslint-disable-next-line @typescript-eslint/typedef + const aVariable = test(true); + + const mock: typeof aVariable = createMock(); + + expect(mock.prop).toEqual(''); + }); + + it('should work for function call of a function with no explicit return type', () => { // eslint-disable-next-line @typescript-eslint/explicit-function-return-type function test(value: boolean) { if (value) {