fix(ngcc): find decorated constructor params on IIFE wrapped classes (#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 #37330 PR Close #37436
This commit is contained in:
parent
d4c0962c7b
commit
818d93d7e9
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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'));
|
||||
|
|
Loading…
Reference in New Issue