fix(ngcc): generate correct metadata for classes with getter/setter properties (#33514)
While processing class metadata, ngtsc generates a `setClassMetadata()` call which (among other things) contains info about property decorators. Previously, processing getter/setter pairs with some of ngcc's `ReflectionHost`s resulted in multiple metadata entries for the same property, which resulted in duplicate object keys, which in turn causes an error in ES5 strict mode. This commit fixes it by ensuring that there are no duplicate property names in the `setClassMetadata()` calls. In addition, `generateSetClassMetadataCall()` is updated to treat `ClassMember#decorators: []` the same as `ClassMember.decorators: null` (i.e. omitting the `ClassMember` from the generated `setClassMetadata()` call). Alternatively, ngcc's `ReflectionHost`s could be updated to do this transformation (`decorators: []` --> `decorators: null`) when reflecting on class members, but this would require changes in many places and be less future-proof. For example, given a class such as: ```ts class Foo { @Input() get bar() { return 'bar'; } set bar(value: any) {} } ``` ...previously the generated `setClassMetadata()` call would look like: ```ts ɵsetClassMetadata(..., { bar: [{type: Input}], bar: [], }); ``` The same class will now result in a call like: ```ts ɵsetClassMetadata(..., { bar: [{type: Input}], }); ``` Fixes #30569 PR Close #33514
This commit is contained in:

committed by
Kara Erickson

parent
c79d50f38f
commit
704775168d
@ -112,6 +112,41 @@ runInEachFileSystem(() => {
|
||||
expect(loadPackage('local-package', _('/dist')).__processed_by_ivy_ngcc__).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should generate correct metadata for decorated getter/setter properties', () => {
|
||||
genNodeModules({
|
||||
'test-package': {
|
||||
'/index.ts': `
|
||||
import {Directive, Input, NgModule} from '@angular/core';
|
||||
|
||||
@Directive({selector: '[foo]'})
|
||||
export class FooDirective {
|
||||
@Input() get bar() { return 'bar'; }
|
||||
set bar(value: string) {}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [FooDirective],
|
||||
})
|
||||
export class FooModule {}
|
||||
`,
|
||||
},
|
||||
});
|
||||
|
||||
mainNgcc({
|
||||
basePath: '/node_modules',
|
||||
targetEntryPointPath: 'test-package',
|
||||
propertiesToConsider: ['main'],
|
||||
});
|
||||
|
||||
const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`)).replace(/\s+/g, ' ');
|
||||
expect(jsContents)
|
||||
.toContain(
|
||||
'/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(FooDirective, ' +
|
||||
'[{ type: Directive, args: [{ selector: \'[foo]\' }] }], ' +
|
||||
'function () { return []; }, ' +
|
||||
'{ bar: [{ type: Input }] });');
|
||||
});
|
||||
|
||||
describe('in async mode', () => {
|
||||
it('should run ngcc without errors for fesm2015', async() => {
|
||||
const promise = mainNgcc({
|
||||
|
@ -264,7 +264,7 @@ A.ɵdir = ɵngcc0.ɵɵdefineDirective({ type: A, selectors: [["", "a", ""]] });`
|
||||
.toEqual(`/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{
|
||||
type: Directive,
|
||||
args: [{ selector: '[a]' }]
|
||||
}], null, { foo: [] });`);
|
||||
}], null, null);`);
|
||||
});
|
||||
|
||||
it('should call removeDecorators with the source code, a map of class decorators that have been analyzed',
|
||||
|
Reference in New Issue
Block a user