fix(ngcc): support minified ES5 scenarios (#33777)
The reflection hosts have been updated to support the following code forms, which were found in some minified library code: * The class IIFE not being wrapped in parentheses. * Calls to `__decorate()` being combined with the IIFE return statement. PR Close #33777
This commit is contained in:

committed by
Kara Erickson

parent
d21471e24e
commit
1e1e242570
@ -142,6 +142,16 @@ var EmptyClass = (function() {
|
||||
}
|
||||
return EmptyClass;
|
||||
}());
|
||||
var NoParensClass = function() {
|
||||
function EmptyClass() {
|
||||
}
|
||||
return EmptyClass;
|
||||
}();
|
||||
var InnerParensClass = (function() {
|
||||
function EmptyClass() {
|
||||
}
|
||||
return EmptyClass;
|
||||
})();
|
||||
var NoDecoratorConstructorClass = (function() {
|
||||
function NoDecoratorConstructorClass(foo) {
|
||||
}
|
||||
@ -1855,6 +1865,40 @@ exports.ExternalModule = ExternalModule;
|
||||
expect(innerSymbol.implementation).toBe(outerSymbol.implementation);
|
||||
});
|
||||
|
||||
it('should return the class symbol for an ES5 class whose IIFE is not wrapped in parens',
|
||||
() => {
|
||||
loadTestFiles([SIMPLE_CLASS_FILE]);
|
||||
const {program, host: compilerHost} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
||||
const host =
|
||||
new CommonJsReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
const outerNode = getDeclaration(
|
||||
program, SIMPLE_CLASS_FILE.name, 'NoParensClass', isNamedVariableDeclaration);
|
||||
const innerNode =
|
||||
getIifeBody(outerNode) !.statements.find(isNamedFunctionDeclaration) !;
|
||||
const classSymbol = host.getClassSymbol(outerNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
expect(classSymbol !.declaration.valueDeclaration).toBe(outerNode);
|
||||
expect(classSymbol !.implementation.valueDeclaration).toBe(innerNode);
|
||||
});
|
||||
|
||||
it('should return the class symbol for an ES5 class whose IIFE is not wrapped with inner parens',
|
||||
() => {
|
||||
loadTestFiles([SIMPLE_CLASS_FILE]);
|
||||
const {program, host: compilerHost} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
||||
const host =
|
||||
new CommonJsReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
const outerNode = getDeclaration(
|
||||
program, SIMPLE_CLASS_FILE.name, 'InnerParensClass', isNamedVariableDeclaration);
|
||||
const innerNode =
|
||||
getIifeBody(outerNode) !.statements.find(isNamedFunctionDeclaration) !;
|
||||
const classSymbol = host.getClassSymbol(outerNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
expect(classSymbol !.declaration.valueDeclaration).toBe(outerNode);
|
||||
expect(classSymbol !.implementation.valueDeclaration).toBe(innerNode);
|
||||
});
|
||||
|
||||
it('should return undefined if node is not an ES5 class', () => {
|
||||
loadTestFiles([FOO_FUNCTION_FILE]);
|
||||
const {program, host: compilerHost} = makeTestBundleProgram(FOO_FUNCTION_FILE.name);
|
||||
|
@ -9,10 +9,10 @@ import * as ts from 'typescript';
|
||||
|
||||
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system';
|
||||
import {TestFile, runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
|
||||
import {ClassMemberKind, Import, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
|
||||
import {ClassMemberKind, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
|
||||
import {getDeclaration} from '../../../src/ngtsc/testing';
|
||||
import {loadFakeCore, loadTestFiles, loadTsLib} from '../../../test/helpers';
|
||||
import {Esm5ReflectionHost} from '../../src/host/esm5_host';
|
||||
import {Esm5ReflectionHost, getIifeBody} from '../../src/host/esm5_host';
|
||||
import {MockLogger} from '../helpers/mock_logger';
|
||||
import {convertToDirectTsLibImport, convertToInlineTsLib, makeTestBundleProgram} from '../helpers/utils';
|
||||
|
||||
@ -166,6 +166,22 @@ export { SomeDirective };
|
||||
export { HttpClientXsrfModule };
|
||||
`
|
||||
},
|
||||
{
|
||||
name: _('/some_minified_directive.js'),
|
||||
contents: `
|
||||
import * as tslib_1 from 'tslib';
|
||||
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
|
||||
return SomeDirective = tslib_1.__decorate([
|
||||
Directive({ selector: '[someDirective]' }),
|
||||
], SomeDirective);
|
||||
}());
|
||||
export { SomeDirective };
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
const DIRECT_IMPORT_FILES = convertToDirectTsLibImport(NAMESPACED_IMPORT_FILES);
|
||||
@ -207,6 +223,28 @@ export { SomeDirective };
|
||||
expect(decorator.args !.map(arg => arg.getText())).toEqual([
|
||||
'{ selector: \'[someDirective]\' }',
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
it('should find the decorators on a minified class', () => {
|
||||
const {program} = makeTestBundleProgram(_('/some_minified_directive.js'));
|
||||
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
const classNode = getDeclaration(
|
||||
program, _('/some_minified_directive.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 find the decorators on a class when mixing `ctorParameters` and `__decorate`',
|
||||
@ -253,6 +291,23 @@ export { SomeDirective };
|
||||
});
|
||||
});
|
||||
|
||||
describe('getClassSymbol()', () => {
|
||||
it('should find a class that has been minified', () => {
|
||||
const {program} = makeTestBundleProgram(_('/some_minified_directive.js'));
|
||||
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
const classNode = getDeclaration(
|
||||
program, _('/some_minified_directive.js'), 'SomeDirective',
|
||||
isNamedVariableDeclaration);
|
||||
const innerNode =
|
||||
getIifeBody(classNode) !.statements.find(isNamedFunctionDeclaration) !;
|
||||
const classSymbol = host.getClassSymbol(classNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
expect(classSymbol !.declaration.valueDeclaration).toBe(classNode);
|
||||
expect(classSymbol !.implementation.valueDeclaration).toBe(innerNode);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMembersOfClass()', () => {
|
||||
it('should find decorated members on a class', () => {
|
||||
const {program} = makeTestBundleProgram(_('/some_directive.js'));
|
||||
|
@ -187,6 +187,16 @@ runInEachFileSystem(() => {
|
||||
}
|
||||
return EmptyClass;
|
||||
}());
|
||||
var NoParensClass = function() {
|
||||
function EmptyClass() {
|
||||
}
|
||||
return EmptyClass;
|
||||
}();
|
||||
var InnerParensClass = (function() {
|
||||
function EmptyClass() {
|
||||
}
|
||||
return EmptyClass;
|
||||
})();
|
||||
var NoDecoratorConstructorClass = (function() {
|
||||
function NoDecoratorConstructorClass(foo) {
|
||||
}
|
||||
@ -2061,6 +2071,36 @@ runInEachFileSystem(() => {
|
||||
expect(innerSymbol.implementation).toBe(outerSymbol.implementation);
|
||||
});
|
||||
|
||||
it('should return the class symbol for an ES5 class whose IIFE is not wrapped in parens',
|
||||
() => {
|
||||
loadTestFiles([SIMPLE_CLASS_FILE]);
|
||||
const {program} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
||||
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
const outerNode = getDeclaration(
|
||||
program, SIMPLE_CLASS_FILE.name, 'NoParensClass', isNamedVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode) !.statements.find(isNamedFunctionDeclaration) !;
|
||||
const classSymbol = host.getClassSymbol(outerNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
expect(classSymbol !.declaration.valueDeclaration).toBe(outerNode);
|
||||
expect(classSymbol !.implementation.valueDeclaration).toBe(innerNode);
|
||||
});
|
||||
|
||||
it('should return the class symbol for an ES5 class whose IIFE is not wrapped with inner parens',
|
||||
() => {
|
||||
loadTestFiles([SIMPLE_CLASS_FILE]);
|
||||
const {program} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
||||
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
const outerNode = getDeclaration(
|
||||
program, SIMPLE_CLASS_FILE.name, 'InnerParensClass', isNamedVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode) !.statements.find(isNamedFunctionDeclaration) !;
|
||||
const classSymbol = host.getClassSymbol(outerNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
expect(classSymbol !.declaration.valueDeclaration).toBe(outerNode);
|
||||
expect(classSymbol !.implementation.valueDeclaration).toBe(innerNode);
|
||||
});
|
||||
|
||||
it('should return undefined if node is not an ES5 class', () => {
|
||||
loadTestFiles([FOO_FUNCTION_FILE]);
|
||||
const {program} = makeTestBundleProgram(FOO_FUNCTION_FILE.name);
|
||||
|
@ -158,6 +158,16 @@ runInEachFileSystem(() => {
|
||||
}
|
||||
return EmptyClass;
|
||||
}());
|
||||
var NoParensClass = function() {
|
||||
function EmptyClass() {
|
||||
}
|
||||
return EmptyClass;
|
||||
}();
|
||||
var InnerParensClass = (function() {
|
||||
function EmptyClass() {
|
||||
}
|
||||
return EmptyClass;
|
||||
})();
|
||||
var NoDecoratorConstructorClass = (function() {
|
||||
function NoDecoratorConstructorClass(foo) {
|
||||
}
|
||||
@ -1872,6 +1882,38 @@ runInEachFileSystem(() => {
|
||||
expect(innerSymbol.implementation).toBe(outerSymbol.implementation);
|
||||
});
|
||||
|
||||
it('should return the class symbol for an ES5 class whose IIFE is not wrapped in parens',
|
||||
() => {
|
||||
loadTestFiles([SIMPLE_CLASS_FILE]);
|
||||
const {program, host: compilerHost} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
||||
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
const outerNode = getDeclaration(
|
||||
program, SIMPLE_CLASS_FILE.name, 'NoParensClass', isNamedVariableDeclaration);
|
||||
const innerNode =
|
||||
getIifeBody(outerNode) !.statements.find(isNamedFunctionDeclaration) !;
|
||||
const classSymbol = host.getClassSymbol(outerNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
expect(classSymbol !.declaration.valueDeclaration).toBe(outerNode);
|
||||
expect(classSymbol !.implementation.valueDeclaration).toBe(innerNode);
|
||||
});
|
||||
|
||||
it('should return the class symbol for an ES5 class whose IIFE is not wrapped with inner parens',
|
||||
() => {
|
||||
loadTestFiles([SIMPLE_CLASS_FILE]);
|
||||
const {program, host: compilerHost} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
||||
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);
|
||||
const outerNode = getDeclaration(
|
||||
program, SIMPLE_CLASS_FILE.name, 'InnerParensClass', isNamedVariableDeclaration);
|
||||
const innerNode =
|
||||
getIifeBody(outerNode) !.statements.find(isNamedFunctionDeclaration) !;
|
||||
const classSymbol = host.getClassSymbol(outerNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
expect(classSymbol !.declaration.valueDeclaration).toBe(outerNode);
|
||||
expect(classSymbol !.implementation.valueDeclaration).toBe(innerNode);
|
||||
});
|
||||
|
||||
it('should return undefined if node is not an ES5 class', () => {
|
||||
loadTestFiles([FOO_FUNCTION_FILE]);
|
||||
const {program, host: compilerHost} = makeTestBundleProgram(FOO_FUNCTION_FILE.name);
|
||||
|
Reference in New Issue
Block a user