fix(ngcc): support TS 3.9 wrapped ES2015 classes (#36884)
In TS 3.9 the compiler will start to wrap ES2015 classes in an IIFE to help with tree-shaking when the class has "associated" statements. E.g. ```ts let PlatformLocation = /** @class */ (() => { ... class PlatformLocation { } ... return PlatformLocation; })(); ``` This commit updates `Esm2015ReflectionHost` to support this format. PR Close #36884
This commit is contained in:
parent
58ea040570
commit
db4c59dad9
|
@ -8,7 +8,7 @@
|
|||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, ConcreteDeclaration, CtorParameter, Declaration, Decorator, EnumMember, isDecoratorIdentifier, KnownDeclaration, reflectObjectLiteral, SpecialDeclarationKind, TypeScriptReflectionHost, TypeValueReference} from '../../../src/ngtsc/reflection';
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, ConcreteDeclaration, CtorParameter, Declaration, Decorator, EnumMember, isDecoratorIdentifier, isNamedClassDeclaration, 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';
|
||||
|
@ -108,12 +108,18 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
* Classes should have a `name` identifier, because they may need to be referenced in other parts
|
||||
* of the program.
|
||||
*
|
||||
* In ES2015, a class may be declared using a variable declaration of the following structure:
|
||||
* 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; })()
|
||||
* ```
|
||||
*
|
||||
* Here, the intermediate `MyClass_1` assignment is optional. In the above example, the
|
||||
* `class MyClass {}` node is returned as declaration of `MyClass`.
|
||||
*
|
||||
|
@ -130,12 +136,18 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
}
|
||||
|
||||
/**
|
||||
* In ES2015, a class may be declared using a variable declaration of the following structure:
|
||||
* 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.
|
||||
|
@ -145,8 +157,8 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
* of a class.
|
||||
*/
|
||||
protected getClassSymbolFromOuterDeclaration(declaration: ts.Node): NgccClassSymbol|undefined {
|
||||
// Create a symbol without inner declaration if the declaration is a regular class declaration.
|
||||
if (ts.isClassDeclaration(declaration) && hasNameIdentifier(declaration)) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
@ -163,12 +175,18 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
}
|
||||
|
||||
/**
|
||||
* In ES2015, a class may be declared using a variable declaration of the following structure:
|
||||
* 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.
|
||||
|
@ -178,11 +196,20 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
* of a class.
|
||||
*/
|
||||
protected getClassSymbolFromInnerDeclaration(declaration: ts.Node): NgccClassSymbol|undefined {
|
||||
if (!ts.isClassExpression(declaration) || !hasNameIdentifier(declaration)) {
|
||||
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;
|
||||
}
|
||||
|
||||
const outerDeclaration = getVariableDeclarationOfDeclaration(declaration);
|
||||
if (outerDeclaration === undefined || !hasNameIdentifier(outerDeclaration)) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -745,13 +772,20 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
|
||||
/**
|
||||
* Try to retrieve the symbol of a static property on a class.
|
||||
*
|
||||
* In some cases, a static property can either be set on the inner declaration inside the class'
|
||||
* IIFE, or it can be set on the outer variable declaration. Therefore, the host checks both
|
||||
* places, first looking up the property on the inner symbol, and if the property is not found it
|
||||
* will fall back to looking up the property on the outer symbol.
|
||||
*
|
||||
* @param symbol the class whose property we are interested in.
|
||||
* @param propertyName the name of static property.
|
||||
* @returns the symbol if it is found or `undefined` if not.
|
||||
*/
|
||||
protected getStaticProperty(symbol: NgccClassSymbol, propertyName: ts.__String): ts.Symbol
|
||||
|undefined {
|
||||
return symbol.declaration.exports && symbol.declaration.exports.get(propertyName);
|
||||
return symbol.implementation.exports && symbol.implementation.exports.get(propertyName) ||
|
||||
symbol.declaration.exports && symbol.declaration.exports.get(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1560,7 +1594,14 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
* @returns an array of statements that may contain helper calls.
|
||||
*/
|
||||
protected getStatementsForClass(classSymbol: NgccClassSymbol): ts.Statement[] {
|
||||
return Array.from(classSymbol.declaration.valueDeclaration.getSourceFile().statements);
|
||||
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);
|
||||
}
|
||||
// We should never arrive here
|
||||
throw new Error(`Unable to find adjacent statements for ${classSymbol.name}`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2048,6 +2089,38 @@ export function isAssignmentStatement(statement: ts.Statement): statement is Ass
|
|||
ts.isIdentifier(statement.expression.left);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the `expression` that is believed to be an IIFE and return the AST node that corresponds to
|
||||
* the body of the IIFE.
|
||||
*
|
||||
* The expression may be wrapped in parentheses, which are stripped off.
|
||||
*
|
||||
* If the IIFE is an arrow function then its body could be a `ts.Expression` rather than a
|
||||
* `ts.FunctionBody`.
|
||||
*
|
||||
* @param expression the expression to parse.
|
||||
* @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 {
|
||||
const call = stripParentheses(expression);
|
||||
if (!ts.isCallExpression(call)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const fn = stripParentheses(call.expression);
|
||||
if (!ts.isFunctionExpression(fn) && !ts.isArrowFunction(fn)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return fn.body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the `node` is an assignment of the form `a = b`.
|
||||
*
|
||||
* @param node The AST node to check.
|
||||
*/
|
||||
export function isAssignment(node: ts.Node): node is ts.AssignmentExpression<ts.EqualsToken> {
|
||||
return ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken;
|
||||
}
|
||||
|
@ -2132,12 +2205,18 @@ function getCalleeName(call: ts.CallExpression): string|null {
|
|||
///////////// Internal Helpers /////////////
|
||||
|
||||
/**
|
||||
* In ES2015, a class may be declared using a variable declaration of the following structure:
|
||||
* 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; })()
|
||||
* ```
|
||||
*
|
||||
* 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.
|
||||
|
@ -2145,23 +2224,40 @@ function getCalleeName(call: ts.CallExpression): string|null {
|
|||
* @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".
|
||||
*/
|
||||
function getInnerClassDeclaration(node: ts.Node): ClassDeclaration<ts.ClassExpression>|null {
|
||||
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 {};`
|
||||
let expression = node.initializer;
|
||||
while (isAssignment(expression)) {
|
||||
expression = expression.right;
|
||||
}
|
||||
|
||||
if (!ts.isClassExpression(expression) || !hasNameIdentifier(expression)) {
|
||||
return null;
|
||||
if (ts.isClassExpression(expression) && hasNameIdentifier(expression)) {
|
||||
return 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);
|
||||
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;
|
||||
}
|
||||
return innerDeclaration;
|
||||
}
|
||||
|
||||
function getDecoratorArgs(node: ts.ObjectLiteralExpression): ts.Expression[] {
|
||||
|
@ -2208,6 +2304,16 @@ function isClassMemberType(node: ts.Declaration): node is ts.ClassElement|
|
|||
* var MyClass = MyClass_1 = class MyClass {};
|
||||
* ```
|
||||
*
|
||||
* or
|
||||
*
|
||||
* ```
|
||||
* var MyClass = MyClass_1 = (() => {
|
||||
* class MyClass {}
|
||||
* ...
|
||||
* return MyClass;
|
||||
* })()
|
||||
```
|
||||
*
|
||||
* and the provided declaration being `class MyClass {}`, this will return the `var MyClass`
|
||||
* declaration.
|
||||
*
|
||||
|
@ -2301,3 +2407,12 @@ function getNonRootPackageFiles(bundle: BundleProgram): ts.SourceFile[] {
|
|||
return bundle.program.getSourceFiles().filter(
|
||||
f => (f !== rootFile) && isWithinPackage(bundle.package, f));
|
||||
}
|
||||
|
||||
function isTopLevel(node: ts.Node): boolean {
|
||||
while (node = node.parent) {
|
||||
if (ts.isBlock(node)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -11,9 +11,8 @@ 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 {Esm2015ReflectionHost, getPropertyValueFromSymbol, isAssignment, isAssignmentStatement, ParamInfo} from './esm2015_host';
|
||||
import {Esm2015ReflectionHost, getIifeConciseBody, getPropertyValueFromSymbol, isAssignment, isAssignmentStatement, ParamInfo} from './esm2015_host';
|
||||
import {NgccClassSymbol} from './ngcc_host';
|
||||
import {stripParentheses} from './utils';
|
||||
|
||||
|
||||
/**
|
||||
|
@ -489,30 +488,6 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
|||
const classDeclarationParent = classSymbol.implementation.valueDeclaration.parent;
|
||||
return ts.isBlock(classDeclarationParent) ? Array.from(classDeclarationParent.statements) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to retrieve the symbol of a static property on a class.
|
||||
*
|
||||
* In ES5, a static property can either be set on the inner function declaration inside the class'
|
||||
* IIFE, or it can be set on the outer variable declaration. Therefore, the ES5 host checks both
|
||||
* places, first looking up the property on the inner symbol, and if the property is not found it
|
||||
* will fall back to looking up the property on the outer symbol.
|
||||
*
|
||||
* @param symbol the class whose property we are interested in.
|
||||
* @param propertyName the name of static property.
|
||||
* @returns the symbol if it is found or `undefined` if not.
|
||||
*/
|
||||
protected getStaticProperty(symbol: NgccClassSymbol, propertyName: ts.__String): ts.Symbol
|
||||
|undefined {
|
||||
// First lets see if the static property can be resolved from the inner class symbol.
|
||||
const prop = symbol.implementation.exports && symbol.implementation.exports.get(propertyName);
|
||||
if (prop !== undefined) {
|
||||
return prop;
|
||||
}
|
||||
|
||||
// Otherwise, lookup the static properties on the outer class symbol.
|
||||
return symbol.declaration.exports && symbol.declaration.exports.get(propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
///////////// Internal Helpers /////////////
|
||||
|
@ -631,17 +606,8 @@ export function getIifeBody(declaration: ts.Declaration): ts.Block|undefined {
|
|||
parenthesizedCall = parenthesizedCall.right;
|
||||
}
|
||||
|
||||
const call = stripParentheses(parenthesizedCall);
|
||||
if (!ts.isCallExpression(call)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const fn = stripParentheses(call.expression);
|
||||
if (!ts.isFunctionExpression(fn)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return fn.body;
|
||||
const body = getIifeConciseBody(parenthesizedCall);
|
||||
return body !== undefined && ts.isBlock(body) ? body : undefined;
|
||||
}
|
||||
|
||||
function getReturnIdentifier(body: ts.Block): ts.Identifier|undefined {
|
||||
|
|
|
@ -30,6 +30,7 @@ runInEachFileSystem(() => {
|
|||
let ACCESSORS_FILE: TestFile;
|
||||
let SIMPLE_CLASS_FILE: TestFile;
|
||||
let CLASS_EXPRESSION_FILE: TestFile;
|
||||
let WRAPPED_CLASS_EXPRESSION_FILE: TestFile;
|
||||
let FOO_FUNCTION_FILE: TestFile;
|
||||
let INVALID_DECORATORS_FILE: TestFile;
|
||||
let INVALID_DECORATOR_ARGS_FILE: TestFile;
|
||||
|
@ -150,6 +151,26 @@ runInEachFileSystem(() => {
|
|||
`,
|
||||
};
|
||||
|
||||
WRAPPED_CLASS_EXPRESSION_FILE = {
|
||||
name: _('/wrapped_class_expression.js'),
|
||||
contents: `
|
||||
import {Directive} from '@angular/core';
|
||||
var AliasedWrappedClass_1;
|
||||
let SimpleWrappedClass = /** @class */ (() => {
|
||||
class SimpleWrappedClass {}
|
||||
return SimpleWrappedClass;
|
||||
})();
|
||||
let AliasedWrappedClass = AliasedWrappedClass_1 = /** @class */ (() => {
|
||||
class AliasedWrappedClass {}
|
||||
AliasedWrappedClass.decorators = [
|
||||
{ type: Directive, args: [{ selector: '[someDirective]' },] }
|
||||
];
|
||||
return AliasedWrappedClass;
|
||||
})();
|
||||
let usageOfWrappedClass = AliasedWrappedClass_1;
|
||||
`,
|
||||
};
|
||||
|
||||
FOO_FUNCTION_FILE = {
|
||||
name: _('/foo_function.js'),
|
||||
contents: `
|
||||
|
@ -762,6 +783,26 @@ runInEachFileSystem(() => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('should find the decorators on an aliased wrapped class', () => {
|
||||
loadTestFiles([WRAPPED_CLASS_EXPRESSION_FILE]);
|
||||
const bundle = makeTestBundleProgram(WRAPPED_CLASS_EXPRESSION_FILE.name);
|
||||
const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
|
||||
const classNode = getDeclaration(
|
||||
bundle.program, WRAPPED_CLASS_EXPRESSION_FILE.name, 'AliasedWrappedClass',
|
||||
isNamedVariableDeclaration);
|
||||
const decorators = host.getDecoratorsOfDeclaration(classNode)!;
|
||||
|
||||
expect(decorators).not.toBe(null!);
|
||||
expect(decorators.length).toEqual(1);
|
||||
|
||||
const decorator = decorators[0];
|
||||
expect(decorator.name).toEqual('Directive');
|
||||
expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'});
|
||||
expect(decorator.args!.map(arg => arg.getText())).toEqual([
|
||||
'{ selector: \'[someDirective]\' }',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return null if the symbol is not a class', () => {
|
||||
loadTestFiles([FOO_FUNCTION_FILE]);
|
||||
const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name);
|
||||
|
@ -1620,6 +1661,22 @@ runInEachFileSystem(() => {
|
|||
.toBe(classDeclaration);
|
||||
});
|
||||
|
||||
it('should return the original declaration of an aliased class', () => {
|
||||
loadTestFiles([WRAPPED_CLASS_EXPRESSION_FILE]);
|
||||
const bundle = makeTestBundleProgram(WRAPPED_CLASS_EXPRESSION_FILE.name);
|
||||
const host = createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
|
||||
const classDeclaration = getDeclaration(
|
||||
bundle.program, WRAPPED_CLASS_EXPRESSION_FILE.name, 'AliasedWrappedClass',
|
||||
ts.isVariableDeclaration);
|
||||
const usageOfWrappedClass = getDeclaration(
|
||||
bundle.program, WRAPPED_CLASS_EXPRESSION_FILE.name, 'usageOfWrappedClass',
|
||||
ts.isVariableDeclaration);
|
||||
const aliasedClassIdentifier = usageOfWrappedClass.initializer as ts.Identifier;
|
||||
expect(aliasedClassIdentifier.text).toBe('AliasedWrappedClass_1');
|
||||
expect(host.getDeclarationOfIdentifier(aliasedClassIdentifier)!.node)
|
||||
.toBe(classDeclaration);
|
||||
});
|
||||
|
||||
it('should recognize enum declarations with string values', () => {
|
||||
const testFile: TestFile = {
|
||||
name: _('/node_modules/test-package/some/file.js'),
|
||||
|
@ -1832,6 +1889,70 @@ runInEachFileSystem(() => {
|
|||
expect(innerSymbol.implementation).toBe(outerSymbol.implementation);
|
||||
});
|
||||
|
||||
it('should return the class symbol for a 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, 'SimpleWrappedClass',
|
||||
isNamedVariableDeclaration);
|
||||
const classSymbol = host.getClassSymbol(outerNode);
|
||||
|
||||
if (classSymbol === undefined) {
|
||||
return fail('Expected classSymbol to be defined');
|
||||
}
|
||||
expect(classSymbol.name).toEqual('SimpleWrappedClass');
|
||||
expect(classSymbol.declaration.valueDeclaration).toBe(outerNode);
|
||||
if (!isNamedClassDeclaration(classSymbol.implementation.valueDeclaration)) {
|
||||
return fail('Expected a named class declaration');
|
||||
}
|
||||
expect(classSymbol.implementation.valueDeclaration.name.text).toBe('SimpleWrappedClass');
|
||||
});
|
||||
|
||||
it('should return the class symbol for a 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, 'SimpleWrappedClass',
|
||||
isNamedVariableDeclaration);
|
||||
const innerNode = ((outerNode as any).initializer.expression.expression.body as ts.Block)
|
||||
.statements[0];
|
||||
const classSymbol = host.getClassSymbol(innerNode);
|
||||
|
||||
if (classSymbol === undefined) {
|
||||
return fail('Expected classSymbol to be defined');
|
||||
}
|
||||
expect(classSymbol.name).toEqual('SimpleWrappedClass');
|
||||
expect(classSymbol.declaration.valueDeclaration).toBe(outerNode);
|
||||
if (!isNamedClassDeclaration(classSymbol.implementation.valueDeclaration)) {
|
||||
return fail('Expected a named class declaration');
|
||||
}
|
||||
expect(classSymbol.implementation.valueDeclaration.name.text).toBe('SimpleWrappedClass');
|
||||
});
|
||||
|
||||
it('should return the same class symbol (of the outer declaration) for 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, 'SimpleWrappedClass',
|
||||
isNamedVariableDeclaration);
|
||||
const innerNode = ((outerNode as any).initializer.expression.expression.body as ts.Block)
|
||||
.statements[0];
|
||||
|
||||
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);
|
||||
|
|
Loading…
Reference in New Issue