feat(ivy): implement esm2015 and esm5 reflection hosts (#24897)
PR Close #24897
This commit is contained in:
parent
4ad2f11919
commit
45cf5b5dad
|
@ -0,0 +1,425 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {ClassMember, ClassMemberKind, Decorator, Parameter} from '../../../ngtsc/host';
|
||||||
|
import {TypeScriptReflectionHost, reflectObjectLiteral} from '../../../ngtsc/metadata';
|
||||||
|
import {getNameText} from '../utils';
|
||||||
|
import {NgccReflectionHost} from './ngcc_host';
|
||||||
|
|
||||||
|
export const DECORATORS = 'decorators' as ts.__String;
|
||||||
|
export const PROP_DECORATORS = 'propDecorators' as ts.__String;
|
||||||
|
export const CONSTRUCTOR = '__constructor' as ts.__String;
|
||||||
|
export const CONSTRUCTOR_PARAMS = 'ctorParameters' as ts.__String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Esm2015 packages contain ECMAScript 2015 classes, etc.
|
||||||
|
* Decorators are defined via static properties on the class. For example:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* class SomeDirective {
|
||||||
|
* }
|
||||||
|
* SomeDirective.decorators = [
|
||||||
|
* { type: Directive, args: [{ selector: '[someDirective]' },] }
|
||||||
|
* ];
|
||||||
|
* SomeDirective.ctorParameters = () => [
|
||||||
|
* { type: ViewContainerRef, },
|
||||||
|
* { type: TemplateRef, },
|
||||||
|
* { type: undefined, decorators: [{ type: Inject, args: [INJECTED_TOKEN,] },] },
|
||||||
|
* ];
|
||||||
|
* SomeDirective.propDecorators = {
|
||||||
|
* "input1": [{ type: Input },],
|
||||||
|
* "input2": [{ type: Input },],
|
||||||
|
* };
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* * Classes are decorated if they have a static property called `decorators`.
|
||||||
|
* * Members are decorated if there is a matching key on a static property
|
||||||
|
* called `propDecorators`.
|
||||||
|
* * Constructor parameters decorators are found on an object returned from
|
||||||
|
* a static method called `ctorParameters`.
|
||||||
|
*/
|
||||||
|
export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements NgccReflectionHost {
|
||||||
|
constructor(checker: ts.TypeChecker) { super(checker); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Examine a declaration (for example, of a class or function) and return metadata about any
|
||||||
|
* decorators present on the declaration.
|
||||||
|
*
|
||||||
|
* @param declaration a TypeScript `ts.Declaration` node representing the class or function over
|
||||||
|
* which to reflect. For example, if the intent is to reflect the decorators of a class and the
|
||||||
|
* source is in ES6 format, this will be a `ts.ClassDeclaration` node. If the source is in ES5
|
||||||
|
* format, this might be a `ts.VariableDeclaration` as classes in ES5 are represented as the
|
||||||
|
* result of an IIFE execution.
|
||||||
|
*
|
||||||
|
* @returns an array of `Decorator` metadata if decorators are present on the declaration, or
|
||||||
|
* `null` if either no decorators were present or if the declaration is not of a decoratable type.
|
||||||
|
*/
|
||||||
|
getDecoratorsOfDeclaration(declaration: ts.Declaration): Decorator[]|null {
|
||||||
|
const symbol = this.getClassSymbol(declaration);
|
||||||
|
if (symbol) {
|
||||||
|
if (symbol.exports && symbol.exports.has(DECORATORS)) {
|
||||||
|
// Symbol of the identifier for `SomeDirective.decorators`.
|
||||||
|
const decoratorsSymbol = symbol.exports.get(DECORATORS) !;
|
||||||
|
const decoratorsIdentifier = decoratorsSymbol.valueDeclaration;
|
||||||
|
|
||||||
|
if (decoratorsIdentifier && decoratorsIdentifier.parent) {
|
||||||
|
if (ts.isBinaryExpression(decoratorsIdentifier.parent)) {
|
||||||
|
// AST of the array of decorator values
|
||||||
|
const decoratorsArray = decoratorsIdentifier.parent.right;
|
||||||
|
return this.reflectDecorators(decoratorsArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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. If the source is in ES6 format, this will be a `ts.ClassDeclaration` node. If the
|
||||||
|
* source is in ES5 format, this might be a `ts.VariableDeclaration` as classes in ES5 are
|
||||||
|
* represented as the result of an IIFE execution.
|
||||||
|
*
|
||||||
|
* @returns an array of `ClassMember` metadata representing the members of the class.
|
||||||
|
*
|
||||||
|
* @throws if `declaration` does not resolve to a class declaration.
|
||||||
|
*/
|
||||||
|
getMembersOfClass(clazz: ts.Declaration): ClassMember[] {
|
||||||
|
const members: ClassMember[] = [];
|
||||||
|
const symbol = this.getClassSymbol(clazz);
|
||||||
|
if (!symbol) {
|
||||||
|
throw new Error(`Attempted to get members of a non-class: "${clazz.getText()}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 = removeFromMap(decoratorsMap, key);
|
||||||
|
const member = this.reflectMember(value, decorators);
|
||||||
|
if (member) {
|
||||||
|
members.push(member);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// The static property map contains all the static properties
|
||||||
|
if (symbol.exports) {
|
||||||
|
symbol.exports.forEach((value, key) => {
|
||||||
|
const decorators = removeFromMap(decoratorsMap, key);
|
||||||
|
const member = this.reflectMember(value, decorators, true);
|
||||||
|
if (member) {
|
||||||
|
members.push(member);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reflect over the constructor of a class and return metadata about its parameters.
|
||||||
|
*
|
||||||
|
* This method only looks at the constructor of a class directly and not at any inherited
|
||||||
|
* constructors.
|
||||||
|
*
|
||||||
|
* @param declaration a TypeScript `ts.Declaration` node representing the class over which to
|
||||||
|
* reflect. If the source is in ES6 format, this will be a `ts.ClassDeclaration` node. If the
|
||||||
|
* source is in ES5 format, this might be a `ts.VariableDeclaration` as classes in ES5 are
|
||||||
|
* represented as the result of an IIFE execution.
|
||||||
|
*
|
||||||
|
* @returns an array of `Parameter` metadata representing the parameters of the constructor, if
|
||||||
|
* a constructor exists. If the constructor exists and has 0 parameters, this array will be empty.
|
||||||
|
* If the class has no constructor, this method returns `null`.
|
||||||
|
*
|
||||||
|
* @throws if `declaration` does not resolve to a class declaration.
|
||||||
|
*/
|
||||||
|
getConstructorParameters(clazz: ts.Declaration): Parameter[]|null {
|
||||||
|
const classSymbol = this.getClassSymbol(clazz);
|
||||||
|
if (!classSymbol) {
|
||||||
|
throw new Error(
|
||||||
|
`Attempted to get constructor parameters of a non-class: "${clazz.getText()}"`);
|
||||||
|
}
|
||||||
|
const parameterNodes = this.getConstructorParameterDeclarations(classSymbol);
|
||||||
|
if (parameterNodes) {
|
||||||
|
const parameters: Parameter[] = [];
|
||||||
|
const decoratorInfo = this.getConstructorDecorators(classSymbol);
|
||||||
|
parameterNodes.forEach((node, index) => {
|
||||||
|
const info = decoratorInfo[index];
|
||||||
|
const decorators =
|
||||||
|
info && info.has('decorators') && this.reflectDecorators(info.get('decorators') !) ||
|
||||||
|
null;
|
||||||
|
const type = info && info.get('type') || null;
|
||||||
|
const nameNode = node.name;
|
||||||
|
parameters.push({name: getNameText(nameNode), nameNode, type, decorators});
|
||||||
|
});
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a symbol for a declaration that we think is a class.
|
||||||
|
* @param declaration The declaration whose symbol we are finding
|
||||||
|
* @returns the symbol for the declaration or `undefined` if it is not
|
||||||
|
* a "class" or has no symbol.
|
||||||
|
*/
|
||||||
|
getClassSymbol(declaration: ts.Declaration): ts.Symbol|undefined {
|
||||||
|
return ts.isClassDeclaration(declaration) ?
|
||||||
|
declaration.name && this.checker.getSymbolAtLocation(declaration.name) :
|
||||||
|
undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Member decorators are declared as static properties of the class in ES2015:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* SomeDirective.propDecorators = {
|
||||||
|
* "ngForOf": [{ type: Input },],
|
||||||
|
* "ngForTrackBy": [{ type: Input },],
|
||||||
|
* "ngForTemplate": [{ type: Input },],
|
||||||
|
* };
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
protected getMemberDecorators(classSymbol: ts.Symbol): Map<string, Decorator[]> {
|
||||||
|
const memberDecorators = new Map<string, Decorator[]>();
|
||||||
|
if (classSymbol.exports && classSymbol.exports.has(PROP_DECORATORS)) {
|
||||||
|
// Symbol of the identifier for `SomeDirective.propDecorators`.
|
||||||
|
const propDecoratorsMap =
|
||||||
|
getPropertyValueFromSymbol(classSymbol.exports.get(PROP_DECORATORS) !);
|
||||||
|
if (propDecoratorsMap && ts.isObjectLiteralExpression(propDecoratorsMap)) {
|
||||||
|
const propertiesMap = reflectObjectLiteral(propDecoratorsMap);
|
||||||
|
propertiesMap.forEach(
|
||||||
|
(value, name) => { memberDecorators.set(name, this.reflectDecorators(value)); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return memberDecorators;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reflect over the given expression and extract decorator information.
|
||||||
|
* @param decoratorsArray An expression that contains decorator information.
|
||||||
|
*/
|
||||||
|
protected reflectDecorators(decoratorsArray: ts.Expression): Decorator[] {
|
||||||
|
const decorators: Decorator[] = [];
|
||||||
|
|
||||||
|
if (ts.isArrayLiteralExpression(decoratorsArray)) {
|
||||||
|
// Add each decorator that is imported from `@angular/core` into the `decorators` array
|
||||||
|
decoratorsArray.elements.forEach(node => {
|
||||||
|
|
||||||
|
// If the decorator is not an object literal expression then we are not interested
|
||||||
|
if (ts.isObjectLiteralExpression(node)) {
|
||||||
|
// We are only interested in objects of the form: `{ type: DecoratorType, args: [...] }`
|
||||||
|
const decorator = reflectObjectLiteral(node);
|
||||||
|
|
||||||
|
// Is the value of the `type` property an identifier?
|
||||||
|
const typeIdentifier = decorator.get('type');
|
||||||
|
if (typeIdentifier && ts.isIdentifier(typeIdentifier)) {
|
||||||
|
decorators.push({
|
||||||
|
name: typeIdentifier.text,
|
||||||
|
import: this.getImportOfIdentifier(typeIdentifier), node,
|
||||||
|
args: getDecoratorArgs(node),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return decorators;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected reflectMember(symbol: ts.Symbol, decorators?: Decorator[], isStatic?: boolean):
|
||||||
|
ClassMember|null {
|
||||||
|
let kind: ClassMemberKind|null = null;
|
||||||
|
let value: ts.Expression|null = null;
|
||||||
|
let name: string|null = null;
|
||||||
|
let nameNode: ts.Identifier|null = null;
|
||||||
|
let type = null;
|
||||||
|
|
||||||
|
|
||||||
|
const node = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0];
|
||||||
|
if (!node || !isClassMemberType(node)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (symbol.flags & ts.SymbolFlags.Method) {
|
||||||
|
kind = ClassMemberKind.Method;
|
||||||
|
} else if (symbol.flags & ts.SymbolFlags.Property) {
|
||||||
|
kind = ClassMemberKind.Property;
|
||||||
|
} else if (symbol.flags & ts.SymbolFlags.GetAccessor) {
|
||||||
|
kind = ClassMemberKind.Getter;
|
||||||
|
} else if (symbol.flags & ts.SymbolFlags.SetAccessor) {
|
||||||
|
kind = ClassMemberKind.Setter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isStatic && isPropertyAccess(node)) {
|
||||||
|
name = node.name.text;
|
||||||
|
value = symbol.flags & ts.SymbolFlags.Property ? node.parent.right : null;
|
||||||
|
} else if (isThisAssignment(node)) {
|
||||||
|
kind = ClassMemberKind.Property;
|
||||||
|
name = node.left.name.text;
|
||||||
|
value = node.right;
|
||||||
|
isStatic = false;
|
||||||
|
} else if (ts.isConstructorDeclaration(node)) {
|
||||||
|
kind = ClassMemberKind.Constructor;
|
||||||
|
name = 'constructor';
|
||||||
|
isStatic = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kind === null) {
|
||||||
|
console.warn(`Unknown member type: "${node.getText()}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
if (isNamedDeclaration(node) && node.name && ts.isIdentifier(node.name)) {
|
||||||
|
name = node.name.text;
|
||||||
|
nameNode = node.name;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have still not determined if this is a static or instance member then
|
||||||
|
// look for the `static` keyword on the declaration
|
||||||
|
if (isStatic === undefined) {
|
||||||
|
isStatic = node.modifiers !== undefined &&
|
||||||
|
node.modifiers.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
node,
|
||||||
|
implementation: node, kind, type, name, nameNode, value, isStatic,
|
||||||
|
decorators: decorators || []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the declarations of the constructor parameters of a class identified by its symbol.
|
||||||
|
* @param classSymbol the class whose parameters we want to find.
|
||||||
|
* @returns an array of `ts.ParameterDeclaration` objects representing each of the parameters in
|
||||||
|
* the
|
||||||
|
* class's constructor or null if there is no constructor.
|
||||||
|
*/
|
||||||
|
protected getConstructorParameterDeclarations(classSymbol: ts.Symbol):
|
||||||
|
ts.ParameterDeclaration[]|null {
|
||||||
|
const constructorSymbol = classSymbol.members && classSymbol.members.get(CONSTRUCTOR);
|
||||||
|
if (constructorSymbol) {
|
||||||
|
// For some reason the constructor does not have a `valueDeclaration` ?!?
|
||||||
|
const constructor = constructorSymbol.declarations &&
|
||||||
|
constructorSymbol.declarations[0] as ts.ConstructorDeclaration;
|
||||||
|
if (constructor && constructor.parameters) {
|
||||||
|
return Array.from(constructor.parameters);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructors parameter decorators are declared in the body of static method of the class in
|
||||||
|
* ES2015:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* SomeDirective.ctorParameters = () => [
|
||||||
|
* { type: ViewContainerRef, },
|
||||||
|
* { type: TemplateRef, },
|
||||||
|
* { type: IterableDiffers, },
|
||||||
|
* { type: undefined, decorators: [{ type: Inject, args: [INJECTED_TOKEN,] },] },
|
||||||
|
* ];
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
protected getConstructorDecorators(classSymbol: ts.Symbol): (Map<string, ts.Expression>|null)[] {
|
||||||
|
if (classSymbol.exports && classSymbol.exports.has(CONSTRUCTOR_PARAMS)) {
|
||||||
|
const paramDecoratorsProperty =
|
||||||
|
getPropertyValueFromSymbol(classSymbol.exports.get(CONSTRUCTOR_PARAMS) !);
|
||||||
|
if (paramDecoratorsProperty && ts.isArrowFunction(paramDecoratorsProperty)) {
|
||||||
|
if (ts.isArrayLiteralExpression(paramDecoratorsProperty.body)) {
|
||||||
|
return paramDecoratorsProperty.body.elements.map(
|
||||||
|
element =>
|
||||||
|
ts.isObjectLiteralExpression(element) ? reflectObjectLiteral(element) : null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The arguments of a decorator are held in the `args` property of its declaration object.
|
||||||
|
*/
|
||||||
|
function getDecoratorArgs(node: ts.ObjectLiteralExpression): ts.Expression[] {
|
||||||
|
const argsProperty = node.properties.filter(ts.isPropertyAssignment)
|
||||||
|
.find(property => getNameText(property.name) === 'args');
|
||||||
|
const argsExpression = argsProperty && argsProperty.initializer;
|
||||||
|
return argsExpression && ts.isArrayLiteralExpression(argsExpression) ?
|
||||||
|
Array.from(argsExpression.elements) :
|
||||||
|
[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to extract the value of a property given the property's "symbol",
|
||||||
|
* which is actually the symbol of the identifier of the property.
|
||||||
|
*/
|
||||||
|
export function getPropertyValueFromSymbol(propSymbol: ts.Symbol): ts.Expression|undefined {
|
||||||
|
const propIdentifier = propSymbol.valueDeclaration;
|
||||||
|
const parent = propIdentifier && propIdentifier.parent;
|
||||||
|
return parent && ts.isBinaryExpression(parent) ? parent.right : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFromMap<T>(map: Map<string, T>, key: ts.__String): T|undefined {
|
||||||
|
const mapKey = key as string;
|
||||||
|
const value = map.get(mapKey);
|
||||||
|
if (value !== undefined) {
|
||||||
|
map.delete(mapKey);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPropertyAccess(node: ts.Node): node is ts.PropertyAccessExpression&
|
||||||
|
{parent: ts.BinaryExpression} {
|
||||||
|
return !!node.parent && ts.isBinaryExpression(node.parent) && ts.isPropertyAccessExpression(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isThisAssignment(node: ts.Declaration): node is ts.BinaryExpression&
|
||||||
|
{left: ts.PropertyAccessExpression} {
|
||||||
|
return ts.isBinaryExpression(node) && ts.isPropertyAccessExpression(node.left) &&
|
||||||
|
node.left.expression.kind === ts.SyntaxKind.ThisKeyword;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNamedDeclaration(node: ts.Declaration): node is ts.NamedDeclaration {
|
||||||
|
return !!(node as any).name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function isClassMemberType(node: ts.Declaration): node is ts.ClassElement|
|
||||||
|
ts.PropertyAccessExpression|ts.BinaryExpression {
|
||||||
|
return ts.isClassElement(node) || isPropertyAccess(node) || ts.isBinaryExpression(node);
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
import {Decorator} from '../../../ngtsc/host';
|
||||||
|
import {ClassMember, ClassMemberKind} from '../../../ngtsc/host/src/reflection';
|
||||||
|
import {reflectObjectLiteral} from '../../../ngtsc/metadata/src/reflector';
|
||||||
|
import {CONSTRUCTOR_PARAMS, Esm2015ReflectionHost, getPropertyValueFromSymbol} from './esm2015_host';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ESM5 packages contain ECMAScript IIFE functions that act like classes. For example:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* var CommonModule = (function () {
|
||||||
|
* function CommonModule() {
|
||||||
|
* }
|
||||||
|
* CommonModule.decorators = [ ... ];
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* * "Classes" are decorated if they have a static property called `decorators`.
|
||||||
|
* * Members are decorated if there is a matching key on a static property
|
||||||
|
* called `propDecorators`.
|
||||||
|
* * Constructor parameters decorators are found on an object returned from
|
||||||
|
* a static method called `ctorParameters`.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||||
|
constructor(checker: ts.TypeChecker) { super(checker); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the given declaration node actually represents a class.
|
||||||
|
*/
|
||||||
|
isClass(node: ts.Declaration): boolean { return !!this.getClassSymbol(node); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In ESM5 the implementation of a class is a function expression that is hidden inside an IIFE.
|
||||||
|
* So we need to dig around inside to get hold of the "class" symbol.
|
||||||
|
* @param declaration the top level declaration that represents an exported class.
|
||||||
|
*/
|
||||||
|
getClassSymbol(declaration: ts.Declaration): ts.Symbol|undefined {
|
||||||
|
if (ts.isVariableDeclaration(declaration)) {
|
||||||
|
const iifeBody = getIifeBody(declaration);
|
||||||
|
if (iifeBody) {
|
||||||
|
const innerClassIdentifier = getReturnIdentifier(iifeBody);
|
||||||
|
if (innerClassIdentifier) {
|
||||||
|
return this.checker.getSymbolAtLocation(innerClassIdentifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* function itself.
|
||||||
|
*/
|
||||||
|
protected getConstructorParameterDeclarations(classSymbol: ts.Symbol): ts.ParameterDeclaration[] {
|
||||||
|
const constructor = classSymbol.valueDeclaration as ts.FunctionDeclaration;
|
||||||
|
if (constructor && constructor.parameters) {
|
||||||
|
return Array.from(constructor.parameters);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructors parameter decorators are declared in the body of static method of the constructor
|
||||||
|
* function in ES5. Note that unlike ESM2105 this is a function expression rather than an arrow
|
||||||
|
* function:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* SomeDirective.ctorParameters = function() { return [
|
||||||
|
* { type: ViewContainerRef, },
|
||||||
|
* { type: TemplateRef, },
|
||||||
|
* { type: IterableDiffers, },
|
||||||
|
* { type: undefined, decorators: [{ type: Inject, args: [INJECTED_TOKEN,] },] },
|
||||||
|
* ]; };
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
protected getConstructorDecorators(classSymbol: ts.Symbol): (Map<string, ts.Expression>|null)[] {
|
||||||
|
const declaration = classSymbol.exports && classSymbol.exports.get(CONSTRUCTOR_PARAMS);
|
||||||
|
const paramDecoratorsProperty = declaration && getPropertyValueFromSymbol(declaration);
|
||||||
|
const returnStatement = getReturnStatement(paramDecoratorsProperty);
|
||||||
|
const expression = returnStatement && returnStatement.expression;
|
||||||
|
return expression && ts.isArrayLiteralExpression(expression) ?
|
||||||
|
expression.elements.map(reflectArrayElement) :
|
||||||
|
[];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected reflectMember(symbol: ts.Symbol, decorators?: Decorator[], isStatic?: boolean):
|
||||||
|
ClassMember|null {
|
||||||
|
const member = super.reflectMember(symbol, decorators, isStatic);
|
||||||
|
if (member && member.kind === ClassMemberKind.Method && member.isStatic && member.node &&
|
||||||
|
ts.isPropertyAccessExpression(member.node) && member.node.parent &&
|
||||||
|
ts.isBinaryExpression(member.node.parent) &&
|
||||||
|
ts.isFunctionExpression(member.node.parent.right)) {
|
||||||
|
// Recompute the implementation for this member:
|
||||||
|
// ES5 static methods are variable declarations so the declaration is actually the
|
||||||
|
// initializer of the variable assignment
|
||||||
|
member.implementation = member.node.parent.right;
|
||||||
|
}
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIifeBody(declaration: ts.VariableDeclaration): ts.Block|undefined {
|
||||||
|
if (!declaration.initializer || !ts.isParenthesizedExpression(declaration.initializer)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const call = declaration.initializer;
|
||||||
|
return ts.isCallExpression(call.expression) &&
|
||||||
|
ts.isFunctionExpression(call.expression.expression) ?
|
||||||
|
call.expression.expression.body :
|
||||||
|
undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReturnIdentifier(body: ts.Block): ts.Identifier|undefined {
|
||||||
|
const returnStatement = body.statements.find(ts.isReturnStatement);
|
||||||
|
return returnStatement && returnStatement.expression &&
|
||||||
|
ts.isIdentifier(returnStatement.expression) ?
|
||||||
|
returnStatement.expression :
|
||||||
|
undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReturnStatement(declaration: ts.Expression | undefined): ts.ReturnStatement|undefined {
|
||||||
|
return declaration && ts.isFunctionExpression(declaration) ?
|
||||||
|
declaration.body.statements.find(ts.isReturnStatement) :
|
||||||
|
undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reflectArrayElement(element: ts.Expression) {
|
||||||
|
return ts.isObjectLiteralExpression(element) ? reflectObjectLiteral(element) : null;
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
import {ReflectionHost} from '../../../ngtsc/host';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reflection host that has extra methods for looking at non-Typescript package formats
|
||||||
|
*/
|
||||||
|
export interface NgccReflectionHost extends ReflectionHost {
|
||||||
|
getClassSymbol(declaration: ts.Declaration): ts.Symbol|undefined;
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
export function getOriginalSymbol(checker: ts.TypeChecker): (symbol: ts.Symbol) => ts.Symbol {
|
||||||
|
return function(symbol: ts.Symbol) {
|
||||||
|
return ts.SymbolFlags.Alias & symbol.flags ? checker.getAliasedSymbol(symbol) : symbol;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDefined<T>(value: T | undefined | null): value is T {
|
||||||
|
return !!value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNameText(name: ts.PropertyName | ts.BindingName): string {
|
||||||
|
return ts.isIdentifier(name) || ts.isLiteralExpression(name) ? name.text : name.getText();
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue