fix(ivy): ngcc - identify all ESM5 decorated classes (#27848)

In ESM5 decorated classes can be indicated by calls to `__decorate()`.
Previously the `ReflectionHost.findDecoratedClasses()` call would identify
helper calls of the form:

```
SomeClass = tslib_1.__decorate(...);
```

But it was missing calls of the form:

```
SomeClass = SomeClass_1 = tslib_1.__decorate(...);
```

This form is common in `@NgModule()` decorations, where the class
being decorated is referenced inside the decorator or another
member.

This commit now ensures that a chain of assignments, of any length,
is now identified as a class decoration if it results in a call to
`__decorate()`.

Fixes #27841

PR Close #27848
This commit is contained in:
Pete Bacon Darwin
2018-12-27 18:49:03 +00:00
committed by Andrew Kushnir
parent d505468fb7
commit e31afb7118
4 changed files with 37 additions and 14 deletions

View File

@ -661,8 +661,10 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
*/
protected getHelperCall(statement: ts.Statement, helperName: string): ts.CallExpression|null {
if (ts.isExpressionStatement(statement)) {
const expression =
isAssignmentStatement(statement) ? statement.expression.right : statement.expression;
let expression = statement.expression;
while (isAssignment(expression)) {
expression = expression.right;
}
if (ts.isCallExpression(expression) && getCalleeName(expression) === helperName) {
return expression;
}

View File

@ -304,6 +304,27 @@ describe('Esm5ReflectionHost [import helper style]', () => {
});
});
describe('findDecoratedClasses', () => {
it('should return an array of all decorated classes in the given source file', () => {
const program = makeTestProgram(...fileSystem.files);
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
const ngModuleFile = program.getSourceFile('/ngmodule.js') !;
const ngModuleClasses = host.findDecoratedClasses(ngModuleFile);
expect(ngModuleClasses.length).toEqual(1);
const ngModuleClass = ngModuleClasses.find(c => c.name === 'HttpClientXsrfModule') !;
expect(ngModuleClass.decorators.map(decorator => decorator.name)).toEqual(['NgModule']);
const someDirectiveFile = program.getSourceFile('/some_directive.js') !;
const someDirectiveClasses = host.findDecoratedClasses(someDirectiveFile);
expect(someDirectiveClasses.length).toEqual(1);
const someDirectiveClass = someDirectiveClasses.find(c => c.name === 'SomeDirective') !;
expect(someDirectiveClass.decorators.map(decorator => decorator.name)).toEqual([
'Directive'
]);
});
});
describe('getDeclarationOfIdentifier', () => {
it('should return the declaration of a locally defined identifier', () => {
const program = makeTestProgram(fileSystem.files[0]);