diff --git a/src/transformer/descriptor/module/module.ts b/src/transformer/descriptor/module/module.ts index 4c356eaba..897f1c1fe 100644 --- a/src/transformer/descriptor/module/module.ts +++ b/src/transformer/descriptor/module/module.ts @@ -6,6 +6,7 @@ import { GetDescriptor } from '../descriptor'; import { TypescriptHelper } from '../helper/helper'; import { GetMockPropertiesFromDeclarations } from '../mock/mockProperties'; import { PropertyLike } from '../mock/propertyLike'; +import { GetTypeQueryDescriptorFromDeclaration } from '../typeQuery/typeQuery'; export function GetModuleDescriptor(node: ts.NamedDeclaration, scope: Scope): ts.Expression { const typeChecker: ts.TypeChecker = TypeChecker(); @@ -30,5 +31,5 @@ export function GetModuleDescriptor(node: ts.NamedDeclaration, scope: Scope): ts return GetMockPropertiesFromDeclarations(properties, [], scope); } - return GetDescriptor(ts.createTypeQueryNode(externalModuleDeclaration.name as ts.Identifier), scope); + return GetTypeQueryDescriptorFromDeclaration(externalModuleDeclaration, scope); } diff --git a/src/transformer/descriptor/typeQuery/typeQuery.ts b/src/transformer/descriptor/typeQuery/typeQuery.ts index a04977aa1..cd7632283 100644 --- a/src/transformer/descriptor/typeQuery/typeQuery.ts +++ b/src/transformer/descriptor/typeQuery/typeQuery.ts @@ -9,65 +9,89 @@ import { TypescriptHelper } from '../helper/helper'; import { GetMethodDeclarationDescriptor } from '../method/methodDeclaration'; import { GetModuleDescriptor } from '../module/module'; import { GetNullDescriptor } from '../null/null'; +import { GetType } from '../type/type'; import { GetTypeReferenceDescriptor } from '../typeReference/typeReference'; export function GetTypeQueryDescriptor(node: ts.TypeQueryNode, scope: Scope): ts.Expression { - const typeChecker: ts.TypeChecker = TypeChecker(); - const declaration: ts.Declaration = getTypeQueryDeclaration(node); + const declaration: ts.NamedDeclaration = getTypeQueryDeclaration(node); + return GetTypeQueryDescriptorFromDeclaration(declaration, scope); +} + +export function GetTypeQueryDescriptorFromDeclaration(declaration: ts.NamedDeclaration, scope: Scope): ts.Expression { + const typeChecker: ts.TypeChecker = TypeChecker(); + + switch (declaration.kind) { + 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.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); + } + + const inferredType: ts.Node = GetType(variable.initializer, scope); + const symbol: ts.Symbol = typeChecker.getSymbolAtLocation(inferredType); + + 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(); + } +} + +function getTypeQueryDeclaration(node: ts.TypeQueryNode): ts.NamedDeclaration { + const typeChecker: ts.TypeChecker = TypeChecker(); + /* + TODO: Find different workaround without casting to any + Cast to any is been done because getSymbolAtLocation doesn't work when the node is an inferred identifier of a type query of a type query + Use case is: + ``` + const myVar = MyEnum; + createMock(); + ``` + here `typeof myVar` is inferred `typeof MyEnum` and the `MyEnum` identifier doesn't play well with getSymbolAtLocation and it returns undefined. + */ + // tslint:disable-next-line no-any + const symbol: ts.Symbol = typeChecker.getSymbolAtLocation(node.exprName) || (node.exprName as any).symbol; - switch (declaration.kind) { - case ts.SyntaxKind.ClassDeclaration: - return TypescriptCreator.createFunctionExpressionReturn( - GetTypeReferenceDescriptor( - ts.createTypeReferenceNode(node.exprName as ts.Identifier, undefined), - scope, - ), - ); - case ts.SyntaxKind.TypeAliasDeclaration: - case ts.SyntaxKind.InterfaceDeclaration: - return GetTypeReferenceDescriptor( - ts.createTypeReferenceNode(node.exprName 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 typeNode: ts.TypeNode = (declaration as ts.VariableDeclaration).type || typeChecker.typeToTypeNode(typeChecker.getTypeFromTypeNode(node)); - return GetDescriptor(typeNode, scope); - default: - TransformerLogger().typeNotSupported(`TypeQuery of ${ts.SyntaxKind[declaration.kind]}`); - return GetNullDescriptor(); - } + return getTypeQueryDeclarationFromSymbol(symbol); } -function getTypeQueryDeclaration(node: ts.TypeQueryNode): ts.Declaration { - const typeChecker: ts.TypeChecker = TypeChecker(); - /* - TODO: Find different workaround without casting to any - Cast to any is been done because getSymbolAtLocation doesn't work when the node is an inferred identifier of a type query of a type query - Use case is: - ``` - const myVar = MyEnum; - createMock(); - ``` - here `typeof myVar` is inferred `typeof MyEnum` and the `MyEnum` identifier doesn't play well with getSymbolAtLocation and it returns undefined. - */ - // tslint:disable-next-line no-any - const symbol: ts.Symbol = typeChecker.getSymbolAtLocation(node.exprName as ts.Identifier) || (node.exprName as any).symbol; - const declaration: ts.Declaration = symbol.declarations[0]; +function getTypeQueryDeclarationFromSymbol(symbol: ts.Symbol): ts.NamedDeclaration { + const declaration: ts.Declaration = symbol.declarations[0]; - if (ts.isImportEqualsDeclaration(declaration)) { - return declaration; - } + if (ts.isImportEqualsDeclaration(declaration)) { + return declaration; + } - return TypescriptHelper.GetDeclarationFromSymbol(symbol); + return TypescriptHelper.GetDeclarationFromSymbol(symbol); } diff --git a/test/transformer/descriptor/typeQuery/typeQuery.test.ts b/test/transformer/descriptor/typeQuery/typeQuery.test.ts index 63a3bfc43..6675761c7 100644 --- a/test/transformer/descriptor/typeQuery/typeQuery.test.ts +++ b/test/transformer/descriptor/typeQuery/typeQuery.test.ts @@ -1,264 +1,276 @@ import { createMock } from 'ts-auto-mock'; +import * as STAR_DEFAULT from '../utils/interfaces/exportDefaultDeclaration'; import { ImportInterface } from '../utils/interfaces/importInterface'; -import REQUIRE = require('../utils/typeQuery/typeQueryUtils'); -import REQUIRE_DEFAULT = require('../utils/interfaces/exportDefaultDeclaration'); -import REQUIRE_EQUAL = require('../utils/interfaces/exportEqualObject'); import * as STAR from '../utils/typeQuery/typeQueryUtils'; -import * as STAR_DEFAULT from '../utils/interfaces/exportDefaultDeclaration'; import { - ExportedClass, - ExportedDeclaredClass, - exportedDeclaredFunction, - ExportedEnum, - exportedFunction, - WrapExportedClass, - WrapExportedEnum, + ExportedClass, + ExportedDeclaredClass, + exportedDeclaredFunction, + ExportedEnum, + exportedFunction, + WrapExportedClass, + WrapExportedEnum, } from '../utils/typeQuery/typeQueryUtils'; +import REQUIRE_DEFAULT = require('../utils/interfaces/exportDefaultDeclaration'); +import REQUIRE_EQUAL = require('../utils/interfaces/exportEqualObject'); +import REQUIRE = require('../utils/typeQuery/typeQueryUtils'); declare function functionDeclaration(): number; describe('typeQuery', () => { - describe('for function', () => { - it('should assign the function mock for a function declaration', () => { - const functionMock: typeof functionDeclaration = createMock(); + describe('for function', () => { + it('should assign the function mock for a function declaration', () => { + const functionMock: typeof functionDeclaration = createMock(); - expect(functionMock()).toEqual(0); - }); + expect(functionMock()).toEqual(0); + }); - it('should assign the function mock for an function declaration with body', () => { - function func(): string { - return 'ok'; - } + it('should assign the function mock for an function declaration with body', () => { + function func(): string { + return 'ok'; + } - const functionMock: typeof func = createMock(); + const functionMock: typeof func = createMock(); - expect(functionMock()).toEqual(''); - }); + expect(functionMock()).toEqual(''); + }); - it('should assign the function mock for an imported function declaration', () => { - const functionMock: typeof exportedDeclaredFunction = createMock(); + it('should assign the function mock for an imported function declaration', () => { + const functionMock: typeof exportedDeclaredFunction = createMock(); - expect(functionMock()).toEqual(''); - }); + expect(functionMock()).toEqual(''); + }); - it('should assign the function mock for an imported function declaration with body', () => { - const functionMock: typeof exportedFunction = createMock(); + it('should assign the function mock for an imported function declaration with body', () => { + const functionMock: typeof exportedFunction = createMock(); - expect(functionMock()).toEqual(0); - }); + expect(functionMock()).toEqual(0); + }); - it('should return undefined for an intersection', () => { - function func(): string { - return 'ok'; - } + it('should return undefined for an intersection', () => { + function func(): string { + return 'ok'; + } - type Intersection = {} & typeof func; + type Intersection = {} & typeof func; - const functionMock: Intersection = createMock(); + const functionMock: Intersection = createMock(); - expect(functionMock).toBeUndefined(); + expect(functionMock).toBeUndefined(); + }); }); - }); - describe('for class', () => { - it('should create a newable class for a class declaration in file', () => { - class MyClass { - prop: string; - } + describe('for class', () => { + it('should create a newable class for a class declaration in file', () => { + class MyClass { + prop: string; + } - const classMock: typeof MyClass = createMock(); + const classMock: typeof MyClass = createMock(); - expect(new classMock().prop).toEqual(''); - }); + expect(new classMock().prop).toEqual(''); + }); - it('should create a newable class for an imported class declaration', () => { - const classMock: typeof ExportedDeclaredClass = createMock(); + it('should create a newable class for an imported class declaration', () => { + const classMock: typeof ExportedDeclaredClass = createMock(); - expect(new classMock().prop).toEqual(''); - }); + expect(new classMock().prop).toEqual(''); + }); - it('should create a newable class for an imported class', () => { - const classMock: typeof ExportedClass = createMock(); + it('should create a newable class for an imported class', () => { + const classMock: typeof ExportedClass = createMock(); - expect(new classMock().prop).toEqual(0); - }); + expect(new classMock().prop).toEqual(0); + }); - it('should create a newable class for an wrapped imported typeof class', () => { - const classMock: WrapExportedClass = createMock(); + it('should create a newable class for an wrapped imported typeof class', () => { + const classMock: WrapExportedClass = createMock(); - expect(new classMock().prop).toEqual(0); - }); + expect(new classMock().prop).toEqual(0); + }); - it('should return undefined for an intersection', () => { - type Intersection = {} & WrapExportedClass; + it('should return undefined for an intersection', () => { + type Intersection = {} & WrapExportedClass; - const functionMock: Intersection = createMock(); + const functionMock: Intersection = createMock(); - expect(functionMock).toBeUndefined(); + expect(functionMock).toBeUndefined(); + }); }); - }); - describe('for enum', () => { - it('should assign the enum to the mock', () => { - enum Enum { - A, - B = 'some' - } + describe('for enum', () => { + it('should assign the enum to the mock', () => { + enum Enum { + A, + B = 'some' + } - const enumMock: typeof Enum = createMock(); + const enumMock: typeof Enum = createMock(); - expect(enumMock.A).toEqual(Enum.A); - expect(enumMock.B).toEqual(Enum.B); - }); + expect(enumMock.A).toEqual(Enum.A); + expect(enumMock.B).toEqual(Enum.B); + }); - it('should assign the imported enum to the mock', () => { - const enumMock: typeof ExportedEnum = createMock(); + it('should assign the imported enum to the mock', () => { + const enumMock: typeof ExportedEnum = createMock(); - expect(enumMock.A).toEqual(0); - expect(enumMock.B).toEqual('B'); - expect(enumMock.C).toEqual('MaybeC'); - }); + expect(enumMock.A).toEqual(0); + expect(enumMock.B).toEqual('B'); + expect(enumMock.C).toEqual('MaybeC'); + }); - it('should assign the imported enum to the mock when typeof wrapped in a type', () => { - type WrapEnum = typeof ExportedEnum; + it('should assign the imported enum to the mock when typeof wrapped in a type', () => { + type WrapEnum = typeof ExportedEnum; - const enumMock: WrapEnum = createMock(); + const enumMock: WrapEnum = createMock(); - expect(enumMock.A).toEqual(0); - expect(enumMock.B).toEqual('B'); - expect(enumMock.C).toEqual('MaybeC'); - }); + expect(enumMock.A).toEqual(0); + expect(enumMock.B).toEqual('B'); + expect(enumMock.C).toEqual('MaybeC'); + }); - it('should assign the enum to the mock when importing enum wrapper', () => { - const enumMock: WrapExportedEnum = createMock(); + it('should assign the enum to the mock when importing enum wrapper', () => { + const enumMock: WrapExportedEnum = createMock(); - expect(enumMock.A).toEqual(0); - expect(enumMock.B).toEqual('B'); - expect(enumMock.C).toEqual('MaybeC'); - }); + expect(enumMock.A).toEqual(0); + expect(enumMock.B).toEqual('B'); + expect(enumMock.C).toEqual('MaybeC'); + }); - it('should return undefined for an intersection', () => { - type Intersection = {} & WrapExportedEnum; + it('should return undefined for an intersection', () => { + type Intersection = {} & WrapExportedEnum; - const functionMock: Intersection = createMock(); + const functionMock: Intersection = createMock(); - expect(functionMock).toBeUndefined(); + expect(functionMock).toBeUndefined(); + }); }); - }); - describe('for variable', () => { - it('should create the imported interface mock from the type of a variable', () => { - let aVariable: ImportInterface; + describe('for variable', () => { + it('should create the imported interface mock from the type of a variable', () => { + let aVariable: ImportInterface; - const mock: typeof aVariable = createMock(); + const mock: typeof aVariable = createMock(); - expect(mock.a.b).toEqual(''); - }); + expect(mock.a.b).toEqual(''); + }); - it('should create the imported typeof enum mock from the type of a variable', () => { - let aVariable: WrapExportedEnum; + it('should create the imported typeof enum mock from the type of a variable', () => { + let aVariable: WrapExportedEnum; - const mock: typeof aVariable = createMock(); + const mock: typeof aVariable = createMock(); - expect(mock.A).toEqual(0); - }); + expect(mock.A).toEqual(0); + }); - it('should work for a method in an object', () => { - let aVariable: { - a(): string; - } = { - a: function(): string { - return "wow"; - } - }; + it('should work for a method in an object', () => { + let aVariable: { + a(): string; + } = { + a: function (): string { + return "wow"; + }, + }; - const mock: typeof aVariable.a = createMock(); + const mock: typeof aVariable.a = createMock(); - expect(mock()).toEqual(''); - }); + expect(mock()).toEqual(''); + }); - it('should return undefined for an intersection', () => { - let aVariable: WrapExportedEnum; + it('should return undefined for an intersection', () => { + let aVariable: WrapExportedEnum; - type Intersection = {} & typeof aVariable; + type Intersection = {} & typeof aVariable; - const functionMock: Intersection = createMock(); + const functionMock: Intersection = createMock(); - expect(functionMock).toBeUndefined(); - }); + expect(functionMock).toBeUndefined(); + }); - describe('import star', () => { - it('should mock every materialisable export (no types or interfaces)', () => { - const mock: typeof STAR = createMock(); + describe('import star', () => { + it('should mock every materialisable export (no types or interfaces)', () => { + const mock: typeof STAR = createMock(); - expect(mock.ExportedEnum.A).toBe(ExportedEnum.A); - expect(new mock.ExportedClass().prop).toEqual(0); - expect(new mock.ExportedDeclaredClass().prop).toEqual(''); - expect(mock.exportedDeclaredFunction()).toEqual(''); - }); + expect(mock.ExportedEnum.A).toBe(ExportedEnum.A); + expect(new mock.ExportedClass().prop).toEqual(0); + expect(new mock.ExportedDeclaredClass().prop).toEqual(''); + expect(mock.exportedDeclaredFunction()).toEqual(''); + }); - it('should mock the default', () => { - const mock: typeof STAR_DEFAULT = createMock(); + it('should mock the default', () => { + const mock: typeof STAR_DEFAULT = createMock(); - expect(mock.default('input')).toBe(false); - }); - }); + expect(mock.functionDefault('input')).toBe(false); + expect(mock.default('input')).toBe(false); + expect(mock.functionNotDefault('input')).toBe(false); + }); + }); - describe('import require', () => { - it('should mock every materialisable export (no types or interfaces)', () => { - const mock: typeof REQUIRE = createMock(); + describe('import require', () => { + it('should mock every materialisable export (no types or interfaces)', () => { + const mock: typeof REQUIRE = createMock(); - expect(mock.ExportedEnum.A).toBe(ExportedEnum.A); - expect(new mock.ExportedClass().prop).toEqual(0); - expect(new mock.ExportedDeclaredClass().prop).toEqual(''); - expect(mock.exportedDeclaredFunction()).toEqual(''); - }); + expect(mock.ExportedEnum.A).toBe(ExportedEnum.A); + expect(new mock.ExportedClass().prop).toEqual(0); + expect(new mock.ExportedDeclaredClass().prop).toEqual(''); + expect(mock.exportedDeclaredFunction()).toEqual(''); + }); - it('should mock the default', () => { - const mock: typeof REQUIRE_DEFAULT = createMock(); + it('should mock the default', () => { + const mock: typeof REQUIRE_DEFAULT = createMock(); - expect(mock.default('input')).toBe(false); - }); + expect(mock.functionDefault('input')).toBe(false); + expect(mock.default('input')).toBe(false); + expect(mock.functionNotDefault('input')).toBe(false); + }); - it('should mock the `export =`', () => { - const mock: typeof REQUIRE_EQUAL = createMock(); + it('should mock the `export =`', () => { + const mock: typeof REQUIRE_EQUAL = createMock(); - expect(new mock().prop).toBe(0); - }); - }); + expect(new mock().prop).toBe(0); + }); + }); + + describe('inferred type', () => { + it('should work for inferred object', () => { + const aVariable = {prop: 'asd'}; + + const mock: typeof aVariable = createMock(); + + expect(mock.prop).toEqual('asd'); + }); - describe('inferred type', () => { - it('should work for inferred object', () => { - const aVariable = { prop: 'asd' }; + it('should work for enum', () => { + const aVariable = ExportedEnum; - const mock: typeof aVariable = createMock(); + const mock: typeof aVariable = createMock(); - expect(mock.prop).toEqual(''); - }); + expect(mock.A).toEqual(0); + }); - it('should work for enum', () => { - const aVariable = ExportedEnum; + it('should work for function call', () => { + function test(value) { + if (value) { + return {prop: 'asd'}; + } - const mock: typeof aVariable = createMock(); + return {second: 7}; + } - expect(mock.A).toEqual(0); - }); + const aVariable = test(true); - it('should work for function call', () => { - function test(value) { - if(value) { - return { prop: 'asd' }; - } + const mock: typeof aVariable = createMock(); - return { second: 7 }; - } + expect(mock.prop).toEqual('asd'); + }); - const aVariable = test(true); + it('should work for module', () => { + const aVariable = STAR; - const mock: typeof aVariable = createMock(); + const mock: typeof aVariable = createMock(); - expect(mock.prop).toEqual(''); - }); + expect(mock.ExportedEnum.A).toEqual(ExportedEnum.A); + }); + }); }); - }); }); diff --git a/test/transformer/descriptor/utils/interfaces/exportDefaultDeclaration.ts b/test/transformer/descriptor/utils/interfaces/exportDefaultDeclaration.ts index 2d877e28f..684061c05 100644 --- a/test/transformer/descriptor/utils/interfaces/exportDefaultDeclaration.ts +++ b/test/transformer/descriptor/utils/interfaces/exportDefaultDeclaration.ts @@ -1,3 +1,6 @@ -declare function functionDeclarationDefault(path: string): boolean; +export function functionDefault(path: string): boolean { + return false; +} -export default functionDeclarationDefault; +export const functionNotDefault = functionDefault; +export default functionDefault;