fix(ngcc): correctly associate decorators with aliased classes (#33878)
In flat bundle formats, multiple classes that have the same name can be suffixed to become unique. In ES5-like bundles this results in the outer declaration from having a different name from the "implementation" declaration within the class' IIFE, as the implementation declaration may not have been suffixed. As an example, the following code would fail to have a `Directive` decorator as ngcc would search for `__decorate` calls that refer to `AliasedDirective$1` by name, whereas the `__decorate` call actually uses the `AliasedDirective` name. ```javascript var AliasedDirective$1 = /** @class */ (function () { function AliasedDirective() {} AliasedDirective = tslib_1.__decorate([ Directive({ selector: '[someDirective]' }), ], AliasedDirective); return AliasedDirective; }()); ``` This commit fixes the problem by not relying on comparing names, but instead finding the declaration and matching it with both the outer and inner declaration. PR Close #33878
This commit is contained in:
@ -68,6 +68,15 @@ __decorate([
|
||||
core.Input(),
|
||||
], OtherDirective.prototype, "input2", void 0);
|
||||
exports.OtherDirective = OtherDirective;
|
||||
|
||||
var AliasedDirective$1 = (function () {
|
||||
function AliasedDirective() {}
|
||||
return AliasedDirective;
|
||||
}());
|
||||
AliasedDirective$1 = __decorate([
|
||||
core.Directive({ selector: '[someDirective]' }),
|
||||
], AliasedDirective$1);
|
||||
exports.AliasedDirective$1 = AliasedDirective$1;
|
||||
`
|
||||
};
|
||||
});
|
||||
@ -92,6 +101,27 @@ exports.OtherDirective = OtherDirective;
|
||||
'{ selector: \'[someDirective]\' }',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should find the decorators on an aliased class at the top level', () => {
|
||||
loadFakeCore(getFileSystem());
|
||||
loadTestFiles([TOPLEVEL_DECORATORS_FILE]);
|
||||
const {program, host: compilerHost} = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name);
|
||||
const host = new CommonJsReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
const classNode = getDeclaration(
|
||||
program, TOPLEVEL_DECORATORS_FILE.name, 'AliasedDirective$1',
|
||||
isNamedVariableDeclaration);
|
||||
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
|
||||
|
||||
expect(decorators.length).toEqual(1);
|
||||
|
||||
const decorator = decorators[0];
|
||||
expect(decorator.name).toEqual('Directive');
|
||||
expect(decorator.identifier !.getText()).toEqual('core.Directive');
|
||||
expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'});
|
||||
expect(decorator.args !.map(arg => arg.getText())).toEqual([
|
||||
'{ selector: \'[someDirective]\' }',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -174,12 +174,27 @@ import { Directive } from '@angular/core';
|
||||
// Note that the IIFE is not in parentheses
|
||||
var SomeDirective = function () {
|
||||
function SomeDirective() {}
|
||||
// Note that the decorator is combined with the return statment
|
||||
// Note that the decorator is combined with the return statement
|
||||
return SomeDirective = tslib_1.__decorate([
|
||||
Directive({ selector: '[someDirective]' }),
|
||||
], SomeDirective);
|
||||
}());
|
||||
}();
|
||||
export { SomeDirective };
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: _('/some_aliased_directive.js'),
|
||||
contents: `
|
||||
import * as tslib_1 from 'tslib';
|
||||
import { Directive } from '@angular/core';
|
||||
var AliasedDirective$1 = /** @class */ (function () {
|
||||
function AliasedDirective() {}
|
||||
AliasedDirective = tslib_1.__decorate([
|
||||
Directive({ selector: '[someDirective]' }),
|
||||
], AliasedDirective);
|
||||
return AliasedDirective;
|
||||
}());
|
||||
export { AliasedDirective$1 };
|
||||
`,
|
||||
},
|
||||
];
|
||||
@ -247,6 +262,26 @@ export { SomeDirective };
|
||||
|
||||
});
|
||||
|
||||
it('should find the decorators on an aliased class', () => {
|
||||
const {program} = makeTestBundleProgram(_('/some_aliased_directive.js'));
|
||||
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
const classNode = getDeclaration(
|
||||
program, _('/some_aliased_directive.js'), 'AliasedDirective$1',
|
||||
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 find the decorators on a class when mixing `ctorParameters` and `__decorate`',
|
||||
() => {
|
||||
const {program} = makeTestBundleProgram(_('/some_directive_ctor_parameters.js'));
|
||||
|
@ -61,6 +61,15 @@ runInEachFileSystem(() => {
|
||||
return SomeDirective;
|
||||
}());
|
||||
exports.SomeDirective = SomeDirective;
|
||||
|
||||
var AliasedDirective$1 = /** @class */ (function () {
|
||||
function AliasedDirective() {}
|
||||
AliasedDirective = __decorate([
|
||||
core.Directive({ selector: '[someDirective]' }),
|
||||
], AliasedDirective);
|
||||
return AliasedDirective;
|
||||
}());
|
||||
exports.AliasedDirective$1 = AliasedDirective$1;
|
||||
})));`,
|
||||
};
|
||||
});
|
||||
@ -85,6 +94,26 @@ runInEachFileSystem(() => {
|
||||
'{ selector: \'[someDirective]\' }',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should find the decorators on an aliased class', () => {
|
||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||
const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
const classNode = getDeclaration(
|
||||
program, SOME_DIRECTIVE_FILE.name, 'AliasedDirective$1', 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('core.Directive');
|
||||
expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'});
|
||||
expect(decorator.args !.map(arg => arg.getText())).toEqual([
|
||||
'{ selector: \'[someDirective]\' }',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMembersOfClass()', () => {
|
||||
|
Reference in New Issue
Block a user