Skip to content

Commit 183e956

Browse files
committed
fix(@ngtools/webpack): skip non-runtime types when transforming constructors
Fixes angular#14876
1 parent a56cc31 commit 183e956

File tree

2 files changed

+73
-1
lines changed

2 files changed

+73
-1
lines changed

packages/ngtools/webpack/src/transformers/ctor-parameters.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ function createCtorParametersClassProperty(
8989
diagnostics: ts.Diagnostic[],
9090
entityNameToExpression: (n: ts.EntityName) => ts.Expression | undefined,
9191
ctorParameters: ParameterDecorationInfo[],
92+
typeChecker: ts.TypeChecker,
9293
): ts.PropertyDeclaration {
9394
const params: ts.Expression[] = [];
9495

@@ -99,7 +100,7 @@ function createCtorParametersClassProperty(
99100
}
100101

101102
const paramType = ctorParam.type
102-
? typeReferenceToExpression(entityNameToExpression, ctorParam.type)
103+
? typeReferenceToExpression(entityNameToExpression, ctorParam.type, typeChecker)
103104
: undefined;
104105
const members = [
105106
ts.createPropertyAssignment('type', paramType || ts.createIdentifier('undefined')),
@@ -147,6 +148,7 @@ function createCtorParametersClassProperty(
147148
function typeReferenceToExpression(
148149
entityNameToExpression: (n: ts.EntityName) => ts.Expression | undefined,
149150
node: ts.TypeNode,
151+
typeChecker: ts.TypeChecker,
150152
): ts.Expression | undefined {
151153
let kind = node.kind;
152154
if (ts.isLiteralTypeNode(node)) {
@@ -175,6 +177,15 @@ function typeReferenceToExpression(
175177
return ts.createIdentifier('Number');
176178
case ts.SyntaxKind.TypeReference:
177179
const typeRef = node as ts.TypeReferenceNode;
180+
const typeSymbol = typeChecker.getSymbolAtLocation(typeRef);
181+
if (!typeSymbol || !(typeSymbol.flags & ts.SymbolFlags.Value)) {
182+
return undefined;
183+
}
184+
185+
const type = typeChecker.getTypeOfSymbolAtLocation(typeSymbol, typeRef);
186+
if (!type || typeChecker.getSignaturesOfType(type, ts.SignatureKind.Construct).length === 0) {
187+
return undefined;
188+
}
178189

179190
// Ignore any generic types, just return the base type.
180191
return entityNameToExpression(typeRef.typeName);
@@ -263,6 +274,7 @@ export function decoratorDownlevelTransformer(
263274
diagnostics,
264275
entityNameToExpression,
265276
parametersInfo,
277+
typeChecker,
266278
);
267279

268280
return [node, ctorProperty];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import { tags } from '@angular-devkit/core'; // tslint:disable-line:no-implicit-dependencies
9+
import { createTypescriptContext, transformTypescript } from './ast_helpers';
10+
import { downlevelConstructorParameters } from './ctor-parameters';
11+
12+
function transform(input: string, additionalFiles?: Record<string, string>) {
13+
const { program, compilerHost } = createTypescriptContext(input, additionalFiles);
14+
const transformer = downlevelConstructorParameters(() => program.getTypeChecker());
15+
const result = transformTypescript(undefined, [transformer], program, compilerHost);
16+
17+
return result;
18+
}
19+
20+
describe('Constructor Parameter Transformer', () => {
21+
it('supports Inject decorators with interfaces in same module', () => {
22+
const input = tags.stripIndent`
23+
export interface InterInject {};
24+
export const INTERFACE_INJECT = new InjectionToken<InterInject>('interface-inject');
25+
26+
export class MyService {
27+
constructor(@Inject(INTERFACE_INJECT) config: InterInject) {}
28+
}
29+
`;
30+
31+
const output = `import * as tslib_1 from "tslib"; ; export const INTERFACE_INJECT = new InjectionToken('interface-inject'); let MyService = class MyService { constructor(config) { } }; MyService.ctorParameters = () => [ { type: undefined, decorators: [{ type: Inject, args: [INTERFACE_INJECT,] }] } ]; MyService = tslib_1.__decorate([ tslib_1.__param(0, Inject(INTERFACE_INJECT)) ], MyService); export { MyService };`;
32+
33+
const result = transform(input);
34+
35+
expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`);
36+
});
37+
38+
it('supports Inject decorators with interfaces in another module', () => {
39+
const injectedModule = {
40+
'module-inject': `
41+
export interface InterInject {};
42+
export const INTERFACE_INJECT = new InjectionToken<InterInject>('interface-inject');
43+
`,
44+
};
45+
46+
const input = tags.stripIndent`
47+
import { INTERFACE_INJECT, InterInject } from './module-inject';
48+
49+
export class MyService {
50+
constructor(@Inject(INTERFACE_INJECT) config: InterInject) {}
51+
}
52+
`;
53+
54+
const output = `import * as tslib_1 from "tslib"; import { INTERFACE_INJECT } from './module-inject'; let MyService = class MyService { constructor(config) { } }; MyService.ctorParameters = () => [ { type: undefined, decorators: [{ type: Inject, args: [INTERFACE_INJECT,] }] } ]; MyService = tslib_1.__decorate([ tslib_1.__param(0, Inject(INTERFACE_INJECT)) ], MyService); export { MyService };`;
55+
56+
const result = transform(input, injectedModule);
57+
58+
expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`);
59+
});
60+
});

0 commit comments

Comments
 (0)