diff --git a/packages/compiler-cli/ngcc/src/host/esm2015_host.ts b/packages/compiler-cli/ngcc/src/host/esm2015_host.ts index 7d5f30fed9..4e2b515a3d 100644 --- a/packages/compiler-cli/ngcc/src/host/esm2015_host.ts +++ b/packages/compiler-cli/ngcc/src/host/esm2015_host.ts @@ -866,10 +866,10 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N /** * Try to retrieve the symbol of a static property on a class. * - * In some cases, a static property can either be set on the inner declaration inside the class' - * IIFE, or it can be set on the outer variable declaration. Therefore, the host checks both - * places, first looking up the property on the inner symbol, and if the property is not found it - * will fall back to looking up the property on the outer symbol. + * In some cases, a static property can either be set on the inner (implementation or adjacent) + * declaration inside the class' IIFE, or it can be set on the outer variable declaration. + * Therefore, the host checks all places, first looking up the property on the inner symbols, and + * if the property is not found it will fall back to looking up the property on the outer symbol. * * @param symbol the class whose property we are interested in. * @param propertyName the name of static property. @@ -877,8 +877,9 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N */ protected getStaticProperty(symbol: NgccClassSymbol, propertyName: ts.__String): ts.Symbol |undefined { - return symbol.implementation.exports && symbol.implementation.exports.get(propertyName) || - symbol.declaration.exports && symbol.declaration.exports.get(propertyName); + return symbol.implementation.exports?.get(propertyName) || + symbol.adjacent?.exports?.get(propertyName) || + symbol.declaration.exports?.get(propertyName); } /** diff --git a/packages/compiler-cli/ngcc/test/host/esm2015_host_import_helper_spec.ts b/packages/compiler-cli/ngcc/test/host/esm2015_host_import_helper_spec.ts index 7fba722593..a87e584794 100644 --- a/packages/compiler-cli/ngcc/test/host/esm2015_host_import_helper_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/esm2015_host_import_helper_spec.ts @@ -93,6 +93,36 @@ runInEachFileSystem(() => { ], SomeDirective); export { SomeDirective }; `, + }, + { + name: _('/some_directive_ctor_parameters_iife.js'), + contents: ` + import * as tslib_1 from 'tslib'; + import { Directive, Inject, InjectionToken, Input } from '@angular/core'; + const INJECTED_TOKEN = new InjectionToken('injected'); + let ViewContainerRef = /** class */ (() => { class ViewContainerRef {} return ViewContainerRef; })(); + let TemplateRef = /** class */ (() => { class TemplateRef {} return TemplateRef; })(); + let SomeDirective = /** @class */ (() => { + let SomeDirective = class SomeDirective { + constructor(_viewContainer, _template, injected) { + this.input1 = ''; + } + }; + SomeDirective.ctorParameters = () => [ + { type: ViewContainerRef, }, + { type: TemplateRef, }, + { type: undefined, decorators: [{ type: Inject, args: [INJECTED_TOKEN,] },] }, + ]; + tslib_1.__decorate([ + Input(), + ], SomeDirective.prototype, "input1", void 0); + SomeDirective = tslib_1.__decorate([ + Directive({ selector: '[someDirective]' }), + tslib_1.__param(2, Inject(INJECTED_TOKEN)), + ], SomeDirective); + })(); + export { SomeDirective }; + `, }, { name: _('/node_modules/@angular/core/some_directive.js'), @@ -203,6 +233,27 @@ runInEachFileSystem(() => { ]); }); + it('should find the decorators on an IIFE wrapped class when mixing `ctorParameters` and `__decorate`', + () => { + const bundle = makeTestBundleProgram(_('/some_directive_ctor_parameters_iife.js')); + const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); + const classNode = getDeclaration( + bundle.program, _('/some_directive_ctor_parameters_iife.js'), 'SomeDirective', + isNamedVariableDeclaration); + const decorators = host.getDecoratorsOfDeclaration(classNode)!; + + expect(decorators).toBeDefined(); + expect(decorators.length).toEqual(1); + + const decorator = decorators[0]; + expect(decorator.name).toEqual('Directive'); + expect(decorator.identifier!.getText()).toEqual('Directive'); + expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); + expect(decorator.args!.map(arg => arg.getText())).toEqual([ + '{ selector: \'[someDirective]\' }', + ]); + }); + it('should support decorators being used inside @angular/core', () => { const bundle = makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js')); @@ -260,6 +311,21 @@ runInEachFileSystem(() => { expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); }); + it('should find decorated members on an IIFE wrapped class when mixing `ctorParameters` and `__decorate`', + () => { + const bundle = makeTestBundleProgram(_('/some_directive_ctor_parameters_iife.js')); + const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); + const classNode = getDeclaration( + bundle.program, _('/some_directive_ctor_parameters_iife.js'), 'SomeDirective', + isNamedVariableDeclaration); + const members = host.getMembersOfClass(classNode); + + const input1 = members.find(member => member.name === 'input1')!; + expect(input1.kind).toEqual(ClassMemberKind.Property); + expect(input1.isStatic).toEqual(false); + expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); + }); + it('should find non decorated properties on a class', () => { const bundle = makeTestBundleProgram(_('/some_directive.js')); const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); @@ -383,6 +449,32 @@ runInEachFileSystem(() => { }); }); + it('should find the decorated constructor parameters on an IIFE wrapped class when mixing `ctorParameters` and `__decorate`', + () => { + const bundle = makeTestBundleProgram(_('/some_directive_ctor_parameters_iife.js')); + const host = new Esm2015ReflectionHost(new MockLogger(), false, bundle); + const classNode = getDeclaration( + bundle.program, _('/some_directive_ctor_parameters_iife.js'), 'SomeDirective', + isNamedVariableDeclaration); + const parameters = host.getConstructorParameters(classNode); + + expect(parameters).toBeDefined(); + expect(parameters!.map(parameter => parameter.name)).toEqual([ + '_viewContainer', '_template', 'injected' + ]); + expectTypeValueReferencesForParameters(parameters!, [ + 'ViewContainerRef', + 'TemplateRef', + null, + ]); + + const decorators = parameters![2].decorators!; + expect(decorators.length).toEqual(1); + expect(decorators[0].name).toBe('Inject'); + expect(decorators[0].import!.from).toBe('@angular/core'); + expect(decorators[0].import!.name).toBe('Inject'); + }); + describe('getDeclarationOfIdentifier', () => { it('should return the declaration of a locally defined identifier', () => { const bundle = makeTestBundleProgram(_('/some_directive.js'));