Skip to content

Commit 818d93d

Browse files
petebacondarwinatscott
authored andcommitted
fix(ngcc): find decorated constructor params on IIFE wrapped classes (angular#37436)
Now in TS 3.9, classes in ES2015 can be wrapped in an IIFE. This commit ensures that we still find the static properties that contain decorator information, even if they are attached to the adjacent node of the class, rather than the implementation or declaration. Fixes angular#37330 PR Close angular#37436
1 parent d4c0962 commit 818d93d

File tree

2 files changed

+99
-6
lines changed

2 files changed

+99
-6
lines changed

packages/compiler-cli/ngcc/src/host/esm2015_host.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -866,19 +866,20 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
866866
/**
867867
* Try to retrieve the symbol of a static property on a class.
868868
*
869-
* In some cases, a static property can either be set on the inner declaration inside the class'
870-
* IIFE, or it can be set on the outer variable declaration. Therefore, the host checks both
871-
* places, first looking up the property on the inner symbol, and if the property is not found it
872-
* will fall back to looking up the property on the outer symbol.
869+
* In some cases, a static property can either be set on the inner (implementation or adjacent)
870+
* declaration inside the class' IIFE, or it can be set on the outer variable declaration.
871+
* Therefore, the host checks all places, first looking up the property on the inner symbols, and
872+
* if the property is not found it will fall back to looking up the property on the outer symbol.
873873
*
874874
* @param symbol the class whose property we are interested in.
875875
* @param propertyName the name of static property.
876876
* @returns the symbol if it is found or `undefined` if not.
877877
*/
878878
protected getStaticProperty(symbol: NgccClassSymbol, propertyName: ts.__String): ts.Symbol
879879
|undefined {
880-
return symbol.implementation.exports && symbol.implementation.exports.get(propertyName) ||
881-
symbol.declaration.exports && symbol.declaration.exports.get(propertyName);
880+
return symbol.implementation.exports?.get(propertyName) ||
881+
symbol.adjacent?.exports?.get(propertyName) ||
882+
symbol.declaration.exports?.get(propertyName);
882883
}
883884

884885
/**

packages/compiler-cli/ngcc/test/host/esm2015_host_import_helper_spec.ts

+92
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,36 @@ runInEachFileSystem(() => {
9393
], SomeDirective);
9494
export { SomeDirective };
9595
`,
96+
},
97+
{
98+
name: _('/some_directive_ctor_parameters_iife.js'),
99+
contents: `
100+
import * as tslib_1 from 'tslib';
101+
import { Directive, Inject, InjectionToken, Input } from '@angular/core';
102+
const INJECTED_TOKEN = new InjectionToken('injected');
103+
let ViewContainerRef = /** class */ (() => { class ViewContainerRef {} return ViewContainerRef; })();
104+
let TemplateRef = /** class */ (() => { class TemplateRef {} return TemplateRef; })();
105+
let SomeDirective = /** @class */ (() => {
106+
let SomeDirective = class SomeDirective {
107+
constructor(_viewContainer, _template, injected) {
108+
this.input1 = '';
109+
}
110+
};
111+
SomeDirective.ctorParameters = () => [
112+
{ type: ViewContainerRef, },
113+
{ type: TemplateRef, },
114+
{ type: undefined, decorators: [{ type: Inject, args: [INJECTED_TOKEN,] },] },
115+
];
116+
tslib_1.__decorate([
117+
Input(),
118+
], SomeDirective.prototype, "input1", void 0);
119+
SomeDirective = tslib_1.__decorate([
120+
Directive({ selector: '[someDirective]' }),
121+
tslib_1.__param(2, Inject(INJECTED_TOKEN)),
122+
], SomeDirective);
123+
})();
124+
export { SomeDirective };
125+
`,
96126
},
97127
{
98128
name: _('/node_modules/@angular/core/some_directive.js'),
@@ -203,6 +233,27 @@ runInEachFileSystem(() => {
203233
]);
204234
});
205235

236+
it('should find the decorators on an IIFE wrapped class when mixing `ctorParameters` and `__decorate`',
237+
() => {
238+
const bundle = makeTestBundleProgram(_('/some_directive_ctor_parameters_iife.js'));
239+
const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle);
240+
const classNode = getDeclaration(
241+
bundle.program, _('/some_directive_ctor_parameters_iife.js'), 'SomeDirective',
242+
isNamedVariableDeclaration);
243+
const decorators = host.getDecoratorsOfDeclaration(classNode)!;
244+
245+
expect(decorators).toBeDefined();
246+
expect(decorators.length).toEqual(1);
247+
248+
const decorator = decorators[0];
249+
expect(decorator.name).toEqual('Directive');
250+
expect(decorator.identifier!.getText()).toEqual('Directive');
251+
expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'});
252+
expect(decorator.args!.map(arg => arg.getText())).toEqual([
253+
'{ selector: \'[someDirective]\' }',
254+
]);
255+
});
256+
206257
it('should support decorators being used inside @angular/core', () => {
207258
const bundle =
208259
makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js'));
@@ -260,6 +311,21 @@ runInEachFileSystem(() => {
260311
expect(input1.decorators!.map(d => d.name)).toEqual(['Input']);
261312
});
262313

314+
it('should find decorated members on an IIFE wrapped class when mixing `ctorParameters` and `__decorate`',
315+
() => {
316+
const bundle = makeTestBundleProgram(_('/some_directive_ctor_parameters_iife.js'));
317+
const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle);
318+
const classNode = getDeclaration(
319+
bundle.program, _('/some_directive_ctor_parameters_iife.js'), 'SomeDirective',
320+
isNamedVariableDeclaration);
321+
const members = host.getMembersOfClass(classNode);
322+
323+
const input1 = members.find(member => member.name === 'input1')!;
324+
expect(input1.kind).toEqual(ClassMemberKind.Property);
325+
expect(input1.isStatic).toEqual(false);
326+
expect(input1.decorators!.map(d => d.name)).toEqual(['Input']);
327+
});
328+
263329
it('should find non decorated properties on a class', () => {
264330
const bundle = makeTestBundleProgram(_('/some_directive.js'));
265331
const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle);
@@ -383,6 +449,32 @@ runInEachFileSystem(() => {
383449
});
384450
});
385451

452+
it('should find the decorated constructor parameters on an IIFE wrapped class when mixing `ctorParameters` and `__decorate`',
453+
() => {
454+
const bundle = makeTestBundleProgram(_('/some_directive_ctor_parameters_iife.js'));
455+
const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle);
456+
const classNode = getDeclaration(
457+
bundle.program, _('/some_directive_ctor_parameters_iife.js'), 'SomeDirective',
458+
isNamedVariableDeclaration);
459+
const parameters = host.getConstructorParameters(classNode);
460+
461+
expect(parameters).toBeDefined();
462+
expect(parameters!.map(parameter => parameter.name)).toEqual([
463+
'_viewContainer', '_template', 'injected'
464+
]);
465+
expectTypeValueReferencesForParameters(parameters!, [
466+
'ViewContainerRef',
467+
'TemplateRef',
468+
null,
469+
]);
470+
471+
const decorators = parameters![2].decorators!;
472+
expect(decorators.length).toEqual(1);
473+
expect(decorators[0].name).toBe('Inject');
474+
expect(decorators[0].import!.from).toBe('@angular/core');
475+
expect(decorators[0].import!.name).toBe('Inject');
476+
});
477+
386478
describe('getDeclarationOfIdentifier', () => {
387479
it('should return the declaration of a locally defined identifier', () => {
388480
const bundle = makeTestBundleProgram(_('/some_directive.js'));

0 commit comments

Comments
 (0)