fix(ivy): handle class declarations consistently in ES5 code (#29209)
PR Close #29209
This commit is contained in:
parent
2790352d04
commit
21835af70c
|
@ -17,7 +17,10 @@ export class DecoratedClass {
|
||||||
* Initialize a `DecoratedClass` that was found in a `DecoratedFile`.
|
* Initialize a `DecoratedClass` that was found in a `DecoratedFile`.
|
||||||
* @param name The name of the class that has been found. This is mostly used
|
* @param name The name of the class that has been found. This is mostly used
|
||||||
* for informational purposes.
|
* for informational purposes.
|
||||||
* @param declaration The TypeScript AST node where this class is declared
|
* @param declaration The TypeScript AST node where this class is declared. In ES5 code, where a
|
||||||
|
* class can be represented by both a variable declaration and a function declaration (inside an
|
||||||
|
* IIFE), `declaration` will always refer to the outer variable declaration, which represents the
|
||||||
|
* class to the rest of the program.
|
||||||
* @param decorators The collection of decorators that have been found on this class.
|
* @param decorators The collection of decorators that have been found on this class.
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
@ -10,7 +10,7 @@ import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Decorator, Import, TypeScriptReflectionHost, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Decorator, Import, TypeScriptReflectionHost, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
||||||
import {BundleProgram} from '../packages/bundle_program';
|
import {BundleProgram} from '../packages/bundle_program';
|
||||||
import {findAll, getNameText, isDefined} from '../utils';
|
import {findAll, getNameText, hasNameIdentifier, isDefined} from '../utils';
|
||||||
|
|
||||||
import {DecoratedClass} from './decorated_class';
|
import {DecoratedClass} from './decorated_class';
|
||||||
import {ModuleWithProvidersFunction, NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host';
|
import {ModuleWithProvidersFunction, NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host';
|
||||||
|
@ -54,6 +54,37 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||||
this.dtsDeclarationMap = dts && this.computeDtsDeclarationMap(dts.path, dts.program) || null;
|
this.dtsDeclarationMap = dts && this.computeDtsDeclarationMap(dts.path, dts.program) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the declaration of a node that we think is a class.
|
||||||
|
* Classes should have a `name` identifier, because they may need to be referenced in other parts
|
||||||
|
* of the program.
|
||||||
|
*
|
||||||
|
* @param node the node that represents the class whose declaration we are finding.
|
||||||
|
* @returns the declaration of the class or `undefined` if it is not a "class".
|
||||||
|
*/
|
||||||
|
getClassDeclaration(node: ts.Node): ClassDeclaration|undefined {
|
||||||
|
if (ts.isVariableDeclaration(node) && node.initializer) {
|
||||||
|
node = node.initializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ts.isClassDeclaration(node) && !ts.isClassExpression(node)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasNameIdentifier(node) ? node : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a symbol for a node that we think is a class.
|
||||||
|
* @param node the node whose symbol we are finding.
|
||||||
|
* @returns the symbol for the node or `undefined` if it is not a "class" or has no symbol.
|
||||||
|
*/
|
||||||
|
getClassSymbol(declaration: ts.Node): ClassSymbol|undefined {
|
||||||
|
const classDeclaration = this.getClassDeclaration(declaration);
|
||||||
|
return classDeclaration &&
|
||||||
|
this.checker.getSymbolAtLocation(classDeclaration.name) as ClassSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Examine a declaration (for example, of a class or function) and return metadata about any
|
* Examine a declaration (for example, of a class or function) and return metadata about any
|
||||||
* decorators present on the declaration.
|
* decorators present on the declaration.
|
||||||
|
@ -86,79 +117,12 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||||
* @throws if `declaration` does not resolve to a class declaration.
|
* @throws if `declaration` does not resolve to a class declaration.
|
||||||
*/
|
*/
|
||||||
getMembersOfClass(clazz: ClassDeclaration): ClassMember[] {
|
getMembersOfClass(clazz: ClassDeclaration): ClassMember[] {
|
||||||
const members: ClassMember[] = [];
|
const classSymbol = this.getClassSymbol(clazz);
|
||||||
const symbol = this.getClassSymbol(clazz);
|
if (!classSymbol) {
|
||||||
if (!symbol) {
|
|
||||||
throw new Error(`Attempted to get members of a non-class: "${clazz.getText()}"`);
|
throw new Error(`Attempted to get members of a non-class: "${clazz.getText()}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The decorators map contains all the properties that are decorated
|
return this.getMembersOfSymbol(classSymbol);
|
||||||
const decoratorsMap = this.getMemberDecorators(symbol);
|
|
||||||
|
|
||||||
// The member map contains all the method (instance and static); and any instance properties
|
|
||||||
// that are initialized in the class.
|
|
||||||
if (symbol.members) {
|
|
||||||
symbol.members.forEach((value, key) => {
|
|
||||||
const decorators = decoratorsMap.get(key as string);
|
|
||||||
const reflectedMembers = this.reflectMembers(value, decorators);
|
|
||||||
if (reflectedMembers) {
|
|
||||||
decoratorsMap.delete(key as string);
|
|
||||||
members.push(...reflectedMembers);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// The static property map contains all the static properties
|
|
||||||
if (symbol.exports) {
|
|
||||||
symbol.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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this class was declared as a VariableDeclaration then it may have static properties
|
|
||||||
// attached to the variable rather than the class itself
|
|
||||||
// For example:
|
|
||||||
// ```
|
|
||||||
// let MyClass = class MyClass {
|
|
||||||
// // no static properties here!
|
|
||||||
// }
|
|
||||||
// MyClass.staticProperty = ...;
|
|
||||||
// ```
|
|
||||||
if (ts.isVariableDeclaration(symbol.valueDeclaration.parent)) {
|
|
||||||
const variableSymbol = this.checker.getSymbolAtLocation(symbol.valueDeclaration.parent.name);
|
|
||||||
if (variableSymbol && variableSymbol.exports) {
|
|
||||||
variableSymbol.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({
|
|
||||||
implementation: null,
|
|
||||||
decorators: value,
|
|
||||||
isStatic: false,
|
|
||||||
kind: ClassMemberKind.Property,
|
|
||||||
name: key,
|
|
||||||
nameNode: null,
|
|
||||||
node: null,
|
|
||||||
type: null,
|
|
||||||
value: null
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return members;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -188,24 +152,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a symbol for a node that we think is a class.
|
|
||||||
* @param node the node whose symbol we are finding.
|
|
||||||
* @returns the symbol for the node or `undefined` if it is not a "class" or has no symbol.
|
|
||||||
*/
|
|
||||||
getClassSymbol(declaration: ts.Node): ClassSymbol|undefined {
|
|
||||||
if (ts.isClassDeclaration(declaration)) {
|
|
||||||
return declaration.name && this.checker.getSymbolAtLocation(declaration.name) as ClassSymbol;
|
|
||||||
}
|
|
||||||
if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
|
|
||||||
declaration = declaration.initializer;
|
|
||||||
}
|
|
||||||
if (ts.isClassExpression(declaration)) {
|
|
||||||
return declaration.name && this.checker.getSymbolAtLocation(declaration.name) as ClassSymbol;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search the given module for variable declarations in which the initializer
|
* Search the given module for variable declarations in which the initializer
|
||||||
* is an identifier marked with the `PRE_R3_MARKER`.
|
* is an identifier marked with the `PRE_R3_MARKER`.
|
||||||
|
@ -497,6 +443,84 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||||
return decorators.length ? decorators : null;
|
return decorators.length ? decorators : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Examine a symbol which should be of a class, and return metadata about its members.
|
||||||
|
*
|
||||||
|
* @param symbol the `ClassSymbol` representing the class over which to reflect.
|
||||||
|
* @returns an array of `ClassMember` metadata representing the members of the class.
|
||||||
|
*/
|
||||||
|
protected getMembersOfSymbol(symbol: ClassSymbol): ClassMember[] {
|
||||||
|
const members: ClassMember[] = [];
|
||||||
|
|
||||||
|
// The decorators map contains all the properties that are decorated
|
||||||
|
const decoratorsMap = this.getMemberDecorators(symbol);
|
||||||
|
|
||||||
|
// The member map contains all the method (instance and static); and any instance properties
|
||||||
|
// that are initialized in the class.
|
||||||
|
if (symbol.members) {
|
||||||
|
symbol.members.forEach((value, key) => {
|
||||||
|
const decorators = decoratorsMap.get(key as string);
|
||||||
|
const reflectedMembers = this.reflectMembers(value, decorators);
|
||||||
|
if (reflectedMembers) {
|
||||||
|
decoratorsMap.delete(key as string);
|
||||||
|
members.push(...reflectedMembers);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// The static property map contains all the static properties
|
||||||
|
if (symbol.exports) {
|
||||||
|
symbol.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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this class was declared as a VariableDeclaration then it may have static properties
|
||||||
|
// attached to the variable rather than the class itself
|
||||||
|
// For example:
|
||||||
|
// ```
|
||||||
|
// let MyClass = class MyClass {
|
||||||
|
// // no static properties here!
|
||||||
|
// }
|
||||||
|
// MyClass.staticProperty = ...;
|
||||||
|
// ```
|
||||||
|
if (ts.isVariableDeclaration(symbol.valueDeclaration.parent)) {
|
||||||
|
const variableSymbol = this.checker.getSymbolAtLocation(symbol.valueDeclaration.parent.name);
|
||||||
|
if (variableSymbol && variableSymbol.exports) {
|
||||||
|
variableSymbol.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({
|
||||||
|
implementation: null,
|
||||||
|
decorators: value,
|
||||||
|
isStatic: false,
|
||||||
|
kind: ClassMemberKind.Property,
|
||||||
|
name: key,
|
||||||
|
nameNode: null,
|
||||||
|
node: null,
|
||||||
|
type: null,
|
||||||
|
value: null
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return members;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all the member decorators for the given class.
|
* Get all the member decorators for the given class.
|
||||||
* @param classSymbol the class whose member decorators we are interested in.
|
* @param classSymbol the class whose member decorators we are interested in.
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, Declaration, Decorator, FunctionDefinition, Parameter, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, FunctionDefinition, Parameter, isNamedVariableDeclaration, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
||||||
import {getNameText, hasNameIdentifier} from '../utils';
|
import {getNameText, hasNameIdentifier} from '../utils';
|
||||||
|
|
||||||
import {Esm2015ReflectionHost, ParamInfo, getPropertyValueFromSymbol, isAssignmentStatement} from './esm2015_host';
|
import {Esm2015ReflectionHost, ParamInfo, getPropertyValueFromSymbol, isAssignmentStatement} from './esm2015_host';
|
||||||
|
@ -36,9 +36,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||||
/**
|
/**
|
||||||
* Check whether the given node actually represents a class.
|
* Check whether the given node actually represents a class.
|
||||||
*/
|
*/
|
||||||
isClass(node: ts.Node): node is ClassDeclaration {
|
isClass(node: ts.Node): node is ClassDeclaration { return !!this.getClassDeclaration(node); }
|
||||||
return super.isClass(node) || !!this.getClassSymbol(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether the given declaration, which should be a "class", has a base "class".
|
* Determines whether the given declaration, which should be a "class", has a base "class".
|
||||||
|
@ -48,11 +46,13 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||||
* @param clazz a `ClassDeclaration` representing the class over which to reflect.
|
* @param clazz a `ClassDeclaration` representing the class over which to reflect.
|
||||||
*/
|
*/
|
||||||
hasBaseClass(clazz: ClassDeclaration): boolean {
|
hasBaseClass(clazz: ClassDeclaration): boolean {
|
||||||
const classSymbol = this.getClassSymbol(clazz);
|
if (super.hasBaseClass(clazz)) return true;
|
||||||
if (!classSymbol) return false;
|
|
||||||
|
|
||||||
const iifeBody = classSymbol.valueDeclaration.parent;
|
const classDeclaration = this.getClassDeclaration(clazz);
|
||||||
if (!iifeBody || !ts.isBlock(iifeBody)) return false;
|
if (!classDeclaration) return false;
|
||||||
|
|
||||||
|
const iifeBody = getIifeBody(classDeclaration);
|
||||||
|
if (!iifeBody) return false;
|
||||||
|
|
||||||
const iife = iifeBody.parent;
|
const iife = iifeBody.parent;
|
||||||
if (!iife || !ts.isFunctionExpression(iife)) return false;
|
if (!iife || !ts.isFunctionExpression(iife)) return false;
|
||||||
|
@ -61,38 +61,39 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a symbol for a node that we think is a class.
|
* Find the declaration of a class given a node that we think represents the class.
|
||||||
*
|
*
|
||||||
* In ES5, the implementation of a class is a function expression that is hidden inside an IIFE.
|
* In ES5, the implementation of a class is a function expression that is hidden inside an IIFE,
|
||||||
* So we might need to dig around inside to get hold of the "class" symbol.
|
* 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.
|
||||||
*
|
*
|
||||||
* `node` might be one of:
|
* `node` might be one of:
|
||||||
* - A class declaration (from a declaration file).
|
* - A class declaration (from a typings file).
|
||||||
* - The declaration of the outer variable, which is assigned the result of the IIFE.
|
* - The declaration of the outer variable, which is assigned the result of the IIFE.
|
||||||
* - The function declaration inside the IIFE, which is eventually returned and assigned to the
|
* - The function declaration inside the IIFE, which is eventually returned and assigned to the
|
||||||
* outer variable.
|
* outer variable.
|
||||||
*
|
*
|
||||||
* @param node the top level declaration that represents an exported class or the function
|
* The returned declaration is either the class declaration (from the typings file) or the outer
|
||||||
* expression inside the IIFE.
|
* variable declaration.
|
||||||
* @returns the symbol for the node or `undefined` if it is not a "class" or has no symbol.
|
*
|
||||||
|
* @param node the node that represents the class whose declaration we are finding.
|
||||||
|
* @returns the declaration of the class or `undefined` if it is not a "class".
|
||||||
*/
|
*/
|
||||||
getClassSymbol(node: ts.Node): ClassSymbol|undefined {
|
getClassDeclaration(node: ts.Node): ClassDeclaration|undefined {
|
||||||
const symbol = super.getClassSymbol(node);
|
const superDeclaration = super.getClassDeclaration(node);
|
||||||
if (symbol) return symbol;
|
if (superDeclaration) return superDeclaration;
|
||||||
|
|
||||||
if (ts.isVariableDeclaration(node)) {
|
const outerClass = getClassDeclarationFromInnerFunctionDeclaration(node);
|
||||||
const iifeBody = getIifeBody(node);
|
if (outerClass) return outerClass;
|
||||||
if (!iifeBody) return undefined;
|
|
||||||
|
|
||||||
const innerClassIdentifier = getReturnIdentifier(iifeBody);
|
// At this point, `node` could be the outer variable declaration of an ES5 class.
|
||||||
if (!innerClassIdentifier) return undefined;
|
// If so, ensure that it has a `name` identifier and the correct structure.
|
||||||
|
if (!isNamedVariableDeclaration(node) ||
|
||||||
return this.checker.getSymbolAtLocation(innerClassIdentifier) as ClassSymbol;
|
!this.getInnerFunctionDeclarationFromClassDeclaration(node)) {
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const outerClassNode = getClassDeclarationFromInnerFunctionDeclaration(node);
|
return node;
|
||||||
|
|
||||||
return outerClassNode && this.getClassSymbol(outerClassNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -115,12 +116,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||||
getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null {
|
getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null {
|
||||||
// Get the identifier for the outer class node (if any).
|
// Get the identifier for the outer class node (if any).
|
||||||
const outerClassNode = getClassDeclarationFromInnerFunctionDeclaration(id.parent);
|
const outerClassNode = getClassDeclarationFromInnerFunctionDeclaration(id.parent);
|
||||||
|
const declaration = super.getDeclarationOfIdentifier(outerClassNode ? outerClassNode.name : id);
|
||||||
if (outerClassNode && hasNameIdentifier(outerClassNode)) {
|
|
||||||
id = outerClassNode.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
const declaration = super.getDeclarationOfIdentifier(id);
|
|
||||||
|
|
||||||
if (!declaration || !ts.isVariableDeclaration(declaration.node) ||
|
if (!declaration || !ts.isVariableDeclaration(declaration.node) ||
|
||||||
declaration.node.initializer !== undefined ||
|
declaration.node.initializer !== undefined ||
|
||||||
|
@ -172,31 +168,149 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||||
return {node, body: statements || null, parameters};
|
return {node, body: statements || null, parameters};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Examine a declaration which should be of a class, and return metadata about the members of the
|
||||||
|
* class.
|
||||||
|
*
|
||||||
|
* @param declaration a TypeScript `ts.Declaration` node representing the class over which to
|
||||||
|
* reflect.
|
||||||
|
*
|
||||||
|
* @returns an array of `ClassMember` metadata representing the members of the class.
|
||||||
|
*
|
||||||
|
* @throws if `declaration` does not resolve to a class declaration.
|
||||||
|
*/
|
||||||
|
getMembersOfClass(clazz: ClassDeclaration): ClassMember[] {
|
||||||
|
if (super.isClass(clazz)) return super.getMembersOfClass(clazz);
|
||||||
|
|
||||||
|
// The necessary info is on the inner function declaration (inside the ES5 class IIFE).
|
||||||
|
const innerFunctionSymbol = this.getInnerFunctionSymbolFromClassDeclaration(clazz);
|
||||||
|
if (!innerFunctionSymbol) {
|
||||||
|
throw new Error(
|
||||||
|
`Attempted to get members of a non-class: "${(clazz as ClassDeclaration).getText()}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.getMembersOfSymbol(innerFunctionSymbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////// Protected Helpers /////////////
|
///////////// 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
|
||||||
|
* and returned to be assigned to a variable outside the IIFE, which is what the rest of the
|
||||||
|
* program interacts with.
|
||||||
|
*
|
||||||
|
* Given the outer variable declaration, we want to get to the inner function declaration.
|
||||||
|
*
|
||||||
|
* @param node a 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".
|
||||||
|
*/
|
||||||
|
protected getInnerFunctionDeclarationFromClassDeclaration(node: ts.Node): ts.FunctionDeclaration
|
||||||
|
|undefined {
|
||||||
|
if (!ts.isVariableDeclaration(node)) return undefined;
|
||||||
|
|
||||||
|
// Extract the IIFE body (if any).
|
||||||
|
const iifeBody = getIifeBody(node);
|
||||||
|
if (!iifeBody) return undefined;
|
||||||
|
|
||||||
|
// Extract the function declaration from inside the IIFE.
|
||||||
|
const functionDeclaration = iifeBody.statements.find(ts.isFunctionDeclaration);
|
||||||
|
if (!functionDeclaration) return undefined;
|
||||||
|
|
||||||
|
// Extract the return identifier of the IIFE.
|
||||||
|
const returnIdentifier = getReturnIdentifier(iifeBody);
|
||||||
|
const returnIdentifierSymbol =
|
||||||
|
returnIdentifier && this.checker.getSymbolAtLocation(returnIdentifier);
|
||||||
|
if (!returnIdentifierSymbol) return undefined;
|
||||||
|
|
||||||
|
// Verify that the inner function is returned.
|
||||||
|
if (returnIdentifierSymbol.valueDeclaration !== functionDeclaration) return undefined;
|
||||||
|
|
||||||
|
return functionDeclaration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the identifier symbol of 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
|
||||||
|
* and returned to be assigned to a variable outside the IIFE, which is what the rest of the
|
||||||
|
* program interacts with.
|
||||||
|
*
|
||||||
|
* Given the outer variable declaration, we want to get to the identifier symbol of the inner
|
||||||
|
* function declaration.
|
||||||
|
*
|
||||||
|
* @param clazz a node that could be the variable expression outside an ES5 class IIFE.
|
||||||
|
* @param checker the TS program TypeChecker
|
||||||
|
* @returns the inner function declaration identifier symbol or `undefined` if it is not a "class"
|
||||||
|
* or has no identifier.
|
||||||
|
*/
|
||||||
|
protected getInnerFunctionSymbolFromClassDeclaration(clazz: ClassDeclaration): ClassSymbol
|
||||||
|
|undefined {
|
||||||
|
const innerFunctionDeclaration = this.getInnerFunctionDeclarationFromClassDeclaration(clazz);
|
||||||
|
if (!innerFunctionDeclaration || !hasNameIdentifier(innerFunctionDeclaration)) return undefined;
|
||||||
|
|
||||||
|
return this.checker.getSymbolAtLocation(innerFunctionDeclaration.name) as ClassSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the declarations of the constructor parameters of a class identified by its symbol.
|
* Find the declarations of the constructor parameters of a class identified by its symbol.
|
||||||
*
|
*
|
||||||
* In ESM5 there is no "class" so the constructor that we want is actually the declaration
|
* In ESM5, there is no "class" so the constructor that we want is actually the inner function
|
||||||
* function itself.
|
* declaration inside the IIFE, whose return value is assigned to the outer variable declaration
|
||||||
|
* (that represents the class to the rest of the program).
|
||||||
*
|
*
|
||||||
* @param classSymbol the class whose parameters we want to find.
|
* @param classSymbol the symbol of the class (i.e. the outer variable declaration) whose
|
||||||
|
* parameters we want to find.
|
||||||
* @returns an array of `ts.ParameterDeclaration` objects representing each of the parameters in
|
* @returns an array of `ts.ParameterDeclaration` objects representing each of the parameters in
|
||||||
* the class's constructor or null if there is no constructor.
|
* the class's constructor or `null` if there is no constructor.
|
||||||
*/
|
*/
|
||||||
protected getConstructorParameterDeclarations(classSymbol: ClassSymbol):
|
protected getConstructorParameterDeclarations(classSymbol: ClassSymbol):
|
||||||
ts.ParameterDeclaration[]|null {
|
ts.ParameterDeclaration[]|null {
|
||||||
const constructor = classSymbol.valueDeclaration as ts.FunctionDeclaration;
|
const constructor =
|
||||||
|
this.getInnerFunctionDeclarationFromClassDeclaration(classSymbol.valueDeclaration);
|
||||||
|
if (!constructor) return null;
|
||||||
|
|
||||||
if (constructor.parameters.length > 0) {
|
if (constructor.parameters.length > 0) {
|
||||||
return Array.from(constructor.parameters);
|
return Array.from(constructor.parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSynthesizedConstructor(constructor)) {
|
if (isSynthesizedConstructor(constructor)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the parameter decorators of a class constructor.
|
||||||
|
*
|
||||||
|
* @param classSymbol the symbol of the class (i.e. the outer variable declaration) whose
|
||||||
|
* parameter info we want to get.
|
||||||
|
* @param parameterNodes the array of TypeScript parameter nodes for this class's constructor.
|
||||||
|
* @returns an array of constructor parameter info objects.
|
||||||
|
*/
|
||||||
|
protected getConstructorParamInfo(
|
||||||
|
classSymbol: ClassSymbol, parameterNodes: ts.ParameterDeclaration[]): CtorParameter[] {
|
||||||
|
// The necessary info is on the inner function declaration (inside the ES5 class IIFE).
|
||||||
|
const innerFunctionSymbol =
|
||||||
|
this.getInnerFunctionSymbolFromClassDeclaration(classSymbol.valueDeclaration);
|
||||||
|
if (!innerFunctionSymbol) return [];
|
||||||
|
|
||||||
|
return super.getConstructorParamInfo(innerFunctionSymbol, parameterNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getDecoratorsOfSymbol(symbol: ClassSymbol): Decorator[]|null {
|
||||||
|
// The necessary info is on the inner function declaration (inside the ES5 class IIFE).
|
||||||
|
const innerFunctionSymbol =
|
||||||
|
this.getInnerFunctionSymbolFromClassDeclaration(symbol.valueDeclaration);
|
||||||
|
if (!innerFunctionSymbol) return null;
|
||||||
|
|
||||||
|
return super.getDecoratorsOfSymbol(innerFunctionSymbol);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the parameter type and decorators for the constructor of a class,
|
* Get the parameter type and decorators for the constructor of a class,
|
||||||
* where the information is stored on a static method of the class.
|
* where the information is stored on a static method of the class.
|
||||||
|
@ -389,8 +503,8 @@ function readPropertyFunctionExpression(object: ts.ObjectLiteralExpression, name
|
||||||
* @param node a node that could be the function expression inside an ES5 class IIFE.
|
* @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".
|
* @returns the outer variable declaration or `undefined` if it is not a "class".
|
||||||
*/
|
*/
|
||||||
function getClassDeclarationFromInnerFunctionDeclaration(node: ts.Node): ts.VariableDeclaration|
|
function getClassDeclarationFromInnerFunctionDeclaration(node: ts.Node):
|
||||||
undefined {
|
ClassDeclaration<ts.VariableDeclaration>|undefined {
|
||||||
if (ts.isFunctionDeclaration(node)) {
|
if (ts.isFunctionDeclaration(node)) {
|
||||||
// It might be the function expression inside the IIFE. We need to go 5 levels up...
|
// It might be the function expression inside the IIFE. We need to go 5 levels up...
|
||||||
|
|
||||||
|
@ -414,14 +528,16 @@ function getClassDeclarationFromInnerFunctionDeclaration(node: ts.Node): ts.Vari
|
||||||
outerNode = outerNode.parent;
|
outerNode = outerNode.parent;
|
||||||
if (!outerNode || !ts.isVariableDeclaration(outerNode)) return undefined;
|
if (!outerNode || !ts.isVariableDeclaration(outerNode)) return undefined;
|
||||||
|
|
||||||
return outerNode;
|
// Finally, ensure that the variable declaration has a `name` identifier.
|
||||||
|
return hasNameIdentifier(outerNode) ? outerNode : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIifeBody(declaration: ts.VariableDeclaration): ts.Block|undefined {
|
export function getIifeBody(declaration: ts.Declaration): ts.Block|undefined {
|
||||||
if (!declaration.initializer || !ts.isParenthesizedExpression(declaration.initializer)) {
|
if (!ts.isVariableDeclaration(declaration) || !declaration.initializer ||
|
||||||
|
!ts.isParenthesizedExpression(declaration.initializer)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const call = declaration.initializer;
|
const call = declaration.initializer;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import MagicString from 'magic-string';
|
import MagicString from 'magic-string';
|
||||||
|
import {getIifeBody} from '../host/esm5_host';
|
||||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||||
import {CompiledClass} from '../analysis/decoration_analyzer';
|
import {CompiledClass} from '../analysis/decoration_analyzer';
|
||||||
import {EsmRenderer} from './esm_renderer';
|
import {EsmRenderer} from './esm_renderer';
|
||||||
|
@ -23,21 +24,18 @@ export class Esm5Renderer extends EsmRenderer {
|
||||||
* Add the definitions to each decorated class
|
* Add the definitions to each decorated class
|
||||||
*/
|
*/
|
||||||
addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string): void {
|
addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string): void {
|
||||||
const classSymbol = this.host.getClassSymbol(compiledClass.declaration);
|
const iifeBody = getIifeBody(compiledClass.declaration);
|
||||||
if (!classSymbol) {
|
if (!iifeBody) {
|
||||||
throw new Error(
|
|
||||||
`Compiled class does not have a valid symbol: ${compiledClass.name} in ${compiledClass.declaration.getSourceFile().fileName}`);
|
|
||||||
}
|
|
||||||
const parent = classSymbol.valueDeclaration && classSymbol.valueDeclaration.parent;
|
|
||||||
if (!parent || !ts.isBlock(parent)) {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Compiled class declaration is not inside an IIFE: ${compiledClass.name} in ${compiledClass.declaration.getSourceFile().fileName}`);
|
`Compiled class declaration is not inside an IIFE: ${compiledClass.name} in ${compiledClass.declaration.getSourceFile().fileName}`);
|
||||||
}
|
}
|
||||||
const returnStatement = parent.statements.find(statement => ts.isReturnStatement(statement));
|
|
||||||
|
const returnStatement = iifeBody.statements.find(ts.isReturnStatement);
|
||||||
if (!returnStatement) {
|
if (!returnStatement) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Compiled class wrapper IIFE does not have a return statement: ${compiledClass.name} in ${compiledClass.declaration.getSourceFile().fileName}`);
|
`Compiled class wrapper IIFE does not have a return statement: ${compiledClass.name} in ${compiledClass.declaration.getSourceFile().fileName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const insertionPoint = returnStatement.getFullStart();
|
const insertionPoint = returnStatement.getFullStart();
|
||||||
output.appendLeft(insertionPoint, '\n' + definitions);
|
output.appendLeft(insertionPoint, '\n' + definitions);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1536,7 +1536,7 @@ describe('Esm2015ReflectionHost', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getModuleWithProvidersFunctions', () => {
|
describe('getModuleWithProvidersFunctions()', () => {
|
||||||
it('should find every exported function that returns an object that looks like a ModuleWithProviders object',
|
it('should find every exported function that returns an object that looks like a ModuleWithProviders object',
|
||||||
() => {
|
() => {
|
||||||
const srcProgram = makeTestProgram(...MODULE_WITH_PROVIDERS_PROGRAM);
|
const srcProgram = makeTestProgram(...MODULE_WITH_PROVIDERS_PROGRAM);
|
||||||
|
|
|
@ -8,9 +8,9 @@
|
||||||
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ClassDeclaration, ClassMemberKind, ClassSymbol, Import, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
|
import {ClassMemberKind, Import, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
|
||||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||||
import {Esm5ReflectionHost} from '../../src/host/esm5_host';
|
import {Esm5ReflectionHost, getIifeBody} from '../../src/host/esm5_host';
|
||||||
import {getDeclaration, makeTestBundleProgram, makeTestProgram} from '../helpers/utils';
|
import {getDeclaration, makeTestBundleProgram, makeTestProgram} from '../helpers/utils';
|
||||||
|
|
||||||
import {expectTypeValueReferencesForParameters} from './util';
|
import {expectTypeValueReferencesForParameters} from './util';
|
||||||
|
@ -96,6 +96,13 @@ const ACCESSORS_FILE = {
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SIMPLE_ES2015_CLASS_FILE = {
|
||||||
|
name: '/simple_es2015_class.d.ts',
|
||||||
|
contents: `
|
||||||
|
export class EmptyClass {}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
const SIMPLE_CLASS_FILE = {
|
const SIMPLE_CLASS_FILE = {
|
||||||
name: '/simple_class.js',
|
name: '/simple_class.js',
|
||||||
contents: `
|
contents: `
|
||||||
|
@ -1095,7 +1102,7 @@ describe('Esm5ReflectionHost', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getConstructorParameters', () => {
|
describe('getConstructorParameters()', () => {
|
||||||
it('should find the decorated constructor parameters', () => {
|
it('should find the decorated constructor parameters', () => {
|
||||||
const program = makeTestProgram(SOME_DIRECTIVE_FILE);
|
const program = makeTestProgram(SOME_DIRECTIVE_FILE);
|
||||||
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
||||||
|
@ -1408,7 +1415,7 @@ describe('Esm5ReflectionHost', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getImportOfIdentifier', () => {
|
describe('getImportOfIdentifier()', () => {
|
||||||
it('should find the import of an identifier', () => {
|
it('should find the import of an identifier', () => {
|
||||||
const program = makeTestProgram(...IMPORTS_FILES);
|
const program = makeTestProgram(...IMPORTS_FILES);
|
||||||
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
||||||
|
@ -1440,7 +1447,7 @@ describe('Esm5ReflectionHost', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getDeclarationOfIdentifier', () => {
|
describe('getDeclarationOfIdentifier()', () => {
|
||||||
it('should return the declaration of a locally defined identifier', () => {
|
it('should return the declaration of a locally defined identifier', () => {
|
||||||
const program = makeTestProgram(SOME_DIRECTIVE_FILE);
|
const program = makeTestProgram(SOME_DIRECTIVE_FILE);
|
||||||
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
||||||
|
@ -1550,21 +1557,15 @@ describe('Esm5ReflectionHost', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getClassSymbol()', () => {
|
describe('getClassSymbol()', () => {
|
||||||
let superGetClassSymbolSpy: jasmine.Spy;
|
it('should return the class symbol for an ES2015 class', () => {
|
||||||
|
const program = makeTestProgram(SIMPLE_ES2015_CLASS_FILE);
|
||||||
|
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
||||||
|
const node = getDeclaration(
|
||||||
|
program, SIMPLE_ES2015_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration);
|
||||||
|
const classSymbol = host.getClassSymbol(node);
|
||||||
|
|
||||||
beforeEach(() => {
|
expect(classSymbol).toBeDefined();
|
||||||
superGetClassSymbolSpy = spyOn(Esm2015ReflectionHost.prototype, 'getClassSymbol');
|
expect(classSymbol !.valueDeclaration).toBe(node);
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the class symbol returned by the superclass (if any)', () => {
|
|
||||||
const mockNode = {} as ts.Node;
|
|
||||||
const mockSymbol = {} as ClassSymbol;
|
|
||||||
superGetClassSymbolSpy.and.returnValue(mockSymbol);
|
|
||||||
|
|
||||||
const host = new Esm5ReflectionHost(false, {} as any);
|
|
||||||
|
|
||||||
expect(host.getClassSymbol(mockNode)).toBe(mockSymbol);
|
|
||||||
expect(superGetClassSymbolSpy).toHaveBeenCalledWith(mockNode);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the class symbol for an ES5 class (outer variable declaration)', () => {
|
it('should return the class symbol for an ES5 class (outer variable declaration)', () => {
|
||||||
|
@ -1572,7 +1573,10 @@ describe('Esm5ReflectionHost', () => {
|
||||||
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
||||||
const node =
|
const node =
|
||||||
getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
||||||
expect(host.getClassSymbol(node)).toBeDefined();
|
const classSymbol = host.getClassSymbol(node);
|
||||||
|
|
||||||
|
expect(classSymbol).toBeDefined();
|
||||||
|
expect(classSymbol !.valueDeclaration).toBe(node);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the class symbol for an ES5 class (inner function declaration)', () => {
|
it('should return the class symbol for an ES5 class (inner function declaration)', () => {
|
||||||
|
@ -1580,27 +1584,22 @@ describe('Esm5ReflectionHost', () => {
|
||||||
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
||||||
const outerNode =
|
const outerNode =
|
||||||
getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
||||||
const innerNode =
|
const innerNode = getIifeBody(outerNode) !.statements.find(isNamedFunctionDeclaration) !;
|
||||||
(((outerNode.initializer as ts.ParenthesizedExpression).expression as ts.CallExpression)
|
const classSymbol = host.getClassSymbol(innerNode);
|
||||||
.expression as ts.FunctionExpression)
|
|
||||||
.body.statements.find(ts.isFunctionDeclaration) !;
|
|
||||||
|
|
||||||
expect(host.getClassSymbol(innerNode)).toBeDefined();
|
expect(classSymbol).toBeDefined();
|
||||||
|
expect(classSymbol !.valueDeclaration).toBe(outerNode);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the same class symbol (of the inner declaration) for outer and inner declarations',
|
it('should return the same class symbol (of the outer declaration) for outer and inner declarations',
|
||||||
() => {
|
() => {
|
||||||
const program = makeTestProgram(SIMPLE_CLASS_FILE);
|
const program = makeTestProgram(SIMPLE_CLASS_FILE);
|
||||||
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
||||||
const outerNode = getDeclaration(
|
const outerNode = getDeclaration(
|
||||||
program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
||||||
const innerNode = (((outerNode.initializer as ts.ParenthesizedExpression)
|
const innerNode = getIifeBody(outerNode) !.statements.find(isNamedFunctionDeclaration) !;
|
||||||
.expression as ts.CallExpression)
|
|
||||||
.expression as ts.FunctionExpression)
|
|
||||||
.body.statements.find(ts.isFunctionDeclaration) as ClassDeclaration;
|
|
||||||
|
|
||||||
expect(host.getClassSymbol(innerNode)).toBe(host.getClassSymbol(outerNode));
|
expect(host.getClassSymbol(innerNode)).toBe(host.getClassSymbol(outerNode));
|
||||||
expect(host.getClassSymbol(innerNode) !.valueDeclaration).toBe(innerNode);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return undefined if node is not an ES5 class', () => {
|
it('should return undefined if node is not an ES5 class', () => {
|
||||||
|
@ -1608,48 +1607,47 @@ describe('Esm5ReflectionHost', () => {
|
||||||
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
||||||
const node =
|
const node =
|
||||||
getDeclaration(program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration);
|
getDeclaration(program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration);
|
||||||
expect(host.getClassSymbol(node)).toBeUndefined();
|
const classSymbol = host.getClassSymbol(node);
|
||||||
|
|
||||||
|
expect(classSymbol).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isClass()', () => {
|
describe('isClass()', () => {
|
||||||
let host: Esm5ReflectionHost;
|
let host: Esm5ReflectionHost;
|
||||||
let mockNode: ts.Node;
|
let mockNode: ts.Node;
|
||||||
let superIsClassSpy: jasmine.Spy;
|
let getClassDeclarationSpy: jasmine.Spy;
|
||||||
let getClassSymbolSpy: jasmine.Spy;
|
let superGetClassDeclarationSpy: jasmine.Spy;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
host = new Esm5ReflectionHost(false, null as any);
|
host = new Esm5ReflectionHost(false, null as any);
|
||||||
mockNode = {} as any;
|
mockNode = {} as any;
|
||||||
|
|
||||||
superIsClassSpy = spyOn(Esm2015ReflectionHost.prototype, 'isClass');
|
getClassDeclarationSpy = spyOn(Esm5ReflectionHost.prototype, 'getClassDeclaration');
|
||||||
getClassSymbolSpy = spyOn(Esm5ReflectionHost.prototype, 'getClassSymbol');
|
superGetClassDeclarationSpy = spyOn(Esm2015ReflectionHost.prototype, 'getClassDeclaration');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true if superclass returns true', () => {
|
it('should return true if superclass returns true', () => {
|
||||||
superIsClassSpy.and.returnValue(true);
|
superGetClassDeclarationSpy.and.returnValue(true);
|
||||||
|
getClassDeclarationSpy.and.callThrough();
|
||||||
|
|
||||||
expect(host.isClass(mockNode)).toBe(true);
|
expect(host.isClass(mockNode)).toBe(true);
|
||||||
expect(superIsClassSpy).toHaveBeenCalledWith(mockNode);
|
expect(getClassDeclarationSpy).toHaveBeenCalledWith(mockNode);
|
||||||
expect(getClassSymbolSpy).not.toHaveBeenCalled();
|
expect(superGetClassDeclarationSpy).toHaveBeenCalledWith(mockNode);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true if it can find a symbol for the class', () => {
|
it('should return true if it can find a declaration for the class', () => {
|
||||||
superIsClassSpy.and.returnValue(false);
|
getClassDeclarationSpy.and.returnValue(true);
|
||||||
getClassSymbolSpy.and.returnValue(true);
|
|
||||||
|
|
||||||
expect(host.isClass(mockNode)).toBe(true);
|
expect(host.isClass(mockNode)).toBe(true);
|
||||||
expect(superIsClassSpy).toHaveBeenCalledWith(mockNode);
|
expect(getClassDeclarationSpy).toHaveBeenCalledWith(mockNode);
|
||||||
expect(getClassSymbolSpy).toHaveBeenCalledWith(mockNode);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false if it cannot find a symbol for the class', () => {
|
it('should return false if it cannot find a declaration for the class', () => {
|
||||||
superIsClassSpy.and.returnValue(false);
|
getClassDeclarationSpy.and.returnValue(false);
|
||||||
getClassSymbolSpy.and.returnValue(false);
|
|
||||||
|
|
||||||
expect(host.isClass(mockNode)).toBe(false);
|
expect(host.isClass(mockNode)).toBe(false);
|
||||||
expect(superIsClassSpy).toHaveBeenCalledWith(mockNode);
|
expect(getClassDeclarationSpy).toHaveBeenCalledWith(mockNode);
|
||||||
expect(getClassSymbolSpy).toHaveBeenCalledWith(mockNode);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -258,28 +258,16 @@ SOME DEFINITION TEXT
|
||||||
const {renderer, host, sourceFile, program} = setup(PROGRAM);
|
const {renderer, host, sourceFile, program} = setup(PROGRAM);
|
||||||
const output = new MagicString(PROGRAM.contents);
|
const output = new MagicString(PROGRAM.contents);
|
||||||
|
|
||||||
const badSymbolDeclaration =
|
|
||||||
getDeclaration(program, sourceFile.fileName, 'A', ts.isVariableDeclaration);
|
|
||||||
const badSymbol: any = {name: 'BadSymbol', declaration: badSymbolDeclaration};
|
|
||||||
const hostSpy = spyOn(host, 'getClassSymbol').and.returnValue(null);
|
|
||||||
expect(() => renderer.addDefinitions(output, badSymbol, 'SOME DEFINITION TEXT'))
|
|
||||||
.toThrowError('Compiled class does not have a valid symbol: BadSymbol in /some/file.js');
|
|
||||||
|
|
||||||
|
|
||||||
const noIifeDeclaration =
|
const noIifeDeclaration =
|
||||||
getDeclaration(program, sourceFile.fileName, 'NoIife', ts.isFunctionDeclaration);
|
getDeclaration(program, sourceFile.fileName, 'NoIife', ts.isFunctionDeclaration);
|
||||||
const mockNoIifeClass: any = {declaration: noIifeDeclaration, name: 'NoIife'};
|
const mockNoIifeClass: any = {declaration: noIifeDeclaration, name: 'NoIife'};
|
||||||
hostSpy.and.returnValue({valueDeclaration: noIifeDeclaration});
|
|
||||||
expect(() => renderer.addDefinitions(output, mockNoIifeClass, 'SOME DEFINITION TEXT'))
|
expect(() => renderer.addDefinitions(output, mockNoIifeClass, 'SOME DEFINITION TEXT'))
|
||||||
.toThrowError(
|
.toThrowError(
|
||||||
'Compiled class declaration is not inside an IIFE: NoIife in /some/file.js');
|
'Compiled class declaration is not inside an IIFE: NoIife in /some/file.js');
|
||||||
|
|
||||||
const badIifeWrapper: any =
|
|
||||||
getDeclaration(program, sourceFile.fileName, 'BadIife', ts.isVariableDeclaration);
|
|
||||||
const badIifeDeclaration =
|
const badIifeDeclaration =
|
||||||
badIifeWrapper.initializer.expression.expression.body.statements[0];
|
getDeclaration(program, sourceFile.fileName, 'BadIife', ts.isVariableDeclaration);
|
||||||
const mockBadIifeClass: any = {declaration: badIifeDeclaration, name: 'BadIife'};
|
const mockBadIifeClass: any = {declaration: badIifeDeclaration, name: 'BadIife'};
|
||||||
hostSpy.and.returnValue({valueDeclaration: badIifeDeclaration});
|
|
||||||
expect(() => renderer.addDefinitions(output, mockBadIifeClass, 'SOME DEFINITION TEXT'))
|
expect(() => renderer.addDefinitions(output, mockBadIifeClass, 'SOME DEFINITION TEXT'))
|
||||||
.toThrowError(
|
.toThrowError(
|
||||||
'Compiled class wrapper IIFE does not have a return statement: BadIife in /some/file.js');
|
'Compiled class wrapper IIFE does not have a return statement: BadIife in /some/file.js');
|
||||||
|
|
Loading…
Reference in New Issue