refactor(ivy): first pass at extracting ReflectionHost for abstract reflection (#24541)
ngtsc needs to reflect over code to property compile it. It performs operations such as enumerating decorators on a type, reading metadata from constructor parameters, etc. Depending on the format (ES5, ES6, etc) of the underlying code, the AST structures over which this reflection takes place can be very different. For example, in TS/ES6 code `class` declarations are `ts.ClassDeclaration` nodes, but in ES5 code they've been downleveled to `ts.VariableDeclaration` nodes that are initialized to IIFEs that build up the classes being defined. The ReflectionHost abstraction allows ngtsc to perform these operations without directly querying the AST. Different implementations of ReflectionHost allow support for different code formats. PR Close #24541
This commit is contained in:
parent
84272e2227
commit
10da6a45c6
|
@ -26,6 +26,7 @@ ts_library(
|
|||
deps = [
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/annotations",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/transform",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -11,6 +11,7 @@ ts_library(
|
|||
module_name = "@angular/compiler-cli/src/ngtsc/annotations",
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/host",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/transform",
|
||||
],
|
||||
|
|
|
@ -9,11 +9,13 @@
|
|||
import {ConstantPool, Expression, R3ComponentMetadata, R3DirectiveMetadata, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator, reflectNonStaticField, reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
||||
import {Decorator, ReflectionHost} from '../../host';
|
||||
import {reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||
|
||||
import {extractDirectiveMetadata} from './directive';
|
||||
import {SelectorScopeRegistry} from './selector_scope';
|
||||
import {isAngularCore} from './util';
|
||||
|
||||
const EMPTY_MAP = new Map<string, Expression>();
|
||||
|
||||
|
@ -21,14 +23,18 @@ const EMPTY_MAP = new Map<string, Expression>();
|
|||
* `DecoratorHandler` which handles the `@Component` annotation.
|
||||
*/
|
||||
export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMetadata> {
|
||||
constructor(private checker: ts.TypeChecker, private scopeRegistry: SelectorScopeRegistry) {}
|
||||
constructor(
|
||||
private checker: ts.TypeChecker, private reflector: ReflectionHost,
|
||||
private scopeRegistry: SelectorScopeRegistry) {}
|
||||
|
||||
detect(decorators: Decorator[]): Decorator|undefined {
|
||||
return decorators.find(
|
||||
decorator => decorator.name === 'Component' && decorator.from === '@angular/core');
|
||||
return decorators.find(decorator => decorator.name === 'Component' && isAngularCore(decorator));
|
||||
}
|
||||
|
||||
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3ComponentMetadata> {
|
||||
if (decorator.args === null || decorator.args.length !== 1) {
|
||||
throw new Error(`Incorrect number of arguments to @Component decorator`);
|
||||
}
|
||||
const meta = decorator.args[0];
|
||||
if (!ts.isObjectLiteralExpression(meta)) {
|
||||
throw new Error(`Decorator argument must be literal.`);
|
||||
|
@ -36,7 +42,8 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
|
|||
|
||||
// @Component inherits @Directive, so begin by extracting the @Directive metadata and building
|
||||
// on it.
|
||||
const directiveMetadata = extractDirectiveMetadata(node, decorator, this.checker);
|
||||
const directiveMetadata =
|
||||
extractDirectiveMetadata(node, decorator, this.checker, this.reflector);
|
||||
if (directiveMetadata === undefined) {
|
||||
// `extractDirectiveMetadata` returns undefined when the @Directive has `jit: true`. In this
|
||||
// case, compilation of the decorator is skipped. Returning an empty object signifies
|
||||
|
@ -93,6 +100,7 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
compile(node: ts.ClassDeclaration, analysis: R3ComponentMetadata): CompileResult {
|
||||
const pool = new ConstantPool();
|
||||
|
||||
|
|
|
@ -9,25 +9,27 @@
|
|||
import {ConstantPool, R3DirectiveMetadata, WrappedNodeExpr, compileDirectiveFromMetadata, makeBindingParser} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator, staticallyResolve} from '../../metadata';
|
||||
import {DecoratedNode, getDecoratedClassElements, reflectNonStaticField, reflectObjectLiteral} from '../../metadata/src/reflector';
|
||||
import {ClassMember, ClassMemberKind, Decorator, Import, ReflectionHost} from '../../host';
|
||||
import {reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
||||
import {filterToMembersWithDecorator} from '../../metadata/src/reflector';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||
|
||||
import {SelectorScopeRegistry} from './selector_scope';
|
||||
import {getConstructorDependencies} from './util';
|
||||
import {getConstructorDependencies, isAngularCore} from './util';
|
||||
|
||||
const EMPTY_OBJECT: {[key: string]: string} = {};
|
||||
|
||||
export class DirectiveDecoratorHandler implements DecoratorHandler<R3DirectiveMetadata> {
|
||||
constructor(private checker: ts.TypeChecker, private scopeRegistry: SelectorScopeRegistry) {}
|
||||
constructor(
|
||||
private checker: ts.TypeChecker, private reflector: ReflectionHost,
|
||||
private scopeRegistry: SelectorScopeRegistry) {}
|
||||
|
||||
detect(decorators: Decorator[]): Decorator|undefined {
|
||||
return decorators.find(
|
||||
decorator => decorator.name === 'Directive' && decorator.from === '@angular/core');
|
||||
return decorators.find(decorator => decorator.name === 'Directive' && isAngularCore(decorator));
|
||||
}
|
||||
|
||||
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3DirectiveMetadata> {
|
||||
const analysis = extractDirectiveMetadata(node, decorator, this.checker);
|
||||
const analysis = extractDirectiveMetadata(node, decorator, this.checker, this.reflector);
|
||||
|
||||
// If the directive has a selector, it should be registered with the `SelectorScopeRegistry` so
|
||||
// when this directive appears in an `@NgModule` scope, its selector can be determined.
|
||||
|
@ -54,8 +56,11 @@ export class DirectiveDecoratorHandler implements DecoratorHandler<R3DirectiveMe
|
|||
* Helper function to extract metadata from a `Directive` or `Component`.
|
||||
*/
|
||||
export function extractDirectiveMetadata(
|
||||
clazz: ts.ClassDeclaration, decorator: Decorator, checker: ts.TypeChecker): R3DirectiveMetadata|
|
||||
undefined {
|
||||
clazz: ts.ClassDeclaration, decorator: Decorator, checker: ts.TypeChecker,
|
||||
reflector: ReflectionHost): R3DirectiveMetadata|undefined {
|
||||
if (decorator.args === null || decorator.args.length !== 1) {
|
||||
throw new Error(`Incorrect number of arguments to @${decorator.name} decorator`);
|
||||
}
|
||||
const meta = decorator.args[0];
|
||||
if (!ts.isObjectLiteralExpression(meta)) {
|
||||
throw new Error(`Decorator argument must be literal.`);
|
||||
|
@ -67,20 +72,24 @@ export function extractDirectiveMetadata(
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const members = reflector.getMembersOfClass(clazz);
|
||||
|
||||
// Precompute a list of ts.ClassElements that have decorators. This includes things like @Input,
|
||||
// @Output, @HostBinding, etc.
|
||||
const decoratedElements = getDecoratedClassElements(clazz, checker);
|
||||
const decoratedElements =
|
||||
members.filter(member => !member.isStatic && member.decorators !== null);
|
||||
|
||||
// Construct the map of inputs both from the @Directive/@Component decorator, and the decorated
|
||||
// Construct the map of inputs both from the @Directive/@Component
|
||||
// decorator, and the decorated
|
||||
// fields.
|
||||
const inputsFromMeta = parseFieldToPropertyMapping(directive, 'inputs', checker);
|
||||
const inputsFromFields = parseDecoratedFields(
|
||||
findDecoratedFields(decoratedElements, '@angular/core', 'Input'), checker);
|
||||
filterToMembersWithDecorator(decoratedElements, 'Input', '@angular/core'), checker);
|
||||
|
||||
// And outputs.
|
||||
const outputsFromMeta = parseFieldToPropertyMapping(directive, 'outputs', checker);
|
||||
const outputsFromFields = parseDecoratedFields(
|
||||
findDecoratedFields(decoratedElements, '@angular/core', 'Output'), checker);
|
||||
filterToMembersWithDecorator(decoratedElements, '@angular/core', 'Output'), checker);
|
||||
|
||||
// Parse the selector.
|
||||
let selector = '';
|
||||
|
@ -93,11 +102,13 @@ export function extractDirectiveMetadata(
|
|||
}
|
||||
|
||||
// Determine if `ngOnChanges` is a lifecycle hook defined on the component.
|
||||
const usesOnChanges = reflectNonStaticField(clazz, 'ngOnChanges') !== null;
|
||||
const usesOnChanges = members.find(
|
||||
member => member.isStatic && member.kind === ClassMemberKind.Method &&
|
||||
member.name === 'ngOnChanges') !== undefined;
|
||||
|
||||
return {
|
||||
name: clazz.name !.text,
|
||||
deps: getConstructorDependencies(clazz, checker),
|
||||
deps: getConstructorDependencies(clazz, reflector),
|
||||
host: {
|
||||
attributes: {},
|
||||
listeners: {},
|
||||
|
@ -123,35 +134,6 @@ function assertIsStringArray(value: any[]): value is string[] {
|
|||
return true;
|
||||
}
|
||||
|
||||
type DecoratedProperty = DecoratedNode<ts.PropertyDeclaration|ts.AccessorDeclaration>;
|
||||
|
||||
/**
|
||||
* Find all fields in the array of `DecoratedNode`s that have a decorator of the given type.
|
||||
*/
|
||||
function findDecoratedFields(
|
||||
elements: DecoratedNode<ts.ClassElement>[], decoratorModule: string,
|
||||
decoratorName: string): DecoratedProperty[] {
|
||||
return elements
|
||||
.map(entry => {
|
||||
const element = entry.element;
|
||||
// Only consider properties and accessors. Filter out everything else.
|
||||
if (!ts.isPropertyDeclaration(element) && !ts.isAccessor(element)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract the array of matching decorators (there could be more than one).
|
||||
const decorators = entry.decorators.filter(
|
||||
decorator => decorator.name === decoratorName && decorator.from === decoratorModule);
|
||||
if (decorators.length === 0) {
|
||||
// No matching decorators, don't include this element.
|
||||
return null;
|
||||
}
|
||||
return {element, decorators};
|
||||
})
|
||||
// Filter out nulls.
|
||||
.filter(entry => entry !== null) as DecoratedProperty[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpret property mapping fields on the decorator (e.g. inputs or outputs) and return the
|
||||
* correctly shaped metadata object.
|
||||
|
@ -185,14 +167,15 @@ function parseFieldToPropertyMapping(
|
|||
* object.
|
||||
*/
|
||||
function parseDecoratedFields(
|
||||
fields: DecoratedProperty[], checker: ts.TypeChecker): {[field: string]: string} {
|
||||
fields: {member: ClassMember, decorators: Decorator[]}[],
|
||||
checker: ts.TypeChecker): {[field: string]: string} {
|
||||
return fields.reduce(
|
||||
(results, field) => {
|
||||
const fieldName = (field.element.name as ts.Identifier).text;
|
||||
const fieldName = field.member.name;
|
||||
field.decorators.forEach(decorator => {
|
||||
// The decorator either doesn't have an argument (@Input()) in which case the property
|
||||
// name is used, or it has one argument (@Output('named')).
|
||||
if (decorator.args.length === 0) {
|
||||
if (decorator.args == null || decorator.args.length === 0) {
|
||||
results[fieldName] = fieldName;
|
||||
} else if (decorator.args.length === 1) {
|
||||
const property = staticallyResolve(decorator.args[0], checker);
|
||||
|
|
|
@ -9,26 +9,26 @@
|
|||
import {Expression, LiteralExpr, R3DependencyMetadata, R3InjectableMetadata, R3ResolvedDependencyType, WrappedNodeExpr, compileInjectable as compileIvyInjectable} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator} from '../../metadata';
|
||||
import {reflectConstructorParameters, reflectImportedIdentifier, reflectObjectLiteral} from '../../metadata/src/reflector';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform/src/api';
|
||||
import {Decorator, ReflectionHost} from '../../host';
|
||||
import {reflectObjectLiteral} from '../../metadata';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||
|
||||
import {getConstructorDependencies} from './util';
|
||||
import {getConstructorDependencies, isAngularCore} from './util';
|
||||
|
||||
|
||||
/**
|
||||
* Adapts the `compileIvyInjectable` compiler for `@Injectable` decorators to the Ivy compiler.
|
||||
*/
|
||||
export class InjectableDecoratorHandler implements DecoratorHandler<R3InjectableMetadata> {
|
||||
constructor(private checker: ts.TypeChecker) {}
|
||||
constructor(private reflector: ReflectionHost) {}
|
||||
|
||||
detect(decorator: Decorator[]): Decorator|undefined {
|
||||
return decorator.find(dec => dec.name === 'Injectable' && dec.from === '@angular/core');
|
||||
return decorator.find(decorator => decorator.name === 'Injectable' && isAngularCore(decorator));
|
||||
}
|
||||
|
||||
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3InjectableMetadata> {
|
||||
return {
|
||||
analysis: extractInjectableMetadata(node, decorator, this.checker),
|
||||
analysis: extractInjectableMetadata(node, decorator, this.reflector),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -49,18 +49,21 @@ export class InjectableDecoratorHandler implements DecoratorHandler<R3Injectable
|
|||
*/
|
||||
function extractInjectableMetadata(
|
||||
clazz: ts.ClassDeclaration, decorator: Decorator,
|
||||
checker: ts.TypeChecker): R3InjectableMetadata {
|
||||
reflector: ReflectionHost): R3InjectableMetadata {
|
||||
if (clazz.name === undefined) {
|
||||
throw new Error(`@Injectables must have names`);
|
||||
}
|
||||
const name = clazz.name.text;
|
||||
const type = new WrappedNodeExpr(clazz.name);
|
||||
if (decorator.args === null) {
|
||||
throw new Error(`@Injectable must be called`);
|
||||
}
|
||||
if (decorator.args.length === 0) {
|
||||
return {
|
||||
name,
|
||||
type,
|
||||
providedIn: new LiteralExpr(null),
|
||||
deps: getConstructorDependencies(clazz, checker),
|
||||
deps: getConstructorDependencies(clazz, reflector),
|
||||
};
|
||||
} else if (decorator.args.length === 1) {
|
||||
const metaNode = decorator.args[0];
|
||||
|
@ -95,11 +98,11 @@ function extractInjectableMetadata(
|
|||
if (depsExpr.elements.length > 0) {
|
||||
throw new Error(`deps not yet supported`);
|
||||
}
|
||||
deps.push(...depsExpr.elements.map(dep => getDep(dep, checker)));
|
||||
deps.push(...depsExpr.elements.map(dep => getDep(dep, reflector)));
|
||||
}
|
||||
return {name, type, providedIn, useFactory: factory, deps};
|
||||
} else {
|
||||
const deps = getConstructorDependencies(clazz, checker);
|
||||
const deps = getConstructorDependencies(clazz, reflector);
|
||||
return {name, type, providedIn, deps};
|
||||
}
|
||||
} else {
|
||||
|
@ -109,7 +112,7 @@ function extractInjectableMetadata(
|
|||
|
||||
|
||||
|
||||
function getDep(dep: ts.Expression, checker: ts.TypeChecker): R3DependencyMetadata {
|
||||
function getDep(dep: ts.Expression, reflector: ReflectionHost): R3DependencyMetadata {
|
||||
const meta: R3DependencyMetadata = {
|
||||
token: new WrappedNodeExpr(dep),
|
||||
host: false,
|
||||
|
@ -119,8 +122,9 @@ function getDep(dep: ts.Expression, checker: ts.TypeChecker): R3DependencyMetada
|
|||
skipSelf: false,
|
||||
};
|
||||
|
||||
function maybeUpdateDecorator(dec: ts.Identifier, token?: ts.Expression): void {
|
||||
const source = reflectImportedIdentifier(dec, checker);
|
||||
function maybeUpdateDecorator(
|
||||
dec: ts.Identifier, reflector: ReflectionHost, token?: ts.Expression): void {
|
||||
const source = reflector.getImportOfIdentifier(dec);
|
||||
if (source === null || source.from !== '@angular/core') {
|
||||
return;
|
||||
}
|
||||
|
@ -145,10 +149,10 @@ function getDep(dep: ts.Expression, checker: ts.TypeChecker): R3DependencyMetada
|
|||
if (ts.isArrayLiteralExpression(dep)) {
|
||||
dep.elements.forEach(el => {
|
||||
if (ts.isIdentifier(el)) {
|
||||
maybeUpdateDecorator(el);
|
||||
maybeUpdateDecorator(el, reflector);
|
||||
} else if (ts.isNewExpression(el) && ts.isIdentifier(el.expression)) {
|
||||
const token = el.arguments && el.arguments.length > 0 && el.arguments[0] || undefined;
|
||||
maybeUpdateDecorator(el.expression, token);
|
||||
maybeUpdateDecorator(el.expression, reflector, token);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,11 +9,12 @@
|
|||
import {ConstantPool, Expression, R3DirectiveMetadata, R3NgModuleMetadata, WrappedNodeExpr, compileNgModule, makeBindingParser, parseTemplate} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator, Reference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
||||
import {Decorator} from '../../host';
|
||||
import {Reference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||
|
||||
import {SelectorScopeRegistry} from './selector_scope';
|
||||
import {referenceToExpression} from './util';
|
||||
import {isAngularCore, referenceToExpression} from './util';
|
||||
|
||||
/**
|
||||
* Compiles @NgModule annotations to ngModuleDef fields.
|
||||
|
@ -24,11 +25,13 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<R3NgModuleMeta
|
|||
constructor(private checker: ts.TypeChecker, private scopeRegistry: SelectorScopeRegistry) {}
|
||||
|
||||
detect(decorators: Decorator[]): Decorator|undefined {
|
||||
return decorators.find(
|
||||
decorator => decorator.name === 'NgModule' && decorator.from === '@angular/core');
|
||||
return decorators.find(decorator => decorator.name === 'NgModule' && isAngularCore(decorator));
|
||||
}
|
||||
|
||||
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3NgModuleMetadata> {
|
||||
if (decorator.args === null || decorator.args.length !== 1) {
|
||||
throw new Error(`Incorrect number of arguments to @NgModule decorator`);
|
||||
}
|
||||
const meta = decorator.args[0];
|
||||
if (!ts.isObjectLiteralExpression(meta)) {
|
||||
throw new Error(`Decorator argument must be literal.`);
|
||||
|
|
|
@ -9,11 +9,14 @@
|
|||
import {Expression, ExternalExpr, ExternalReference} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {AbsoluteReference, Reference, reflectStaticField, reflectTypeEntityToDeclaration} from '../../metadata';
|
||||
import {ReflectionHost} from '../../host';
|
||||
import {AbsoluteReference, Reference, reflectTypeEntityToDeclaration} from '../../metadata';
|
||||
import {reflectIdentifierOfDeclaration, reflectNameOfDeclaration} from '../../metadata/src/reflector';
|
||||
|
||||
import {referenceToExpression} from './util';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Metadata extracted for a given NgModule that can be used to compute selector scopes.
|
||||
*/
|
||||
|
@ -59,55 +62,55 @@ export class SelectorScopeRegistry {
|
|||
/**
|
||||
* Map of modules declared in the current compilation unit to their (local) metadata.
|
||||
*/
|
||||
private _moduleToData = new Map<ts.ClassDeclaration, ModuleData>();
|
||||
private _moduleToData = new Map<ts.Declaration, ModuleData>();
|
||||
|
||||
/**
|
||||
* Map of modules to their cached `CompilationScope`s.
|
||||
*/
|
||||
private _compilationScopeCache = new Map<ts.ClassDeclaration, CompilationScope<Reference>>();
|
||||
private _compilationScopeCache = new Map<ts.Declaration, CompilationScope<Reference>>();
|
||||
|
||||
/**
|
||||
* Map of components/directives to their selector.
|
||||
*/
|
||||
private _directiveToSelector = new Map<ts.ClassDeclaration, string>();
|
||||
private _directiveToSelector = new Map<ts.Declaration, string>();
|
||||
|
||||
/**
|
||||
* Map of pipes to their name.
|
||||
*/
|
||||
private _pipeToName = new Map<ts.ClassDeclaration, string>();
|
||||
private _pipeToName = new Map<ts.Declaration, string>();
|
||||
|
||||
/**
|
||||
* Map of components/directives/pipes to their module.
|
||||
*/
|
||||
private _declararedTypeToModule = new Map<ts.ClassDeclaration, ts.ClassDeclaration>();
|
||||
private _declararedTypeToModule = new Map<ts.Declaration, ts.Declaration>();
|
||||
|
||||
constructor(private checker: ts.TypeChecker) {}
|
||||
constructor(private checker: ts.TypeChecker, private reflector: ReflectionHost) {}
|
||||
|
||||
/**
|
||||
* Register a module's metadata with the registry.
|
||||
*/
|
||||
registerModule(node: ts.ClassDeclaration, data: ModuleData): void {
|
||||
node = ts.getOriginalNode(node) as ts.ClassDeclaration;
|
||||
registerModule(node: ts.Declaration, data: ModuleData): void {
|
||||
node = ts.getOriginalNode(node) as ts.Declaration;
|
||||
|
||||
if (this._moduleToData.has(node)) {
|
||||
throw new Error(`Module already registered: ${node.name!.text}`);
|
||||
throw new Error(`Module already registered: ${reflectNameOfDeclaration(node)}`);
|
||||
}
|
||||
this._moduleToData.set(node, data);
|
||||
|
||||
// Register all of the module's declarations in the context map as belonging to this module.
|
||||
data.declarations.forEach(decl => {
|
||||
this._declararedTypeToModule.set(ts.getOriginalNode(decl.node) as ts.ClassDeclaration, node);
|
||||
this._declararedTypeToModule.set(ts.getOriginalNode(decl.node) as ts.Declaration, node);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the selector of a component or directive with the registry.
|
||||
*/
|
||||
registerSelector(node: ts.ClassDeclaration, selector: string): void {
|
||||
node = ts.getOriginalNode(node) as ts.ClassDeclaration;
|
||||
registerSelector(node: ts.Declaration, selector: string): void {
|
||||
node = ts.getOriginalNode(node) as ts.Declaration;
|
||||
|
||||
if (this._directiveToSelector.has(node)) {
|
||||
throw new Error(`Selector already registered: ${node.name!.text} ${selector}`);
|
||||
throw new Error(`Selector already registered: ${reflectNameOfDeclaration(node)} ${selector}`);
|
||||
}
|
||||
this._directiveToSelector.set(node, selector);
|
||||
}
|
||||
|
@ -115,14 +118,14 @@ export class SelectorScopeRegistry {
|
|||
/**
|
||||
* Register the name of a pipe with the registry.
|
||||
*/
|
||||
registerPipe(node: ts.ClassDeclaration, name: string): void { this._pipeToName.set(node, name); }
|
||||
registerPipe(node: ts.Declaration, name: string): void { this._pipeToName.set(node, name); }
|
||||
|
||||
/**
|
||||
* Produce the compilation scope of a component, which is determined by the module that declares
|
||||
* it.
|
||||
*/
|
||||
lookupCompilationScope(node: ts.ClassDeclaration): CompilationScope<Expression>|null {
|
||||
node = ts.getOriginalNode(node) as ts.ClassDeclaration;
|
||||
lookupCompilationScope(node: ts.Declaration): CompilationScope<Expression>|null {
|
||||
node = ts.getOriginalNode(node) as ts.Declaration;
|
||||
|
||||
// If the component has no associated module, then it has no compilation scope.
|
||||
if (!this._declararedTypeToModule.has(node)) {
|
||||
|
@ -150,8 +153,7 @@ export class SelectorScopeRegistry {
|
|||
// The initial value of ngModuleImportedFrom is 'null' which signifies that the NgModule
|
||||
// was not imported from a .d.ts source.
|
||||
this.lookupScopes(module !, /* ngModuleImportedFrom */ null).compilation.forEach(ref => {
|
||||
const selector =
|
||||
this.lookupDirectiveSelector(ts.getOriginalNode(ref.node) as ts.ClassDeclaration);
|
||||
const selector = this.lookupDirectiveSelector(ts.getOriginalNode(ref.node) as ts.Declaration);
|
||||
// Only directives/components with selectors get added to the scope.
|
||||
if (selector != null) {
|
||||
directives.set(selector, ref);
|
||||
|
@ -174,8 +176,7 @@ export class SelectorScopeRegistry {
|
|||
* (`ngModuleImportedFrom`) then all of its declarations are exported at that same path, as well
|
||||
* as imports and exports from other modules that are relatively imported.
|
||||
*/
|
||||
private lookupScopes(node: ts.ClassDeclaration, ngModuleImportedFrom: string|null):
|
||||
SelectorScopes {
|
||||
private lookupScopes(node: ts.Declaration, ngModuleImportedFrom: string|null): SelectorScopes {
|
||||
let data: ModuleData|null = null;
|
||||
|
||||
// Either this module was analyzed directly, or has a precompiled ngModuleDef.
|
||||
|
@ -195,7 +196,7 @@ export class SelectorScopeRegistry {
|
|||
}
|
||||
|
||||
if (data === null) {
|
||||
throw new Error(`Module not registered: ${node.name!.text}`);
|
||||
throw new Error(`Module not registered: ${reflectNameOfDeclaration(node)}`);
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -203,20 +204,18 @@ export class SelectorScopeRegistry {
|
|||
...data.declarations,
|
||||
// Expand imports to the exported scope of those imports.
|
||||
...flatten(data.imports.map(
|
||||
ref => this.lookupScopes(ref.node as ts.ClassDeclaration, absoluteModuleName(ref))
|
||||
.exported)),
|
||||
ref =>
|
||||
this.lookupScopes(ref.node as ts.Declaration, absoluteModuleName(ref)).exported)),
|
||||
// And include the compilation scope of exported modules.
|
||||
...flatten(
|
||||
data.exports.filter(ref => this._moduleToData.has(ref.node as ts.ClassDeclaration))
|
||||
data.exports.filter(ref => this._moduleToData.has(ref.node as ts.Declaration))
|
||||
.map(
|
||||
ref =>
|
||||
this.lookupScopes(ref.node as ts.ClassDeclaration, absoluteModuleName(ref))
|
||||
ref => this.lookupScopes(ref.node as ts.Declaration, absoluteModuleName(ref))
|
||||
.exported))
|
||||
],
|
||||
exported: flatten(data.exports.map(ref => {
|
||||
if (this._moduleToData.has(ref.node as ts.ClassDeclaration)) {
|
||||
return this.lookupScopes(ref.node as ts.ClassDeclaration, absoluteModuleName(ref))
|
||||
.exported;
|
||||
if (this._moduleToData.has(ref.node as ts.Declaration)) {
|
||||
return this.lookupScopes(ref.node as ts.Declaration, absoluteModuleName(ref)).exported;
|
||||
} else {
|
||||
return [ref];
|
||||
}
|
||||
|
@ -231,7 +230,7 @@ export class SelectorScopeRegistry {
|
|||
* ngComponentDef/ngDirectiveDef. In this case, the type metadata of that definition is read
|
||||
* to determine the selector.
|
||||
*/
|
||||
private lookupDirectiveSelector(node: ts.ClassDeclaration): string|null {
|
||||
private lookupDirectiveSelector(node: ts.Declaration): string|null {
|
||||
if (this._directiveToSelector.has(node)) {
|
||||
return this._directiveToSelector.get(node) !;
|
||||
} else {
|
||||
|
@ -239,7 +238,7 @@ export class SelectorScopeRegistry {
|
|||
}
|
||||
}
|
||||
|
||||
private lookupPipeName(node: ts.ClassDeclaration): string|undefined {
|
||||
private lookupPipeName(node: ts.Declaration): string|undefined {
|
||||
return this._pipeToName.get(node);
|
||||
}
|
||||
|
||||
|
@ -251,16 +250,17 @@ export class SelectorScopeRegistry {
|
|||
* @param ngModuleImportedFrom module specifier of the import path to assume for all declarations
|
||||
* stemming from this module.
|
||||
*/
|
||||
private _readMetadataFromCompiledClass(clazz: ts.ClassDeclaration, ngModuleImportedFrom: string):
|
||||
private _readMetadataFromCompiledClass(clazz: ts.Declaration, ngModuleImportedFrom: string):
|
||||
ModuleData|null {
|
||||
// This operation is explicitly not memoized, as it depends on `ngModuleImportedFrom`.
|
||||
// TODO(alxhub): investigate caching of .d.ts module metadata.
|
||||
const ngModuleDef = reflectStaticField(clazz, 'ngModuleDef');
|
||||
if (ngModuleDef === null) {
|
||||
const ngModuleDef = this.reflector.getMembersOfClass(clazz).find(
|
||||
member => member.name === 'ngModuleDef' && member.isStatic);
|
||||
if (ngModuleDef === undefined) {
|
||||
return null;
|
||||
} else if (
|
||||
// Validate that the shape of the ngModuleDef type is correct.
|
||||
ngModuleDef.type === undefined || !ts.isTypeReferenceNode(ngModuleDef.type) ||
|
||||
ngModuleDef.type === null || !ts.isTypeReferenceNode(ngModuleDef.type) ||
|
||||
ngModuleDef.type.typeArguments === undefined ||
|
||||
ngModuleDef.type.typeArguments.length !== 4) {
|
||||
return null;
|
||||
|
@ -279,14 +279,15 @@ export class SelectorScopeRegistry {
|
|||
* Get the selector from type metadata for a class with a precompiled ngComponentDef or
|
||||
* ngDirectiveDef.
|
||||
*/
|
||||
private _readSelectorFromCompiledClass(clazz: ts.ClassDeclaration): string|null {
|
||||
const def =
|
||||
reflectStaticField(clazz, 'ngComponentDef') || reflectStaticField(clazz, 'ngDirectiveDef');
|
||||
if (def === null) {
|
||||
private _readSelectorFromCompiledClass(clazz: ts.Declaration): string|null {
|
||||
const def = this.reflector.getMembersOfClass(clazz).find(
|
||||
field =>
|
||||
field.isStatic && (field.name === 'ngComponentDef' || field.name === 'ngDirectiveDef'));
|
||||
if (def === undefined) {
|
||||
// No definition could be found.
|
||||
return null;
|
||||
} else if (
|
||||
def.type === undefined || !ts.isTypeReferenceNode(def.type) ||
|
||||
def.type === null || !ts.isTypeReferenceNode(def.type) ||
|
||||
def.type.typeArguments === undefined || def.type.typeArguments.length !== 2) {
|
||||
// The type metadata was the wrong shape.
|
||||
return null;
|
||||
|
@ -317,8 +318,9 @@ export class SelectorScopeRegistry {
|
|||
const type = element.typeName;
|
||||
const {node, from} = reflectTypeEntityToDeclaration(type, this.checker);
|
||||
const moduleName = (from !== null && !from.startsWith('.') ? from : ngModuleImportedFrom);
|
||||
const clazz = node as ts.ClassDeclaration;
|
||||
return new AbsoluteReference(node, clazz.name !, moduleName, clazz.name !.text);
|
||||
const clazz = node as ts.Declaration;
|
||||
const id = reflectIdentifierOfDeclaration(clazz);
|
||||
return new AbsoluteReference(node, id !, moduleName, id !.text);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -331,7 +333,6 @@ function flatten<T>(array: T[][]): T[] {
|
|||
}
|
||||
|
||||
function absoluteModuleName(ref: Reference): string|null {
|
||||
const name = (ref.node as ts.ClassDeclaration).name !.text;
|
||||
if (!(ref instanceof AbsoluteReference)) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -9,20 +9,20 @@
|
|||
import {Expression, R3DependencyMetadata, R3ResolvedDependencyType, WrappedNodeExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Reference, reflectConstructorParameters} from '../../metadata';
|
||||
import {reflectImportedIdentifier} from '../../metadata/src/reflector';
|
||||
import {Decorator, ReflectionHost} from '../../host';
|
||||
import {Reference} from '../../metadata';
|
||||
|
||||
export function getConstructorDependencies(
|
||||
clazz: ts.ClassDeclaration, checker: ts.TypeChecker): R3DependencyMetadata[] {
|
||||
clazz: ts.ClassDeclaration, reflector: ReflectionHost): R3DependencyMetadata[] {
|
||||
const useType: R3DependencyMetadata[] = [];
|
||||
const ctorParams = (reflectConstructorParameters(clazz, checker) || []);
|
||||
ctorParams.forEach(param => {
|
||||
let tokenExpr = param.typeValueExpr;
|
||||
const ctorParams = reflector.getConstructorParameters(clazz) || [];
|
||||
ctorParams.forEach((param, idx) => {
|
||||
let tokenExpr = param.type;
|
||||
let optional = false, self = false, skipSelf = false, host = false;
|
||||
let resolved = R3ResolvedDependencyType.Token;
|
||||
param.decorators.filter(dec => dec.from === '@angular/core').forEach(dec => {
|
||||
(param.decorators || []).filter(isAngularCore).forEach(dec => {
|
||||
if (dec.name === 'Inject') {
|
||||
if (dec.args.length !== 1) {
|
||||
if (dec.args === null || dec.args.length !== 1) {
|
||||
throw new Error(`Unexpected number of arguments to @Inject().`);
|
||||
}
|
||||
tokenExpr = dec.args[0];
|
||||
|
@ -35,7 +35,7 @@ export function getConstructorDependencies(
|
|||
} else if (dec.name === 'Host') {
|
||||
host = true;
|
||||
} else if (dec.name === 'Attribute') {
|
||||
if (dec.args.length !== 1) {
|
||||
if (dec.args === null || dec.args.length !== 1) {
|
||||
throw new Error(`Unexpected number of arguments to @Attribute().`);
|
||||
}
|
||||
tokenExpr = dec.args[0];
|
||||
|
@ -46,10 +46,10 @@ export function getConstructorDependencies(
|
|||
});
|
||||
if (tokenExpr === null) {
|
||||
throw new Error(
|
||||
`No suitable token for parameter ${(param.name as ts.Identifier).text} of class ${clazz.name!.text} with decorators ${param.decorators.map(dec => dec.from + '#' + dec.name).join(',')}`);
|
||||
`No suitable token for parameter ${param.name || idx} of class ${clazz.name!.text}`);
|
||||
}
|
||||
if (ts.isIdentifier(tokenExpr)) {
|
||||
const importedSymbol = reflectImportedIdentifier(tokenExpr, checker);
|
||||
const importedSymbol = reflector.getImportOfIdentifier(tokenExpr);
|
||||
if (importedSymbol !== null && importedSymbol.from === '@angular/core') {
|
||||
switch (importedSymbol.name) {
|
||||
case 'ElementRef':
|
||||
|
@ -82,3 +82,7 @@ export function referenceToExpression(ref: Reference, context: ts.SourceFile): E
|
|||
}
|
||||
return exp;
|
||||
}
|
||||
|
||||
export function isAngularCore(decorator: Decorator): boolean {
|
||||
return decorator.import !== null && decorator.import.from === '@angular/core';
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {TypeScriptReflectionHost} from '../../metadata';
|
||||
import {AbsoluteReference, ResolvedReference} from '../../metadata/src/resolver';
|
||||
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
||||
import {NgModuleDecoratorHandler} from '../src/ng_module';
|
||||
|
@ -53,6 +54,7 @@ describe('SelectorScopeRegistry', () => {
|
|||
},
|
||||
]);
|
||||
const checker = program.getTypeChecker();
|
||||
const host = new TypeScriptReflectionHost(checker);
|
||||
const ProgramModule =
|
||||
getDeclaration(program, 'entry.ts', 'ProgramModule', ts.isClassDeclaration);
|
||||
const ProgramCmp = getDeclaration(program, 'entry.ts', 'ProgramCmp', ts.isClassDeclaration);
|
||||
|
@ -61,7 +63,7 @@ describe('SelectorScopeRegistry', () => {
|
|||
expect(ProgramModule).toBeDefined();
|
||||
expect(SomeModule).toBeDefined();
|
||||
|
||||
const registry = new SelectorScopeRegistry(checker);
|
||||
const registry = new SelectorScopeRegistry(checker, host);
|
||||
|
||||
registry.registerModule(ProgramModule, {
|
||||
declarations: [new ResolvedReference(ProgramCmp, ProgramCmp.name !)],
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "host",
|
||||
srcs = glob([
|
||||
"index.ts",
|
||||
"src/**/*.ts",
|
||||
]),
|
||||
module_name = "@angular/compiler-cli/src/ngtsc/host",
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
export * from './src/reflection';
|
|
@ -0,0 +1,223 @@
|
|||
/**
|
||||
* @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';
|
||||
|
||||
/**
|
||||
* Metadata extracted from an instance of a decorator on another declaration.
|
||||
*/
|
||||
export interface Decorator {
|
||||
/**
|
||||
* Name by which the decorator was invoked in the user's code.
|
||||
*
|
||||
* This is distinct from the name by which the decorator was imported (though in practice they
|
||||
* will usually be the same).
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* `Import` by which the decorator was brought into the module in which it was invoked, or `null`
|
||||
* if the decorator was declared in the same module and not imported.
|
||||
*/
|
||||
import : Import | null;
|
||||
|
||||
/**
|
||||
* TypeScript reference to the decorator itself.
|
||||
*/
|
||||
node: ts.Node;
|
||||
|
||||
/**
|
||||
* Arguments of the invocation of the decorator, if the decorator is invoked, or `null` otherwise.
|
||||
*/
|
||||
args: ts.Expression[]|null;
|
||||
}
|
||||
|
||||
/**
|
||||
* An enumeration of possible kinds of class members.
|
||||
*/
|
||||
export enum ClassMemberKind {
|
||||
Constructor,
|
||||
Getter,
|
||||
Setter,
|
||||
Property,
|
||||
Method,
|
||||
}
|
||||
|
||||
/**
|
||||
* A member of a class, such as a property, method, or constructor.
|
||||
*/
|
||||
export interface ClassMember {
|
||||
/**
|
||||
* TypeScript reference to the class member itself.
|
||||
*/
|
||||
node: ts.Node;
|
||||
|
||||
/**
|
||||
* Indication of which type of member this is (property, method, etc).
|
||||
*/
|
||||
kind: ClassMemberKind;
|
||||
|
||||
/**
|
||||
* TypeScript `ts.TypeNode` representing the type of the member, or `null` if not present or
|
||||
* applicable.
|
||||
*/
|
||||
type: ts.TypeNode|null;
|
||||
|
||||
/**
|
||||
* Name of the class member.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* TypeScript `ts.Identifier` representing the name of the member, or `null` if no such node
|
||||
* is present.
|
||||
*
|
||||
* The `nameNode` is useful in writing references to this member that will be correctly source-
|
||||
* mapped back to the original file.
|
||||
*/
|
||||
nameNode: ts.Identifier|null;
|
||||
|
||||
/**
|
||||
* TypeScript `ts.Expression` which initializes this member, if the member is a property, or
|
||||
* `null` otherwise.
|
||||
*/
|
||||
initializer: ts.Expression|null;
|
||||
|
||||
/**
|
||||
* Whether the member is static or not.
|
||||
*/
|
||||
isStatic: boolean;
|
||||
|
||||
/**
|
||||
* Any `Decorator`s which are present on the member, or `null` if none are present.
|
||||
*/
|
||||
decorators: Decorator[]|null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A parameter to a function or constructor.
|
||||
*/
|
||||
export interface Parameter {
|
||||
/**
|
||||
* Name of the parameter, if available.
|
||||
*
|
||||
* Some parameters don't have a simple string name (for example, parameters which are destructured
|
||||
* into multiple variables). In these cases, `name` can be `null`.
|
||||
*/
|
||||
name: string|null;
|
||||
|
||||
/**
|
||||
* TypeScript `ts.BindingName` representing the name of the parameter.
|
||||
*
|
||||
* The `nameNode` is useful in writing references to this member that will be correctly source-
|
||||
* mapped back to the original file.
|
||||
*/
|
||||
nameNode: ts.BindingName;
|
||||
|
||||
/**
|
||||
* TypeScript `ts.Expression` representing the type of the parameter, if the type is a simple
|
||||
* expression type.
|
||||
*
|
||||
* If the type is not present or cannot be represented as an expression, `type` is `null`.
|
||||
*/
|
||||
type: ts.Expression|null;
|
||||
|
||||
/**
|
||||
* Any `Decorator`s which are present on the parameter, or `null` if none are present.
|
||||
*/
|
||||
decorators: Decorator[]|null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The source of an imported symbol, including the original symbol name and the module from which it
|
||||
* was imported.
|
||||
*/
|
||||
export interface Import {
|
||||
/**
|
||||
* The name of the imported symbol under which it was exported (not imported).
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The module from which the symbol was imported.
|
||||
*
|
||||
* This could either be an absolute module name (@angular/core for example) or a relative path.
|
||||
*/
|
||||
from: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstracts reflection operations on a TypeScript AST.
|
||||
*
|
||||
* Depending on the format of the code being interpreted, different concepts are represented with
|
||||
* different syntactical structures. The `ReflectionHost` abstracts over those differences and
|
||||
* presents a single API by which the compiler can query specific information about the AST.
|
||||
*
|
||||
* All operations on the `ReflectionHost` require the use of TypeScript `ts.Node`s with binding
|
||||
* information already available (that is, nodes that come from a `ts.Program` that has been
|
||||
* type-checked, and are not synthetically created).
|
||||
*/
|
||||
export interface ReflectionHost {
|
||||
/**
|
||||
* 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 decorable type.
|
||||
*/
|
||||
getDecoratorsOfDeclaration(declaration: ts.Declaration): Decorator[]|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[];
|
||||
|
||||
/**
|
||||
* 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`.
|
||||
*/
|
||||
getConstructorParameters(declaration: ts.Declaration): Parameter[]|null;
|
||||
|
||||
/**
|
||||
* Determine if an identifier was imported from another module and return `Import` metadata
|
||||
* describing its origin.
|
||||
*
|
||||
* @param id a TypeScript `ts.Identifer` to reflect.
|
||||
*
|
||||
* @returns metadata about the `Import` if the identifier was imported from another module, or
|
||||
* `null` if the identifier doesn't resolve to an import but instead is locally defined.
|
||||
*/
|
||||
getImportOfIdentifier(id: ts.Identifier): Import|null;
|
||||
}
|
|
@ -12,5 +12,6 @@ ts_library(
|
|||
deps = [
|
||||
"//packages:types",
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/host",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -6,6 +6,5 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export {Decorator, Parameter, reflectConstructorParameters, reflectDecorator, reflectNonStaticField, reflectObjectLiteral, reflectStaticField, reflectTypeEntityToDeclaration,} from './src/reflector';
|
||||
|
||||
export {TypeScriptReflectionHost, filterToMembersWithDecorator, findMember, reflectObjectLiteral, reflectTypeEntityToDeclaration} from './src/reflector';
|
||||
export {AbsoluteReference, Reference, ResolvedValue, isDynamicValue, staticallyResolve} from './src/resolver';
|
||||
|
|
|
@ -8,100 +8,43 @@
|
|||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ClassMember, ClassMemberKind, Decorator, Import, Parameter, ReflectionHost} from '../../host';
|
||||
|
||||
/**
|
||||
* reflector.ts implements static reflection of declarations using the TypeScript `ts.TypeChecker`.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A reflected parameter of a function, method, or constructor, indicating the name, any
|
||||
* decorators, and an expression representing a reference to the value side of the parameter's
|
||||
* declared type, if applicable.
|
||||
*/
|
||||
export interface Parameter {
|
||||
/**
|
||||
* Name of the parameter as a `ts.BindingName`, which allows the parameter name to be identified
|
||||
* via sourcemaps.
|
||||
*/
|
||||
name: ts.BindingName;
|
||||
export class TypeScriptReflectionHost implements ReflectionHost {
|
||||
constructor(protected checker: ts.TypeChecker) {}
|
||||
|
||||
/**
|
||||
* A `ts.Expression` which represents a reference to the value side of the parameter's type.
|
||||
*/
|
||||
typeValueExpr: ts.Expression|null;
|
||||
|
||||
/**
|
||||
* Array of decorators present on the parameter.
|
||||
*/
|
||||
decorators: Decorator[];
|
||||
getDecoratorsOfDeclaration(declaration: ts.Declaration): Decorator[]|null {
|
||||
if (declaration.decorators === undefined || declaration.decorators.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return declaration.decorators.map(decorator => this._reflectDecorator(decorator))
|
||||
.filter((dec): dec is Decorator => dec !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* A reflected decorator, indicating the name, where it was imported from, and any arguments if the
|
||||
* decorator is a call expression.
|
||||
*/
|
||||
export interface Decorator {
|
||||
/**
|
||||
* Name of the decorator, extracted from the decoration expression.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Import path (relative to the decorator's file) of the decorator itself.
|
||||
*/
|
||||
from: string;
|
||||
|
||||
/**
|
||||
* The decorator node itself (useful for printing sourcemap based references to the decorator).
|
||||
*/
|
||||
node: ts.Decorator;
|
||||
|
||||
/**
|
||||
* Any arguments of a call expression, if one is present. If the decorator was not a call
|
||||
* expression, then this will be an empty array.
|
||||
*/
|
||||
args: ts.Expression[];
|
||||
getMembersOfClass(declaration: ts.Declaration): ClassMember[] {
|
||||
const clazz = castDeclarationToClassOrDie(declaration);
|
||||
return clazz.members.map(member => this._reflectMember(member))
|
||||
.filter((member): member is ClassMember => member !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflect a `ts.ClassDeclaration` and determine the list of parameters.
|
||||
*
|
||||
* Note that this only reflects the referenced class and not any potential parent class - that must
|
||||
* be handled by the caller.
|
||||
*
|
||||
* @param node the `ts.ClassDeclaration` to reflect
|
||||
* @param checker a `ts.TypeChecker` used for reflection
|
||||
* @returns a `Parameter` instance for each argument of the constructor, or `null` if no constructor
|
||||
*/
|
||||
export function reflectConstructorParameters(
|
||||
node: ts.ClassDeclaration, checker: ts.TypeChecker): Parameter[]|null {
|
||||
// Firstly, look for a constructor.
|
||||
// clang-format off
|
||||
const maybeCtor: ts.ConstructorDeclaration[] = node
|
||||
.members
|
||||
.filter(element => ts.isConstructorDeclaration(element)) as ts.ConstructorDeclaration[];
|
||||
// clang-format on
|
||||
getConstructorParameters(declaration: ts.Declaration): Parameter[]|null {
|
||||
const clazz = castDeclarationToClassOrDie(declaration);
|
||||
|
||||
if (maybeCtor.length !== 1) {
|
||||
// No constructor.
|
||||
// First, find the constructor.
|
||||
const ctor = clazz.members.find(ts.isConstructorDeclaration);
|
||||
if (ctor === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Reflect each parameter.
|
||||
return maybeCtor[0].parameters.map(param => reflectParameter(param, checker));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflect a `ts.ParameterDeclaration` and determine its name, a token which refers to the value
|
||||
* declaration of its type (if possible to statically determine), and its decorators, if any.
|
||||
*/
|
||||
function reflectParameter(node: ts.ParameterDeclaration, checker: ts.TypeChecker): Parameter {
|
||||
return ctor.parameters.map(node => {
|
||||
// The name of the parameter is easy.
|
||||
const name = node.name;
|
||||
const name = parameterName(node.name);
|
||||
|
||||
const decorators = node.decorators &&
|
||||
node.decorators.map(decorator => reflectDecorator(decorator, checker))
|
||||
.filter(decorator => decorator !== null) as Decorator[] ||
|
||||
[];
|
||||
const decorators = this.getDecoratorsOfDeclaration(node);
|
||||
|
||||
// It may or may not be possible to write an expression that refers to the value side of the
|
||||
// type named for the parameter.
|
||||
|
@ -109,104 +52,28 @@ function reflectParameter(node: ts.ParameterDeclaration, checker: ts.TypeChecker
|
|||
|
||||
// It's not possible to get a value expression if the parameter doesn't even have a type.
|
||||
if (node.type !== undefined) {
|
||||
// It's only valid to convert a type reference to a value reference if the type actually has a
|
||||
// It's only valid to convert a type reference to a value reference if the type actually has
|
||||
// a
|
||||
// value declaration associated with it.
|
||||
const type = checker.getTypeFromTypeNode(node.type);
|
||||
const type = this.checker.getTypeFromTypeNode(node.type);
|
||||
if (type.symbol !== undefined && type.symbol.valueDeclaration !== undefined) {
|
||||
// The type points to a valid value declaration. Rewrite the TypeReference into an Expression
|
||||
// The type points to a valid value declaration. Rewrite the TypeReference into an
|
||||
// Expression
|
||||
// which references the value pointed to by the TypeReference, if possible.
|
||||
typeValueExpr = typeNodeToValueExpr(node.type);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name, typeValueExpr, decorators,
|
||||
name,
|
||||
nameNode: node.name,
|
||||
type: typeValueExpr, decorators,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflect a decorator and return a structure describing where it comes from and any arguments.
|
||||
*
|
||||
* Only imported decorators are considered, not locally defined decorators.
|
||||
*/
|
||||
export function reflectDecorator(decorator: ts.Decorator, checker: ts.TypeChecker): Decorator|null {
|
||||
// Attempt to resolve the decorator expression into a reference to a concrete Identifier. The
|
||||
// expression may contain a call to a function which returns the decorator function, in which
|
||||
// case we want to return the arguments.
|
||||
let decoratorOfInterest: ts.Expression = decorator.expression;
|
||||
let args: ts.Expression[] = [];
|
||||
|
||||
// Check for call expressions.
|
||||
if (ts.isCallExpression(decoratorOfInterest)) {
|
||||
args = Array.from(decoratorOfInterest.arguments);
|
||||
decoratorOfInterest = decoratorOfInterest.expression;
|
||||
}
|
||||
|
||||
// The final resolved decorator should be a `ts.Identifier` - if it's not, then something is
|
||||
// wrong and the decorator can't be resolved statically.
|
||||
if (!ts.isIdentifier(decoratorOfInterest)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const importDecl = reflectImportedIdentifier(decoratorOfInterest, checker);
|
||||
if (importDecl === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...importDecl,
|
||||
node: decorator, args,
|
||||
};
|
||||
}
|
||||
|
||||
function typeNodeToValueExpr(node: ts.TypeNode): ts.Expression|null {
|
||||
if (ts.isTypeReferenceNode(node)) {
|
||||
return entityNameToValue(node.typeName);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function entityNameToValue(node: ts.EntityName): ts.Expression|null {
|
||||
if (ts.isQualifiedName(node)) {
|
||||
const left = entityNameToValue(node.left);
|
||||
return left !== null ? ts.createPropertyAccess(left, node.right) : null;
|
||||
} else if (ts.isIdentifier(node)) {
|
||||
return ts.getMutableClone(node);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function propertyNameToValue(node: ts.PropertyName): string|null {
|
||||
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
|
||||
return node.text;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function reflectObjectLiteral(node: ts.ObjectLiteralExpression): Map<string, ts.Expression> {
|
||||
const map = new Map<string, ts.Expression>();
|
||||
node.properties.forEach(prop => {
|
||||
if (ts.isPropertyAssignment(prop)) {
|
||||
const name = propertyNameToValue(prop.name);
|
||||
if (name === null) {
|
||||
return;
|
||||
}
|
||||
map.set(name, prop.initializer);
|
||||
} else if (ts.isShorthandPropertyAssignment(prop)) {
|
||||
map.set(prop.name.text, prop.name);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
export function reflectImportedIdentifier(
|
||||
id: ts.Identifier, checker: ts.TypeChecker): {name: string, from: string}|null {
|
||||
const symbol = checker.getSymbolAtLocation(id);
|
||||
getImportOfIdentifier(id: ts.Identifier): Import|null {
|
||||
const symbol = this.checker.getSymbolAtLocation(id);
|
||||
|
||||
if (symbol === undefined || symbol.declarations === undefined ||
|
||||
symbol.declarations.length !== 1) {
|
||||
|
@ -237,60 +104,91 @@ export function reflectImportedIdentifier(
|
|||
return {from, name};
|
||||
}
|
||||
|
||||
export interface DecoratedNode<T extends ts.Node> {
|
||||
element: T;
|
||||
decorators: Decorator[];
|
||||
isClass(node: ts.Node): node is ts.Declaration { return ts.isClassDeclaration(node); }
|
||||
|
||||
private _reflectDecorator(node: ts.Decorator): Decorator|null {
|
||||
// Attempt to resolve the decorator expression into a reference to a concrete Identifier. The
|
||||
// expression may contain a call to a function which returns the decorator function, in which
|
||||
// case we want to return the arguments.
|
||||
let decoratorExpr: ts.Expression = node.expression;
|
||||
let args: ts.Expression[]|null = null;
|
||||
|
||||
// Check for call expressions.
|
||||
if (ts.isCallExpression(decoratorExpr)) {
|
||||
args = Array.from(decoratorExpr.arguments);
|
||||
decoratorExpr = decoratorExpr.expression;
|
||||
}
|
||||
|
||||
export function getDecoratedClassElements(
|
||||
clazz: ts.ClassDeclaration, checker: ts.TypeChecker): DecoratedNode<ts.ClassElement>[] {
|
||||
const decoratedElements: DecoratedNode<ts.ClassElement>[] = [];
|
||||
clazz.members.forEach(element => {
|
||||
if (element.decorators !== undefined) {
|
||||
const decorators = element.decorators.map(decorator => reflectDecorator(decorator, checker))
|
||||
.filter(decorator => decorator != null) as Decorator[];
|
||||
if (decorators.length > 0) {
|
||||
decoratedElements.push({element, decorators});
|
||||
}
|
||||
}
|
||||
});
|
||||
return decoratedElements;
|
||||
// The final resolved decorator should be a `ts.Identifier` - if it's not, then something is
|
||||
// wrong and the decorator can't be resolved statically.
|
||||
if (!ts.isIdentifier(decoratorExpr)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function reflectStaticField(
|
||||
clazz: ts.ClassDeclaration, field: string): ts.PropertyDeclaration|null {
|
||||
return clazz.members.find((member: ts.ClassElement): member is ts.PropertyDeclaration => {
|
||||
// Check if the name matches.
|
||||
if (member.name === undefined || !ts.isIdentifier(member.name) || member.name.text !== field) {
|
||||
return false;
|
||||
}
|
||||
// Check if the property is static.
|
||||
if (member.modifiers === undefined ||
|
||||
!member.modifiers.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword)) {
|
||||
return false;
|
||||
}
|
||||
// Found the field.
|
||||
return true;
|
||||
}) ||
|
||||
null;
|
||||
const importDecl = this.getImportOfIdentifier(decoratorExpr);
|
||||
|
||||
return {
|
||||
name: decoratorExpr.text,
|
||||
import: importDecl, node, args,
|
||||
};
|
||||
}
|
||||
|
||||
export function reflectNonStaticField(
|
||||
clazz: ts.ClassDeclaration, field: string): ts.PropertyDeclaration|null {
|
||||
return clazz.members.find((member: ts.ClassElement): member is ts.PropertyDeclaration => {
|
||||
// Check if the name matches.
|
||||
if (member.name === undefined || !ts.isIdentifier(member.name) || member.name.text !== field) {
|
||||
return false;
|
||||
private _reflectMember(node: ts.ClassElement): ClassMember|null {
|
||||
let kind: ClassMemberKind|null = null;
|
||||
let initializer: ts.Expression|null = null;
|
||||
let name: string|null = null;
|
||||
let nameNode: ts.Identifier|null = null;
|
||||
|
||||
if (ts.isPropertyDeclaration(node)) {
|
||||
kind = ClassMemberKind.Property;
|
||||
initializer = node.initializer || null;
|
||||
} else if (ts.isGetAccessorDeclaration(node)) {
|
||||
kind = ClassMemberKind.Getter;
|
||||
} else if (ts.isSetAccessorDeclaration(node)) {
|
||||
kind = ClassMemberKind.Setter;
|
||||
} else if (ts.isMethodDeclaration(node)) {
|
||||
kind = ClassMemberKind.Method;
|
||||
} else if (ts.isConstructorDeclaration(node)) {
|
||||
kind = ClassMemberKind.Constructor;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
// Check if the property is static.
|
||||
if (member.modifiers !== undefined &&
|
||||
member.modifiers.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword)) {
|
||||
return false;
|
||||
|
||||
if (ts.isConstructorDeclaration(node)) {
|
||||
name = 'constructor';
|
||||
} else if (ts.isIdentifier(node.name)) {
|
||||
name = node.name.text;
|
||||
nameNode = node.name;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
// Found the field.
|
||||
return true;
|
||||
}) ||
|
||||
null;
|
||||
|
||||
const decorators = this.getDecoratorsOfDeclaration(node);
|
||||
const isStatic = node.modifiers !== undefined &&
|
||||
node.modifiers.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword);
|
||||
|
||||
return {
|
||||
node,
|
||||
kind,
|
||||
type: node.type || null, name, nameNode, decorators, initializer, isStatic,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function reflectNameOfDeclaration(decl: ts.Declaration): string|null {
|
||||
const id = reflectIdentifierOfDeclaration(decl);
|
||||
return id && id.text || null;
|
||||
}
|
||||
|
||||
export function reflectIdentifierOfDeclaration(decl: ts.Declaration): ts.Identifier|null {
|
||||
if (ts.isClassDeclaration(decl) || ts.isFunctionDeclaration(decl)) {
|
||||
return decl.name || null;
|
||||
} else if (ts.isVariableDeclaration(decl)) {
|
||||
if (ts.isIdentifier(decl.name)) {
|
||||
return decl.name;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function reflectTypeEntityToDeclaration(
|
||||
|
@ -336,3 +234,94 @@ export function reflectTypeEntityToDeclaration(
|
|||
return {node, from: null};
|
||||
}
|
||||
}
|
||||
|
||||
export function filterToMembersWithDecorator(members: ClassMember[], name: string, module?: string):
|
||||
{member: ClassMember, decorators: Decorator[]}[] {
|
||||
return members.filter(member => !member.isStatic)
|
||||
.map(member => {
|
||||
if (member.decorators === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const decorators = member.decorators.filter(dec => {
|
||||
if (dec.import !== null) {
|
||||
return dec.import.name === name && (module === undefined || dec.import.from === module);
|
||||
} else {
|
||||
return dec.name === name && module === undefined;
|
||||
}
|
||||
});
|
||||
|
||||
if (decorators.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {member, decorators};
|
||||
})
|
||||
.filter((value): value is {member: ClassMember, decorators: Decorator[]} => value !== null);
|
||||
}
|
||||
|
||||
export function findMember(
|
||||
members: ClassMember[], name: string, isStatic: boolean = false): ClassMember|null {
|
||||
return members.find(member => member.isStatic === isStatic && member.name === name) || null;
|
||||
}
|
||||
|
||||
export function reflectObjectLiteral(node: ts.ObjectLiteralExpression): Map<string, ts.Expression> {
|
||||
const map = new Map<string, ts.Expression>();
|
||||
node.properties.forEach(prop => {
|
||||
if (ts.isPropertyAssignment(prop)) {
|
||||
const name = propertyNameToString(prop.name);
|
||||
if (name === null) {
|
||||
return;
|
||||
}
|
||||
map.set(name, prop.initializer);
|
||||
} else if (ts.isShorthandPropertyAssignment(prop)) {
|
||||
map.set(prop.name.text, prop.name);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
function castDeclarationToClassOrDie(declaration: ts.Declaration): ts.ClassDeclaration {
|
||||
if (!ts.isClassDeclaration(declaration)) {
|
||||
throw new Error(
|
||||
`Reflecting on a ${ts.SyntaxKind[declaration.kind]} instead of a ClassDeclaration.`);
|
||||
}
|
||||
return declaration;
|
||||
}
|
||||
|
||||
function parameterName(name: ts.BindingName): string|null {
|
||||
if (ts.isIdentifier(name)) {
|
||||
return name.text;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function typeNodeToValueExpr(node: ts.TypeNode): ts.Expression|null {
|
||||
if (ts.isTypeReferenceNode(node)) {
|
||||
return entityNameToValue(node.typeName);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function entityNameToValue(node: ts.EntityName): ts.Expression|null {
|
||||
if (ts.isQualifiedName(node)) {
|
||||
const left = entityNameToValue(node.left);
|
||||
return left !== null ? ts.createPropertyAccess(left, node.right) : null;
|
||||
} else if (ts.isIdentifier(node)) {
|
||||
return ts.getMutableClone(node);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function propertyNameToString(node: ts.PropertyName): string|null {
|
||||
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
|
||||
return node.text;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ ts_library(
|
|||
deps = [
|
||||
"//packages:types",
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/host",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/testing",
|
||||
],
|
||||
|
|
|
@ -8,8 +8,9 @@
|
|||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Parameter} from '../../host';
|
||||
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
||||
import {Parameter, reflectConstructorParameters} from '../src/reflector';
|
||||
import {TypeScriptReflectionHost} from '../src/reflector';
|
||||
|
||||
describe('reflector', () => {
|
||||
describe('ctor params', () => {
|
||||
|
@ -26,9 +27,10 @@ describe('reflector', () => {
|
|||
}]);
|
||||
const clazz = getDeclaration(program, 'entry.ts', 'Foo', ts.isClassDeclaration);
|
||||
const checker = program.getTypeChecker();
|
||||
const args = reflectConstructorParameters(clazz, checker) !;
|
||||
const host = new TypeScriptReflectionHost(checker);
|
||||
const args = host.getConstructorParameters(clazz) !;
|
||||
expect(args.length).toBe(1);
|
||||
expectArgument(args[0], 'bar', 'Bar');
|
||||
expectParameter(args[0], 'bar', 'Bar');
|
||||
});
|
||||
|
||||
it('should reflect a decorated argument', () => {
|
||||
|
@ -54,9 +56,10 @@ describe('reflector', () => {
|
|||
]);
|
||||
const clazz = getDeclaration(program, 'entry.ts', 'Foo', ts.isClassDeclaration);
|
||||
const checker = program.getTypeChecker();
|
||||
const args = reflectConstructorParameters(clazz, checker) !;
|
||||
const host = new TypeScriptReflectionHost(checker);
|
||||
const args = host.getConstructorParameters(clazz) !;
|
||||
expect(args.length).toBe(1);
|
||||
expectArgument(args[0], 'bar', 'Bar', 'dec', './dec');
|
||||
expectParameter(args[0], 'bar', 'Bar', 'dec', './dec');
|
||||
});
|
||||
|
||||
it('should reflect a decorated argument with a call', () => {
|
||||
|
@ -82,9 +85,10 @@ describe('reflector', () => {
|
|||
]);
|
||||
const clazz = getDeclaration(program, 'entry.ts', 'Foo', ts.isClassDeclaration);
|
||||
const checker = program.getTypeChecker();
|
||||
const args = reflectConstructorParameters(clazz, checker) !;
|
||||
const host = new TypeScriptReflectionHost(checker);
|
||||
const args = host.getConstructorParameters(clazz) !;
|
||||
expect(args.length).toBe(1);
|
||||
expectArgument(args[0], 'bar', 'Bar', 'dec', './dec');
|
||||
expectParameter(args[0], 'bar', 'Bar', 'dec', './dec');
|
||||
});
|
||||
|
||||
it('should reflect a decorated argument with an indirection', () => {
|
||||
|
@ -109,26 +113,31 @@ describe('reflector', () => {
|
|||
]);
|
||||
const clazz = getDeclaration(program, 'entry.ts', 'Foo', ts.isClassDeclaration);
|
||||
const checker = program.getTypeChecker();
|
||||
const args = reflectConstructorParameters(clazz, checker) !;
|
||||
const host = new TypeScriptReflectionHost(checker);
|
||||
const args = host.getConstructorParameters(clazz) !;
|
||||
expect(args.length).toBe(2);
|
||||
expectArgument(args[0], 'bar', 'Bar');
|
||||
expectArgument(args[1], 'otherBar', 'star.Bar');
|
||||
expectParameter(args[0], 'bar', 'Bar');
|
||||
expectParameter(args[1], 'otherBar', 'star.Bar');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function expectArgument(
|
||||
arg: Parameter, name: string, type?: string, decorator?: string, decoratorFrom?: string): void {
|
||||
expect(argExpressionToString(arg.name)).toEqual(name);
|
||||
function expectParameter(
|
||||
param: Parameter, name: string, type?: string, decorator?: string,
|
||||
decoratorFrom?: string): void {
|
||||
expect(param.name !).toEqual(name);
|
||||
if (type === undefined) {
|
||||
expect(arg.typeValueExpr).toBeNull();
|
||||
expect(param.type).toBeNull();
|
||||
} else {
|
||||
expect(arg.typeValueExpr).not.toBeNull();
|
||||
expect(argExpressionToString(arg.typeValueExpr !)).toEqual(type);
|
||||
expect(param.type).not.toBeNull();
|
||||
expect(argExpressionToString(param.type !)).toEqual(type);
|
||||
}
|
||||
if (decorator !== undefined) {
|
||||
expect(arg.decorators.length).toBeGreaterThan(0);
|
||||
expect(arg.decorators.some(dec => dec.name === decorator && dec.from === decoratorFrom))
|
||||
expect(param.decorators).not.toBeNull();
|
||||
expect(param.decorators !.length).toBeGreaterThan(0);
|
||||
expect(param.decorators !.some(
|
||||
dec => dec.name === decorator && dec.import !== null &&
|
||||
dec.import.from === decoratorFrom))
|
||||
.toBe(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import * as api from '../transformers/api';
|
|||
|
||||
import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, SelectorScopeRegistry} from './annotations';
|
||||
import {CompilerHost} from './compiler_host';
|
||||
import {TypeScriptReflectionHost} from './metadata';
|
||||
import {IvyCompilation, ivyTransformFactory} from './transform';
|
||||
|
||||
export class NgtscProgram implements api.Program {
|
||||
|
@ -90,16 +91,17 @@ export class NgtscProgram implements api.Program {
|
|||
const mergeEmitResultsCallback = opts && opts.mergeEmitResultsCallback || mergeEmitResults;
|
||||
|
||||
const checker = this.tsProgram.getTypeChecker();
|
||||
const scopeRegistry = new SelectorScopeRegistry(checker);
|
||||
const reflector = new TypeScriptReflectionHost(checker);
|
||||
const scopeRegistry = new SelectorScopeRegistry(checker, reflector);
|
||||
|
||||
// Set up the IvyCompilation, which manages state for the Ivy transformer.
|
||||
const handlers = [
|
||||
new ComponentDecoratorHandler(checker, scopeRegistry),
|
||||
new DirectiveDecoratorHandler(checker, scopeRegistry),
|
||||
new InjectableDecoratorHandler(checker),
|
||||
new ComponentDecoratorHandler(checker, reflector, scopeRegistry),
|
||||
new DirectiveDecoratorHandler(checker, reflector, scopeRegistry),
|
||||
new InjectableDecoratorHandler(reflector),
|
||||
new NgModuleDecoratorHandler(checker, scopeRegistry),
|
||||
];
|
||||
const compilation = new IvyCompilation(handlers, checker);
|
||||
const compilation = new IvyCompilation(handlers, checker, reflector);
|
||||
|
||||
// Analyze every source file in the program.
|
||||
this.tsProgram.getSourceFiles()
|
||||
|
|
|
@ -11,6 +11,7 @@ ts_library(
|
|||
module_name = "@angular/compiler-cli/src/ngtsc/transform",
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/host",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/util",
|
||||
],
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import {Expression, Statement, Type} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator} from '../../metadata';
|
||||
import {Decorator} from '../../host';
|
||||
|
||||
/**
|
||||
* Provides the interface between a decorator compiler from @angular/compiler and the Typescript
|
||||
|
@ -31,13 +31,13 @@ export interface DecoratorHandler<A> {
|
|||
* if successful, or an array of diagnostic messages if the analysis fails or the decorator
|
||||
* isn't valid.
|
||||
*/
|
||||
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<A>;
|
||||
analyze(node: ts.Declaration, decorator: Decorator): AnalysisOutput<A>;
|
||||
|
||||
/**
|
||||
* Generate a description of the field which should be added to the class, including any
|
||||
* initialization code to be generated.
|
||||
*/
|
||||
compile(node: ts.ClassDeclaration, analysis: A): CompileResult;
|
||||
compile(node: ts.Declaration, analysis: A): CompileResult;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
import {Expression, Type} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator, reflectDecorator} from '../../metadata';
|
||||
import {Decorator, ReflectionHost} from '../../host';
|
||||
import {reflectNameOfDeclaration} from '../../metadata/src/reflector';
|
||||
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from './api';
|
||||
import {DtsFileTransformer} from './declaration';
|
||||
|
@ -24,7 +25,7 @@ import {ImportManager, translateType} from './translator';
|
|||
interface EmitFieldOperation<T> {
|
||||
adapter: DecoratorHandler<T>;
|
||||
analysis: AnalysisOutput<T>;
|
||||
decorator: ts.Decorator;
|
||||
decorator: Decorator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,29 +39,29 @@ export class IvyCompilation {
|
|||
* Tracks classes which have been analyzed and found to have an Ivy decorator, and the
|
||||
* information recorded about them for later compilation.
|
||||
*/
|
||||
private analysis = new Map<ts.ClassDeclaration, EmitFieldOperation<any>>();
|
||||
private analysis = new Map<ts.Declaration, EmitFieldOperation<any>>();
|
||||
|
||||
/**
|
||||
* Tracks the `DtsFileTransformer`s for each TS file that needs .d.ts transformations.
|
||||
*/
|
||||
private dtsMap = new Map<string, DtsFileTransformer>();
|
||||
|
||||
constructor(private handlers: DecoratorHandler<any>[], private checker: ts.TypeChecker) {}
|
||||
constructor(
|
||||
private handlers: DecoratorHandler<any>[], private checker: ts.TypeChecker,
|
||||
private reflector: ReflectionHost) {}
|
||||
|
||||
/**
|
||||
* Analyze a source file and produce diagnostics for it (if any).
|
||||
*/
|
||||
analyze(sf: ts.SourceFile): ts.Diagnostic[] {
|
||||
const diagnostics: ts.Diagnostic[] = [];
|
||||
const visit = (node: ts.Node) => {
|
||||
// Process nodes recursively, and look for class declarations with decorators.
|
||||
if (ts.isClassDeclaration(node) && node.decorators !== undefined) {
|
||||
// The first step is to reflect the decorators, which will identify decorators
|
||||
// that are imported from another module.
|
||||
const decorators =
|
||||
node.decorators.map(decorator => reflectDecorator(decorator, this.checker))
|
||||
.filter(decorator => decorator !== null) as Decorator[];
|
||||
|
||||
const analyzeClass = (node: ts.Declaration): void => {
|
||||
// The first step is to reflect the decorators.
|
||||
const decorators = this.reflector.getDecoratorsOfDeclaration(node);
|
||||
if (decorators === null) {
|
||||
return;
|
||||
}
|
||||
// Look through the DecoratorHandlers to see if any are relevant.
|
||||
this.handlers.forEach(adapter => {
|
||||
// An adapter is relevant if it matches one of the decorators on the class.
|
||||
|
@ -84,13 +85,17 @@ export class IvyCompilation {
|
|||
if (analysis.analysis !== undefined) {
|
||||
this.analysis.set(node, {
|
||||
adapter,
|
||||
analysis: analysis.analysis,
|
||||
decorator: decorator.node,
|
||||
analysis: analysis.analysis, decorator,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const visit = (node: ts.Node): void => {
|
||||
// Process nodes recursively, and look for class declarations with decorators.
|
||||
if (ts.isClassDeclaration(node)) {
|
||||
analyzeClass(node);
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
|
||||
|
@ -102,9 +107,9 @@ export class IvyCompilation {
|
|||
* Perform a compilation operation on the given class declaration and return instructions to an
|
||||
* AST transformer if any are available.
|
||||
*/
|
||||
compileIvyFieldFor(node: ts.ClassDeclaration): CompileResult|undefined {
|
||||
compileIvyFieldFor(node: ts.Declaration): CompileResult|undefined {
|
||||
// Look to see whether the original node was analyzed. If not, there's nothing to do.
|
||||
const original = ts.getOriginalNode(node) as ts.ClassDeclaration;
|
||||
const original = ts.getOriginalNode(node) as ts.Declaration;
|
||||
if (!this.analysis.has(original)) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -117,7 +122,7 @@ export class IvyCompilation {
|
|||
// which will allow the .d.ts to be transformed later.
|
||||
const fileName = node.getSourceFile().fileName;
|
||||
const dtsTransformer = this.getDtsTransformer(fileName);
|
||||
dtsTransformer.recordStaticField(node.name !.text, res);
|
||||
dtsTransformer.recordStaticField(reflectNameOfDeclaration(node) !, res);
|
||||
|
||||
// Return the instruction to the transformer so the field will be added.
|
||||
return res;
|
||||
|
@ -126,8 +131,8 @@ export class IvyCompilation {
|
|||
/**
|
||||
* Lookup the `ts.Decorator` which triggered transformation of a particular class declaration.
|
||||
*/
|
||||
ivyDecoratorFor(node: ts.ClassDeclaration): ts.Decorator|undefined {
|
||||
const original = ts.getOriginalNode(node) as ts.ClassDeclaration;
|
||||
ivyDecoratorFor(node: ts.Declaration): Decorator|undefined {
|
||||
const original = ts.getOriginalNode(node) as ts.Declaration;
|
||||
if (!this.analysis.has(original)) {
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -46,7 +46,8 @@ class IvyVisitor extends Visitor {
|
|||
node = ts.updateClassDeclaration(
|
||||
node,
|
||||
// Remove the decorator which triggered this compilation, leaving the others alone.
|
||||
maybeFilterDecorator(node.decorators, this.compilation.ivyDecoratorFor(node) !),
|
||||
maybeFilterDecorator(
|
||||
node.decorators, this.compilation.ivyDecoratorFor(node) !.node as ts.Decorator),
|
||||
node.modifiers, node.name, node.typeParameters, node.heritageClauses || [],
|
||||
[...node.members, property]);
|
||||
const statements = res.statements.map(stmt => translateStatement(stmt, this.importManager));
|
||||
|
|
Loading…
Reference in New Issue