fix(ngcc): ensure reflection hosts can handle TS 3.9 IIFE wrapped classes (#36989)
In TS 3.9, ES2015 output can contain ES classes that are wrapped in an IIFE. So now ES2015 class declarations can look like one of: ``` class OuterClass1 {} ``` ``` let OuterClass = class InnerClass {}; ``` ``` var AliasClass; let OuterClass = AliasClass = class InnerClass {}; ``` ``` let OuterClass = (() => class InnerClass {}}; ``` ``` var AliasClass; let OuterClass = AliasClass = (() => class InnerClass {})(); ``` ``` let OuterClass = (() => { let AdjacentClass = class InnerClass {}; // ... static properties or decorators attached to `AdjacentClass` return AdjacentClass; })(); ``` ``` var AliasClass; let OuterClass = AliasClass = (() => { let AdjacentClass = class InnerClass {}; // ... static properties or decorators attached to `AdjacentClass` return AdjacentClass; })(); ``` The `Esm5ReflectionHost` already handles slightly different IIFE wrappers around function-based classes. This can be substantially reused when fixing `Esm2015ReflectionHost`, since there is a lot of commonality between the two. This commit moves code from the `Esm5ReflectionHost` into the `Esm2015ReflectionHost` and looks to share as much as possible between the two hosts. PR Close #36989
This commit is contained in:
parent
a2b8dc1cfb
commit
d7440c452a
|
@ -8,7 +8,7 @@
|
|||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, CtorParameter, Declaration, Decorator, EnumMember, isDecoratorIdentifier, isNamedClassDeclaration, KnownDeclaration, reflectObjectLiteral, SpecialDeclarationKind, TypeScriptReflectionHost, TypeValueReference} from '../../../src/ngtsc/reflection';
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, CtorParameter, Declaration, Decorator, EnumMember, isDecoratorIdentifier, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, KnownDeclaration, reflectObjectLiteral, SpecialDeclarationKind, TypeScriptReflectionHost, TypeValueReference} from '../../../src/ngtsc/reflection';
|
||||
import {isWithinPackage} from '../analysis/util';
|
||||
import {Logger} from '../logging/logger';
|
||||
import {BundleProgram} from '../packages/bundle_program';
|
||||
|
@ -135,122 +135,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
return this.getClassSymbolFromInnerDeclaration(declaration);
|
||||
}
|
||||
|
||||
/**
|
||||
* In ES2015, a class may be declared using a variable declaration of the following structures:
|
||||
*
|
||||
* ```
|
||||
* var MyClass = MyClass_1 = class MyClass {};
|
||||
* ```
|
||||
*
|
||||
* or
|
||||
*
|
||||
* ```
|
||||
* var MyClass = MyClass_1 = (() => { class MyClass {} ... return MyClass; })()
|
||||
* ```
|
||||
*
|
||||
* This method extracts the `NgccClassSymbol` for `MyClass` when provided with the `var MyClass`
|
||||
* declaration node. When the `class MyClass {}` node or any other node is given, this method will
|
||||
* return undefined instead.
|
||||
*
|
||||
* @param declaration the declaration whose symbol we are finding.
|
||||
* @returns the symbol for the node or `undefined` if it does not represent an outer declaration
|
||||
* of a class.
|
||||
*/
|
||||
protected getClassSymbolFromOuterDeclaration(declaration: ts.Node): NgccClassSymbol|undefined {
|
||||
// Create a symbol without inner declaration if it is a regular "top level" class declaration.
|
||||
if (isNamedClassDeclaration(declaration) && isTopLevel(declaration)) {
|
||||
return this.createClassSymbol(declaration, null);
|
||||
}
|
||||
|
||||
// Otherwise, the declaration may be a variable declaration, in which case it must be
|
||||
// initialized using a class expression as inner declaration.
|
||||
if (ts.isVariableDeclaration(declaration) && hasNameIdentifier(declaration)) {
|
||||
const innerDeclaration = getInnerClassDeclaration(declaration);
|
||||
if (innerDeclaration !== null) {
|
||||
return this.createClassSymbol(declaration, innerDeclaration);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* In ES2015, a class may be declared using a variable declaration of the following structures:
|
||||
*
|
||||
* ```
|
||||
* var MyClass = MyClass_1 = class MyClass {};
|
||||
* ```
|
||||
*
|
||||
* or
|
||||
*
|
||||
* ```
|
||||
* var MyClass = MyClass_1 = (() => { class MyClass {} ... return MyClass; })()
|
||||
* ```
|
||||
*
|
||||
* This method extracts the `NgccClassSymbol` for `MyClass` when provided with the
|
||||
* `class MyClass {}` declaration node. When the `var MyClass` node or any other node is given,
|
||||
* this method will return undefined instead.
|
||||
*
|
||||
* @param declaration the declaration whose symbol we are finding.
|
||||
* @returns the symbol for the node or `undefined` if it does not represent an inner declaration
|
||||
* of a class.
|
||||
*/
|
||||
protected getClassSymbolFromInnerDeclaration(declaration: ts.Node): NgccClassSymbol|undefined {
|
||||
let outerDeclaration: ts.VariableDeclaration|undefined = undefined;
|
||||
|
||||
if (isNamedClassDeclaration(declaration) && !isTopLevel(declaration)) {
|
||||
let node = declaration.parent;
|
||||
while (node !== undefined && !ts.isVariableDeclaration(node)) {
|
||||
node = node.parent;
|
||||
}
|
||||
outerDeclaration = node;
|
||||
} else if (ts.isClassExpression(declaration) && hasNameIdentifier(declaration)) {
|
||||
outerDeclaration = getVariableDeclarationOfDeclaration(declaration);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (outerDeclaration === undefined || !hasNameIdentifier(outerDeclaration)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.createClassSymbol(outerDeclaration, declaration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an `NgccClassSymbol` from an outer and inner declaration. If a class only has an outer
|
||||
* declaration, the "implementation" symbol of the created `NgccClassSymbol` will be set equal to
|
||||
* the "declaration" symbol.
|
||||
*
|
||||
* @param outerDeclaration The outer declaration node of the class.
|
||||
* @param innerDeclaration The inner declaration node of the class, or undefined if no inner
|
||||
* declaration is present.
|
||||
* @returns the `NgccClassSymbol` representing the class, or undefined if a `ts.Symbol` for any of
|
||||
* the declarations could not be resolved.
|
||||
*/
|
||||
protected createClassSymbol(
|
||||
outerDeclaration: ClassDeclaration, innerDeclaration: ClassDeclaration|null): NgccClassSymbol
|
||||
|undefined {
|
||||
const declarationSymbol =
|
||||
this.checker.getSymbolAtLocation(outerDeclaration.name) as ClassSymbol | undefined;
|
||||
if (declarationSymbol === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const implementationSymbol = innerDeclaration !== null ?
|
||||
this.checker.getSymbolAtLocation(innerDeclaration.name) :
|
||||
declarationSymbol;
|
||||
if (implementationSymbol === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
name: declarationSymbol.name,
|
||||
declaration: declarationSymbol,
|
||||
implementation: implementationSymbol,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Examine a declaration (for example, of a class or function) and return metadata about any
|
||||
* decorators present on the declaration.
|
||||
|
@ -319,17 +203,60 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
}
|
||||
|
||||
getBaseClassExpression(clazz: ClassDeclaration): ts.Expression|null {
|
||||
// First try getting the base class from the "outer" declaration
|
||||
// First try getting the base class from an ES2015 class declaration
|
||||
const superBaseClassIdentifier = super.getBaseClassExpression(clazz);
|
||||
if (superBaseClassIdentifier) {
|
||||
return superBaseClassIdentifier;
|
||||
}
|
||||
|
||||
// That didn't work so now try getting it from the "inner" declaration.
|
||||
const innerClassDeclaration = getInnerClassDeclaration(clazz);
|
||||
if (innerClassDeclaration === null) {
|
||||
const classSymbol = this.getClassSymbol(clazz);
|
||||
if (classSymbol === undefined ||
|
||||
!isNamedDeclaration(classSymbol.implementation.valueDeclaration)) {
|
||||
return null;
|
||||
}
|
||||
return super.getBaseClassExpression(innerClassDeclaration);
|
||||
return super.getBaseClassExpression(classSymbol.implementation.valueDeclaration);
|
||||
}
|
||||
|
||||
getInternalNameOfClass(clazz: ClassDeclaration): ts.Identifier {
|
||||
const classSymbol = this.getClassSymbol(clazz);
|
||||
if (classSymbol === undefined) {
|
||||
throw new Error(`getInternalNameOfClass() called on a non-class: expected ${
|
||||
clazz.name.text} to be a class declaration.`);
|
||||
}
|
||||
return this.getNameFromClassSymbolDeclaration(
|
||||
classSymbol, classSymbol.implementation.valueDeclaration);
|
||||
}
|
||||
|
||||
getAdjacentNameOfClass(clazz: ClassDeclaration): ts.Identifier {
|
||||
const classSymbol = this.getClassSymbol(clazz);
|
||||
if (classSymbol === undefined) {
|
||||
throw new Error(`getAdjacentNameOfClass() called on a non-class: expected ${
|
||||
clazz.name.text} to be a class declaration.`);
|
||||
}
|
||||
|
||||
if (classSymbol.adjacent !== undefined) {
|
||||
return this.getNameFromClassSymbolDeclaration(
|
||||
classSymbol, classSymbol.adjacent.valueDeclaration);
|
||||
} else {
|
||||
return this.getNameFromClassSymbolDeclaration(
|
||||
classSymbol, classSymbol.implementation.valueDeclaration);
|
||||
}
|
||||
}
|
||||
|
||||
private getNameFromClassSymbolDeclaration(
|
||||
classSymbol: NgccClassSymbol, declaration: ts.Declaration): ts.Identifier {
|
||||
if (declaration === undefined) {
|
||||
throw new Error(
|
||||
`getInternalNameOfClass() called on a class with an undefined internal declaration. External class name: ${
|
||||
classSymbol.name}; internal class name: ${classSymbol.implementation.name}.`);
|
||||
}
|
||||
if (!isNamedDeclaration(declaration)) {
|
||||
throw new Error(
|
||||
`getInternalNameOfClass() called on a class with an anonymous inner declaration: expected a name on:\n${
|
||||
declaration.getText()}`);
|
||||
}
|
||||
return declaration.name;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -368,22 +295,30 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
return superDeclaration;
|
||||
}
|
||||
|
||||
const outerClassNode = getClassDeclarationFromInnerDeclaration(superDeclaration.node);
|
||||
const declaration = outerClassNode !== null ?
|
||||
this.getDeclarationOfIdentifier(outerClassNode.name) :
|
||||
superDeclaration;
|
||||
if (declaration === null || declaration.node === null || declaration.known !== null) {
|
||||
return declaration;
|
||||
}
|
||||
|
||||
// The identifier may have been of an additional class assignment such as `MyClass_1` that was
|
||||
// present as alias for `MyClass`. If so, resolve such aliases to their original declaration.
|
||||
const aliasedIdentifier = this.resolveAliasedClassIdentifier(superDeclaration.node);
|
||||
const aliasedIdentifier = this.resolveAliasedClassIdentifier(declaration.node);
|
||||
if (aliasedIdentifier !== null) {
|
||||
return this.getDeclarationOfIdentifier(aliasedIdentifier);
|
||||
}
|
||||
|
||||
// Variable declarations may represent an enum declaration, so attempt to resolve its members.
|
||||
if (ts.isVariableDeclaration(superDeclaration.node)) {
|
||||
const enumMembers = this.resolveEnumMembers(superDeclaration.node);
|
||||
if (ts.isVariableDeclaration(declaration.node)) {
|
||||
const enumMembers = this.resolveEnumMembers(declaration.node);
|
||||
if (enumMembers !== null) {
|
||||
superDeclaration.identity = {kind: SpecialDeclarationKind.DownleveledEnum, enumMembers};
|
||||
declaration.identity = {kind: SpecialDeclarationKind.DownleveledEnum, enumMembers};
|
||||
}
|
||||
}
|
||||
|
||||
return superDeclaration;
|
||||
return declaration;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -555,31 +490,47 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
}
|
||||
|
||||
getEndOfClass(classSymbol: NgccClassSymbol): ts.Node {
|
||||
let last: ts.Node = classSymbol.declaration.valueDeclaration;
|
||||
const implementation = classSymbol.implementation;
|
||||
let last: ts.Node = implementation.valueDeclaration;
|
||||
const implementationStatement = getContainingStatement(last);
|
||||
if (implementationStatement === null) return last;
|
||||
|
||||
// If there are static members on this class then find the last one
|
||||
if (classSymbol.declaration.exports !== undefined) {
|
||||
classSymbol.declaration.exports.forEach(exportSymbol => {
|
||||
if (exportSymbol.valueDeclaration === undefined) {
|
||||
return;
|
||||
}
|
||||
const exportStatement = getContainingStatement(exportSymbol.valueDeclaration);
|
||||
if (exportStatement !== null && last.getEnd() < exportStatement.getEnd()) {
|
||||
last = exportStatement;
|
||||
const container = implementationStatement.parent;
|
||||
if (ts.isBlock(container)) {
|
||||
// Assume that the implementation is inside an IIFE
|
||||
const returnStatementIndex = container.statements.findIndex(ts.isReturnStatement);
|
||||
if (returnStatementIndex === -1) {
|
||||
throw new Error(
|
||||
`Compiled class wrapper IIFE does not have a return statement: ${classSymbol.name} in ${
|
||||
classSymbol.declaration.valueDeclaration.getSourceFile().fileName}`);
|
||||
}
|
||||
|
||||
// Return the statement before the IIFE return statement
|
||||
last = container.statements[returnStatementIndex - 1];
|
||||
} else if (ts.isSourceFile(container)) {
|
||||
// If there are static members on this class then find the last one
|
||||
if (implementation.exports !== undefined) {
|
||||
implementation.exports.forEach(exportSymbol => {
|
||||
if (exportSymbol.valueDeclaration === undefined) {
|
||||
return;
|
||||
}
|
||||
const exportStatement = getContainingStatement(exportSymbol.valueDeclaration);
|
||||
if (exportStatement !== null && last.getEnd() < exportStatement.getEnd()) {
|
||||
last = exportStatement;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// If there are helper calls for this class then find the last one
|
||||
const helpers = this.getHelperCallsForClass(
|
||||
classSymbol, ['__decorate', '__extends', '__param', '__metadata']);
|
||||
helpers.forEach(helper => {
|
||||
const helperStatement = getContainingStatement(helper);
|
||||
if (helperStatement !== null && last.getEnd() < helperStatement.getEnd()) {
|
||||
last = helperStatement;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// If there are helper calls for this class then find the last one
|
||||
const helpers = this.getHelperCallsForClass(
|
||||
classSymbol, ['__decorate', '__extends', '__param', '__metadata']);
|
||||
helpers.forEach(helper => {
|
||||
const helperStatement = getContainingStatement(helper);
|
||||
if (helperStatement !== null && last.getEnd() < helperStatement.getEnd()) {
|
||||
last = helperStatement;
|
||||
}
|
||||
});
|
||||
|
||||
return last;
|
||||
}
|
||||
|
||||
|
@ -602,6 +553,186 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
|
||||
///////////// Protected Helpers /////////////
|
||||
|
||||
/**
|
||||
* A class may be declared as a top level class declaration:
|
||||
*
|
||||
* ```
|
||||
* class OuterClass { ... }
|
||||
* ```
|
||||
*
|
||||
* or in a variable declaration to a class expression:
|
||||
*
|
||||
* ```
|
||||
* var OuterClass = ClassAlias = class InnerClass {};
|
||||
* ```
|
||||
*
|
||||
* or in a variable declaration to an IIFE containing a class declaration
|
||||
*
|
||||
* ```
|
||||
* var OuterClass = ClassAlias = (() => {
|
||||
* class InnerClass {}
|
||||
* ...
|
||||
* return InnerClass;
|
||||
* })()
|
||||
* ```
|
||||
*
|
||||
* or in a variable declaration to an IIFE containing a function declaration
|
||||
*
|
||||
* ```
|
||||
* var OuterClass = ClassAlias = (() => {
|
||||
* function InnerClass() {}
|
||||
* ...
|
||||
* return InnerClass;
|
||||
* })()
|
||||
* ```
|
||||
*
|
||||
* This method returns an `NgccClassSymbol` when provided with one of these cases.
|
||||
*
|
||||
* @param declaration the declaration whose symbol we are finding.
|
||||
* @returns the symbol for the class or `undefined` if `declaration` does not represent an outer
|
||||
* declaration of a class.
|
||||
*/
|
||||
protected getClassSymbolFromOuterDeclaration(declaration: ts.Node): NgccClassSymbol|undefined {
|
||||
// Return a class symbol without an inner declaration if it is a regular "top level" class
|
||||
if (isNamedClassDeclaration(declaration) && isTopLevel(declaration)) {
|
||||
return this.createClassSymbol(declaration, null);
|
||||
}
|
||||
|
||||
// Otherwise, an outer class declaration must be an initialized variable declaration:
|
||||
if (!isInitializedVariableClassDeclaration(declaration)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const innerDeclaration = getInnerClassDeclaration(skipClassAliases(declaration));
|
||||
if (innerDeclaration !== null) {
|
||||
return this.createClassSymbol(declaration, innerDeclaration);
|
||||
}
|
||||
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* In ES2015, a class may be declared using a variable declaration of the following structures:
|
||||
*
|
||||
* ```
|
||||
* let MyClass = MyClass_1 = class MyClass {};
|
||||
* ```
|
||||
*
|
||||
* or
|
||||
*
|
||||
* ```
|
||||
* let MyClass = MyClass_1 = (() => { class MyClass {} ... return MyClass; })()
|
||||
* ```
|
||||
*
|
||||
* or
|
||||
*
|
||||
* ```
|
||||
* let MyClass = MyClass_1 = (() => { let MyClass = class MyClass {}; ... return MyClass; })()
|
||||
* ```
|
||||
*
|
||||
* This method extracts the `NgccClassSymbol` for `MyClass` when provided with the
|
||||
* `class MyClass {}` declaration node. When the `var MyClass` node or any other node is given,
|
||||
* this method will return undefined instead.
|
||||
*
|
||||
* @param declaration the declaration whose symbol we are finding.
|
||||
* @returns the symbol for the node or `undefined` if it does not represent an inner declaration
|
||||
* of a class.
|
||||
*/
|
||||
protected getClassSymbolFromInnerDeclaration(declaration: ts.Node): NgccClassSymbol|undefined {
|
||||
let outerDeclaration: ts.ClassDeclaration|ts.VariableDeclaration|undefined = undefined;
|
||||
|
||||
if (ts.isClassExpression(declaration) && hasNameIdentifier(declaration)) {
|
||||
// Handle `let MyClass = MyClass_1 = class MyClass {};`
|
||||
outerDeclaration = getFarLeftHandSideOfAssignment(declaration);
|
||||
|
||||
// Handle this being in an IIFE
|
||||
if (outerDeclaration !== undefined && !isTopLevel(outerDeclaration)) {
|
||||
outerDeclaration = getContainingVariableDeclaration(outerDeclaration);
|
||||
}
|
||||
} else if (isNamedClassDeclaration(declaration)) {
|
||||
// Handle `class MyClass {}` statement
|
||||
if (isTopLevel(declaration)) {
|
||||
// At the top level
|
||||
outerDeclaration = declaration;
|
||||
} else {
|
||||
// Or inside an IIFE
|
||||
outerDeclaration = getContainingVariableDeclaration(declaration);
|
||||
}
|
||||
}
|
||||
|
||||
if (outerDeclaration === undefined || !hasNameIdentifier(outerDeclaration)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.createClassSymbol(outerDeclaration, declaration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an `NgccClassSymbol` from an outer and inner declaration. If a class only has an outer
|
||||
* declaration, the "implementation" symbol of the created `NgccClassSymbol` will be set equal to
|
||||
* the "declaration" symbol.
|
||||
*
|
||||
* @param outerDeclaration The outer declaration node of the class.
|
||||
* @param innerDeclaration The inner declaration node of the class, or undefined if no inner
|
||||
* declaration is present.
|
||||
* @returns the `NgccClassSymbol` representing the class, or undefined if a `ts.Symbol` for any of
|
||||
* the declarations could not be resolved.
|
||||
*/
|
||||
protected createClassSymbol(outerDeclaration: ClassDeclaration, innerDeclaration: ts.Node|null):
|
||||
NgccClassSymbol|undefined {
|
||||
const declarationSymbol =
|
||||
this.checker.getSymbolAtLocation(outerDeclaration.name) as ClassSymbol | undefined;
|
||||
if (declarationSymbol === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let implementationSymbol = declarationSymbol;
|
||||
if (innerDeclaration !== null && isNamedDeclaration(innerDeclaration)) {
|
||||
implementationSymbol = this.checker.getSymbolAtLocation(innerDeclaration.name) as ClassSymbol;
|
||||
}
|
||||
|
||||
if (implementationSymbol === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const classSymbol: NgccClassSymbol = {
|
||||
name: declarationSymbol.name,
|
||||
declaration: declarationSymbol,
|
||||
implementation: implementationSymbol,
|
||||
};
|
||||
|
||||
let adjacent = this.getAdjacentSymbol(declarationSymbol, implementationSymbol);
|
||||
if (adjacent !== null) {
|
||||
classSymbol.adjacent = adjacent;
|
||||
}
|
||||
|
||||
return classSymbol;
|
||||
}
|
||||
|
||||
private getAdjacentSymbol(declarationSymbol: ClassSymbol, implementationSymbol: ClassSymbol):
|
||||
ClassSymbol|undefined {
|
||||
if (declarationSymbol === implementationSymbol) {
|
||||
return undefined;
|
||||
}
|
||||
const innerDeclaration = implementationSymbol.valueDeclaration;
|
||||
if (!ts.isClassExpression(innerDeclaration) && !ts.isFunctionExpression(innerDeclaration)) {
|
||||
return undefined;
|
||||
}
|
||||
// Deal with the inner class looking like this inside an IIFE:
|
||||
// `let MyClass = class MyClass {};` or `var MyClass = function MyClass() {};`
|
||||
const adjacentDeclaration = getFarLeftHandSideOfAssignment(innerDeclaration);
|
||||
if (adjacentDeclaration === undefined || !isNamedVariableDeclaration(adjacentDeclaration)) {
|
||||
return undefined;
|
||||
}
|
||||
const adjacentSymbol =
|
||||
this.checker.getSymbolAtLocation(adjacentDeclaration.name) as ClassSymbol;
|
||||
if (adjacentSymbol === declarationSymbol || adjacentSymbol === implementationSymbol) {
|
||||
return undefined;
|
||||
}
|
||||
return adjacentSymbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a `ts.Symbol` to its declaration and detect whether it corresponds with a known
|
||||
* declaration.
|
||||
|
@ -611,7 +742,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
const declaration = super.getDeclarationOfSymbol(symbol, originalId);
|
||||
if (declaration === null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return this.detectKnownDeclaration(declaration);
|
||||
}
|
||||
|
||||
|
@ -892,6 +1023,33 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
}
|
||||
}
|
||||
|
||||
// If this class was declared as a VariableDeclaration inside an IIFE, then it may have static
|
||||
// properties attached to the variable rather than the class itself.
|
||||
//
|
||||
// For example:
|
||||
// ```
|
||||
// let OuterClass = (() => {
|
||||
// let AdjacentClass = class InternalClass {
|
||||
// // no static properties here!
|
||||
// }
|
||||
// AdjacentClass.staticProperty = ...;
|
||||
// })();
|
||||
// ```
|
||||
if (symbol.adjacent !== undefined) {
|
||||
if (ts.isVariableDeclaration(symbol.adjacent.valueDeclaration)) {
|
||||
if (symbol.adjacent.exports !== undefined) {
|
||||
symbol.adjacent.exports.forEach((value, key) => {
|
||||
const decorators = decoratorsMap.get(key as string);
|
||||
const reflectedMembers = this.reflectMembers(value, decorators, true);
|
||||
if (reflectedMembers) {
|
||||
decoratorsMap.delete(key as string);
|
||||
members.push(...reflectedMembers);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deal with any decorated properties that were not initialized in the class
|
||||
decoratorsMap.forEach((value, key) => {
|
||||
members.push({
|
||||
|
@ -991,15 +1149,13 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
|
||||
const outerDeclaration = classSymbol.declaration.valueDeclaration;
|
||||
const innerDeclaration = classSymbol.implementation.valueDeclaration;
|
||||
const adjacentDeclaration =
|
||||
this.getAdjacentNameOfClass((classSymbol.declaration.valueDeclaration)).parent;
|
||||
const matchesClass = (identifier: ts.Identifier) => {
|
||||
const decl = this.getDeclarationOfIdentifier(identifier);
|
||||
if (decl === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The identifier corresponds with the class if its declaration is either the outer or inner
|
||||
// declaration.
|
||||
return decl.node === outerDeclaration || decl.node === innerDeclaration;
|
||||
return decl !== null &&
|
||||
(decl.node === adjacentDeclaration || decl.node === outerDeclaration ||
|
||||
decl.node === innerDeclaration);
|
||||
};
|
||||
|
||||
for (const helperCall of helperCalls) {
|
||||
|
@ -1545,8 +1701,10 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
const classNode = classSymbol.implementation.valueDeclaration;
|
||||
if (isTopLevel(classNode)) {
|
||||
return this.getModuleStatements(classNode.getSourceFile());
|
||||
} else if (ts.isBlock(classNode.parent)) {
|
||||
return Array.from(classNode.parent.statements);
|
||||
}
|
||||
const statement = getContainingStatement(classNode);
|
||||
if (ts.isBlock(statement.parent)) {
|
||||
return Array.from(statement.parent.statements);
|
||||
}
|
||||
// We should never arrive here
|
||||
throw new Error(`Unable to find adjacent statements for ${classSymbol.name}`);
|
||||
|
@ -1991,7 +2149,7 @@ export function isAssignmentStatement(statement: ts.Statement): statement is Ass
|
|||
* @returns the `ts.Expression` or `ts.FunctionBody` that holds the body of the IIFE or `undefined`
|
||||
* if the `expression` did not have the correct shape.
|
||||
*/
|
||||
export function getIifeConciseBody(expression: ts.Expression): ts.ConciseBody|undefined {
|
||||
export function getIifeBody(expression: ts.Expression): ts.ConciseBody|undefined {
|
||||
const call = stripParentheses(expression);
|
||||
if (!ts.isCallExpression(call)) {
|
||||
return undefined;
|
||||
|
@ -2093,60 +2251,107 @@ function getCalleeName(call: ts.CallExpression): string|null {
|
|||
|
||||
///////////// Internal Helpers /////////////
|
||||
|
||||
type InitializedVariableClassDeclaration =
|
||||
ClassDeclaration<ts.VariableDeclaration>&{initializer: ts.Expression};
|
||||
|
||||
function isInitializedVariableClassDeclaration(node: ts.Node):
|
||||
node is InitializedVariableClassDeclaration {
|
||||
return isNamedVariableDeclaration(node) && node.initializer !== undefined;
|
||||
}
|
||||
/**
|
||||
* In ES2015, a class may be declared using a variable declaration of the following structures:
|
||||
* Handle a variable declaration of the form
|
||||
*
|
||||
* ```
|
||||
* var MyClass = MyClass_1 = class MyClass {};
|
||||
* var MyClass = alias1 = alias2 = <<declaration>>
|
||||
* ```
|
||||
*
|
||||
* or
|
||||
*
|
||||
* ```
|
||||
* var MyClass = MyClass_1 = (() => { class MyClass {} ... return MyClass; })()
|
||||
* ```
|
||||
*
|
||||
* Here, the intermediate `MyClass_1` assignment is optional. In the above example, the
|
||||
* `class MyClass {}` expression is returned as declaration of `var MyClass`. If the variable
|
||||
* is not initialized using a class expression, null is returned.
|
||||
*
|
||||
* @param node the node that represents the class whose declaration we are finding.
|
||||
* @returns the declaration of the class or `null` if it is not a "class".
|
||||
* @node the LHS of a variable declaration.
|
||||
* @returns the original AST node or the RHS of a series of assignments in a variable
|
||||
* declaration.
|
||||
*/
|
||||
function getInnerClassDeclaration(node: ts.Node):
|
||||
ClassDeclaration<ts.ClassExpression|ts.ClassDeclaration>|null {
|
||||
if (!ts.isVariableDeclaration(node) || node.initializer === undefined) {
|
||||
return null;
|
||||
}
|
||||
// Recognize a variable declaration of the form `var MyClass = class MyClass {}` or
|
||||
// `var MyClass = MyClass_1 = class MyClass {};`
|
||||
export function skipClassAliases(node: InitializedVariableClassDeclaration): ts.Expression {
|
||||
let expression = node.initializer;
|
||||
while (isAssignment(expression)) {
|
||||
expression = expression.right;
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* This expression could either be a class expression
|
||||
*
|
||||
* ```
|
||||
* class MyClass {};
|
||||
* ```
|
||||
*
|
||||
* or an IIFE wrapped class expression
|
||||
*
|
||||
* ```
|
||||
* (() => {
|
||||
* class MyClass {}
|
||||
* ...
|
||||
* return MyClass;
|
||||
* })()
|
||||
* ```
|
||||
*
|
||||
* or an IIFE wrapped aliased class expression
|
||||
*
|
||||
* ```
|
||||
* (() => {
|
||||
* let MyClass = class MyClass {}
|
||||
* ...
|
||||
* return MyClass;
|
||||
* })()
|
||||
* ```
|
||||
*
|
||||
* or an IFFE wrapped ES5 class function
|
||||
*
|
||||
* ```
|
||||
* (function () {
|
||||
* function MyClass() {}
|
||||
* ...
|
||||
* return MyClass
|
||||
* })()
|
||||
* ```
|
||||
*
|
||||
* @param expression the node that represents the class whose declaration we are finding.
|
||||
* @returns the declaration of the class or `null` if it is not a "class".
|
||||
*/
|
||||
function getInnerClassDeclaration(expression: ts.Expression):
|
||||
ClassDeclaration<ts.ClassExpression|ts.ClassDeclaration|ts.FunctionDeclaration>|null {
|
||||
if (ts.isClassExpression(expression) && hasNameIdentifier(expression)) {
|
||||
return expression;
|
||||
}
|
||||
|
||||
// Try to parse out a class declaration wrapped in an IIFE (as generated by TS 3.9)
|
||||
// e.g.
|
||||
// /* @class */ = (() => {
|
||||
// class MyClass {}
|
||||
// ...
|
||||
// return MyClass;
|
||||
// })();
|
||||
const iifeBody = getIifeConciseBody(expression);
|
||||
const iifeBody = getIifeBody(expression);
|
||||
if (iifeBody === undefined) {
|
||||
return null;
|
||||
}
|
||||
// Extract the class declaration from inside the IIFE.
|
||||
const innerDeclaration = ts.isBlock(iifeBody) ?
|
||||
iifeBody.statements.find(ts.isClassDeclaration) :
|
||||
ts.isClassExpression(iifeBody) ? iifeBody : undefined;
|
||||
if (innerDeclaration === undefined || !hasNameIdentifier(innerDeclaration)) {
|
||||
return null;
|
||||
|
||||
if (!ts.isBlock(iifeBody)) {
|
||||
// Handle the fat arrow expression case: `() => ClassExpression`
|
||||
return ts.isClassExpression(iifeBody) && isNamedDeclaration(iifeBody) ? iifeBody : null;
|
||||
} else {
|
||||
// Handle the case of a normal or fat-arrow function with a body.
|
||||
// Return the first ClassDeclaration/VariableDeclaration inside the body
|
||||
for (const statement of iifeBody.statements) {
|
||||
if (isNamedClassDeclaration(statement) || isNamedFunctionDeclaration(statement)) {
|
||||
return statement;
|
||||
}
|
||||
if (ts.isVariableStatement(statement)) {
|
||||
for (const declaration of statement.declarationList.declarations) {
|
||||
if (isInitializedVariableClassDeclaration(declaration)) {
|
||||
const expression = skipClassAliases(declaration);
|
||||
if (ts.isClassExpression(expression) && hasNameIdentifier(expression)) {
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return innerDeclaration;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getDecoratorArgs(node: ts.ObjectLiteralExpression): ts.Expression[] {
|
||||
|
@ -2170,8 +2375,7 @@ function isThisAssignment(node: ts.Declaration): node is ts.BinaryExpression&
|
|||
node.left.expression.kind === ts.SyntaxKind.ThisKeyword;
|
||||
}
|
||||
|
||||
function isNamedDeclaration(node: ts.Declaration): node is ts.NamedDeclaration&
|
||||
{name: ts.Identifier} {
|
||||
function isNamedDeclaration(node: ts.Node): node is ts.NamedDeclaration&{name: ts.Identifier} {
|
||||
const anyNode: any = node;
|
||||
return !!anyNode.name && ts.isIdentifier(anyNode.name);
|
||||
}
|
||||
|
@ -2209,7 +2413,7 @@ function isClassMemberType(node: ts.Declaration): node is ts.ClassElement|
|
|||
* @param declaration The declaration for which any variable declaration should be obtained.
|
||||
* @returns the outer variable declaration if found, undefined otherwise.
|
||||
*/
|
||||
function getVariableDeclarationOfDeclaration(declaration: ts.Declaration): ts.VariableDeclaration|
|
||||
function getFarLeftHandSideOfAssignment(declaration: ts.Declaration): ts.VariableDeclaration|
|
||||
undefined {
|
||||
let node = declaration.parent;
|
||||
|
||||
|
@ -2221,6 +2425,18 @@ function getVariableDeclarationOfDeclaration(declaration: ts.Declaration): ts.Va
|
|||
return ts.isVariableDeclaration(node) ? node : undefined;
|
||||
}
|
||||
|
||||
function getContainingVariableDeclaration(node: ts.Node): ClassDeclaration<ts.VariableDeclaration>|
|
||||
undefined {
|
||||
node = node.parent;
|
||||
while (node !== undefined) {
|
||||
if (isNamedVariableDeclaration(node)) {
|
||||
return node;
|
||||
}
|
||||
node = node.parent;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructor function may have been "synthesized" by TypeScript during JavaScript emit,
|
||||
* in the case no user-defined constructor exists and e.g. property initializers are used.
|
||||
|
@ -2273,14 +2489,14 @@ function isSynthesizedSuperCall(expression: ts.Expression): boolean {
|
|||
* Find the statement that contains the given node
|
||||
* @param node a node whose containing statement we wish to find
|
||||
*/
|
||||
function getContainingStatement(node: ts.Node): ts.ExpressionStatement|null {
|
||||
while (node) {
|
||||
if (ts.isExpressionStatement(node)) {
|
||||
function getContainingStatement(node: ts.Node): ts.Statement {
|
||||
while (node.parent) {
|
||||
if (ts.isBlock(node.parent) || ts.isSourceFile(node.parent)) {
|
||||
break;
|
||||
}
|
||||
node = node.parent;
|
||||
}
|
||||
return node || null;
|
||||
return node as ts.Statement;
|
||||
}
|
||||
|
||||
function getRootFileOrFail(bundle: BundleProgram): ts.SourceFile {
|
||||
|
@ -2305,3 +2521,52 @@ function isTopLevel(node: ts.Node): boolean {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actual (outer) declaration of a class.
|
||||
*
|
||||
* In ES5, the implementation of a class is a function expression that is hidden inside an IIFE and
|
||||
* returned to be assigned to a variable outside the IIFE, which is what the rest of the program
|
||||
* interacts with.
|
||||
*
|
||||
* Given the inner function declaration, we want to get to the declaration of the outer variable
|
||||
* that represents the class.
|
||||
*
|
||||
* @param node a node that could be the function expression inside an ES5 class IIFE.
|
||||
* @returns the outer variable declaration or `undefined` if it is not a "class".
|
||||
*/
|
||||
export function getClassDeclarationFromInnerDeclaration(node: ts.Node):
|
||||
ClassDeclaration<ts.VariableDeclaration>|null {
|
||||
if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) {
|
||||
// It might be the function expression inside the IIFE. We need to go 5 levels up...
|
||||
|
||||
// - IIFE body.
|
||||
let outerNode = node.parent;
|
||||
if (!outerNode || !ts.isBlock(outerNode)) return null;
|
||||
|
||||
// - IIFE function expression.
|
||||
outerNode = outerNode.parent;
|
||||
if (!outerNode || (!ts.isFunctionExpression(outerNode) && !ts.isArrowFunction(outerNode))) {
|
||||
return null;
|
||||
}
|
||||
outerNode = outerNode.parent;
|
||||
|
||||
// - Parenthesis inside IIFE.
|
||||
if (outerNode && ts.isParenthesizedExpression(outerNode)) outerNode = outerNode.parent;
|
||||
|
||||
// - IIFE call expression.
|
||||
if (!outerNode || !ts.isCallExpression(outerNode)) return null;
|
||||
outerNode = outerNode.parent;
|
||||
|
||||
// - Parenthesis around IIFE.
|
||||
if (outerNode && ts.isParenthesizedExpression(outerNode)) outerNode = outerNode.parent;
|
||||
|
||||
// - Outer variable declaration.
|
||||
if (!outerNode || !ts.isVariableDeclaration(outerNode)) return null;
|
||||
|
||||
// Finally, ensure that the variable declaration has a `name` identifier.
|
||||
return hasNameIdentifier(outerNode) ? outerNode : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -8,10 +8,10 @@
|
|||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, Declaration, Decorator, FunctionDefinition, isNamedVariableDeclaration, Parameter, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
||||
import {getNameText, getTsHelperFnFromDeclaration, getTsHelperFnFromIdentifier, hasNameIdentifier} from '../utils';
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, Declaration, Decorator, FunctionDefinition, Parameter, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
||||
import {getTsHelperFnFromDeclaration, getTsHelperFnFromIdentifier, hasNameIdentifier} from '../utils';
|
||||
|
||||
import {Esm2015ReflectionHost, getIifeConciseBody, getPropertyValueFromSymbol, isAssignment, isAssignmentStatement, ParamInfo} from './esm2015_host';
|
||||
import {Esm2015ReflectionHost, getClassDeclarationFromInnerDeclaration, getPropertyValueFromSymbol, isAssignmentStatement, ParamInfo} from './esm2015_host';
|
||||
import {NgccClassSymbol} from './ngcc_host';
|
||||
|
||||
|
||||
|
@ -23,6 +23,7 @@ import {NgccClassSymbol} from './ngcc_host';
|
|||
* function CommonModule() {
|
||||
* }
|
||||
* CommonModule.decorators = [ ... ];
|
||||
* return CommonModule;
|
||||
* ```
|
||||
*
|
||||
* * "Classes" are decorated if they have a static property called `decorators`.
|
||||
|
@ -34,16 +35,13 @@ import {NgccClassSymbol} from './ngcc_host';
|
|||
*/
|
||||
export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||
getBaseClassExpression(clazz: ClassDeclaration): ts.Expression|null {
|
||||
const classSymbol = this.getClassSymbol(clazz);
|
||||
if (classSymbol === undefined) {
|
||||
return null;
|
||||
const superBaseClassExpression = super.getBaseClassExpression(clazz);
|
||||
if (superBaseClassExpression !== null) {
|
||||
return superBaseClassExpression;
|
||||
}
|
||||
|
||||
const iifeBody = getIifeBody(classSymbol.declaration.valueDeclaration);
|
||||
if (!iifeBody) return null;
|
||||
|
||||
const iife = iifeBody.parent;
|
||||
if (!iife || !ts.isFunctionExpression(iife)) return null;
|
||||
const iife = getIifeFn(this.getClassSymbol(clazz));
|
||||
if (iife === null) return null;
|
||||
|
||||
if (iife.parameters.length !== 1 || !isSuperIdentifier(iife.parameters[0].name)) {
|
||||
return null;
|
||||
|
@ -56,100 +54,6 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
|||
return iife.parent.arguments[0];
|
||||
}
|
||||
|
||||
getInternalNameOfClass(clazz: ClassDeclaration): ts.Identifier {
|
||||
const innerClass = this.getInnerFunctionDeclarationFromClassDeclaration(clazz);
|
||||
if (innerClass === undefined) {
|
||||
throw new Error(`getInternalNameOfClass() called on a non-ES5 class: expected ${
|
||||
clazz.name.text} to have an inner class declaration`);
|
||||
}
|
||||
if (innerClass.name === undefined) {
|
||||
throw new Error(
|
||||
`getInternalNameOfClass() called on a class with an anonymous inner declaration: expected a name on:\n${
|
||||
innerClass.getText()}`);
|
||||
}
|
||||
return innerClass.name;
|
||||
}
|
||||
|
||||
getAdjacentNameOfClass(clazz: ClassDeclaration): ts.Identifier {
|
||||
return this.getInternalNameOfClass(clazz);
|
||||
}
|
||||
|
||||
getEndOfClass(classSymbol: NgccClassSymbol): ts.Node {
|
||||
const iifeBody = getIifeBody(classSymbol.declaration.valueDeclaration);
|
||||
if (!iifeBody) {
|
||||
throw new Error(`Compiled class declaration is not inside an IIFE: ${classSymbol.name} in ${
|
||||
classSymbol.declaration.valueDeclaration.getSourceFile().fileName}`);
|
||||
}
|
||||
|
||||
const returnStatementIndex = iifeBody.statements.findIndex(ts.isReturnStatement);
|
||||
if (returnStatementIndex === -1) {
|
||||
throw new Error(
|
||||
`Compiled class wrapper IIFE does not have a return statement: ${classSymbol.name} in ${
|
||||
classSymbol.declaration.valueDeclaration.getSourceFile().fileName}`);
|
||||
}
|
||||
|
||||
// Return the statement before the IIFE return statement
|
||||
return iifeBody.statements[returnStatementIndex - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* In ES5, the implementation of a class is a function expression that is hidden inside an IIFE,
|
||||
* whose value is assigned to a variable (which represents the class to the rest of the program).
|
||||
* So we might need to dig around to get hold of the "class" declaration.
|
||||
*
|
||||
* This method extracts a `NgccClassSymbol` if `declaration` is the outer variable which is
|
||||
* assigned the result of the IIFE. Otherwise, undefined is returned.
|
||||
*
|
||||
* @param declaration the declaration whose symbol we are finding.
|
||||
* @returns the symbol for the node or `undefined` if it is not a "class" or has no symbol.
|
||||
*/
|
||||
protected getClassSymbolFromOuterDeclaration(declaration: ts.Node): NgccClassSymbol|undefined {
|
||||
const classSymbol = super.getClassSymbolFromOuterDeclaration(declaration);
|
||||
if (classSymbol !== undefined) {
|
||||
return classSymbol;
|
||||
}
|
||||
|
||||
if (!isNamedVariableDeclaration(declaration)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const innerDeclaration = this.getInnerFunctionDeclarationFromClassDeclaration(declaration);
|
||||
if (innerDeclaration === undefined || !hasNameIdentifier(innerDeclaration)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.createClassSymbol(declaration, innerDeclaration);
|
||||
}
|
||||
|
||||
/**
|
||||
* In ES5, the implementation of a class is a function expression that is hidden inside an IIFE,
|
||||
* whose value is assigned to a variable (which represents the class to the rest of the program).
|
||||
* So we might need to dig around to get hold of the "class" declaration.
|
||||
*
|
||||
* This method extracts a `NgccClassSymbol` if `declaration` is the function declaration inside
|
||||
* the IIFE. Otherwise, undefined is returned.
|
||||
*
|
||||
* @param declaration the declaration whose symbol we are finding.
|
||||
* @returns the symbol for the node or `undefined` if it is not a "class" or has no symbol.
|
||||
*/
|
||||
protected getClassSymbolFromInnerDeclaration(declaration: ts.Node): NgccClassSymbol|undefined {
|
||||
const classSymbol = super.getClassSymbolFromInnerDeclaration(declaration);
|
||||
if (classSymbol !== undefined) {
|
||||
return classSymbol;
|
||||
}
|
||||
|
||||
if (!ts.isFunctionDeclaration(declaration) || !hasNameIdentifier(declaration)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const outerDeclaration = getClassDeclarationFromInnerFunctionDeclaration(declaration);
|
||||
if (outerDeclaration === null || !hasNameIdentifier(outerDeclaration)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.createClassSymbol(outerDeclaration, declaration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trace an identifier to its declaration, if possible.
|
||||
*
|
||||
|
@ -168,9 +72,9 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
|||
* otherwise.
|
||||
*/
|
||||
getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null {
|
||||
const superDeclaration = super.getDeclarationOfIdentifier(id);
|
||||
const declaration = super.getDeclarationOfIdentifier(id);
|
||||
|
||||
if (superDeclaration === null) {
|
||||
if (declaration === null) {
|
||||
const nonEmittedNorImportedTsHelperDeclaration = getTsHelperFnFromIdentifier(id);
|
||||
if (nonEmittedNorImportedTsHelperDeclaration !== null) {
|
||||
// No declaration could be found for this identifier and its name matches a known TS helper
|
||||
|
@ -186,17 +90,6 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
|||
}
|
||||
}
|
||||
|
||||
if (superDeclaration === null || superDeclaration.node === null ||
|
||||
superDeclaration.known !== null) {
|
||||
return superDeclaration;
|
||||
}
|
||||
|
||||
// Get the identifier for the outer class node (if any).
|
||||
const outerClassNode = getClassDeclarationFromInnerFunctionDeclaration(superDeclaration.node);
|
||||
const declaration = outerClassNode !== null ?
|
||||
super.getDeclarationOfIdentifier(outerClassNode.name) :
|
||||
superDeclaration;
|
||||
|
||||
if (declaration === null || declaration.node === null || declaration.known !== null) {
|
||||
return declaration;
|
||||
}
|
||||
|
@ -235,23 +128,24 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
|||
* @returns an object containing the node, statements and parameters of the function.
|
||||
*/
|
||||
getDefinitionOfFunction(node: ts.Node): FunctionDefinition|null {
|
||||
if (!ts.isFunctionDeclaration(node) && !ts.isMethodDeclaration(node) &&
|
||||
!ts.isFunctionExpression(node)) {
|
||||
const definition = super.getDefinitionOfFunction(node);
|
||||
if (definition === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parameters =
|
||||
node.parameters.map(p => ({name: getNameText(p.name), node: p, initializer: null}));
|
||||
let lookingForParamInitializers = true;
|
||||
// Filter out and capture parameter initializers
|
||||
if (definition.body !== null) {
|
||||
let lookingForInitializers = true;
|
||||
const statements = definition.body.filter(s => {
|
||||
lookingForInitializers =
|
||||
lookingForInitializers && captureParamInitializer(s, definition.parameters);
|
||||
// If we are no longer looking for parameter initializers then we include this statement
|
||||
return !lookingForInitializers;
|
||||
});
|
||||
definition.body = statements;
|
||||
}
|
||||
|
||||
const statements = node.body && node.body.statements.filter(s => {
|
||||
lookingForParamInitializers =
|
||||
lookingForParamInitializers && reflectParamInitializer(s, parameters);
|
||||
// If we are no longer looking for parameter initializers then we include this statement
|
||||
return !lookingForParamInitializers;
|
||||
});
|
||||
|
||||
return {node, body: statements || null, parameters};
|
||||
return definition;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -276,38 +170,32 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
|||
///////////// Protected Helpers /////////////
|
||||
|
||||
/**
|
||||
* Get the inner function declaration of an ES5-style class.
|
||||
* In ES5, the implementation of a class is a function expression that is hidden inside an IIFE,
|
||||
* whose value is assigned to a variable (which represents the class to the rest of the program).
|
||||
* So we might need to dig around to get hold of the "class" declaration.
|
||||
*
|
||||
* In ES5, the implementation of a class is a function expression that is hidden inside an IIFE
|
||||
* and returned to be assigned to a variable outside the IIFE, which is what the rest of the
|
||||
* program interacts with.
|
||||
* This method extracts a `NgccClassSymbol` if `declaration` is the function declaration inside
|
||||
* the IIFE. Otherwise, undefined is returned.
|
||||
*
|
||||
* Given the outer variable declaration, we want to get to the inner function declaration.
|
||||
*
|
||||
* @param decl a declaration node that could be the variable expression outside an ES5 class IIFE.
|
||||
* @param checker the TS program TypeChecker
|
||||
* @returns the inner function declaration or `undefined` if it is not a "class".
|
||||
* @param declaration the declaration whose symbol we are finding.
|
||||
* @returns the symbol for the node or `undefined` if it is not a "class" or has no symbol.
|
||||
*/
|
||||
protected getInnerFunctionDeclarationFromClassDeclaration(decl: ts.Declaration):
|
||||
ts.FunctionDeclaration|undefined {
|
||||
// Extract the IIFE body (if any).
|
||||
const iifeBody = getIifeBody(decl);
|
||||
if (!iifeBody) return undefined;
|
||||
protected getClassSymbolFromInnerDeclaration(declaration: ts.Node): NgccClassSymbol|undefined {
|
||||
const classSymbol = super.getClassSymbolFromInnerDeclaration(declaration);
|
||||
if (classSymbol !== undefined) {
|
||||
return classSymbol;
|
||||
}
|
||||
|
||||
// Extract the function declaration from inside the IIFE.
|
||||
const functionDeclaration = iifeBody.statements.find(ts.isFunctionDeclaration);
|
||||
if (!functionDeclaration) return undefined;
|
||||
if (!ts.isFunctionDeclaration(declaration) || !hasNameIdentifier(declaration)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Extract the return identifier of the IIFE.
|
||||
const returnIdentifier = getReturnIdentifier(iifeBody);
|
||||
const returnIdentifierSymbol =
|
||||
returnIdentifier && this.checker.getSymbolAtLocation(returnIdentifier);
|
||||
if (!returnIdentifierSymbol) return undefined;
|
||||
const outerDeclaration = getClassDeclarationFromInnerDeclaration(declaration);
|
||||
if (outerDeclaration === null || !hasNameIdentifier(outerDeclaration)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Verify that the inner function is returned.
|
||||
if (returnIdentifierSymbol.valueDeclaration !== functionDeclaration) return undefined;
|
||||
|
||||
return functionDeclaration;
|
||||
return this.createClassSymbol(outerDeclaration, declaration);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -524,83 +412,6 @@ function readPropertyFunctionExpression(object: ts.ObjectLiteralExpression, name
|
|||
return property && ts.isFunctionExpression(property.initializer) && property.initializer || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actual (outer) declaration of a class.
|
||||
*
|
||||
* In ES5, the implementation of a class is a function expression that is hidden inside an IIFE and
|
||||
* returned to be assigned to a variable outside the IIFE, which is what the rest of the program
|
||||
* interacts with.
|
||||
*
|
||||
* Given the inner function declaration, we want to get to the declaration of the outer variable
|
||||
* that represents the class.
|
||||
*
|
||||
* @param node a node that could be the function expression inside an ES5 class IIFE.
|
||||
* @returns the outer variable declaration or `undefined` if it is not a "class".
|
||||
*/
|
||||
function getClassDeclarationFromInnerFunctionDeclaration(node: ts.Node):
|
||||
ClassDeclaration<ts.VariableDeclaration>|null {
|
||||
if (ts.isFunctionDeclaration(node)) {
|
||||
// It might be the function expression inside the IIFE. We need to go 5 levels up...
|
||||
|
||||
// 1. IIFE body.
|
||||
let outerNode = node.parent;
|
||||
if (!outerNode || !ts.isBlock(outerNode)) return null;
|
||||
|
||||
// 2. IIFE function expression.
|
||||
outerNode = outerNode.parent;
|
||||
if (!outerNode || !ts.isFunctionExpression(outerNode)) return null;
|
||||
|
||||
// 3. IIFE call expression.
|
||||
outerNode = outerNode.parent;
|
||||
if (!outerNode || !ts.isCallExpression(outerNode)) return null;
|
||||
|
||||
// 4. Parenthesis around IIFE.
|
||||
outerNode = outerNode.parent;
|
||||
if (!outerNode || !ts.isParenthesizedExpression(outerNode)) return null;
|
||||
|
||||
// 5. Outer variable declaration.
|
||||
outerNode = outerNode.parent;
|
||||
if (!outerNode || !ts.isVariableDeclaration(outerNode)) return null;
|
||||
|
||||
// Finally, ensure that the variable declaration has a `name` identifier.
|
||||
return hasNameIdentifier(outerNode) ? outerNode : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getIifeBody(declaration: ts.Declaration): ts.Block|undefined {
|
||||
if (!ts.isVariableDeclaration(declaration) || !declaration.initializer) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Recognize a variable declaration of one of the forms:
|
||||
// - `var MyClass = (function () { ... }());`
|
||||
// - `var MyClass = MyClass_1 = (function () { ... }());`
|
||||
let parenthesizedCall = declaration.initializer;
|
||||
while (isAssignment(parenthesizedCall)) {
|
||||
parenthesizedCall = parenthesizedCall.right;
|
||||
}
|
||||
|
||||
const body = getIifeConciseBody(parenthesizedCall);
|
||||
return body !== undefined && ts.isBlock(body) ? body : undefined;
|
||||
}
|
||||
|
||||
function getReturnIdentifier(body: ts.Block): ts.Identifier|undefined {
|
||||
const returnStatement = body.statements.find(ts.isReturnStatement);
|
||||
if (!returnStatement || !returnStatement.expression) {
|
||||
return undefined;
|
||||
}
|
||||
if (ts.isIdentifier(returnStatement.expression)) {
|
||||
return returnStatement.expression;
|
||||
}
|
||||
if (isAssignment(returnStatement.expression) &&
|
||||
ts.isIdentifier(returnStatement.expression.left)) {
|
||||
return returnStatement.expression.left;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getReturnStatement(declaration: ts.Expression|undefined): ts.ReturnStatement|undefined {
|
||||
return declaration && ts.isFunctionExpression(declaration) ?
|
||||
declaration.body.statements.find(ts.isReturnStatement) :
|
||||
|
@ -766,7 +577,7 @@ function isSuperIdentifier(node: ts.Node): boolean {
|
|||
* @param parameters the collection of parameters that were found in the function definition
|
||||
* @returns true if the statement was a parameter initializer
|
||||
*/
|
||||
function reflectParamInitializer(statement: ts.Statement, parameters: Parameter[]) {
|
||||
function captureParamInitializer(statement: ts.Statement, parameters: Parameter[]) {
|
||||
if (ts.isIfStatement(statement) && isUndefinedComparison(statement.expression) &&
|
||||
ts.isBlock(statement.thenStatement) && statement.thenStatement.statements.length === 1) {
|
||||
const ifStatementComparison = statement.expression; // (arg === void 0)
|
||||
|
@ -792,3 +603,34 @@ function isUndefinedComparison(expression: ts.Expression): expression is ts.Expr
|
|||
expression.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken &&
|
||||
ts.isVoidExpression(expression.right) && ts.isIdentifier(expression.left);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the declaration of the given `classSymbol` to find the IIFE wrapper function.
|
||||
*
|
||||
* This function may accept a `_super` argument if there is a base class.
|
||||
*
|
||||
* ```
|
||||
* var TestClass = (function (_super) {
|
||||
* __extends(TestClass, _super);
|
||||
* function TestClass() {}
|
||||
* return TestClass;
|
||||
* }(BaseClass));
|
||||
* ```
|
||||
*
|
||||
* @param classSymbol the class whose iife wrapper function we want to get.
|
||||
* @returns the IIFE function or null if it could not be parsed.
|
||||
*/
|
||||
function getIifeFn(classSymbol: NgccClassSymbol|undefined): ts.FunctionExpression|null {
|
||||
if (classSymbol === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const innerDeclaration = classSymbol.implementation.valueDeclaration;
|
||||
const iifeBody = innerDeclaration.parent;
|
||||
if (!ts.isBlock(iifeBody)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const iifeWrapper = iifeBody.parent;
|
||||
return iifeWrapper && ts.isFunctionExpression(iifeWrapper) ? iifeWrapper : null;
|
||||
}
|
||||
|
|
|
@ -48,6 +48,12 @@ export interface NgccClassSymbol {
|
|||
* declaration.
|
||||
*/
|
||||
implementation: ts.Symbol;
|
||||
|
||||
/**
|
||||
* Represents the symbol corresponding to a variable within a class IIFE that may be used to
|
||||
* attach static properties or decorated.
|
||||
*/
|
||||
adjacent?: ts.Symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,7 +14,7 @@ import {getDeclaration} from '../../../src/ngtsc/testing';
|
|||
import {loadFakeCore, loadTestFiles} from '../../../test/helpers';
|
||||
import {CommonJsReflectionHost} from '../../src/host/commonjs_host';
|
||||
import {DelegatingReflectionHost} from '../../src/host/delegating_host';
|
||||
import {getIifeBody} from '../../src/host/esm5_host';
|
||||
import {getIifeBody} from '../../src/host/esm2015_host';
|
||||
import {NgccReflectionHost} from '../../src/host/ngcc_host';
|
||||
import {BundleProgram} from '../../src/packages/bundle_program';
|
||||
import {MockLogger} from '../helpers/mock_logger';
|
||||
|
@ -2212,7 +2212,8 @@ exports.MissingClass2 = MissingClass2;
|
|||
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
||||
const outerNode = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
||||
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
|
||||
.statements.find(isNamedFunctionDeclaration)!;
|
||||
const classSymbol = host.getClassSymbol(outerNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
|
@ -2227,7 +2228,8 @@ exports.MissingClass2 = MissingClass2;
|
|||
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
||||
const outerNode = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
||||
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
|
||||
.statements.find(isNamedFunctionDeclaration)!;
|
||||
const classSymbol = host.getClassSymbol(innerNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
|
@ -2243,7 +2245,8 @@ exports.MissingClass2 = MissingClass2;
|
|||
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
||||
const outerNode = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
||||
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
|
||||
.statements.find(isNamedFunctionDeclaration)!;
|
||||
|
||||
const innerSymbol = host.getClassSymbol(innerNode)!;
|
||||
const outerSymbol = host.getClassSymbol(outerNode)!;
|
||||
|
@ -2260,7 +2263,8 @@ exports.MissingClass2 = MissingClass2;
|
|||
const outerNode = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'NoParensClass',
|
||||
isNamedVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
||||
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
|
||||
.statements.find(isNamedFunctionDeclaration)!;
|
||||
const classSymbol = host.getClassSymbol(outerNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
|
@ -2277,7 +2281,8 @@ exports.MissingClass2 = MissingClass2;
|
|||
const outerNode = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'InnerParensClass',
|
||||
isNamedVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
||||
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
|
||||
.statements.find(isNamedFunctionDeclaration)!;
|
||||
const classSymbol = host.getClassSymbol(outerNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
|
@ -2345,7 +2350,8 @@ exports.MissingClass2 = MissingClass2;
|
|||
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
||||
const outerNode = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
||||
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
|
||||
.statements.find(isNamedFunctionDeclaration)!;
|
||||
expect(host.isClass(innerNode)).toBe(true);
|
||||
});
|
||||
|
||||
|
|
|
@ -133,6 +133,14 @@ runInEachFileSystem(() => {
|
|||
class NoDecoratorConstructorClass {
|
||||
constructor(foo) {}
|
||||
}
|
||||
let SimpleWrappedClass = /** @class */ (() => {
|
||||
class SimpleWrappedClassInner {}
|
||||
return SimpleWrappedClassInner;
|
||||
})();
|
||||
let AliasedWrappedClass = /** @class */ (() => {
|
||||
let AliasedWrappedClassAdjacent = class AliasedWrappedClassInner {};
|
||||
return SimpleWrappedClassAdjacent;
|
||||
})();
|
||||
`,
|
||||
};
|
||||
|
||||
|
@ -166,6 +174,11 @@ runInEachFileSystem(() => {
|
|||
];
|
||||
return AliasedWrappedClass;
|
||||
})();
|
||||
let DecoratedWrappedClass = /** @class */ (() => {
|
||||
let DecoratedWrappedClass = class DecoratedWrappedClass {}
|
||||
// ... add decorations ...
|
||||
return DecoratedWrappedClass;
|
||||
})();
|
||||
let usageOfWrappedClass = AliasedWrappedClass_1;
|
||||
`,
|
||||
};
|
||||
|
@ -534,7 +547,27 @@ runInEachFileSystem(() => {
|
|||
{ type: Directive, args: [{ selector: '[b]' }] }
|
||||
];
|
||||
class C {}
|
||||
export { A, x, C };
|
||||
var AliasedClass_1;
|
||||
let AliasedClass = AliasedClass_1 = class AliasedClass {}
|
||||
AliasedClass.decorators = [
|
||||
{ type: Directive, args: [{ selector: '[aliased]' },] }
|
||||
];
|
||||
let Wrapped1 = /** @class */ (() => {
|
||||
let Wrapped1 = class Wrapped1 {
|
||||
};
|
||||
Wrapped1 = __decorate([
|
||||
Directive({selector: '[wrapped-1]'})
|
||||
], Wrapped1);
|
||||
return Wrapped1;
|
||||
})();
|
||||
let Wrapped2 = /** @class */ (() => {
|
||||
class Wrapped2 {}
|
||||
Wrapped2.decorators = [
|
||||
{ type: Directive, args: [{ selector: '[wrapped-2]' },] }
|
||||
];
|
||||
return Wrapped2;
|
||||
})();
|
||||
export { A, x, C, AliasedClass, Wrapped1, Wrapped2 };
|
||||
`
|
||||
},
|
||||
{
|
||||
|
@ -1605,6 +1638,30 @@ runInEachFileSystem(() => {
|
|||
.toBe(classDeclaration);
|
||||
});
|
||||
|
||||
it('should return the correct declaration for an inner class identifier inside an IIFE',
|
||||
() => {
|
||||
loadTestFiles([SIMPLE_CLASS_FILE]);
|
||||
const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
||||
const host =
|
||||
createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
|
||||
|
||||
const outerDeclaration = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'SimpleWrappedClass',
|
||||
isNamedVariableDeclaration);
|
||||
const innerDeclaration =
|
||||
((((outerDeclaration.initializer as ts.CallExpression).expression as
|
||||
ts.ParenthesizedExpression)
|
||||
.expression as ts.ArrowFunction)
|
||||
.body as ts.Block)
|
||||
.statements[0] as ts.ClassDeclaration;
|
||||
|
||||
const outerIdentifier = outerDeclaration.name as ts.Identifier;
|
||||
const innerIdentifier = innerDeclaration.name as ts.Identifier;
|
||||
|
||||
expect(host.getDeclarationOfIdentifier(outerIdentifier)!.node).toBe(outerDeclaration);
|
||||
expect(host.getDeclarationOfIdentifier(innerIdentifier)!.node).toBe(outerDeclaration);
|
||||
});
|
||||
|
||||
it('should recognize enum declarations with string values', () => {
|
||||
const testFile: TestFile = {
|
||||
name: _('/node_modules/test-package/some/file.js'),
|
||||
|
@ -1767,6 +1824,7 @@ runInEachFileSystem(() => {
|
|||
expect(classSymbol).toBeDefined();
|
||||
expect(classSymbol!.declaration.valueDeclaration).toBe(node);
|
||||
expect(classSymbol!.implementation.valueDeclaration).toBe(node);
|
||||
expect(classSymbol!.adjacent).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return the class symbol for a class expression (outer variable declaration)',
|
||||
|
@ -1784,6 +1842,7 @@ runInEachFileSystem(() => {
|
|||
expect(classSymbol).toBeDefined();
|
||||
expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode);
|
||||
expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode);
|
||||
expect(classSymbol!.adjacent).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return the class symbol for a class expression (inner class expression)', () => {
|
||||
|
@ -1798,6 +1857,7 @@ runInEachFileSystem(() => {
|
|||
expect(classSymbol).toBeDefined();
|
||||
expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode);
|
||||
expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode);
|
||||
expect(classSymbol!.adjacent).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return the same class symbol (of the outer declaration) for outer and inner declarations',
|
||||
|
@ -1837,6 +1897,7 @@ runInEachFileSystem(() => {
|
|||
return fail('Expected a named class declaration');
|
||||
}
|
||||
expect(classSymbol.implementation.valueDeclaration.name.text).toBe('SimpleWrappedClass');
|
||||
expect(classSymbol.adjacent).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return the class symbol for a wrapped class expression (inner class expression)',
|
||||
|
@ -1861,6 +1922,7 @@ runInEachFileSystem(() => {
|
|||
return fail('Expected a named class declaration');
|
||||
}
|
||||
expect(classSymbol.implementation.valueDeclaration.name.text).toBe('SimpleWrappedClass');
|
||||
expect(classSymbol.adjacent).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return the same class symbol (of the outer declaration) for wrapped outer and inner declarations',
|
||||
|
@ -1881,6 +1943,87 @@ runInEachFileSystem(() => {
|
|||
expect(innerSymbol.implementation).toBe(outerSymbol.implementation);
|
||||
});
|
||||
|
||||
it('should return the class symbol for a decorated wrapped class expression (outer variable declaration)',
|
||||
() => {
|
||||
loadTestFiles([WRAPPED_CLASS_EXPRESSION_FILE]);
|
||||
const bundle = makeTestBundleProgram(WRAPPED_CLASS_EXPRESSION_FILE.name);
|
||||
const host =
|
||||
createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
|
||||
const outerNode = getDeclaration(
|
||||
bundle.program, WRAPPED_CLASS_EXPRESSION_FILE.name, 'DecoratedWrappedClass',
|
||||
isNamedVariableDeclaration);
|
||||
const classSymbol = host.getClassSymbol(outerNode);
|
||||
|
||||
if (classSymbol === undefined) {
|
||||
return fail('Expected classSymbol to be defined');
|
||||
}
|
||||
expect(classSymbol.name).toEqual('DecoratedWrappedClass');
|
||||
expect(classSymbol.declaration.valueDeclaration).toBe(outerNode);
|
||||
|
||||
if (!ts.isClassExpression(classSymbol.implementation.valueDeclaration)) {
|
||||
return fail('Expected a named class declaration');
|
||||
}
|
||||
expect(classSymbol.implementation.valueDeclaration.name!.text)
|
||||
.toBe('DecoratedWrappedClass');
|
||||
|
||||
if (classSymbol.adjacent === undefined ||
|
||||
!isNamedVariableDeclaration(classSymbol.adjacent.valueDeclaration)) {
|
||||
return fail('Expected a named variable declaration for the adjacent symbol');
|
||||
}
|
||||
expect(classSymbol.adjacent.valueDeclaration.name.text).toBe('DecoratedWrappedClass');
|
||||
});
|
||||
|
||||
it('should return the class symbol for a decorated wrapped class expression (inner class expression)',
|
||||
() => {
|
||||
loadTestFiles([WRAPPED_CLASS_EXPRESSION_FILE]);
|
||||
const bundle = makeTestBundleProgram(WRAPPED_CLASS_EXPRESSION_FILE.name);
|
||||
const host =
|
||||
createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
|
||||
const outerNode = getDeclaration(
|
||||
bundle.program, WRAPPED_CLASS_EXPRESSION_FILE.name, 'DecoratedWrappedClass',
|
||||
isNamedVariableDeclaration);
|
||||
const innerNode: ts.ClassExpression =
|
||||
(outerNode as any)
|
||||
.initializer.expression.expression.body.statements[0]
|
||||
.declarationList.declarations[0]
|
||||
.initializer;
|
||||
const classSymbol = host.getClassSymbol(innerNode);
|
||||
|
||||
if (classSymbol === undefined) {
|
||||
return fail('Expected classSymbol to be defined');
|
||||
}
|
||||
expect(classSymbol.name).toEqual('DecoratedWrappedClass');
|
||||
expect(classSymbol.declaration.valueDeclaration).toBe(outerNode);
|
||||
expect(classSymbol.implementation.valueDeclaration).toBe(innerNode);
|
||||
|
||||
if (classSymbol.adjacent === undefined ||
|
||||
!isNamedVariableDeclaration(classSymbol.adjacent.valueDeclaration)) {
|
||||
return fail('Expected a named variable declaration for the adjacent symbol');
|
||||
}
|
||||
expect(classSymbol.adjacent.valueDeclaration.name.text).toBe('DecoratedWrappedClass');
|
||||
});
|
||||
|
||||
it('should return the same class symbol (of the outer declaration) for decorated wrapped outer and inner declarations',
|
||||
() => {
|
||||
loadTestFiles([WRAPPED_CLASS_EXPRESSION_FILE]);
|
||||
const bundle = makeTestBundleProgram(WRAPPED_CLASS_EXPRESSION_FILE.name);
|
||||
const host =
|
||||
createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
|
||||
const outerNode = getDeclaration(
|
||||
bundle.program, WRAPPED_CLASS_EXPRESSION_FILE.name, 'DecoratedWrappedClass',
|
||||
isNamedVariableDeclaration);
|
||||
const innerNode: ts.ClassExpression =
|
||||
(outerNode as any)
|
||||
.initializer.expression.expression.body.statements[0]
|
||||
.declarationList.declarations[0]
|
||||
.initializer;
|
||||
|
||||
const innerSymbol = host.getClassSymbol(innerNode)!;
|
||||
const outerSymbol = host.getClassSymbol(outerNode)!;
|
||||
expect(innerSymbol.declaration).toBe(outerSymbol.declaration);
|
||||
expect(innerSymbol.implementation).toBe(outerSymbol.implementation);
|
||||
});
|
||||
|
||||
it('should return undefined if node is not a class', () => {
|
||||
loadTestFiles([FOO_FUNCTION_FILE]);
|
||||
const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name);
|
||||
|
@ -1892,7 +2035,7 @@ runInEachFileSystem(() => {
|
|||
expect(classSymbol).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined if variable declaration is not initialized using a class expression',
|
||||
it('should return undefined if variable declaration is not initialized to a valid class definition',
|
||||
() => {
|
||||
const testFile = {
|
||||
name: _('/test.js'),
|
||||
|
@ -2115,8 +2258,9 @@ runInEachFileSystem(() => {
|
|||
const secondaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[1].name);
|
||||
|
||||
const classSymbolsPrimary = host.findClassSymbols(primaryFile);
|
||||
expect(classSymbolsPrimary.length).toEqual(3);
|
||||
expect(classSymbolsPrimary.map(c => c.name)).toEqual(['A', 'B', 'C']);
|
||||
expect(classSymbolsPrimary.map(c => c.name)).toEqual([
|
||||
'A', 'B', 'C', 'AliasedClass', 'Wrapped1', 'Wrapped2'
|
||||
]);
|
||||
|
||||
const classSymbolsSecondary = host.findClassSymbols(secondaryFile);
|
||||
expect(classSymbolsSecondary.length).toEqual(1);
|
||||
|
@ -2134,10 +2278,14 @@ runInEachFileSystem(() => {
|
|||
|
||||
const classSymbolsPrimary = host.findClassSymbols(primaryFile);
|
||||
const classDecoratorsPrimary = classSymbolsPrimary.map(s => host.getDecoratorsOfSymbol(s));
|
||||
expect(classDecoratorsPrimary.length).toEqual(3);
|
||||
|
||||
expect(classDecoratorsPrimary.length).toEqual(6);
|
||||
expect(classDecoratorsPrimary[0]!.map(d => d.name)).toEqual(['Directive']);
|
||||
expect(classDecoratorsPrimary[1]!.map(d => d.name)).toEqual(['Directive']);
|
||||
expect(classDecoratorsPrimary[2]).toBe(null);
|
||||
expect(classDecoratorsPrimary[3]!.map(d => d.name)).toEqual(['Directive']);
|
||||
expect(classDecoratorsPrimary[4]!.map(d => d.name)).toEqual(['Directive']);
|
||||
expect(classDecoratorsPrimary[5]!.map(d => d.name)).toEqual(['Directive']);
|
||||
|
||||
const classSymbolsSecondary = host.findClassSymbols(secondaryFile);
|
||||
const classDecoratorsSecondary =
|
||||
|
@ -2339,7 +2487,7 @@ runInEachFileSystem(() => {
|
|||
});
|
||||
|
||||
describe('getInternalNameOfClass()', () => {
|
||||
it('should return the name of the class (there is no separate inner class in ES2015)', () => {
|
||||
it('should return the name of the class (if there is no separate inner class)', () => {
|
||||
loadTestFiles([SIMPLE_CLASS_FILE]);
|
||||
const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
||||
const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
|
||||
|
@ -2347,10 +2495,32 @@ runInEachFileSystem(() => {
|
|||
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration);
|
||||
expect(host.getInternalNameOfClass(node).text).toEqual('EmptyClass');
|
||||
});
|
||||
|
||||
it('should return the name of the inner class (if there is an IIFE)', () => {
|
||||
loadTestFiles([SIMPLE_CLASS_FILE]);
|
||||
const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
||||
const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
|
||||
const node = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'SimpleWrappedClass',
|
||||
isNamedVariableDeclaration);
|
||||
expect(host.getInternalNameOfClass(node).text).toEqual('SimpleWrappedClassInner');
|
||||
});
|
||||
|
||||
it('should return the name of the inner variable declaration (if there is an aliased class in an IIFE)',
|
||||
() => {
|
||||
loadTestFiles([SIMPLE_CLASS_FILE]);
|
||||
const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
||||
const host =
|
||||
createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
|
||||
const node = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'AliasedWrappedClass',
|
||||
isNamedVariableDeclaration);
|
||||
expect(host.getInternalNameOfClass(node).text).toEqual('AliasedWrappedClassInner');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAdjacentNameOfClass()', () => {
|
||||
it('should return the name of the class (there is no separate inner class in ES2015)', () => {
|
||||
it('should return the name of the class (if there is no separate inner class)', () => {
|
||||
loadTestFiles([SIMPLE_CLASS_FILE]);
|
||||
const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
||||
const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
|
||||
|
@ -2358,6 +2528,28 @@ runInEachFileSystem(() => {
|
|||
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration);
|
||||
expect(host.getAdjacentNameOfClass(node).text).toEqual('EmptyClass');
|
||||
});
|
||||
|
||||
it('should return the name of the inner class (if there is an IIFE)', () => {
|
||||
loadTestFiles([SIMPLE_CLASS_FILE]);
|
||||
const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
||||
const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
|
||||
const node = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'SimpleWrappedClass',
|
||||
isNamedVariableDeclaration);
|
||||
expect(host.getAdjacentNameOfClass(node).text).toEqual('SimpleWrappedClassInner');
|
||||
});
|
||||
|
||||
it('should return the name of the inner variable declaration (if there is an aliased class in an IIFE)',
|
||||
() => {
|
||||
loadTestFiles([SIMPLE_CLASS_FILE]);
|
||||
const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
||||
const host =
|
||||
createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
|
||||
const node = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'AliasedWrappedClass',
|
||||
isNamedVariableDeclaration);
|
||||
expect(host.getAdjacentNameOfClass(node).text).toEqual('AliasedWrappedClassAdjacent');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEndOfClass()', () => {
|
||||
|
|
|
@ -12,7 +12,8 @@ import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/test
|
|||
import {ClassMemberKind, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
|
||||
import {getDeclaration} from '../../../src/ngtsc/testing';
|
||||
import {loadFakeCore, loadTestFiles, loadTsLib} from '../../../test/helpers';
|
||||
import {Esm5ReflectionHost, getIifeBody} from '../../src/host/esm5_host';
|
||||
import {getIifeBody} from '../../src/host/esm2015_host';
|
||||
import {Esm5ReflectionHost} from '../../src/host/esm5_host';
|
||||
import {MockLogger} from '../helpers/mock_logger';
|
||||
import {convertToDirectTsLibImport, convertToInlineTsLib, makeTestBundleProgram} from '../helpers/utils';
|
||||
|
||||
|
@ -331,7 +332,8 @@ export { AliasedDirective$1 };
|
|||
const classNode = getDeclaration(
|
||||
bundle.program, _('/some_minified_directive.js'), 'SomeDirective',
|
||||
isNamedVariableDeclaration);
|
||||
const innerNode = getIifeBody(classNode)!.statements.find(isNamedFunctionDeclaration)!;
|
||||
const innerNode = (getIifeBody(classNode.initializer!) as ts.Block)
|
||||
.statements.find(isNamedFunctionDeclaration)!;
|
||||
const classSymbol = host.getClassSymbol(classNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
|
|
|
@ -14,8 +14,8 @@ import {ClassMemberKind, ConcreteDeclaration, CtorParameter, Decorator, Downleve
|
|||
import {getDeclaration} from '../../../src/ngtsc/testing';
|
||||
import {loadFakeCore, loadTestFiles} from '../../../test/helpers';
|
||||
import {DelegatingReflectionHost} from '../../src/host/delegating_host';
|
||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||
import {Esm5ReflectionHost, getIifeBody} from '../../src/host/esm5_host';
|
||||
import {Esm2015ReflectionHost, getIifeBody} from '../../src/host/esm2015_host';
|
||||
import {Esm5ReflectionHost} from '../../src/host/esm5_host';
|
||||
import {NgccReflectionHost} from '../../src/host/ngcc_host';
|
||||
import {BundleProgram} from '../../src/packages/bundle_program';
|
||||
import {MockLogger} from '../helpers/mock_logger';
|
||||
|
@ -2283,7 +2283,8 @@ runInEachFileSystem(() => {
|
|||
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
|
||||
const outerNode = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
||||
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
|
||||
.statements.find(isNamedFunctionDeclaration)!;
|
||||
const classSymbol = host.getClassSymbol(outerNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
|
@ -2297,7 +2298,8 @@ runInEachFileSystem(() => {
|
|||
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
|
||||
const outerNode = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
||||
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
|
||||
.statements.find(isNamedFunctionDeclaration)!;
|
||||
const classSymbol = host.getClassSymbol(innerNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
|
@ -2312,7 +2314,8 @@ runInEachFileSystem(() => {
|
|||
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
|
||||
const outerNode = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
||||
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
|
||||
.statements.find(isNamedFunctionDeclaration)!;
|
||||
|
||||
const innerSymbol = host.getClassSymbol(innerNode)!;
|
||||
const outerSymbol = host.getClassSymbol(outerNode)!;
|
||||
|
@ -2327,7 +2330,8 @@ runInEachFileSystem(() => {
|
|||
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
|
||||
const outerNode = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'NoParensClass', isNamedVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
||||
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
|
||||
.statements.find(isNamedFunctionDeclaration)!;
|
||||
const classSymbol = host.getClassSymbol(outerNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
|
@ -2343,7 +2347,8 @@ runInEachFileSystem(() => {
|
|||
const outerNode = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'InnerParensClass',
|
||||
isNamedVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
||||
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
|
||||
.statements.find(isNamedFunctionDeclaration)!;
|
||||
const classSymbol = host.getClassSymbol(outerNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
|
@ -2403,7 +2408,8 @@ runInEachFileSystem(() => {
|
|||
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
|
||||
const outerNode = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
||||
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
|
||||
.statements.find(isNamedFunctionDeclaration)!;
|
||||
expect(host.isClass(innerNode)).toBe(true);
|
||||
});
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import {ClassMemberKind, ConcreteDeclaration, CtorParameter, DownleveledEnum, Im
|
|||
import {getDeclaration} from '../../../src/ngtsc/testing';
|
||||
import {loadFakeCore, loadTestFiles} from '../../../test/helpers';
|
||||
import {DelegatingReflectionHost} from '../../src/host/delegating_host';
|
||||
import {getIifeBody} from '../../src/host/esm5_host';
|
||||
import {getIifeBody} from '../../src/host/esm2015_host';
|
||||
import {NgccReflectionHost} from '../../src/host/ngcc_host';
|
||||
import {parseStatementForUmdModule, UmdReflectionHost} from '../../src/host/umd_host';
|
||||
import {BundleProgram} from '../../src/packages/bundle_program';
|
||||
|
@ -2399,7 +2399,8 @@ runInEachFileSystem(() => {
|
|||
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
|
||||
const outerNode = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
||||
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
|
||||
.statements.find(isNamedFunctionDeclaration)!;
|
||||
const classSymbol = host.getClassSymbol(outerNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
|
@ -2413,7 +2414,8 @@ runInEachFileSystem(() => {
|
|||
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
|
||||
const outerNode = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
||||
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
|
||||
.statements.find(isNamedFunctionDeclaration)!;
|
||||
const classSymbol = host.getClassSymbol(innerNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
|
@ -2428,7 +2430,8 @@ runInEachFileSystem(() => {
|
|||
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
|
||||
const outerNode = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
||||
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
|
||||
.statements.find(isNamedFunctionDeclaration)!;
|
||||
|
||||
const innerSymbol = host.getClassSymbol(innerNode)!;
|
||||
const outerSymbol = host.getClassSymbol(outerNode)!;
|
||||
|
@ -2443,7 +2446,8 @@ runInEachFileSystem(() => {
|
|||
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
|
||||
const outerNode = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'NoParensClass', isNamedVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
||||
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
|
||||
.statements.find(isNamedFunctionDeclaration)!;
|
||||
const classSymbol = host.getClassSymbol(outerNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
|
@ -2459,7 +2463,8 @@ runInEachFileSystem(() => {
|
|||
const outerNode = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'InnerParensClass',
|
||||
isNamedVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
||||
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
|
||||
.statements.find(isNamedFunctionDeclaration)!;
|
||||
const classSymbol = host.getClassSymbol(outerNode);
|
||||
|
||||
expect(classSymbol).toBeDefined();
|
||||
|
@ -2519,7 +2524,8 @@ runInEachFileSystem(() => {
|
|||
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
|
||||
const outerNode = getDeclaration(
|
||||
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isVariableDeclaration);
|
||||
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
||||
const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block)
|
||||
.statements.find(isNamedFunctionDeclaration)!;
|
||||
expect(host.isClass(innerNode)).toBe(true);
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue