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:
Pete Bacon Darwin 2020-05-12 08:20:00 +01:00 committed by Kara Erickson
parent a2b8dc1cfb
commit d7440c452a
8 changed files with 792 additions and 467 deletions

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
/**

View File

@ -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);
});

View File

@ -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()', () => {

View File

@ -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();

View File

@ -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);
});

View File

@ -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);
});