Skip to content

Commit 09aa3b3

Browse files
committed
fix(typeof-module): prevent unsupported declaration to be transformer when mocking typeof of a module that uses exports =
chore(module): add type to lambda refactor(typeof-module): extract interface
1 parent 830a063 commit 09aa3b3

File tree

5 files changed

+61
-21
lines changed

5 files changed

+61
-21
lines changed

src/transformer/descriptor/module/module.ts

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -42,35 +42,48 @@ function GetPropertiesFromSourceFileOrModuleDeclarationDescriptor(sourceFile: Ex
4242
return GetMockPropertiesFromDeclarations(GetPropertiesFromSourceFileOrModuleDeclaration(symbol, scope), [], scope);
4343
}
4444

45+
interface ModuleExportsDeclarations {
46+
declaration: ts.Declaration;
47+
originalDeclaration: ts.NamedDeclaration;
48+
}
49+
4550
export function GetPropertiesFromSourceFileOrModuleDeclaration(symbol: ts.Symbol, scope: Scope): ts.PropertySignature[] {
4651
const typeChecker: ts.TypeChecker = TypeChecker();
4752
const moduleExports: ts.Symbol[] = typeChecker.getExportsOfModule(symbol);
4853

49-
return moduleExports.map((prop: ts.Symbol): ts.PropertySignature => {
54+
return moduleExports.map((prop: ts.Symbol): ModuleExportsDeclarations => {
5055
const originalSymbol: ts.Symbol = TypescriptHelper.GetAliasedSymbolSafe(prop);
51-
const originalDeclaration: ts.NamedDeclaration = originalSymbol.declarations[0];
52-
const declaration: ts.Declaration = prop.declarations[0];
53-
54-
if (ts.isExportAssignment(declaration)) {
55-
return TypescriptCreator.createPropertySignature('default', ts.createTypeQueryNode(originalDeclaration.name as ts.Identifier));
56-
}
56+
const originalDeclaration: ts.NamedDeclaration = originalSymbol?.declarations?.[0];
57+
const declaration: ts.Declaration = prop?.declarations?.[0];
58+
59+
return {
60+
declaration,
61+
originalDeclaration,
62+
};
63+
}).filter(
64+
(d: ModuleExportsDeclarations) => !!d.originalDeclaration && d.declaration
65+
).map(
66+
(d: ModuleExportsDeclarations): ts.PropertySignature => {
67+
if (ts.isExportAssignment(d.declaration)) {
68+
return TypescriptCreator.createPropertySignature('default', ts.createTypeQueryNode(d.originalDeclaration.name as ts.Identifier));
69+
}
5770

58-
if (ts.isExportSpecifier(declaration) && ts.isSourceFile(originalDeclaration)) {
59-
const exportSpecifierSymbol: ts.Symbol | undefined = typeChecker.getSymbolAtLocation(declaration.name);
71+
if (ts.isExportSpecifier(d.declaration) && ts.isSourceFile(d.originalDeclaration)) {
72+
const exportSpecifierSymbol: ts.Symbol | undefined = typeChecker.getSymbolAtLocation(d.declaration.name);
6073

61-
if (!exportSpecifierSymbol) {
62-
throw new Error(
63-
`The type checker failed to look up symbol for \`${declaration.name.getText()}'.`,
64-
);
65-
}
74+
if (!exportSpecifierSymbol) {
75+
throw new Error(
76+
`The type checker failed to look up symbol for \`${d.declaration.name.getText()}'.`,
77+
);
78+
}
6679

67-
const exportSpecifierAliasSymbol: ts.Symbol = typeChecker.getAliasedSymbol(exportSpecifierSymbol);
68-
const exportSpecifierProperties: ts.PropertySignature[] = GetPropertiesFromSourceFileOrModuleDeclaration(exportSpecifierAliasSymbol, scope);
69-
const propertyType: ts.TypeNode = ts.createTypeLiteralNode(exportSpecifierProperties);
80+
const exportSpecifierAliasSymbol: ts.Symbol = typeChecker.getAliasedSymbol(exportSpecifierSymbol);
81+
const exportSpecifierProperties: ts.PropertySignature[] = GetPropertiesFromSourceFileOrModuleDeclaration(exportSpecifierAliasSymbol, scope);
82+
const propertyType: ts.TypeNode = ts.createTypeLiteralNode(exportSpecifierProperties);
7083

71-
return TypescriptCreator.createPropertySignature(declaration.name, propertyType);
72-
}
84+
return TypescriptCreator.createPropertySignature(d.declaration.name, propertyType);
85+
}
7386

74-
return TypescriptCreator.createPropertySignature(originalDeclaration.name as ts.Identifier, ts.createTypeQueryNode(originalDeclaration.name as ts.Identifier));
75-
});
87+
return TypescriptCreator.createPropertySignature(d.originalDeclaration.name as ts.Identifier, ts.createTypeQueryNode(d.originalDeclaration.name as ts.Identifier));
88+
});
7689
}

test/transformer/descriptor/typeQuery/typeQuery.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ describe('typeQuery', () => {
6161

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

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

75+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
7476
// @ts-ignore
7577
expect(functionMock()).toEqual({
7678
b: '',
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { createMock } from 'ts-auto-mock';
2+
import exportEqual = require('../utils/export/exportEqual');
3+
4+
describe('TypeQuery export equal classes', () => {
5+
it('should exclude the class and not fail', () => {
6+
// When transforming typeof a module there are some scenario that are not playing nicely with ts-auto-mock implementation
7+
// Ts auto mock uses typeChecker getExportsOfModule functionality to find symbols
8+
// When the module uses export = ClassName ts auto mock will find the prototype but it will not be able to find the original declaration.
9+
const mock: typeof exportEqual = createMock<typeof exportEqual>();
10+
11+
expect(mock).toBeDefined();
12+
});
13+
});

test/transformer/descriptor/typeQuery/typeQueryUndefined.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ describe('typeQuery undefined ', () => {
66
// make sure if a set of type declarations are faulty (from DefinitelyTyped
77
// for example) they will "fail" silently.
88

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
declare namespace Test {
2+
interface HelloWorld {
3+
prop: string;
4+
}
5+
}
6+
7+
class Test {
8+
public a: string;
9+
}
10+
11+
export = Test;

0 commit comments

Comments
 (0)