fix(ivy): ngcc - render namespaced imported decorators correctly (#31426)

The support for decorators that were imported via a namespace,
e.g. `import * as core from `@angular/core` was implemented
piecemeal. This meant that it was easy to miss situations where
a decorator identifier needed to be handled as a namepsaced
import rather than a direct import.

One such issue was that UMD processing of decorators was not
correct: the namespace was being omitted from references to
decorators.

Now the types have been modified to make it clear that a
`Decorator.identifier` could hold a namespaced identifier,
and the corresponding code that uses these types has been
fixed.

Fixes #31394

PR Close #31426
This commit is contained in:
Pete Bacon Darwin
2019-07-05 11:19:11 +01:00
committed by Miško Hevery
parent b66d82e308
commit dd664f694c
6 changed files with 73 additions and 89 deletions

View File

@ -9,7 +9,7 @@
import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../../src/ngtsc/file_system';
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, Import, TypeScriptReflectionHost, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, Import, TypeScriptReflectionHost, isDecoratorIdentifier, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
import {Logger} from '../logging/logger';
import {BundleProgram} from '../packages/bundle_program';
import {findAll, getNameText, hasNameIdentifier, isDefined} from '../utils';
@ -803,12 +803,11 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
* is not a valid decorator call.
*/
protected reflectDecoratorCall(call: ts.CallExpression): Decorator|null {
// The call could be of the form `Decorator(...)` or `namespace_1.Decorator(...)`
const decoratorExpression =
ts.isPropertyAccessExpression(call.expression) ? call.expression.name : call.expression;
if (ts.isIdentifier(decoratorExpression)) {
const decoratorExpression = call.expression;
if (isDecoratorIdentifier(decoratorExpression)) {
// We found a decorator!
const decoratorIdentifier = decoratorExpression;
const decoratorIdentifier =
ts.isIdentifier(decoratorExpression) ? decoratorExpression : decoratorExpression.name;
return {
name: decoratorIdentifier.text,
identifier: decoratorIdentifier,
@ -872,16 +871,14 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
// Is the value of the `type` property an identifier?
if (decorator.has('type')) {
let typeIdentifier = decorator.get('type') !;
if (ts.isPropertyAccessExpression(typeIdentifier)) {
// the type is in a namespace, e.g. `core.Directive`
typeIdentifier = typeIdentifier.name;
}
if (ts.isIdentifier(typeIdentifier)) {
let decoratorType = decorator.get('type') !;
if (isDecoratorIdentifier(decoratorType)) {
const decoratorIdentifier =
ts.isIdentifier(decoratorType) ? decoratorType : decoratorType.name;
decorators.push({
name: typeIdentifier.text,
identifier: typeIdentifier,
import: this.getImportOfIdentifier(typeIdentifier), node,
name: decoratorIdentifier.text,
identifier: decoratorType,
import: this.getImportOfIdentifier(decoratorIdentifier), node,
args: getDecoratorArgs(node),
});
}
@ -1223,51 +1220,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
return Array.from(classSymbol.valueDeclaration.getSourceFile().statements);
}
/**
* Try to get the import info for this identifier as though it is a namespaced import.
* For example, if the identifier is the `__metadata` part of a property access chain like:
*
* ```
* tslib_1.__metadata
* ```
*
* then it might be that `tslib_1` is a namespace import such as:
*
* ```
* import * as tslib_1 from 'tslib';
* ```
* @param id the TypeScript identifier to find the import info for.
* @returns The import info if this is a namespaced import or `null`.
*/
protected getImportOfNamespacedIdentifier(id: ts.Identifier): Import|null {
if (!(ts.isPropertyAccessExpression(id.parent) && id.parent.name === id)) {
return null;
}
const namespaceIdentifier = getFarLeftIdentifier(id.parent);
const namespaceSymbol =
namespaceIdentifier && this.checker.getSymbolAtLocation(namespaceIdentifier);
const declaration = namespaceSymbol && namespaceSymbol.declarations.length === 1 ?
namespaceSymbol.declarations[0] :
null;
const namespaceDeclaration =
declaration && ts.isNamespaceImport(declaration) ? declaration : null;
if (!namespaceDeclaration) {
return null;
}
const importDeclaration = namespaceDeclaration.parent.parent;
if (!ts.isStringLiteral(importDeclaration.moduleSpecifier)) {
// Should not happen as this would be invalid TypesScript
return null;
}
return {
from: importDeclaration.moduleSpecifier.text,
name: id.text,
};
}
/**
* Test whether a decorator was imported from `@angular/core`.
*
@ -1546,19 +1498,6 @@ function isClassMemberType(node: ts.Declaration): node is ts.ClassElement|
return ts.isClassElement(node) || isPropertyAccess(node) || ts.isBinaryExpression(node);
}
/**
* Compute the left most identifier in a property access chain. E.g. the `a` of `a.b.c.d`.
* @param propertyAccess The starting property access expression from which we want to compute
* the left most identifier.
* @returns the left most identifier in the chain or `null` if it is not an identifier.
*/
function getFarLeftIdentifier(propertyAccess: ts.PropertyAccessExpression): ts.Identifier|null {
while (ts.isPropertyAccessExpression(propertyAccess.expression)) {
propertyAccess = propertyAccess.expression;
}
return ts.isIdentifier(propertyAccess.expression) ? propertyAccess.expression : null;
}
/**
* Collect mappings between exported declarations in a source file and its associated
* declaration in the typings program.