Skip to content

Commit

Permalink
fix(typeof-module): prevent unsupported declaration to be transformer…
Browse files Browse the repository at this point in the history
… when mocking typeof of a module that uses exports =

chore(module): add type to lambda

refactor(typeof-module): extract interface
  • Loading branch information
uittorio committed Jul 11, 2020
1 parent 830a063 commit 09aa3b3
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 21 deletions.
55 changes: 34 additions & 21 deletions src/transformer/descriptor/module/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,35 +42,48 @@ function GetPropertiesFromSourceFileOrModuleDeclarationDescriptor(sourceFile: Ex
return GetMockPropertiesFromDeclarations(GetPropertiesFromSourceFileOrModuleDeclaration(symbol, scope), [], scope);
}

interface ModuleExportsDeclarations {
declaration: ts.Declaration;
originalDeclaration: ts.NamedDeclaration;
}

export function GetPropertiesFromSourceFileOrModuleDeclaration(symbol: ts.Symbol, scope: Scope): ts.PropertySignature[] {
const typeChecker: ts.TypeChecker = TypeChecker();
const moduleExports: ts.Symbol[] = typeChecker.getExportsOfModule(symbol);

return moduleExports.map((prop: ts.Symbol): ts.PropertySignature => {
return moduleExports.map((prop: ts.Symbol): ModuleExportsDeclarations => {
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.createPropertySignature('default', ts.createTypeQueryNode(originalDeclaration.name as ts.Identifier));
}
const originalDeclaration: ts.NamedDeclaration = originalSymbol?.declarations?.[0];
const declaration: ts.Declaration = prop?.declarations?.[0];

return {
declaration,
originalDeclaration,
};
}).filter(
(d: ModuleExportsDeclarations) => !!d.originalDeclaration && d.declaration
).map(
(d: ModuleExportsDeclarations): ts.PropertySignature => {
if (ts.isExportAssignment(d.declaration)) {
return TypescriptCreator.createPropertySignature('default', ts.createTypeQueryNode(d.originalDeclaration.name as ts.Identifier));
}

if (ts.isExportSpecifier(declaration) && ts.isSourceFile(originalDeclaration)) {
const exportSpecifierSymbol: ts.Symbol | undefined = typeChecker.getSymbolAtLocation(declaration.name);
if (ts.isExportSpecifier(d.declaration) && ts.isSourceFile(d.originalDeclaration)) {
const exportSpecifierSymbol: ts.Symbol | undefined = typeChecker.getSymbolAtLocation(d.declaration.name);

if (!exportSpecifierSymbol) {
throw new Error(
`The type checker failed to look up symbol for \`${declaration.name.getText()}'.`,
);
}
if (!exportSpecifierSymbol) {
throw new Error(
`The type checker failed to look up symbol for \`${d.declaration.name.getText()}'.`,
);
}

const exportSpecifierAliasSymbol: ts.Symbol = typeChecker.getAliasedSymbol(exportSpecifierSymbol);
const exportSpecifierProperties: ts.PropertySignature[] = GetPropertiesFromSourceFileOrModuleDeclaration(exportSpecifierAliasSymbol, scope);
const propertyType: ts.TypeNode = ts.createTypeLiteralNode(exportSpecifierProperties);
const exportSpecifierAliasSymbol: ts.Symbol = typeChecker.getAliasedSymbol(exportSpecifierSymbol);
const exportSpecifierProperties: ts.PropertySignature[] = GetPropertiesFromSourceFileOrModuleDeclaration(exportSpecifierAliasSymbol, scope);
const propertyType: ts.TypeNode = ts.createTypeLiteralNode(exportSpecifierProperties);

return TypescriptCreator.createPropertySignature(declaration.name, propertyType);
}
return TypescriptCreator.createPropertySignature(d.declaration.name, propertyType);
}

return TypescriptCreator.createPropertySignature(originalDeclaration.name as ts.Identifier, ts.createTypeQueryNode(originalDeclaration.name as ts.Identifier));
});
return TypescriptCreator.createPropertySignature(d.originalDeclaration.name as ts.Identifier, ts.createTypeQueryNode(d.originalDeclaration.name as ts.Identifier));
});
}
2 changes: 2 additions & 0 deletions test/transformer/descriptor/typeQuery/typeQuery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ describe('typeQuery', () => {

it('should return correct properties with multiple declarations', () => {
function MultipleDeclaration(): MultipleDeclaration {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return { a: 's'};
}
Expand All @@ -71,6 +72,7 @@ describe('typeQuery', () => {

const functionMock: typeof MultipleDeclaration = createMock<typeof MultipleDeclaration>();

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
expect(functionMock()).toEqual({
b: '',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createMock } from 'ts-auto-mock';
import exportEqual = require('../utils/export/exportEqual');

describe('TypeQuery export equal classes', () => {
it('should exclude the class and not fail', () => {
// When transforming typeof a module there are some scenario that are not playing nicely with ts-auto-mock implementation
// Ts auto mock uses typeChecker getExportsOfModule functionality to find symbols
// When the module uses export = ClassName ts auto mock will find the prototype but it will not be able to find the original declaration.
const mock: typeof exportEqual = createMock<typeof exportEqual>();

expect(mock).toBeDefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ describe('typeQuery undefined ', () => {
// make sure if a set of type declarations are faulty (from DefinitelyTyped
// for example) they will "fail" silently.

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const type: typeof undefined = createMock<typeof undefined>();

Expand Down
11 changes: 11 additions & 0 deletions test/transformer/descriptor/utils/export/exportEqual.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
declare namespace Test {
interface HelloWorld {
prop: string;
}
}

class Test {
public a: string;
}

export = Test;

0 comments on commit 09aa3b3

Please sign in to comment.