feat(ivy): ngcc - recognize static properties on the outer symbol in ES5 (#30795)
Packages that have been compiled using an older version of TypeScript can have their decorators at the top-level of the ES5 bundles, instead of inside the IIFE that is emitted for the class. Before this change, ngcc only took static property assignments inside the IIFE into account, therefore missing the decorators that were assigned at the top-level. This commit extends the ES5 host to look for static properties in two places. Testcases for all bundle formats that contain ES5 have been added to ensure that this works in the various flavours. A patch is included to support UMD bundles. The UMD factory affects how TypeScripts binds the static properties to symbols, see the docblock of the patch function for more details. PR Close #30795
This commit is contained in:
@ -57,6 +57,39 @@ const SOME_DIRECTIVE_FILE = {
|
||||
})));`,
|
||||
};
|
||||
|
||||
const TOPLEVEL_DECORATORS_FILE = {
|
||||
name: '/toplevel_decorators.umd.js',
|
||||
contents: `
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) :
|
||||
typeof define === 'function' && define.amd ? define('some_directive', ['exports', '@angular/core'], factory) :
|
||||
(factory(global.some_directive,global.ng.core));
|
||||
}(this, (function (exports,core) { 'use strict';
|
||||
|
||||
var INJECTED_TOKEN = new InjectionToken('injected');
|
||||
var ViewContainerRef = {};
|
||||
var TemplateRef = {};
|
||||
|
||||
var SomeDirective = (function() {
|
||||
function SomeDirective(_viewContainer, _template, injected) {}
|
||||
return SomeDirective;
|
||||
}());
|
||||
SomeDirective.decorators = [
|
||||
{ type: core.Directive, args: [{ selector: '[someDirective]' },] }
|
||||
];
|
||||
SomeDirective.ctorParameters = function() { return [
|
||||
{ type: ViewContainerRef, },
|
||||
{ type: TemplateRef, },
|
||||
{ type: undefined, decorators: [{ type: core.Inject, args: [INJECTED_TOKEN,] },] },
|
||||
]; };
|
||||
SomeDirective.propDecorators = {
|
||||
"input1": [{ type: core.Input },],
|
||||
"input2": [{ type: core.Input },],
|
||||
};
|
||||
exports.SomeDirective = SomeDirective;
|
||||
})));`,
|
||||
};
|
||||
|
||||
const SIMPLE_ES2015_CLASS_FILE = {
|
||||
name: '/simple_es2015_class.d.ts',
|
||||
contents: `
|
||||
@ -864,6 +897,24 @@ describe('UmdReflectionHost', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should find the decorators on a class at the top level', () => {
|
||||
const {program, host: compilerHost} = makeTestBundleProgram([TOPLEVEL_DECORATORS_FILE]);
|
||||
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
const classNode = getDeclaration(
|
||||
program, TOPLEVEL_DECORATORS_FILE.name, '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.import).toEqual({name: 'Directive', from: '@angular/core'});
|
||||
expect(decorator.args !.map(arg => arg.getText())).toEqual([
|
||||
'{ selector: \'[someDirective]\' }',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return null if the symbol is not a class', () => {
|
||||
const {program, host: compilerHost} = makeTestBundleProgram([FOO_FUNCTION_FILE]);
|
||||
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
@ -1002,6 +1053,24 @@ describe('UmdReflectionHost', () => {
|
||||
expect(input1.decorators !.map(d => d.name)).toEqual(['Input']);
|
||||
});
|
||||
|
||||
it('should find decorated members on a class at the top level', () => {
|
||||
const {program, host: compilerHost} = makeTestBundleProgram([TOPLEVEL_DECORATORS_FILE]);
|
||||
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
const classNode = getDeclaration(
|
||||
program, TOPLEVEL_DECORATORS_FILE.name, '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']);
|
||||
|
||||
const input2 = members.find(member => member.name === 'input2') !;
|
||||
expect(input2.kind).toEqual(ClassMemberKind.Property);
|
||||
expect(input2.isStatic).toEqual(false);
|
||||
expect(input1.decorators !.map(d => d.name)).toEqual(['Input']);
|
||||
});
|
||||
|
||||
it('should find non decorated properties on a class', () => {
|
||||
const {program, host: compilerHost} = makeTestBundleProgram([SOME_DIRECTIVE_FILE]);
|
||||
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
@ -1203,6 +1272,24 @@ describe('UmdReflectionHost', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should find the decorated constructor parameters at the top level', () => {
|
||||
const {program, host: compilerHost} = makeTestBundleProgram([TOPLEVEL_DECORATORS_FILE]);
|
||||
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
const classNode = getDeclaration(
|
||||
program, TOPLEVEL_DECORATORS_FILE.name, '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,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should throw if the symbol is not a class', () => {
|
||||
const {program, host: compilerHost} = makeTestBundleProgram([FOO_FUNCTION_FILE]);
|
||||
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
|
Reference in New Issue
Block a user