fix(ngcc): handle presence of both ctorParameters
and __decorate
(#32901)
Recently ng-packagr was updated to include a transform that used to be done in tsickle (https://github.com/ng-packagr/ng-packagr/pull/1401), where only constructor parameter decorators are emitted in tsickle's format, not any of the other decorators. ngcc used to extract decorators from only a single format, so once it saw the `ctorParameters` static property it assumed the library is using the tsickle format. Therefore, none of the `__decorate` calls were considered. This resulted in missing decorator information, preventing proper processing of a package. This commit changes how decorators are extracted by always looking at both the static properties and the `__decorate` calls, merging these sources appropriately. Resolves FW-1573 PR Close #32901
This commit is contained in:
parent
002a97d852
commit
747f0cff9e
@ -655,12 +655,16 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||||||
return this.decoratorCache.get(decl) !;
|
return this.decoratorCache.get(decl) !;
|
||||||
}
|
}
|
||||||
|
|
||||||
// First attempt extracting decorators from static properties.
|
// Extract decorators from static properties and `__decorate` helper calls, then merge them
|
||||||
let decoratorInfo = this.computeDecoratorInfoFromStaticProperties(classSymbol);
|
// together where the information from the static properties is preferred.
|
||||||
if (decoratorInfo === null) {
|
const staticProps = this.computeDecoratorInfoFromStaticProperties(classSymbol);
|
||||||
// If none were present, use the `__decorate` helper calls instead.
|
const helperCalls = this.computeDecoratorInfoFromHelperCalls(classSymbol);
|
||||||
decoratorInfo = this.computeDecoratorInfoFromHelperCalls(classSymbol);
|
|
||||||
}
|
const decoratorInfo: DecoratorInfo = {
|
||||||
|
classDecorators: staticProps.classDecorators || helperCalls.classDecorators,
|
||||||
|
memberDecorators: staticProps.memberDecorators || helperCalls.memberDecorators,
|
||||||
|
constructorParamInfo: staticProps.constructorParamInfo || helperCalls.constructorParamInfo,
|
||||||
|
};
|
||||||
|
|
||||||
this.decoratorCache.set(decl, decoratorInfo);
|
this.decoratorCache.set(decl, decoratorInfo);
|
||||||
return decoratorInfo;
|
return decoratorInfo;
|
||||||
@ -676,8 +680,10 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||||||
* @returns All information on the decorators as extracted from static properties, or `null` if
|
* @returns All information on the decorators as extracted from static properties, or `null` if
|
||||||
* none of the static properties exist.
|
* none of the static properties exist.
|
||||||
*/
|
*/
|
||||||
protected computeDecoratorInfoFromStaticProperties(classSymbol: NgccClassSymbol): DecoratorInfo
|
protected computeDecoratorInfoFromStaticProperties(classSymbol: NgccClassSymbol): {
|
||||||
|null {
|
classDecorators: Decorator[] | null; memberDecorators: Map<string, Decorator[]>| null;
|
||||||
|
constructorParamInfo: ParamInfo[] | null;
|
||||||
|
} {
|
||||||
let classDecorators: Decorator[]|null = null;
|
let classDecorators: Decorator[]|null = null;
|
||||||
let memberDecorators: Map<string, Decorator[]>|null = null;
|
let memberDecorators: Map<string, Decorator[]>|null = null;
|
||||||
let constructorParamInfo: ParamInfo[]|null = null;
|
let constructorParamInfo: ParamInfo[]|null = null;
|
||||||
@ -697,16 +703,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||||||
constructorParamInfo = this.getParamInfoFromStaticProperty(constructorParamsProperty);
|
constructorParamInfo = this.getParamInfoFromStaticProperty(constructorParamsProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If none of the static properties were present, no decorator info could be computed.
|
return {classDecorators, memberDecorators, constructorParamInfo};
|
||||||
if (classDecorators === null && memberDecorators === null && constructorParamInfo === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
classDecorators,
|
|
||||||
memberDecorators: memberDecorators || new Map<string, Decorator[]>(),
|
|
||||||
constructorParamInfo: constructorParamInfo || [],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,6 +62,36 @@ runInEachFileSystem(() => {
|
|||||||
TemplateRef, String])
|
TemplateRef, String])
|
||||||
], SomeDirective);
|
], SomeDirective);
|
||||||
export { SomeDirective };
|
export { SomeDirective };
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: _('/some_directive_ctor_parameters.js'),
|
||||||
|
contents: `
|
||||||
|
import * as tslib_1 from 'tslib';
|
||||||
|
import { Directive, Inject, InjectionToken, Input } from '@angular/core';
|
||||||
|
const INJECTED_TOKEN = new InjectionToken('injected');
|
||||||
|
class ViewContainerRef {
|
||||||
|
}
|
||||||
|
class TemplateRef {
|
||||||
|
}
|
||||||
|
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 };
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -152,6 +182,28 @@ runInEachFileSystem(() => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should find the decorators on a class when mixing `ctorParameters` and `__decorate`',
|
||||||
|
() => {
|
||||||
|
const {program} = makeTestBundleProgram(_('/some_directive_ctor_parameters.js'));
|
||||||
|
const host =
|
||||||
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||||
|
const classNode = getDeclaration(
|
||||||
|
program, _('/some_directive_ctor_parameters.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', () => {
|
it('should support decorators being used inside @angular/core', () => {
|
||||||
const {program} =
|
const {program} =
|
||||||
makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js'));
|
makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js'));
|
||||||
@ -195,6 +247,22 @@ runInEachFileSystem(() => {
|
|||||||
expect(input1.decorators !.map(d => d.name)).toEqual(['Input']);
|
expect(input1.decorators !.map(d => d.name)).toEqual(['Input']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should find decorated members on a class when mixing `ctorParameters` and `__decorate`',
|
||||||
|
() => {
|
||||||
|
const {program} = makeTestBundleProgram(_('/some_directive_ctor_parameters.js'));
|
||||||
|
const host =
|
||||||
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||||
|
const classNode = getDeclaration(
|
||||||
|
program, _('/some_directive_ctor_parameters.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', () => {
|
it('should find non decorated properties on a class', () => {
|
||||||
const {program} = makeTestBundleProgram(_('/some_directive.js'));
|
const {program} = makeTestBundleProgram(_('/some_directive.js'));
|
||||||
const host =
|
const host =
|
||||||
@ -292,6 +360,27 @@ runInEachFileSystem(() => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should find the decorated constructor parameters when mixing `ctorParameters` and `__decorate`',
|
||||||
|
() => {
|
||||||
|
const {program} = makeTestBundleProgram(_('/some_directive_ctor_parameters.js'));
|
||||||
|
const host =
|
||||||
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||||
|
const classNode = getDeclaration(
|
||||||
|
program, _('/some_directive_ctor_parameters.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,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
describe('(returned parameters `decorators`)', () => {
|
describe('(returned parameters `decorators`)', () => {
|
||||||
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
|
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
|
||||||
const mockImportInfo = {} as Import;
|
const mockImportInfo = {} as Import;
|
||||||
|
@ -76,6 +76,43 @@ runInEachFileSystem(() => {
|
|||||||
return SomeDirective;
|
return SomeDirective;
|
||||||
}());
|
}());
|
||||||
export { SomeDirective };
|
export { SomeDirective };
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: _('/some_directive_ctor_parameters.js'),
|
||||||
|
contents: `
|
||||||
|
import * as tslib_1 from 'tslib';
|
||||||
|
import { Directive, Inject, InjectionToken, Input } from '@angular/core';
|
||||||
|
var INJECTED_TOKEN = new InjectionToken('injected');
|
||||||
|
var ViewContainerRef = /** @class */ (function () {
|
||||||
|
function ViewContainerRef() {
|
||||||
|
}
|
||||||
|
return ViewContainerRef;
|
||||||
|
}());
|
||||||
|
var TemplateRef = /** @class */ (function () {
|
||||||
|
function TemplateRef() {
|
||||||
|
}
|
||||||
|
return TemplateRef;
|
||||||
|
}());
|
||||||
|
var SomeDirective = /** @class */ (function () {
|
||||||
|
function SomeDirective(_viewContainer, _template, injected) {
|
||||||
|
this.input1 = '';
|
||||||
|
}
|
||||||
|
SomeDirective.ctorParameters = function() { return [
|
||||||
|
{ 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);
|
||||||
|
return SomeDirective;
|
||||||
|
}());
|
||||||
|
export { SomeDirective };
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -172,6 +209,28 @@ export { SomeDirective };
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should find the decorators on a class when mixing `ctorParameters` and `__decorate`',
|
||||||
|
() => {
|
||||||
|
const {program} = makeTestBundleProgram(_('/some_directive_ctor_parameters.js'));
|
||||||
|
const host =
|
||||||
|
new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||||
|
const classNode = getDeclaration(
|
||||||
|
program, _('/some_directive_ctor_parameters.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', () => {
|
it('should support decorators being used inside @angular/core', () => {
|
||||||
const {program} =
|
const {program} =
|
||||||
makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js'));
|
makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js'));
|
||||||
@ -213,6 +272,22 @@ export { SomeDirective };
|
|||||||
expect(input1.decorators !.map(d => d.name)).toEqual(['Input']);
|
expect(input1.decorators !.map(d => d.name)).toEqual(['Input']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should find decorated members on a class when mixing `ctorParameters` and `__decorate`',
|
||||||
|
() => {
|
||||||
|
const {program} = makeTestBundleProgram(_('/some_directive_ctor_parameters.js'));
|
||||||
|
const host =
|
||||||
|
new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||||
|
const classNode = getDeclaration(
|
||||||
|
program, _('/some_directive_ctor_parameters.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', () => {
|
it('should find non decorated properties on a class', () => {
|
||||||
const {program} = makeTestBundleProgram(_('/some_directive.js'));
|
const {program} = makeTestBundleProgram(_('/some_directive.js'));
|
||||||
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||||
@ -288,6 +363,27 @@ export { SomeDirective };
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should find the decorated constructor parameters when mixing `ctorParameters` and `__decorate`',
|
||||||
|
() => {
|
||||||
|
const {program} = makeTestBundleProgram(_('/some_directive_ctor_parameters.js'));
|
||||||
|
const host =
|
||||||
|
new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||||
|
const classNode = getDeclaration(
|
||||||
|
program, _('/some_directive_ctor_parameters.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,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
describe('(returned parameters `decorators`)', () => {
|
describe('(returned parameters `decorators`)', () => {
|
||||||
it('should have import information on decorators', () => {
|
it('should have import information on decorators', () => {
|
||||||
const {program} = makeTestBundleProgram(_('/some_directive.js'));
|
const {program} = makeTestBundleProgram(_('/some_directive.js'));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user