refactor(ivy): correctly type class declarations in ngtsc/ngcc (#29209)

Previously, several `ngtsc` and `ngcc` APIs dealing with class
declaration nodes used inconsistent types. For example, some methods of
the `DecoratorHandler` interface expected a `ts.Declaration` argument,
but actual `DecoratorHandler` implementations specified a stricter
`ts.ClassDeclaration` type.

As a result, the stricter methods would operate under the incorrect
assumption that their arguments were of type `ts.ClassDeclaration`,
while the actual arguments might be of different types (e.g. `ngcc`
would call them with `ts.FunctionDeclaration` or
`ts.VariableDeclaration` arguments, when compiling ES5 code).

Additionally, since we need those class declarations to be referenced in
other parts of the program, `ngtsc`/`ngcc` had to either repeatedly
check for `ts.isIdentifier(node.name)` or assume there was a `name`
identifier and use `node.name!`. While this assumption happens to be
true in the current implementation, working around type-checking is
error-prone (e.g. the assumption might stop being true in the future).

This commit fixes this by introducing a new type to be used for such
class declarations (`ts.Declaration & {name: ts.Identifier}`) and using
it consistently throughput the code.

PR Close #29209
This commit is contained in:
George Kalpakas 2019-03-20 12:10:57 +02:00 committed by Miško Hevery
parent 2d859a8c3a
commit bb6a3632f6
36 changed files with 229 additions and 194 deletions

View File

@ -6,8 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import * as ts from 'typescript'; import {ClassDeclaration, Decorator} from '../../../src/ngtsc/reflection';
import {Decorator} from '../../../src/ngtsc/reflection';
/** /**
* A simple container that holds the details of a decorated class that has been * A simple container that holds the details of a decorated class that has been
@ -22,5 +21,5 @@ export class DecoratedClass {
* @param decorators The collection of decorators that have been found on this class. * @param decorators The collection of decorators that have been found on this class.
*/ */
constructor( constructor(
public name: string, public declaration: ts.Declaration, public decorators: Decorator[], ) {} public name: string, public declaration: ClassDeclaration, public decorators: Decorator[]) {}
} }

View File

@ -8,7 +8,7 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {ClassMember, ClassMemberKind, CtorParameter, Decorator, Import, TypeScriptReflectionHost, reflectObjectLiteral} from '../../../src/ngtsc/reflection'; import {ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Decorator, Import, TypeScriptReflectionHost, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
import {BundleProgram} from '../packages/bundle_program'; import {BundleProgram} from '../packages/bundle_program';
import {findAll, getNameText, isDefined} from '../utils'; import {findAll, getNameText, isDefined} from '../utils';
@ -199,15 +199,15 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
* @param node the node whose symbol we are finding. * @param node the node whose symbol we are finding.
* @returns the symbol for the node or `undefined` if it is not a "class" or has no symbol. * @returns the symbol for the node or `undefined` if it is not a "class" or has no symbol.
*/ */
getClassSymbol(declaration: ts.Node): ts.Symbol|undefined { getClassSymbol(declaration: ts.Node): ClassSymbol|undefined {
if (ts.isClassDeclaration(declaration)) { if (ts.isClassDeclaration(declaration)) {
return declaration.name && this.checker.getSymbolAtLocation(declaration.name); return declaration.name && this.checker.getSymbolAtLocation(declaration.name) as ClassSymbol;
} }
if (ts.isVariableDeclaration(declaration) && declaration.initializer) { if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
declaration = declaration.initializer; declaration = declaration.initializer;
} }
if (ts.isClassExpression(declaration)) { if (ts.isClassExpression(declaration)) {
return declaration.name && this.checker.getSymbolAtLocation(declaration.name); return declaration.name && this.checker.getSymbolAtLocation(declaration.name) as ClassSymbol;
} }
return undefined; return undefined;
} }
@ -405,7 +405,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
} }
} }
protected getDecoratedClassFromSymbol(symbol: ts.Symbol|undefined): DecoratedClass|null { protected getDecoratedClassFromSymbol(symbol: ClassSymbol|undefined): DecoratedClass|null {
if (symbol) { if (symbol) {
const decorators = this.getDecoratorsOfSymbol(symbol); const decorators = this.getDecoratorsOfSymbol(symbol);
if (decorators && decorators.length) { if (decorators && decorators.length) {

View File

@ -8,7 +8,7 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {ClassMember, ClassMemberKind, Declaration, Decorator, FunctionDefinition, Parameter, reflectObjectLiteral} from '../../../src/ngtsc/reflection'; import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, Declaration, Decorator, FunctionDefinition, Parameter, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
import {getNameText, hasNameIdentifier} from '../utils'; import {getNameText, hasNameIdentifier} from '../utils';
import {Esm2015ReflectionHost, ParamInfo, getPropertyValueFromSymbol, isAssignmentStatement} from './esm2015_host'; import {Esm2015ReflectionHost, ParamInfo, getPropertyValueFromSymbol, isAssignmentStatement} from './esm2015_host';
@ -36,7 +36,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
/** /**
* Check whether the given node actually represents a class. * Check whether the given node actually represents a class.
*/ */
isClass(node: ts.Node): node is ts.NamedDeclaration { isClass(node: ts.Node): node is ClassDeclaration {
return super.isClass(node) || !!this.getClassSymbol(node); return super.isClass(node) || !!this.getClassSymbol(node);
} }
@ -74,7 +74,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
* expression inside the IIFE. * expression inside the IIFE.
* @returns the symbol for the node or `undefined` if it is not a "class" or has no symbol. * @returns the symbol for the node or `undefined` if it is not a "class" or has no symbol.
*/ */
getClassSymbol(node: ts.Node): ts.Symbol|undefined { getClassSymbol(node: ts.Node): ClassSymbol|undefined {
const symbol = super.getClassSymbol(node); const symbol = super.getClassSymbol(node);
if (symbol) return symbol; if (symbol) return symbol;
@ -85,7 +85,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
const innerClassIdentifier = getReturnIdentifier(iifeBody); const innerClassIdentifier = getReturnIdentifier(iifeBody);
if (!innerClassIdentifier) return undefined; if (!innerClassIdentifier) return undefined;
return this.checker.getSymbolAtLocation(innerClassIdentifier); return this.checker.getSymbolAtLocation(innerClassIdentifier) as ClassSymbol;
} }
const outerClassNode = getClassDeclarationFromInnerFunctionDeclaration(node); const outerClassNode = getClassDeclarationFromInnerFunctionDeclaration(node);

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import * as ts from 'typescript'; import * as ts from 'typescript';
import {ReflectionHost} from '../../../src/ngtsc/reflection'; import {ClassSymbol, ReflectionHost} from '../../../src/ngtsc/reflection';
import {DecoratedClass} from './decorated_class'; import {DecoratedClass} from './decorated_class';
export const PRE_R3_MARKER = '__PRE_R3__'; export const PRE_R3_MARKER = '__PRE_R3__';
@ -52,7 +52,7 @@ export interface NgccReflectionHost extends ReflectionHost {
* @returns the symbol for the declaration or `undefined` if it is not * @returns the symbol for the declaration or `undefined` if it is not
* a "class" or has no symbol. * a "class" or has no symbol.
*/ */
getClassSymbol(node: ts.Node): ts.Symbol|undefined; getClassSymbol(node: ts.Node): ClassSymbol|undefined;
/** /**
* Search the given module for variable declarations in which the initializer * Search the given module for variable declarations in which the initializer

View File

@ -481,7 +481,7 @@ export function renderConstantPool(
export function renderDefinitions( export function renderDefinitions(
sourceFile: ts.SourceFile, compiledClass: CompiledClass, imports: ImportManager): string { sourceFile: ts.SourceFile, compiledClass: CompiledClass, imports: ImportManager): string {
const printer = ts.createPrinter(); const printer = ts.createPrinter();
const name = (compiledClass.declaration as ts.NamedDeclaration).name !; const name = compiledClass.declaration.name;
const translate = (stmt: Statement) => const translate = (stmt: Statement) =>
translateStatement(stmt, imports, NOOP_DEFAULT_IMPORT_RECORDER); translateStatement(stmt, imports, NOOP_DEFAULT_IMPORT_RECORDER);
const definitions = const definitions =

View File

@ -8,7 +8,7 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {ClassMemberKind, Import} from '../../../src/ngtsc/reflection'; import {ClassDeclaration, ClassMemberKind, ClassSymbol, Import} from '../../../src/ngtsc/reflection';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {Esm5ReflectionHost} from '../../src/host/esm5_host'; import {Esm5ReflectionHost} from '../../src/host/esm5_host';
import {getDeclaration, makeTestBundleProgram, makeTestProgram} from '../helpers/utils'; import {getDeclaration, makeTestBundleProgram, makeTestProgram} from '../helpers/utils';
@ -1551,7 +1551,7 @@ describe('Esm5ReflectionHost', () => {
it('should return the class symbol returned by the superclass (if any)', () => { it('should return the class symbol returned by the superclass (if any)', () => {
const mockNode = {} as ts.Node; const mockNode = {} as ts.Node;
const mockSymbol = {} as ts.Symbol; const mockSymbol = {} as ClassSymbol;
superGetClassSymbolSpy.and.returnValue(mockSymbol); superGetClassSymbolSpy.and.returnValue(mockSymbol);
const host = new Esm5ReflectionHost(false, {} as any); const host = new Esm5ReflectionHost(false, {} as any);
@ -1590,7 +1590,7 @@ describe('Esm5ReflectionHost', () => {
const innerNode = (((outerNode.initializer as ts.ParenthesizedExpression) const innerNode = (((outerNode.initializer as ts.ParenthesizedExpression)
.expression as ts.CallExpression) .expression as ts.CallExpression)
.expression as ts.FunctionExpression) .expression as ts.FunctionExpression)
.body.statements.find(ts.isFunctionDeclaration) !; .body.statements.find(ts.isFunctionDeclaration) as ClassDeclaration;
expect(host.getClassSymbol(innerNode)).toBe(host.getClassSymbol(outerNode)); expect(host.getClassSymbol(innerNode)).toBe(host.getClassSymbol(outerNode));
expect(host.getClassSymbol(innerNode) !.valueDeclaration).toBe(innerNode); expect(host.getClassSymbol(innerNode) !.valueDeclaration).toBe(innerNode);

View File

@ -7,10 +7,9 @@
*/ */
import {R3BaseRefMetaData, compileBaseDefFromMetadata} from '@angular/compiler'; import {R3BaseRefMetaData, compileBaseDefFromMetadata} from '@angular/compiler';
import * as ts from 'typescript';
import {PartialEvaluator} from '../../partial_evaluator'; import {PartialEvaluator} from '../../partial_evaluator';
import {ClassMember, Decorator, ReflectionHost} from '../../reflection'; import {ClassDeclaration, ClassMember, Decorator, ReflectionHost} from '../../reflection';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform'; import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
import {isAngularDecorator} from './util'; import {isAngularDecorator} from './util';
@ -33,7 +32,7 @@ export class BaseDefDecoratorHandler implements
readonly precedence = HandlerPrecedence.WEAK; readonly precedence = HandlerPrecedence.WEAK;
detect(node: ts.ClassDeclaration, decorators: Decorator[]|null): detect(node: ClassDeclaration, decorators: Decorator[]|null):
DetectResult<R3BaseRefDecoratorDetection>|undefined { DetectResult<R3BaseRefDecoratorDetection>|undefined {
if (containsNgTopLevelDecorator(decorators, this.isCore)) { if (containsNgTopLevelDecorator(decorators, this.isCore)) {
// If the class is already decorated by @Component or @Directive let that // If the class is already decorated by @Component or @Directive let that
@ -70,7 +69,7 @@ export class BaseDefDecoratorHandler implements
} }
} }
analyze(node: ts.ClassDeclaration, metadata: R3BaseRefDecoratorDetection): analyze(node: ClassDeclaration, metadata: R3BaseRefDecoratorDetection):
AnalysisOutput<R3BaseRefMetaData> { AnalysisOutput<R3BaseRefMetaData> {
const analysis: R3BaseRefMetaData = {}; const analysis: R3BaseRefMetaData = {};
if (metadata.inputs) { if (metadata.inputs) {
@ -114,7 +113,7 @@ export class BaseDefDecoratorHandler implements
return {analysis}; return {analysis};
} }
compile(node: ts.Declaration, analysis: R3BaseRefMetaData): CompileResult[]|CompileResult { compile(node: ClassDeclaration, analysis: R3BaseRefMetaData): CompileResult[]|CompileResult {
const {expression, type} = compileBaseDefFromMetadata(analysis); const {expression, type} = compileBaseDefFromMetadata(analysis);
return { return {

View File

@ -14,7 +14,7 @@ import {CycleAnalyzer} from '../../cycles';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {DefaultImportRecorder, ModuleResolver, Reference, ReferenceEmitter} from '../../imports'; import {DefaultImportRecorder, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
import {EnumValue, PartialEvaluator} from '../../partial_evaluator'; import {EnumValue, PartialEvaluator} from '../../partial_evaluator';
import {Decorator, ReflectionHost, filterToMembersWithDecorator, reflectObjectLiteral} from '../../reflection'; import {ClassDeclaration, Decorator, ReflectionHost, filterToMembersWithDecorator, reflectObjectLiteral} from '../../reflection';
import {LocalModuleScopeRegistry, ScopeDirective, extractDirectiveGuards} from '../../scope'; import {LocalModuleScopeRegistry, ScopeDirective, extractDirectiveGuards} from '../../scope';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform'; import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
import {TypeCheckContext} from '../../typecheck'; import {TypeCheckContext} from '../../typecheck';
@ -60,7 +60,7 @@ export class ComponentDecoratorHandler implements
readonly precedence = HandlerPrecedence.PRIMARY; readonly precedence = HandlerPrecedence.PRIMARY;
detect(node: ts.Declaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined { detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined {
if (!decorators) { if (!decorators) {
return undefined; return undefined;
} }
@ -75,7 +75,7 @@ export class ComponentDecoratorHandler implements
} }
} }
preanalyze(node: ts.ClassDeclaration, decorator: Decorator): Promise<void>|undefined { preanalyze(node: ClassDeclaration, decorator: Decorator): Promise<void>|undefined {
// In preanalyze, resource URLs associated with the component are asynchronously preloaded via // In preanalyze, resource URLs associated with the component are asynchronously preloaded via
// the resourceLoader. This is the only time async operations are allowed for a component. // the resourceLoader. This is the only time async operations are allowed for a component.
// These resources are: // These resources are:
@ -127,7 +127,7 @@ export class ComponentDecoratorHandler implements
} }
} }
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<ComponentHandlerData> { analyze(node: ClassDeclaration, decorator: Decorator): AnalysisOutput<ComponentHandlerData> {
const containingFile = node.getSourceFile().fileName; const containingFile = node.getSourceFile().fileName;
const meta = this._resolveLiteral(decorator); const meta = this._resolveLiteral(decorator);
this.literalCache.delete(decorator); this.literalCache.delete(decorator);
@ -213,7 +213,7 @@ export class ComponentDecoratorHandler implements
const ref = new Reference(node); const ref = new Reference(node);
this.scopeRegistry.registerDirective({ this.scopeRegistry.registerDirective({
ref, ref,
name: node.name !.text, name: node.name.text,
selector: metadata.selector, selector: metadata.selector,
exportAs: metadata.exportAs, exportAs: metadata.exportAs,
inputs: metadata.inputs, inputs: metadata.inputs,
@ -297,7 +297,7 @@ export class ComponentDecoratorHandler implements
return output; return output;
} }
typeCheck(ctx: TypeCheckContext, node: ts.Declaration, meta: ComponentHandlerData): void { typeCheck(ctx: TypeCheckContext, node: ClassDeclaration, meta: ComponentHandlerData): void {
if (!ts.isClassDeclaration(node)) { if (!ts.isClassDeclaration(node)) {
return; return;
} }
@ -312,7 +312,7 @@ export class ComponentDecoratorHandler implements
} }
} }
resolve(node: ts.ClassDeclaration, analysis: ComponentHandlerData): ResolveResult { resolve(node: ClassDeclaration, analysis: ComponentHandlerData): ResolveResult {
const context = node.getSourceFile(); const context = node.getSourceFile();
// Check whether this component was registered with an NgModule. If so, it should be compiled // Check whether this component was registered with an NgModule. If so, it should be compiled
// under that module's compilation scope. // under that module's compilation scope.
@ -345,9 +345,9 @@ export class ComponentDecoratorHandler implements
if (!cycleDetected) { if (!cycleDetected) {
const wrapDirectivesAndPipesInClosure = const wrapDirectivesAndPipesInClosure =
directives.some( directives.some(
dir => isExpressionForwardReference(dir.expression, node.name !, context)) || dir => isExpressionForwardReference(dir.expression, node.name, context)) ||
Array.from(pipes.values()) Array.from(pipes.values())
.some(pipe => isExpressionForwardReference(pipe, node.name !, context)); .some(pipe => isExpressionForwardReference(pipe, node.name, context));
metadata.directives = directives; metadata.directives = directives;
metadata.pipes = pipes; metadata.pipes = pipes;
metadata.wrapDirectivesAndPipesInClosure = wrapDirectivesAndPipesInClosure; metadata.wrapDirectivesAndPipesInClosure = wrapDirectivesAndPipesInClosure;
@ -374,7 +374,7 @@ export class ComponentDecoratorHandler implements
return {}; return {};
} }
compile(node: ts.ClassDeclaration, analysis: ComponentHandlerData, pool: ConstantPool): compile(node: ClassDeclaration, analysis: ComponentHandlerData, pool: ConstantPool):
CompileResult { CompileResult {
const res = compileComponentFromMetadata(analysis.meta, pool, makeBindingParser()); const res = compileComponentFromMetadata(analysis.meta, pool, makeBindingParser());

View File

@ -12,7 +12,7 @@ import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {DefaultImportRecorder, Reference} from '../../imports'; import {DefaultImportRecorder, Reference} from '../../imports';
import {DynamicValue, EnumValue, PartialEvaluator} from '../../partial_evaluator'; import {DynamicValue, EnumValue, PartialEvaluator} from '../../partial_evaluator';
import {ClassMember, ClassMemberKind, Decorator, ReflectionHost, filterToMembersWithDecorator, reflectObjectLiteral} from '../../reflection'; import {ClassDeclaration, ClassMember, ClassMemberKind, Decorator, ReflectionHost, filterToMembersWithDecorator, reflectObjectLiteral} from '../../reflection';
import {LocalModuleScopeRegistry} from '../../scope/src/local'; import {LocalModuleScopeRegistry} from '../../scope/src/local';
import {extractDirectiveGuards} from '../../scope/src/util'; import {extractDirectiveGuards} from '../../scope/src/util';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform'; import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
@ -35,7 +35,7 @@ export class DirectiveDecoratorHandler implements
readonly precedence = HandlerPrecedence.PRIMARY; readonly precedence = HandlerPrecedence.PRIMARY;
detect(node: ts.Declaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined { detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined {
if (!decorators) { if (!decorators) {
return undefined; return undefined;
} }
@ -50,7 +50,7 @@ export class DirectiveDecoratorHandler implements
} }
} }
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<DirectiveHandlerData> { analyze(node: ClassDeclaration, decorator: Decorator): AnalysisOutput<DirectiveHandlerData> {
const directiveResult = extractDirectiveMetadata( const directiveResult = extractDirectiveMetadata(
node, decorator, this.reflector, this.evaluator, this.defaultImportRecorder, this.isCore); node, decorator, this.reflector, this.evaluator, this.defaultImportRecorder, this.isCore);
const analysis = directiveResult && directiveResult.metadata; const analysis = directiveResult && directiveResult.metadata;
@ -61,7 +61,7 @@ export class DirectiveDecoratorHandler implements
const ref = new Reference(node); const ref = new Reference(node);
this.scopeRegistry.registerDirective({ this.scopeRegistry.registerDirective({
ref, ref,
name: node.name !.text, name: node.name.text,
selector: analysis.selector, selector: analysis.selector,
exportAs: analysis.exportAs, exportAs: analysis.exportAs,
inputs: analysis.inputs, inputs: analysis.inputs,
@ -84,7 +84,7 @@ export class DirectiveDecoratorHandler implements
}; };
} }
compile(node: ts.ClassDeclaration, analysis: DirectiveHandlerData, pool: ConstantPool): compile(node: ClassDeclaration, analysis: DirectiveHandlerData, pool: ConstantPool):
CompileResult { CompileResult {
const res = compileDirectiveFromMetadata(analysis.meta, pool, makeBindingParser()); const res = compileDirectiveFromMetadata(analysis.meta, pool, makeBindingParser());
const statements = res.statements; const statements = res.statements;
@ -104,7 +104,7 @@ export class DirectiveDecoratorHandler implements
* Helper function to extract metadata from a `Directive` or `Component`. * Helper function to extract metadata from a `Directive` or `Component`.
*/ */
export function extractDirectiveMetadata( export function extractDirectiveMetadata(
clazz: ts.ClassDeclaration, decorator: Decorator, reflector: ReflectionHost, clazz: ClassDeclaration, decorator: Decorator, reflector: ReflectionHost,
evaluator: PartialEvaluator, defaultImportRecorder: DefaultImportRecorder, isCore: boolean, evaluator: PartialEvaluator, defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
defaultSelector: string | null = null): { defaultSelector: string | null = null): {
decorator: Map<string, ts.Expression>, decorator: Map<string, ts.Expression>,
@ -189,7 +189,7 @@ export function extractDirectiveMetadata(
selector = resolved === '' ? defaultSelector : resolved; selector = resolved === '' ? defaultSelector : resolved;
} }
if (!selector) { if (!selector) {
throw new Error(`Directive ${clazz.name !.text} has no selector, please add it!`); throw new Error(`Directive ${clazz.name.text} has no selector, please add it!`);
} }
const host = extractHostBindings(directive, decoratedElements, evaluator, coreModule); const host = extractHostBindings(directive, decoratedElements, evaluator, coreModule);
@ -217,14 +217,14 @@ export function extractDirectiveMetadata(
// Detect if the component inherits from another class // Detect if the component inherits from another class
const usesInheritance = reflector.hasBaseClass(clazz); const usesInheritance = reflector.hasBaseClass(clazz);
const metadata: R3DirectiveMetadata = { const metadata: R3DirectiveMetadata = {
name: clazz.name !.text, name: clazz.name.text,
deps: getValidConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore), host, deps: getValidConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore), host,
lifecycle: { lifecycle: {
usesOnChanges, usesOnChanges,
}, },
inputs: {...inputsFromMeta, ...inputsFromFields}, inputs: {...inputsFromMeta, ...inputsFromFields},
outputs: {...outputsFromMeta, ...outputsFromFields}, queries, viewQueries, selector, outputs: {...outputsFromMeta, ...outputsFromFields}, queries, viewQueries, selector,
type: new WrappedNodeExpr(clazz.name !), type: new WrappedNodeExpr(clazz.name),
typeArgumentCount: reflector.getGenericArityOfClass(clazz) || 0, typeArgumentCount: reflector.getGenericArityOfClass(clazz) || 0,
typeSourceSpan: null !, usesInheritance, exportAs, providers typeSourceSpan: null !, usesInheritance, exportAs, providers
}; };

View File

@ -11,7 +11,7 @@ import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {DefaultImportRecorder} from '../../imports'; import {DefaultImportRecorder} from '../../imports';
import {Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection'; import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform'; import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
import {generateSetClassMetadataCall} from './metadata'; import {generateSetClassMetadataCall} from './metadata';
@ -33,7 +33,7 @@ export class InjectableDecoratorHandler implements
readonly precedence = HandlerPrecedence.SHARED; readonly precedence = HandlerPrecedence.SHARED;
detect(node: ts.Declaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined { detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined {
if (!decorators) { if (!decorators) {
return undefined; return undefined;
} }
@ -48,7 +48,7 @@ export class InjectableDecoratorHandler implements
} }
} }
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<InjectableHandlerData> { analyze(node: ClassDeclaration, decorator: Decorator): AnalysisOutput<InjectableHandlerData> {
return { return {
analysis: { analysis: {
meta: extractInjectableMetadata( meta: extractInjectableMetadata(
@ -60,7 +60,7 @@ export class InjectableDecoratorHandler implements
}; };
} }
compile(node: ts.ClassDeclaration, analysis: InjectableHandlerData): CompileResult { compile(node: ClassDeclaration, analysis: InjectableHandlerData): CompileResult {
const res = compileIvyInjectable(analysis.meta); const res = compileIvyInjectable(analysis.meta);
const statements = res.statements; const statements = res.statements;
if (analysis.metadataStmt !== null) { if (analysis.metadataStmt !== null) {
@ -81,13 +81,9 @@ export class InjectableDecoratorHandler implements
* A `null` return value indicates this is @Injectable has invalid data. * A `null` return value indicates this is @Injectable has invalid data.
*/ */
function extractInjectableMetadata( function extractInjectableMetadata(
clazz: ts.ClassDeclaration, decorator: Decorator, reflector: ReflectionHost, clazz: ClassDeclaration, decorator: Decorator, reflector: ReflectionHost,
defaultImportRecorder: DefaultImportRecorder, isCore: boolean, defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
strictCtorDeps: boolean): R3InjectableMetadata { strictCtorDeps: boolean): R3InjectableMetadata {
if (clazz.name === undefined) {
throw new FatalDiagnosticError(
ErrorCode.DECORATOR_ON_ANONYMOUS_CLASS, decorator.node, `@Injectable on anonymous class`);
}
const name = clazz.name.text; const name = clazz.name.text;
const type = new WrappedNodeExpr(clazz.name); const type = new WrappedNodeExpr(clazz.name);
const typeArgumentCount = reflector.getGenericArityOfClass(clazz) || 0; const typeArgumentCount = reflector.getGenericArityOfClass(clazz) || 0;

View File

@ -25,7 +25,7 @@ import {valueReferenceToExpression} from './util';
export function generateSetClassMetadataCall( export function generateSetClassMetadataCall(
clazz: ts.Declaration, reflection: ReflectionHost, defaultImportRecorder: DefaultImportRecorder, clazz: ts.Declaration, reflection: ReflectionHost, defaultImportRecorder: DefaultImportRecorder,
isCore: boolean): Statement|null { isCore: boolean): Statement|null {
if (!reflection.isClass(clazz) || clazz.name === undefined || !ts.isIdentifier(clazz.name)) { if (!reflection.isClass(clazz)) {
return null; return null;
} }
const id = ts.updateIdentifier(clazz.name); const id = ts.updateIdentifier(clazz.name);

View File

@ -12,7 +12,7 @@ import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {DefaultImportRecorder, Reference, ReferenceEmitter} from '../../imports'; import {DefaultImportRecorder, Reference, ReferenceEmitter} from '../../imports';
import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator'; import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
import {Decorator, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection'; import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection';
import {NgModuleRouteAnalyzer} from '../../routing'; import {NgModuleRouteAnalyzer} from '../../routing';
import {LocalModuleScopeRegistry} from '../../scope'; import {LocalModuleScopeRegistry} from '../../scope';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform'; import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
@ -26,7 +26,7 @@ export interface NgModuleAnalysis {
ngModuleDef: R3NgModuleMetadata; ngModuleDef: R3NgModuleMetadata;
ngInjectorDef: R3InjectorMetadata; ngInjectorDef: R3InjectorMetadata;
metadataStmt: Statement|null; metadataStmt: Statement|null;
declarations: Reference<ts.Declaration>[]; declarations: Reference<ClassDeclaration>[];
} }
/** /**
@ -44,7 +44,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
readonly precedence = HandlerPrecedence.PRIMARY; readonly precedence = HandlerPrecedence.PRIMARY;
detect(node: ts.Declaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined { detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined {
if (!decorators) { if (!decorators) {
return undefined; return undefined;
} }
@ -59,8 +59,8 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
} }
} }
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<NgModuleAnalysis> { analyze(node: ClassDeclaration, decorator: Decorator): AnalysisOutput<NgModuleAnalysis> {
const name = node.name !.text; const name = node.name.text;
if (decorator.args === null || decorator.args.length > 1) { if (decorator.args === null || decorator.args.length > 1) {
throw new FatalDiagnosticError( throw new FatalDiagnosticError(
ErrorCode.DECORATOR_ARITY_WRONG, decorator.node, ErrorCode.DECORATOR_ARITY_WRONG, decorator.node,
@ -90,20 +90,20 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
]); ]);
// Extract the module declarations, imports, and exports. // Extract the module declarations, imports, and exports.
let declarationRefs: Reference<ts.Declaration>[] = []; let declarationRefs: Reference<ClassDeclaration>[] = [];
if (ngModule.has('declarations')) { if (ngModule.has('declarations')) {
const expr = ngModule.get('declarations') !; const expr = ngModule.get('declarations') !;
const declarationMeta = this.evaluator.evaluate(expr, forwardRefResolver); const declarationMeta = this.evaluator.evaluate(expr, forwardRefResolver);
declarationRefs = this.resolveTypeList(expr, declarationMeta, name, 'declarations'); declarationRefs = this.resolveTypeList(expr, declarationMeta, name, 'declarations');
} }
let importRefs: Reference<ts.Declaration>[] = []; let importRefs: Reference<ClassDeclaration>[] = [];
let rawImports: ts.Expression|null = null; let rawImports: ts.Expression|null = null;
if (ngModule.has('imports')) { if (ngModule.has('imports')) {
rawImports = ngModule.get('imports') !; rawImports = ngModule.get('imports') !;
const importsMeta = this.evaluator.evaluate(rawImports, moduleResolvers); const importsMeta = this.evaluator.evaluate(rawImports, moduleResolvers);
importRefs = this.resolveTypeList(rawImports, importsMeta, name, 'imports'); importRefs = this.resolveTypeList(rawImports, importsMeta, name, 'imports');
} }
let exportRefs: Reference<ts.Declaration>[] = []; let exportRefs: Reference<ClassDeclaration>[] = [];
let rawExports: ts.Expression|null = null; let rawExports: ts.Expression|null = null;
if (ngModule.has('exports')) { if (ngModule.has('exports')) {
rawExports = ngModule.get('exports') !; rawExports = ngModule.get('exports') !;
@ -111,7 +111,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
exportRefs = this.resolveTypeList(rawExports, exportsMeta, name, 'exports'); exportRefs = this.resolveTypeList(rawExports, exportsMeta, name, 'exports');
this.referencesRegistry.add(node, ...exportRefs); this.referencesRegistry.add(node, ...exportRefs);
} }
let bootstrapRefs: Reference<ts.Declaration>[] = []; let bootstrapRefs: Reference<ClassDeclaration>[] = [];
if (ngModule.has('bootstrap')) { if (ngModule.has('bootstrap')) {
const expr = ngModule.get('bootstrap') !; const expr = ngModule.get('bootstrap') !;
const bootstrapMeta = this.evaluator.evaluate(expr, forwardRefResolver); const bootstrapMeta = this.evaluator.evaluate(expr, forwardRefResolver);
@ -146,7 +146,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
exports.some(isForwardReference); exports.some(isForwardReference);
const ngModuleDef: R3NgModuleMetadata = { const ngModuleDef: R3NgModuleMetadata = {
type: new WrappedNodeExpr(node.name !), type: new WrappedNodeExpr(node.name),
bootstrap, bootstrap,
declarations, declarations,
exports, exports,
@ -176,7 +176,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
const ngInjectorDef: R3InjectorMetadata = { const ngInjectorDef: R3InjectorMetadata = {
name, name,
type: new WrappedNodeExpr(node.name !), type: new WrappedNodeExpr(node.name),
deps: getValidConstructorDependencies( deps: getValidConstructorDependencies(
node, this.reflector, this.defaultImportRecorder, this.isCore), node, this.reflector, this.defaultImportRecorder, this.isCore),
providers, providers,
@ -191,11 +191,11 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
metadataStmt: generateSetClassMetadataCall( metadataStmt: generateSetClassMetadataCall(
node, this.reflector, this.defaultImportRecorder, this.isCore), node, this.reflector, this.defaultImportRecorder, this.isCore),
}, },
factorySymbolName: node.name !== undefined ? node.name.text : undefined, factorySymbolName: node.name.text,
}; };
} }
resolve(node: ts.Declaration, analysis: NgModuleAnalysis): ResolveResult { resolve(node: ClassDeclaration, analysis: NgModuleAnalysis): ResolveResult {
const scope = this.scopeRegistry.getScopeOfModule(node); const scope = this.scopeRegistry.getScopeOfModule(node);
const diagnostics = this.scopeRegistry.getDiagnosticsOfModule(node) || undefined; const diagnostics = this.scopeRegistry.getDiagnosticsOfModule(node) || undefined;
if (scope === null || scope.reexports === null) { if (scope === null || scope.reexports === null) {
@ -208,7 +208,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
} }
} }
compile(node: ts.ClassDeclaration, analysis: NgModuleAnalysis): CompileResult[] { compile(node: ClassDeclaration, analysis: NgModuleAnalysis): CompileResult[] {
const ngInjectorDef = compileInjector(analysis.ngInjectorDef); const ngInjectorDef = compileInjector(analysis.ngInjectorDef);
const ngModuleDef = compileNgModule(analysis.ngModuleDef); const ngModuleDef = compileNgModule(analysis.ngModuleDef);
const ngModuleStatements = ngModuleDef.additionalStatements; const ngModuleStatements = ngModuleDef.additionalStatements;
@ -218,8 +218,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
const context = getSourceFile(node); const context = getSourceFile(node);
for (const decl of analysis.declarations) { for (const decl of analysis.declarations) {
if (this.scopeRegistry.getRequiresRemoteScope(decl.node)) { if (this.scopeRegistry.getRequiresRemoteScope(decl.node)) {
const scope = const scope = this.scopeRegistry.getScopeOfModule(ts.getOriginalNode(node) as typeof node);
this.scopeRegistry.getScopeOfModule(ts.getOriginalNode(node) as ts.Declaration);
if (scope === null) { if (scope === null) {
continue; continue;
} }
@ -340,13 +339,19 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
return null; return null;
} }
// Verify that a `ts.Declaration` reference is a `ClassDeclaration` reference.
private isClassDeclarationReference(ref: Reference<ts.Declaration>):
ref is Reference<ClassDeclaration> {
return this.reflector.isClass(ref.node);
}
/** /**
* Compute a list of `Reference`s from a resolved metadata value. * Compute a list of `Reference`s from a resolved metadata value.
*/ */
private resolveTypeList( private resolveTypeList(
expr: ts.Node, resolvedList: ResolvedValue, className: string, expr: ts.Node, resolvedList: ResolvedValue, className: string,
arrayName: string): Reference<ts.Declaration>[] { arrayName: string): Reference<ClassDeclaration>[] {
const refList: Reference<ts.Declaration>[] = []; const refList: Reference<ClassDeclaration>[] = [];
if (!Array.isArray(resolvedList)) { if (!Array.isArray(resolvedList)) {
throw new FatalDiagnosticError( throw new FatalDiagnosticError(
ErrorCode.VALUE_HAS_WRONG_TYPE, expr, ErrorCode.VALUE_HAS_WRONG_TYPE, expr,
@ -364,7 +369,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
// Recurse into nested arrays. // Recurse into nested arrays.
refList.push(...this.resolveTypeList(expr, entry, className, arrayName)); refList.push(...this.resolveTypeList(expr, entry, className, arrayName));
} else if (isDeclarationReference(entry)) { } else if (isDeclarationReference(entry)) {
if (!this.reflector.isClass(entry.node)) { if (!this.isClassDeclarationReference(entry)) {
throw new FatalDiagnosticError( throw new FatalDiagnosticError(
ErrorCode.VALUE_HAS_WRONG_TYPE, entry.node, ErrorCode.VALUE_HAS_WRONG_TYPE, entry.node,
`Value at position ${idx} in the NgModule.${arrayName}s of ${className} is not a class`); `Value at position ${idx} in the NgModule.${arrayName}s of ${className} is not a class`);

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {LiteralExpr, R3PipeMetadata, Statement, WrappedNodeExpr, compilePipeFromMetadata} from '@angular/compiler'; import {R3PipeMetadata, Statement, WrappedNodeExpr, compilePipeFromMetadata} from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {DefaultImportRecorder, Reference} from '../../imports'; import {DefaultImportRecorder, Reference} from '../../imports';
import {PartialEvaluator} from '../../partial_evaluator'; import {PartialEvaluator} from '../../partial_evaluator';
import {Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection'; import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
import {LocalModuleScopeRegistry} from '../../scope/src/local'; import {LocalModuleScopeRegistry} from '../../scope/src/local';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform'; import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
@ -32,7 +32,7 @@ export class PipeDecoratorHandler implements DecoratorHandler<PipeHandlerData, D
readonly precedence = HandlerPrecedence.PRIMARY; readonly precedence = HandlerPrecedence.PRIMARY;
detect(node: ts.Declaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined { detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<Decorator>|undefined {
if (!decorators) { if (!decorators) {
return undefined; return undefined;
} }
@ -47,11 +47,7 @@ export class PipeDecoratorHandler implements DecoratorHandler<PipeHandlerData, D
} }
} }
analyze(clazz: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<PipeHandlerData> { analyze(clazz: ClassDeclaration, decorator: Decorator): AnalysisOutput<PipeHandlerData> {
if (clazz.name === undefined) {
throw new FatalDiagnosticError(
ErrorCode.DECORATOR_ON_ANONYMOUS_CLASS, clazz, `@Pipes must have names`);
}
const name = clazz.name.text; const name = clazz.name.text;
const type = new WrappedNodeExpr(clazz.name); const type = new WrappedNodeExpr(clazz.name);
if (decorator.args === null) { if (decorator.args === null) {
@ -109,7 +105,7 @@ export class PipeDecoratorHandler implements DecoratorHandler<PipeHandlerData, D
}; };
} }
compile(node: ts.ClassDeclaration, analysis: PipeHandlerData): CompileResult { compile(node: ClassDeclaration, analysis: PipeHandlerData): CompileResult {
const res = compilePipeFromMetadata(analysis.meta); const res = compilePipeFromMetadata(analysis.meta);
const statements = res.statements; const statements = res.statements;
if (analysis.metadataStmt !== null) { if (analysis.metadataStmt !== null) {

View File

@ -12,7 +12,7 @@ import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {DefaultImportRecorder, ImportMode, Reference, ReferenceEmitter} from '../../imports'; import {DefaultImportRecorder, ImportMode, Reference, ReferenceEmitter} from '../../imports';
import {ForeignFunctionResolver} from '../../partial_evaluator'; import {ForeignFunctionResolver} from '../../partial_evaluator';
import {ClassMemberKind, CtorParameter, Decorator, Import, ReflectionHost, TypeValueReference} from '../../reflection'; import {ClassDeclaration, CtorParameter, Decorator, Import, ReflectionHost, TypeValueReference} from '../../reflection';
export enum ConstructorDepErrorKind { export enum ConstructorDepErrorKind {
NO_SUITABLE_TOKEN, NO_SUITABLE_TOKEN,
@ -33,7 +33,7 @@ export interface ConstructorDepError {
} }
export function getConstructorDependencies( export function getConstructorDependencies(
clazz: ts.ClassDeclaration, reflector: ReflectionHost, clazz: ClassDeclaration, reflector: ReflectionHost,
defaultImportRecorder: DefaultImportRecorder, isCore: boolean): ConstructorDeps|null { defaultImportRecorder: DefaultImportRecorder, isCore: boolean): ConstructorDeps|null {
const deps: R3DependencyMetadata[] = []; const deps: R3DependencyMetadata[] = [];
const errors: ConstructorDepError[] = []; const errors: ConstructorDepError[] = [];
@ -128,14 +128,14 @@ export function valueReferenceToExpression(
} }
export function getValidConstructorDependencies( export function getValidConstructorDependencies(
clazz: ts.ClassDeclaration, reflector: ReflectionHost, clazz: ClassDeclaration, reflector: ReflectionHost,
defaultImportRecorder: DefaultImportRecorder, isCore: boolean): R3DependencyMetadata[]|null { defaultImportRecorder: DefaultImportRecorder, isCore: boolean): R3DependencyMetadata[]|null {
return validateConstructorDependencies( return validateConstructorDependencies(
clazz, getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore)); clazz, getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore));
} }
export function validateConstructorDependencies( export function validateConstructorDependencies(
clazz: ts.ClassDeclaration, deps: ConstructorDeps | null): R3DependencyMetadata[]|null { clazz: ClassDeclaration, deps: ConstructorDeps | null): R3DependencyMetadata[]|null {
if (deps === null) { if (deps === null) {
return null; return null;
} else if (deps.deps !== null) { } else if (deps.deps !== null) {

View File

@ -15,6 +15,7 @@ import {PartialEvaluator} from '../../partial_evaluator';
import {TypeScriptReflectionHost} from '../../reflection'; import {TypeScriptReflectionHost} from '../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope'; import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript'; import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
import {isNamedClassDeclaration} from '../../util/src/typescript';
import {ResourceLoader} from '../src/api'; import {ResourceLoader} from '../src/api';
import {ComponentDecoratorHandler} from '../src/component'; import {ComponentDecoratorHandler} from '../src/component';
@ -56,7 +57,7 @@ describe('ComponentDecoratorHandler', () => {
const handler = new ComponentDecoratorHandler( const handler = new ComponentDecoratorHandler(
reflectionHost, evaluator, scopeRegistry, false, new NoopResourceLoader(), [''], false, reflectionHost, evaluator, scopeRegistry, false, new NoopResourceLoader(), [''], false,
true, moduleResolver, cycleAnalyzer, refEmitter, NOOP_DEFAULT_IMPORT_RECORDER); true, moduleResolver, cycleAnalyzer, refEmitter, NOOP_DEFAULT_IMPORT_RECORDER);
const TestCmp = getDeclaration(program, 'entry.ts', 'TestCmp', ts.isClassDeclaration); const TestCmp = getDeclaration(program, 'entry.ts', 'TestCmp', isNamedClassDeclaration);
const detected = handler.detect(TestCmp, reflectionHost.getDecoratorsOfDeclaration(TestCmp)); const detected = handler.detect(TestCmp, reflectionHost.getDecoratorsOfDeclaration(TestCmp));
if (detected === undefined) { if (detected === undefined) {
return fail('Failed to recognize @Component'); return fail('Failed to recognize @Component');

View File

@ -13,6 +13,7 @@ import {PartialEvaluator} from '../../partial_evaluator';
import {TypeScriptReflectionHost} from '../../reflection'; import {TypeScriptReflectionHost} from '../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope'; import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript'; import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
import {isNamedClassDeclaration} from '../../util/src/typescript';
import {DirectiveDecoratorHandler} from '../src/directive'; import {DirectiveDecoratorHandler} from '../src/directive';
@ -47,7 +48,7 @@ describe('DirectiveDecoratorHandler', () => {
reflectionHost, evaluator, scopeRegistry, NOOP_DEFAULT_IMPORT_RECORDER, false); reflectionHost, evaluator, scopeRegistry, NOOP_DEFAULT_IMPORT_RECORDER, false);
const analyzeDirective = (dirName: string) => { const analyzeDirective = (dirName: string) => {
const DirNode = getDeclaration(program, 'entry.ts', dirName, ts.isClassDeclaration); const DirNode = getDeclaration(program, 'entry.ts', dirName, isNamedClassDeclaration);
const detected = handler.detect(DirNode, reflectionHost.getDecoratorsOfDeclaration(DirNode)); const detected = handler.detect(DirNode, reflectionHost.getDecoratorsOfDeclaration(DirNode));
if (detected === undefined) { if (detected === undefined) {

View File

@ -15,6 +15,7 @@ import {PartialEvaluator} from '../../partial_evaluator';
import {TypeScriptReflectionHost} from '../../reflection'; import {TypeScriptReflectionHost} from '../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope'; import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript'; import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
import {isNamedClassDeclaration} from '../../util/src/typescript';
import {NgModuleDecoratorHandler} from '../src/ng_module'; import {NgModuleDecoratorHandler} from '../src/ng_module';
import {NoopReferencesRegistry} from '../src/references_registry'; import {NoopReferencesRegistry} from '../src/references_registry';
@ -63,7 +64,7 @@ describe('NgModuleDecoratorHandler', () => {
const handler = new NgModuleDecoratorHandler( const handler = new NgModuleDecoratorHandler(
reflectionHost, evaluator, scopeRegistry, referencesRegistry, false, null, refEmitter, reflectionHost, evaluator, scopeRegistry, referencesRegistry, false, null, refEmitter,
NOOP_DEFAULT_IMPORT_RECORDER); NOOP_DEFAULT_IMPORT_RECORDER);
const TestModule = getDeclaration(program, 'entry.ts', 'TestModule', ts.isClassDeclaration); const TestModule = getDeclaration(program, 'entry.ts', 'TestModule', isNamedClassDeclaration);
const detected = const detected =
handler.detect(TestModule, reflectionHost.getDecoratorsOfDeclaration(TestModule)); handler.detect(TestModule, reflectionHost.getDecoratorsOfDeclaration(TestModule));
if (detected === undefined) { if (detected === undefined) {

View File

@ -11,6 +11,7 @@ ts_library(
"//packages:types", "//packages:types",
"//packages/compiler", "//packages/compiler",
"//packages/compiler-cli/src/ngtsc/path", "//packages/compiler-cli/src/ngtsc/path",
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/util", "//packages/compiler-cli/src/ngtsc/util",
"@npm//@types/node", "@npm//@types/node",
"@npm//typescript", "@npm//typescript",

View File

@ -9,6 +9,7 @@
import {Expression, ExternalExpr} from '@angular/compiler'; import {Expression, ExternalExpr} from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {ClassDeclaration} from '../../reflection';
import {FileToModuleHost, ReferenceEmitStrategy} from './emitter'; import {FileToModuleHost, ReferenceEmitStrategy} from './emitter';
import {ImportMode, Reference} from './references'; import {ImportMode, Reference} from './references';
@ -18,20 +19,16 @@ const CHARS_TO_ESCAPE = /[^a-zA-Z0-9/_]/g;
export class AliasGenerator { export class AliasGenerator {
constructor(private fileToModuleHost: FileToModuleHost) {} constructor(private fileToModuleHost: FileToModuleHost) {}
aliasSymbolName(decl: ts.Declaration, context: ts.SourceFile): string { aliasSymbolName(decl: ClassDeclaration, context: ts.SourceFile): string {
if (!ts.isClassDeclaration(decl)) {
throw new Error(`Attempt to write an alias to something which isn't a class`);
}
// The declared module is used to get the name of the alias. // The declared module is used to get the name of the alias.
const declModule = const declModule =
this.fileToModuleHost.fileNameToModuleName(decl.getSourceFile().fileName, context.fileName); this.fileToModuleHost.fileNameToModuleName(decl.getSourceFile().fileName, context.fileName);
const replaced = declModule.replace(CHARS_TO_ESCAPE, '_').replace(/\//g, '$'); const replaced = declModule.replace(CHARS_TO_ESCAPE, '_').replace(/\//g, '$');
return 'ɵng$' + replaced + '$$' + decl.name !.text; return 'ɵng$' + replaced + '$$' + decl.name.text;
} }
aliasTo(decl: ts.Declaration, via: ts.SourceFile): Expression { aliasTo(decl: ClassDeclaration, via: ts.SourceFile): Expression {
const name = this.aliasSymbolName(decl, via); const name = this.aliasSymbolName(decl, via);
// viaModule is the module it'll actually be imported from. // viaModule is the module it'll actually be imported from.
const moduleName = this.fileToModuleHost.fileNameToModuleName(via.fileName, via.fileName); const moduleName = this.fileToModuleHost.fileNameToModuleName(via.fileName, via.fileName);

View File

@ -42,6 +42,29 @@ export interface Decorator {
args: ts.Expression[]|null; args: ts.Expression[]|null;
} }
/**
* The `ts.Declaration` of a "class".
*
* Classes are represented differently in different code formats:
* - In TS code, they are typically defined using the `class` keyword.
* - In ES2015 code, they are usually defined using the `class` keyword, but they can also be
* variable declarations, which are initialized to a class expression (e.g.
* `let Foo = Foo1 = class Foo {}`).
* - In ES5 code, they are typically defined as variable declarations being assigned the return
* value of an IIFE. The actual "class" is implemented as a constructor function inside the IIFE,
* but the outer variable declaration represents the "class" to the rest of the program.
*
* For `ReflectionHost` purposes, a class declaration should always have a `name` identifier,
* because we need to be able to reference it in other parts of the program.
*/
export type ClassDeclaration<T extends ts.Declaration = ts.Declaration> = T & {name: ts.Identifier};
/**
* The symbol corresponding to a "class" declaration. I.e. a `ts.Symbol` whose `valueDeclaration` is
* a `ClassDeclaration`.
*/
export type ClassSymbol = ts.Symbol & {valueDeclaration: ClassDeclaration};
/** /**
* An enumeration of possible kinds of class members. * An enumeration of possible kinds of class members.
*/ */
@ -452,7 +475,7 @@ export interface ReflectionHost {
/** /**
* Check whether the given node actually represents a class. * Check whether the given node actually represents a class.
*/ */
isClass(node: ts.Node): node is ts.NamedDeclaration; isClass(node: ts.Node): node is ClassDeclaration;
/** /**
* Determines whether the given declaration has a base class. * Determines whether the given declaration has a base class.

View File

@ -8,7 +8,7 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {ClassMember, ClassMemberKind, CtorParameter, Declaration, Decorator, FunctionDefinition, Import, ReflectionHost, TypeValueReference} from './host'; import {ClassDeclaration, ClassMember, ClassMemberKind, CtorParameter, Declaration, Decorator, FunctionDefinition, Import, ReflectionHost} from './host';
import {typeToValue} from './type_to_value'; import {typeToValue} from './type_to_value';
/** /**
@ -133,9 +133,10 @@ export class TypeScriptReflectionHost implements ReflectionHost {
return map; return map;
} }
isClass(node: ts.Node): node is ts.NamedDeclaration { isClass(node: ts.Node): node is ClassDeclaration {
// In TypeScript code, classes are ts.ClassDeclarations. // In TypeScript code, classes are ts.ClassDeclarations.
return ts.isClassDeclaration(node); // (`name` can be undefined in unnamed default exports: `default export class { ... }`)
return ts.isClassDeclaration(node) && (node.name !== undefined) && ts.isIdentifier(node.name);
} }
hasBaseClass(node: ts.Declaration): boolean { hasBaseClass(node: ts.Declaration): boolean {

View File

@ -6,9 +6,8 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import * as ts from 'typescript';
import {Reference} from '../../imports'; import {Reference} from '../../imports';
import {ClassDeclaration} from '../../reflection';
import {TypeCheckableDirectiveMeta} from '../../typecheck'; import {TypeCheckableDirectiveMeta} from '../../typecheck';
/** /**
@ -51,6 +50,6 @@ export interface ScopeDirective extends TypeCheckableDirectiveMeta {
* Metadata for a given pipe within an NgModule's scope. * Metadata for a given pipe within an NgModule's scope.
*/ */
export interface ScopePipe { export interface ScopePipe {
ref: Reference<ts.Declaration>; ref: Reference<ClassDeclaration>;
name: string; name: string;
} }

View File

@ -9,13 +9,13 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {AliasGenerator, Reference} from '../../imports'; import {AliasGenerator, Reference} from '../../imports';
import {ReflectionHost} from '../../reflection'; import {ClassDeclaration, ReflectionHost} from '../../reflection';
import {ExportScope, ScopeDirective, ScopePipe} from './api'; import {ExportScope, ScopeDirective, ScopePipe} from './api';
import {extractDirectiveGuards, extractReferencesFromType, readStringArrayType, readStringMapType, readStringType} from './util'; import {extractDirectiveGuards, extractReferencesFromType, readStringArrayType, readStringMapType, readStringType} from './util';
export interface DtsModuleScopeResolver { export interface DtsModuleScopeResolver {
resolve(ref: Reference<ts.ClassDeclaration>): ExportScope|null; resolve(ref: Reference<ClassDeclaration>): ExportScope|null;
} }
/** /**
@ -29,7 +29,7 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
/** /**
* Cache which holds fully resolved scopes for NgModule classes from .d.ts files. * Cache which holds fully resolved scopes for NgModule classes from .d.ts files.
*/ */
private cache = new Map<ts.ClassDeclaration, ExportScope|null>(); private cache = new Map<ClassDeclaration, ExportScope|null>();
constructor( constructor(
private checker: ts.TypeChecker, private reflector: ReflectionHost, private checker: ts.TypeChecker, private reflector: ReflectionHost,
@ -42,7 +42,7 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
* This operation relies on a `Reference` instead of a direct TypeScrpt node as the `Reference`s * This operation relies on a `Reference` instead of a direct TypeScrpt node as the `Reference`s
* produced depend on how the original NgModule was imported. * produced depend on how the original NgModule was imported.
*/ */
resolve(ref: Reference<ts.ClassDeclaration>): ExportScope|null { resolve(ref: Reference<ClassDeclaration>): ExportScope|null {
const clazz = ref.node; const clazz = ref.node;
const sourceFile = clazz.getSourceFile(); const sourceFile = clazz.getSourceFile();
if (!sourceFile.isDeclarationFile) { if (!sourceFile.isDeclarationFile) {
@ -64,7 +64,7 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
return null; return null;
} }
const declarations = new Set<ts.Declaration>(); const declarations = new Set<ClassDeclaration>();
for (const declRef of meta.declarations) { for (const declRef of meta.declarations) {
declarations.add(declRef.node); declarations.add(declRef.node);
} }
@ -171,7 +171,7 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
/** /**
* Read directive (or component) metadata from a referenced class in a .d.ts file. * Read directive (or component) metadata from a referenced class in a .d.ts file.
*/ */
private readScopeDirectiveFromClassWithDef(ref: Reference<ts.ClassDeclaration>): ScopeDirective private readScopeDirectiveFromClassWithDef(ref: Reference<ClassDeclaration>): ScopeDirective
|null { |null {
const clazz = ref.node; const clazz = ref.node;
const def = this.reflector.getMembersOfClass(clazz).find( const def = this.reflector.getMembersOfClass(clazz).find(
@ -193,7 +193,7 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
return { return {
ref, ref,
name: clazz.name !.text, name: clazz.name.text,
isComponent: def.name === 'ngComponentDef', selector, isComponent: def.name === 'ngComponentDef', selector,
exportAs: readStringArrayType(def.type.typeArguments[2]), exportAs: readStringArrayType(def.type.typeArguments[2]),
inputs: readStringMapType(def.type.typeArguments[3]), inputs: readStringMapType(def.type.typeArguments[3]),
@ -206,7 +206,7 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
/** /**
* Read pipe metadata from a referenced class in a .d.ts file. * Read pipe metadata from a referenced class in a .d.ts file.
*/ */
private readScopePipeFromClassWithDef(ref: Reference<ts.ClassDeclaration>): ScopePipe|null { private readScopePipeFromClassWithDef(ref: Reference<ClassDeclaration>): ScopePipe|null {
const def = this.reflector.getMembersOfClass(ref.node).find( const def = this.reflector.getMembersOfClass(ref.node).find(
field => field.isStatic && field.name === 'ngPipeDef'); field => field.isStatic && field.name === 'ngPipeDef');
if (def === undefined) { if (def === undefined) {
@ -248,7 +248,7 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
* Raw metadata read from the .d.ts info of an ngModuleDef field on a compiled NgModule class. * Raw metadata read from the .d.ts info of an ngModuleDef field on a compiled NgModule class.
*/ */
interface RawDependencyMetadata { interface RawDependencyMetadata {
declarations: Reference<ts.ClassDeclaration>[]; declarations: Reference<ClassDeclaration>[];
imports: Reference<ts.ClassDeclaration>[]; imports: Reference<ClassDeclaration>[];
exports: Reference<ts.ClassDeclaration>[]; exports: Reference<ClassDeclaration>[];
} }

View File

@ -11,15 +11,16 @@ import * as ts from 'typescript';
import {ErrorCode, makeDiagnostic} from '../../diagnostics'; import {ErrorCode, makeDiagnostic} from '../../diagnostics';
import {AliasGenerator, Reexport, Reference, ReferenceEmitter} from '../../imports'; import {AliasGenerator, Reexport, Reference, ReferenceEmitter} from '../../imports';
import {ClassDeclaration} from '../../reflection';
import {identifierOfNode, nodeNameForError} from '../../util/src/typescript'; import {identifierOfNode, nodeNameForError} from '../../util/src/typescript';
import {ExportScope, ScopeData, ScopeDirective, ScopePipe} from './api'; import {ExportScope, ScopeData, ScopeDirective, ScopePipe} from './api';
import {DtsModuleScopeResolver} from './dependency'; import {DtsModuleScopeResolver} from './dependency';
export interface LocalNgModuleData { export interface LocalNgModuleData {
declarations: Reference<ts.Declaration>[]; declarations: Reference<ClassDeclaration>[];
imports: Reference<ts.Declaration>[]; imports: Reference<ClassDeclaration>[];
exports: Reference<ts.Declaration>[]; exports: Reference<ClassDeclaration>[];
} }
export interface LocalModuleScope extends ExportScope { export interface LocalModuleScope extends ExportScope {
@ -57,17 +58,17 @@ export class LocalModuleScopeRegistry {
/** /**
* Metadata for each local NgModule registered. * Metadata for each local NgModule registered.
*/ */
private ngModuleData = new Map<ts.Declaration, LocalNgModuleData>(); private ngModuleData = new Map<ClassDeclaration, LocalNgModuleData>();
/** /**
* Metadata for each local directive registered. * Metadata for each local directive registered.
*/ */
private directiveData = new Map<ts.Declaration, ScopeDirective>(); private directiveData = new Map<ClassDeclaration, ScopeDirective>();
/** /**
* Metadata for each local pipe registered. * Metadata for each local pipe registered.
*/ */
private pipeData = new Map<ts.Declaration, ScopePipe>(); private pipeData = new Map<ClassDeclaration, ScopePipe>();
/** /**
* A map of components from the current compilation unit to the NgModule which declared them. * A map of components from the current compilation unit to the NgModule which declared them.
@ -76,7 +77,7 @@ export class LocalModuleScopeRegistry {
* contain directives. This doesn't cause any problems but isn't useful as there is no concept of * contain directives. This doesn't cause any problems but isn't useful as there is no concept of
* a directive's compilation scope. * a directive's compilation scope.
*/ */
private declarationToModule = new Map<ts.Declaration, ts.Declaration>(); private declarationToModule = new Map<ClassDeclaration, ClassDeclaration>();
/** /**
* A cache of calculated `LocalModuleScope`s for each NgModule declared in the current program. * A cache of calculated `LocalModuleScope`s for each NgModule declared in the current program.
@ -84,7 +85,7 @@ export class LocalModuleScopeRegistry {
* A value of `undefined` indicates the scope was invalid and produced errors (therefore, * A value of `undefined` indicates the scope was invalid and produced errors (therefore,
* diagnostics should exist in the `scopeErrors` map). * diagnostics should exist in the `scopeErrors` map).
*/ */
private cache = new Map<ts.Declaration, LocalModuleScope|undefined>(); private cache = new Map<ClassDeclaration, LocalModuleScope|undefined>();
/** /**
* Tracks whether a given component requires "remote scoping". * Tracks whether a given component requires "remote scoping".
@ -94,12 +95,12 @@ export class LocalModuleScopeRegistry {
* around cyclic import issues). This is not used in calculation of `LocalModuleScope`s, but is * around cyclic import issues). This is not used in calculation of `LocalModuleScope`s, but is
* tracked here for convenience. * tracked here for convenience.
*/ */
private remoteScoping = new Set<ts.Declaration>(); private remoteScoping = new Set<ClassDeclaration>();
/** /**
* Tracks errors accumulated in the processing of scopes for each module declaration. * Tracks errors accumulated in the processing of scopes for each module declaration.
*/ */
private scopeErrors = new Map<ts.Declaration, ts.Diagnostic[]>(); private scopeErrors = new Map<ClassDeclaration, ts.Diagnostic[]>();
constructor( constructor(
private dependencyScopeReader: DtsModuleScopeResolver, private refEmitter: ReferenceEmitter, private dependencyScopeReader: DtsModuleScopeResolver, private refEmitter: ReferenceEmitter,
@ -108,7 +109,7 @@ export class LocalModuleScopeRegistry {
/** /**
* Add an NgModule's data to the registry. * Add an NgModule's data to the registry.
*/ */
registerNgModule(clazz: ts.Declaration, data: LocalNgModuleData): void { registerNgModule(clazz: ClassDeclaration, data: LocalNgModuleData): void {
this.assertCollecting(); this.assertCollecting();
this.ngModuleData.set(clazz, data); this.ngModuleData.set(clazz, data);
for (const decl of data.declarations) { for (const decl of data.declarations) {
@ -126,7 +127,7 @@ export class LocalModuleScopeRegistry {
this.pipeData.set(pipe.ref.node, pipe); this.pipeData.set(pipe.ref.node, pipe);
} }
getScopeForComponent(clazz: ts.ClassDeclaration): LocalModuleScope|null { getScopeForComponent(clazz: ClassDeclaration): LocalModuleScope|null {
if (!this.declarationToModule.has(clazz)) { if (!this.declarationToModule.has(clazz)) {
return null; return null;
} }
@ -141,7 +142,7 @@ export class LocalModuleScopeRegistry {
* `LocalModuleScope` for the given NgModule if one can be produced, and `null` if no scope is * `LocalModuleScope` for the given NgModule if one can be produced, and `null` if no scope is
* available or the scope contains errors. * available or the scope contains errors.
*/ */
getScopeOfModule(clazz: ts.Declaration): LocalModuleScope|null { getScopeOfModule(clazz: ClassDeclaration): LocalModuleScope|null {
const scope = this.getScopeOfModuleInternal(clazz); const scope = this.getScopeOfModuleInternal(clazz);
// Translate undefined -> null. // Translate undefined -> null.
return scope !== undefined ? scope : null; return scope !== undefined ? scope : null;
@ -151,7 +152,7 @@ export class LocalModuleScopeRegistry {
* Retrieves any `ts.Diagnostic`s produced during the calculation of the `LocalModuleScope` for * Retrieves any `ts.Diagnostic`s produced during the calculation of the `LocalModuleScope` for
* the given NgModule, or `null` if no errors were present. * the given NgModule, or `null` if no errors were present.
*/ */
getDiagnosticsOfModule(clazz: ts.Declaration): ts.Diagnostic[]|null { getDiagnosticsOfModule(clazz: ClassDeclaration): ts.Diagnostic[]|null {
// Required to ensure the errors are populated for the given class. If it has been processed // Required to ensure the errors are populated for the given class. If it has been processed
// before, this will be a no-op due to the scope cache. // before, this will be a no-op due to the scope cache.
this.getScopeOfModule(clazz); this.getScopeOfModule(clazz);
@ -167,7 +168,7 @@ export class LocalModuleScopeRegistry {
* Implementation of `getScopeOfModule` which differentiates between no scope being available * Implementation of `getScopeOfModule` which differentiates between no scope being available
* (returns `null`) and a scope being produced with errors (returns `undefined`). * (returns `null`) and a scope being produced with errors (returns `undefined`).
*/ */
private getScopeOfModuleInternal(clazz: ts.Declaration): LocalModuleScope|null|undefined { private getScopeOfModuleInternal(clazz: ClassDeclaration): LocalModuleScope|null|undefined {
// Seal the registry to protect the integrity of the `LocalModuleScope` cache. // Seal the registry to protect the integrity of the `LocalModuleScope` cache.
this.sealed = true; this.sealed = true;
@ -218,8 +219,7 @@ export class LocalModuleScopeRegistry {
for (const decl of ngModule.declarations) { for (const decl of ngModule.declarations) {
if (this.directiveData.has(decl.node)) { if (this.directiveData.has(decl.node)) {
const directive = this.directiveData.get(decl.node) !; const directive = this.directiveData.get(decl.node) !;
compilationDirectives.set( compilationDirectives.set(decl.node, {...directive, ref: decl});
decl.node, {...directive, ref: decl as Reference<ts.ClassDeclaration>});
} else if (this.pipeData.has(decl.node)) { } else if (this.pipeData.has(decl.node)) {
const pipe = this.pipeData.get(decl.node) !; const pipe = this.pipeData.get(decl.node) !;
compilationPipes.set(decl.node, {...pipe, ref: decl}); compilationPipes.set(decl.node, {...pipe, ref: decl});
@ -303,7 +303,7 @@ export class LocalModuleScopeRegistry {
let reexports: Reexport[]|null = null; let reexports: Reexport[]|null = null;
if (this.aliasGenerator !== null) { if (this.aliasGenerator !== null) {
reexports = []; reexports = [];
const addReexport = (ref: Reference<ts.Declaration>) => { const addReexport = (ref: Reference<ClassDeclaration>) => {
if (!declared.has(ref.node) && ref.node.getSourceFile() !== sourceFile) { if (!declared.has(ref.node) && ref.node.getSourceFile() !== sourceFile) {
const exportName = this.aliasGenerator !.aliasSymbolName(ref.node, sourceFile); const exportName = this.aliasGenerator !.aliasSymbolName(ref.node, sourceFile);
if (ref.alias && ref.alias instanceof ExternalExpr) { if (ref.alias && ref.alias instanceof ExternalExpr) {
@ -362,12 +362,14 @@ export class LocalModuleScopeRegistry {
/** /**
* Check whether a component requires remote scoping. * Check whether a component requires remote scoping.
*/ */
getRequiresRemoteScope(node: ts.Declaration): boolean { return this.remoteScoping.has(node); } getRequiresRemoteScope(node: ClassDeclaration): boolean { return this.remoteScoping.has(node); }
/** /**
* Set a component as requiring remote scoping. * Set a component as requiring remote scoping.
*/ */
setComponentAsRequiringRemoteScoping(node: ts.Declaration): void { this.remoteScoping.add(node); } setComponentAsRequiringRemoteScoping(node: ClassDeclaration): void {
this.remoteScoping.add(node);
}
/** /**
* Look up the `ExportScope` of a given `Reference` to an NgModule. * Look up the `ExportScope` of a given `Reference` to an NgModule.
@ -380,8 +382,8 @@ export class LocalModuleScopeRegistry {
* array parameter. * array parameter.
*/ */
private getExportedScope( private getExportedScope(
ref: Reference<ts.Declaration>, diagnostics: ts.Diagnostic[], ownerForErrors: ts.Declaration, ref: Reference<ClassDeclaration>, diagnostics: ts.Diagnostic[],
type: 'import'|'export'): ExportScope|null|undefined { ownerForErrors: ts.Declaration, type: 'import'|'export'): ExportScope|null|undefined {
if (ref.node.getSourceFile().isDeclarationFile) { if (ref.node.getSourceFile().isDeclarationFile) {
// The NgModule is declared in a .d.ts file. Resolve it with the `DependencyScopeReader`. // The NgModule is declared in a .d.ts file. Resolve it with the `DependencyScopeReader`.
if (!ts.isClassDeclaration(ref.node)) { if (!ts.isClassDeclaration(ref.node)) {
@ -394,7 +396,7 @@ export class LocalModuleScopeRegistry {
`Appears in the NgModule.${type}s of ${nodeNameForError(ownerForErrors)}, but could not be resolved to an NgModule`)); `Appears in the NgModule.${type}s of ${nodeNameForError(ownerForErrors)}, but could not be resolved to an NgModule`));
return undefined; return undefined;
} }
return this.dependencyScopeReader.resolve(ref as Reference<ts.ClassDeclaration>); return this.dependencyScopeReader.resolve(ref);
} else { } else {
// The NgModule is declared locally in the current program. Resolve it from the registry. // The NgModule is declared locally in the current program. Resolve it from the registry.
return this.getScopeOfModuleInternal(ref.node); return this.getScopeOfModuleInternal(ref.node);

View File

@ -9,12 +9,12 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {Reference} from '../../imports'; import {Reference} from '../../imports';
import {ClassMemberKind, ReflectionHost, reflectTypeEntityToDeclaration} from '../../reflection'; import {ClassDeclaration, ClassMemberKind, ReflectionHost, reflectTypeEntityToDeclaration} from '../../reflection';
import {nodeDebugInfo} from '../../util/src/typescript'; import {isNamedClassDeclaration, nodeDebugInfo} from '../../util/src/typescript';
export function extractReferencesFromType( export function extractReferencesFromType(
checker: ts.TypeChecker, def: ts.TypeNode, ngModuleImportedFrom: string | null, checker: ts.TypeChecker, def: ts.TypeNode, ngModuleImportedFrom: string | null,
resolutionContext: string): Reference<ts.ClassDeclaration>[] { resolutionContext: string): Reference<ClassDeclaration>[] {
if (!ts.isTupleTypeNode(def)) { if (!ts.isTupleTypeNode(def)) {
return []; return [];
} }
@ -24,8 +24,8 @@ export function extractReferencesFromType(
} }
const type = element.exprName; const type = element.exprName;
const {node, from} = reflectTypeEntityToDeclaration(type, checker); const {node, from} = reflectTypeEntityToDeclaration(type, checker);
if (!ts.isClassDeclaration(node)) { if (!isNamedClassDeclaration(node)) {
throw new Error(`Expected ClassDeclaration: ${nodeDebugInfo(node)}`); throw new Error(`Expected named ClassDeclaration: ${nodeDebugInfo(node)}`);
} }
const specifier = (from !== null && !from.startsWith('.') ? from : ngModuleImportedFrom); const specifier = (from !== null && !from.startsWith('.') ? from : ngModuleImportedFrom);
if (specifier !== null) { if (specifier !== null) {

View File

@ -10,7 +10,7 @@ import {ExternalExpr, ExternalReference} from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {AliasGenerator, FileToModuleHost, Reference} from '../../imports'; import {AliasGenerator, FileToModuleHost, Reference} from '../../imports';
import {TypeScriptReflectionHost} from '../../reflection'; import {ClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
import {makeProgram} from '../../testing/in_memory_typescript'; import {makeProgram} from '../../testing/in_memory_typescript';
import {ExportScope} from '../src/api'; import {ExportScope} from '../src/api';
import {MetadataDtsModuleScopeResolver} from '../src/dependency'; import {MetadataDtsModuleScopeResolver} from '../src/dependency';
@ -42,7 +42,7 @@ export declare type PipeMeta<A, B> = never;
*/ */
function makeTestEnv( function makeTestEnv(
modules: {[module: string]: string}, aliasGenerator: AliasGenerator | null = null): { modules: {[module: string]: string}, aliasGenerator: AliasGenerator | null = null): {
refs: {[name: string]: Reference<ts.ClassDeclaration>}, refs: {[name: string]: Reference<ClassDeclaration>},
resolver: MetadataDtsModuleScopeResolver, resolver: MetadataDtsModuleScopeResolver,
} { } {
// Map the modules object to an array of files for `makeProgram`. // Map the modules object to an array of files for `makeProgram`.
@ -270,13 +270,13 @@ describe('MetadataDtsModuleScopeResolver', () => {
}); });
}); });
function scopeToRefs(scope: ExportScope): Reference<ts.ClassDeclaration>[] { function scopeToRefs(scope: ExportScope): Reference<ClassDeclaration>[] {
const directives = scope.exported.directives.map(dir => dir.ref); const directives = scope.exported.directives.map(dir => dir.ref);
const pipes = scope.exported.pipes.map(pipe => pipe.ref as Reference<ts.ClassDeclaration>); const pipes = scope.exported.pipes.map(pipe => pipe.ref);
return [...directives, ...pipes].sort((a, b) => a.debugName !.localeCompare(b.debugName !)); return [...directives, ...pipes].sort((a, b) => a.debugName !.localeCompare(b.debugName !));
} }
function getAlias(ref: Reference<ts.ClassDeclaration>): ExternalReference|null { function getAlias(ref: Reference<ClassDeclaration>): ExternalReference|null {
if (ref.alias === null) { if (ref.alias === null) {
return null; return null;
} else { } else {

View File

@ -9,16 +9,17 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {Reference, ReferenceEmitter} from '../../imports'; import {Reference, ReferenceEmitter} from '../../imports';
import {ClassDeclaration} from '../../reflection';
import {ScopeData, ScopeDirective, ScopePipe} from '../src/api'; import {ScopeData, ScopeDirective, ScopePipe} from '../src/api';
import {DtsModuleScopeResolver} from '../src/dependency'; import {DtsModuleScopeResolver} from '../src/dependency';
import {LocalModuleScopeRegistry} from '../src/local'; import {LocalModuleScopeRegistry} from '../src/local';
function registerFakeRefs(registry: LocalModuleScopeRegistry): function registerFakeRefs(registry: LocalModuleScopeRegistry):
{[name: string]: Reference<ts.ClassDeclaration>} { {[name: string]: Reference<ClassDeclaration>} {
const get = (target: {}, name: string): Reference<ts.ClassDeclaration> => { const get = (target: {}, name: string): Reference<ClassDeclaration> => {
const sf = ts.createSourceFile( const sf = ts.createSourceFile(
name + '.ts', `export class ${name} {}`, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); name + '.ts', `export class ${name} {}`, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
const clazz = sf.statements[0] as ts.ClassDeclaration; const clazz = sf.statements[0] as unknown as ClassDeclaration;
const ref = new Reference(clazz); const ref = new Reference(clazz);
if (name.startsWith('Dir') || name.startsWith('Cmp')) { if (name.startsWith('Dir') || name.startsWith('Cmp')) {
registry.registerDirective(fakeDirective(ref)); registry.registerDirective(fakeDirective(ref));
@ -136,7 +137,7 @@ describe('LocalModuleScopeRegistry', () => {
}); });
}); });
function fakeDirective(ref: Reference<ts.ClassDeclaration>): ScopeDirective { function fakeDirective(ref: Reference<ClassDeclaration>): ScopeDirective {
const name = ref.debugName !; const name = ref.debugName !;
return { return {
ref, ref,
@ -152,16 +153,16 @@ function fakeDirective(ref: Reference<ts.ClassDeclaration>): ScopeDirective {
}; };
} }
function fakePipe(ref: Reference<ts.ClassDeclaration>): ScopePipe { function fakePipe(ref: Reference<ClassDeclaration>): ScopePipe {
const name = ref.debugName !; const name = ref.debugName !;
return {ref, name}; return {ref, name};
} }
class MockDtsModuleScopeResolver implements DtsModuleScopeResolver { class MockDtsModuleScopeResolver implements DtsModuleScopeResolver {
resolve(ref: Reference<ts.ClassDeclaration>): null { return null; } resolve(ref: Reference<ClassDeclaration>): null { return null; }
} }
function scopeToRefs(scopeData: ScopeData): Reference<ts.Declaration>[] { function scopeToRefs(scopeData: ScopeData): Reference<ClassDeclaration>[] {
const directives = scopeData.directives.map(dir => dir.ref); const directives = scopeData.directives.map(dir => dir.ref);
const pipes = scopeData.pipes.map(pipe => pipe.ref); const pipes = scopeData.pipes.map(pipe => pipe.ref);
return [...directives, ...pipes].sort((a, b) => a.debugName !.localeCompare(b.debugName !)); return [...directives, ...pipes].sort((a, b) => a.debugName !.localeCompare(b.debugName !));

View File

@ -10,7 +10,7 @@ import {ConstantPool, Expression, Statement, Type} from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {Reexport} from '../../imports'; import {Reexport} from '../../imports';
import {Decorator} from '../../reflection'; import {ClassDeclaration, Decorator} from '../../reflection';
import {TypeCheckContext} from '../../typecheck'; import {TypeCheckContext} from '../../typecheck';
export enum HandlerPrecedence { export enum HandlerPrecedence {
@ -58,7 +58,7 @@ export interface DecoratorHandler<A, M> {
* Scan a set of reflected decorators and determine if this handler is responsible for compilation * Scan a set of reflected decorators and determine if this handler is responsible for compilation
* of one of them. * of one of them.
*/ */
detect(node: ts.Declaration, decorators: Decorator[]|null): DetectResult<M>|undefined; detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<M>|undefined;
/** /**
@ -67,14 +67,14 @@ export interface DecoratorHandler<A, M> {
* `preAnalyze` is optional and is not guaranteed to be called through all compilation flows. It * `preAnalyze` is optional and is not guaranteed to be called through all compilation flows. It
* will only be called if asynchronicity is supported in the CompilerHost. * will only be called if asynchronicity is supported in the CompilerHost.
*/ */
preanalyze?(node: ts.Declaration, metadata: M): Promise<void>|undefined; preanalyze?(node: ClassDeclaration, metadata: M): Promise<void>|undefined;
/** /**
* Perform analysis on the decorator/class combination, producing instructions for compilation * Perform analysis on the decorator/class combination, producing instructions for compilation
* if successful, or an array of diagnostic messages if the analysis fails or the decorator * if successful, or an array of diagnostic messages if the analysis fails or the decorator
* isn't valid. * isn't valid.
*/ */
analyze(node: ts.Declaration, metadata: M): AnalysisOutput<A>; analyze(node: ClassDeclaration, metadata: M): AnalysisOutput<A>;
/** /**
* Perform resolution on the given decorator along with the result of analysis. * Perform resolution on the given decorator along with the result of analysis.
@ -83,15 +83,15 @@ export interface DecoratorHandler<A, M> {
* `DecoratorHandler` a chance to leverage information from the whole compilation unit to enhance * `DecoratorHandler` a chance to leverage information from the whole compilation unit to enhance
* the `analysis` before the emit phase. * the `analysis` before the emit phase.
*/ */
resolve?(node: ts.Declaration, analysis: A): ResolveResult; resolve?(node: ClassDeclaration, analysis: A): ResolveResult;
typeCheck?(ctx: TypeCheckContext, node: ts.Declaration, metadata: A): void; typeCheck?(ctx: TypeCheckContext, node: ClassDeclaration, metadata: A): void;
/** /**
* Generate a description of the field which should be added to the class, including any * Generate a description of the field which should be added to the class, including any
* initialization code to be generated. * initialization code to be generated.
*/ */
compile(node: ts.Declaration, analysis: A, constantPool: ConstantPool): CompileResult compile(node: ClassDeclaration, analysis: A, constantPool: ConstantPool): CompileResult
|CompileResult[]; |CompileResult[];
} }

View File

@ -6,14 +6,14 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ConstantPool, ExternalExpr} from '@angular/compiler'; import {ConstantPool} from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {ImportRewriter} from '../../imports'; import {ImportRewriter} from '../../imports';
import {ReflectionHost, reflectNameOfDeclaration} from '../../reflection'; import {ClassDeclaration, ReflectionHost, reflectNameOfDeclaration} from '../../reflection';
import {TypeCheckContext} from '../../typecheck'; import {TypeCheckContext} from '../../typecheck';
import {getSourceFile} from '../../util/src/typescript'; import {getSourceFile, isNamedClassDeclaration} from '../../util/src/typescript';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from './api'; import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from './api';
import {DtsFileTransformer} from './declaration'; import {DtsFileTransformer} from './declaration';
@ -48,7 +48,7 @@ export class IvyCompilation {
* Tracks classes which have been analyzed and found to have an Ivy decorator, and the * Tracks classes which have been analyzed and found to have an Ivy decorator, and the
* information recorded about them for later compilation. * information recorded about them for later compilation.
*/ */
private ivyClasses = new Map<ts.Declaration, IvyClass>(); private ivyClasses = new Map<ClassDeclaration, IvyClass>();
/** /**
* Tracks factory information which needs to be generated. * Tracks factory information which needs to be generated.
@ -84,7 +84,7 @@ export class IvyCompilation {
analyzeAsync(sf: ts.SourceFile): Promise<void>|undefined { return this.analyze(sf, true); } analyzeAsync(sf: ts.SourceFile): Promise<void>|undefined { return this.analyze(sf, true); }
private detectHandlersForClass(node: ts.Declaration): IvyClass|null { private detectHandlersForClass(node: ClassDeclaration): IvyClass|null {
// The first step is to reflect the decorators. // The first step is to reflect the decorators.
const classDecorators = this.reflector.getDecoratorsOfDeclaration(node); const classDecorators = this.reflector.getDecoratorsOfDeclaration(node);
let ivyClass: IvyClass|null = null; let ivyClass: IvyClass|null = null;
@ -169,7 +169,7 @@ export class IvyCompilation {
private analyze(sf: ts.SourceFile, preanalyze: boolean): Promise<void>|undefined { private analyze(sf: ts.SourceFile, preanalyze: boolean): Promise<void>|undefined {
const promises: Promise<void>[] = []; const promises: Promise<void>[] = [];
const analyzeClass = (node: ts.Declaration): void => { const analyzeClass = (node: ClassDeclaration): void => {
const ivyClass = this.detectHandlersForClass(node); const ivyClass = this.detectHandlersForClass(node);
// If the class has no Ivy behavior (or had errors), skip it. // If the class has no Ivy behavior (or had errors), skip it.
@ -227,7 +227,7 @@ export class IvyCompilation {
const visit = (node: ts.Node): void => { const visit = (node: ts.Node): void => {
// Process nodes recursively, and look for class declarations with decorators. // Process nodes recursively, and look for class declarations with decorators.
if (ts.isClassDeclaration(node)) { if (isNamedClassDeclaration(node)) {
analyzeClass(node); analyzeClass(node);
} }
ts.forEachChild(node, visit); ts.forEachChild(node, visit);
@ -291,8 +291,8 @@ export class IvyCompilation {
*/ */
compileIvyFieldFor(node: ts.Declaration, constantPool: ConstantPool): CompileResult[]|undefined { compileIvyFieldFor(node: ts.Declaration, constantPool: ConstantPool): CompileResult[]|undefined {
// Look to see whether the original node was analyzed. If not, there's nothing to do. // Look to see whether the original node was analyzed. If not, there's nothing to do.
const original = ts.getOriginalNode(node) as ts.Declaration; const original = ts.getOriginalNode(node) as typeof node;
if (!this.ivyClasses.has(original)) { if (!isNamedClassDeclaration(original) || !this.ivyClasses.has(original)) {
return undefined; return undefined;
} }
@ -305,7 +305,8 @@ export class IvyCompilation {
continue; continue;
} }
const compileMatchRes = match.handler.compile(node, match.analyzed.analysis, constantPool); const compileMatchRes =
match.handler.compile(node as ClassDeclaration, match.analyzed.analysis, constantPool);
if (!Array.isArray(compileMatchRes)) { if (!Array.isArray(compileMatchRes)) {
res.push(compileMatchRes); res.push(compileMatchRes);
} else { } else {
@ -327,9 +328,9 @@ export class IvyCompilation {
* Lookup the `ts.Decorator` which triggered transformation of a particular class declaration. * Lookup the `ts.Decorator` which triggered transformation of a particular class declaration.
*/ */
ivyDecoratorsFor(node: ts.Declaration): ts.Decorator[] { ivyDecoratorsFor(node: ts.Declaration): ts.Decorator[] {
const original = ts.getOriginalNode(node) as ts.Declaration; const original = ts.getOriginalNode(node) as typeof node;
if (!this.ivyClasses.has(original)) { if (!isNamedClassDeclaration(original) || !this.ivyClasses.has(original)) {
return EMPTY_ARRAY; return EMPTY_ARRAY;
} }
const ivyClass = this.ivyClasses.get(original) !; const ivyClass = this.ivyClasses.get(original) !;

View File

@ -9,6 +9,7 @@ ts_library(
"//packages:types", "//packages:types",
"//packages/compiler", "//packages/compiler",
"//packages/compiler-cli/src/ngtsc/imports", "//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/translator", "//packages/compiler-cli/src/ngtsc/translator",
"//packages/compiler-cli/src/ngtsc/util", "//packages/compiler-cli/src/ngtsc/util",
"@npm//typescript", "@npm//typescript",

View File

@ -7,16 +7,16 @@
*/ */
import {BoundTarget, DirectiveMeta} from '@angular/compiler'; import {BoundTarget, DirectiveMeta} from '@angular/compiler';
import * as ts from 'typescript';
import {Reference} from '../../imports'; import {Reference} from '../../imports';
import {ClassDeclaration} from '../../reflection';
/** /**
* Extension of `DirectiveMeta` that includes additional information required to type-check the * Extension of `DirectiveMeta` that includes additional information required to type-check the
* usage of a particular directive. * usage of a particular directive.
*/ */
export interface TypeCheckableDirectiveMeta extends DirectiveMeta { export interface TypeCheckableDirectiveMeta extends DirectiveMeta {
ref: Reference<ts.ClassDeclaration>; ref: Reference<ClassDeclaration>;
queries: string[]; queries: string[];
ngTemplateGuards: string[]; ngTemplateGuards: string[];
hasNgTemplateContextGuard: boolean; hasNgTemplateContextGuard: boolean;

View File

@ -10,6 +10,7 @@ import {BoundTarget} from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {NoopImportRewriter, ReferenceEmitter} from '../../imports'; import {NoopImportRewriter, ReferenceEmitter} from '../../imports';
import {ClassDeclaration} from '../../reflection';
import {ImportManager} from '../../translator'; import {ImportManager} from '../../translator';
import {TypeCheckBlockMetadata, TypeCheckableDirectiveMeta, TypeCtorMetadata} from './api'; import {TypeCheckBlockMetadata, TypeCheckableDirectiveMeta, TypeCtorMetadata} from './api';
@ -42,16 +43,12 @@ export class TypeCheckContext {
* @param template AST nodes of the template being recorded. * @param template AST nodes of the template being recorded.
* @param matcher `SelectorMatcher` which tracks directives that are in scope for this template. * @param matcher `SelectorMatcher` which tracks directives that are in scope for this template.
*/ */
addTemplate(node: ts.ClassDeclaration, boundTarget: BoundTarget<TypeCheckableDirectiveMeta>): addTemplate(
void { node: ClassDeclaration<ts.ClassDeclaration>,
// Only write TCBs for named classes. boundTarget: BoundTarget<TypeCheckableDirectiveMeta>): void {
if (node.name === undefined) {
throw new Error(`Assertion: class must be named`);
}
// Get all of the directives used in the template and record type constructors for all of them. // Get all of the directives used in the template and record type constructors for all of them.
boundTarget.getUsedDirectives().forEach(dir => { boundTarget.getUsedDirectives().forEach(dir => {
const dirNode = dir.ref.node; const dirNode = dir.ref.node as ClassDeclaration<ts.ClassDeclaration>;
// Add a type constructor operation for the directive. // Add a type constructor operation for the directive.
this.addTypeCtor(dirNode.getSourceFile(), dirNode, { this.addTypeCtor(dirNode.getSourceFile(), dirNode, {
fnName: 'ngTypeCtor', fnName: 'ngTypeCtor',
@ -77,7 +74,9 @@ export class TypeCheckContext {
/** /**
* Record a type constructor for the given `node` with the given `ctorMetadata`. * Record a type constructor for the given `node` with the given `ctorMetadata`.
*/ */
addTypeCtor(sf: ts.SourceFile, node: ts.ClassDeclaration, ctorMeta: TypeCtorMetadata): void { addTypeCtor(
sf: ts.SourceFile, node: ClassDeclaration<ts.ClassDeclaration>,
ctorMeta: TypeCtorMetadata): void {
// Lazily construct the operation map. // Lazily construct the operation map.
if (!this.opMap.has(sf)) { if (!this.opMap.has(sf)) {
this.opMap.set(sf, []); this.opMap.set(sf, []);
@ -136,7 +135,8 @@ export class TypeCheckContext {
} }
private addTypeCheckBlock( private addTypeCheckBlock(
sf: ts.SourceFile, node: ts.ClassDeclaration, tcbMeta: TypeCheckBlockMetadata): void { sf: ts.SourceFile, node: ClassDeclaration<ts.ClassDeclaration>,
tcbMeta: TypeCheckBlockMetadata): void {
if (!this.opMap.has(sf)) { if (!this.opMap.has(sf)) {
this.opMap.set(sf, []); this.opMap.set(sf, []);
} }
@ -152,7 +152,7 @@ interface Op {
/** /**
* The node in the file which will have code generated for it. * The node in the file which will have code generated for it.
*/ */
readonly node: ts.ClassDeclaration; readonly node: ClassDeclaration<ts.ClassDeclaration>;
/** /**
* Index into the source text where the code generated by the operation should be inserted. * Index into the source text where the code generated by the operation should be inserted.
@ -170,7 +170,9 @@ interface Op {
* A type check block operation which produces type check code for a particular component. * A type check block operation which produces type check code for a particular component.
*/ */
class TcbOp implements Op { class TcbOp implements Op {
constructor(readonly node: ts.ClassDeclaration, readonly meta: TypeCheckBlockMetadata) {} constructor(
readonly node: ClassDeclaration<ts.ClassDeclaration>, readonly meta: TypeCheckBlockMetadata) {
}
/** /**
* Type check blocks are inserted immediately after the end of the component class. * Type check blocks are inserted immediately after the end of the component class.
@ -188,7 +190,8 @@ class TcbOp implements Op {
* A type constructor operation which produces type constructor code for a particular directive. * A type constructor operation which produces type constructor code for a particular directive.
*/ */
class TypeCtorOp implements Op { class TypeCtorOp implements Op {
constructor(readonly node: ts.ClassDeclaration, readonly meta: TypeCtorMetadata) {} constructor(
readonly node: ClassDeclaration<ts.ClassDeclaration>, readonly meta: TypeCtorMetadata) {}
/** /**
* Type constructor operations are inserted immediately before the end of the directive class. * Type constructor operations are inserted immediately before the end of the directive class.

View File

@ -10,6 +10,7 @@ import {AST, BindingType, BoundTarget, ImplicitReceiver, PropertyRead, TmplAstBo
import * as ts from 'typescript'; import * as ts from 'typescript';
import {NOOP_DEFAULT_IMPORT_RECORDER, Reference, ReferenceEmitter} from '../../imports'; import {NOOP_DEFAULT_IMPORT_RECORDER, Reference, ReferenceEmitter} from '../../imports';
import {ClassDeclaration} from '../../reflection';
import {ImportManager, translateExpression} from '../../translator'; import {ImportManager, translateExpression} from '../../translator';
import {TypeCheckBlockMetadata, TypeCheckableDirectiveMeta} from './api'; import {TypeCheckBlockMetadata, TypeCheckableDirectiveMeta} from './api';
@ -29,8 +30,8 @@ import {astToTypescript} from './expression';
* @param importManager an `ImportManager` for the file into which the TCB will be written. * @param importManager an `ImportManager` for the file into which the TCB will be written.
*/ */
export function generateTypeCheckBlock( export function generateTypeCheckBlock(
node: ts.ClassDeclaration, meta: TypeCheckBlockMetadata, importManager: ImportManager, node: ClassDeclaration<ts.ClassDeclaration>, meta: TypeCheckBlockMetadata,
refEmitter: ReferenceEmitter): ts.FunctionDeclaration { importManager: ImportManager, refEmitter: ReferenceEmitter): ts.FunctionDeclaration {
const tcb = new Context(meta.boundTarget, node.getSourceFile(), importManager, refEmitter); const tcb = new Context(meta.boundTarget, node.getSourceFile(), importManager, refEmitter);
const scope = new Scope(tcb); const scope = new Scope(tcb);
tcbProcessNodes(meta.boundTarget.target.template !, tcb, scope); tcbProcessNodes(meta.boundTarget.target.template !, tcb, scope);

View File

@ -8,6 +8,7 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {ClassDeclaration} from '../../reflection';
import {TypeCtorMetadata} from './api'; import {TypeCtorMetadata} from './api';
/** /**
@ -29,19 +30,20 @@ import {TypeCtorMetadata} from './api';
* *
* NgForOf.ngTypeCtor(init: {ngForOf: ['foo', 'bar']}); // Infers a type of NgForOf<string>. * NgForOf.ngTypeCtor(init: {ngForOf: ['foo', 'bar']}); // Infers a type of NgForOf<string>.
* *
* @param node the `ts.ClassDeclaration` for which a type constructor will be generated. * @param node the `ClassDeclaration<ts.ClassDeclaration>` for which a type constructor will be
* generated.
* @param meta additional metadata required to generate the type constructor. * @param meta additional metadata required to generate the type constructor.
* @returns a `ts.MethodDeclaration` for the type constructor. * @returns a `ts.MethodDeclaration` for the type constructor.
*/ */
export function generateTypeCtor( export function generateTypeCtor(
node: ts.ClassDeclaration, meta: TypeCtorMetadata): ts.MethodDeclaration { node: ClassDeclaration<ts.ClassDeclaration>, meta: TypeCtorMetadata): ts.MethodDeclaration {
// Build rawType, a `ts.TypeNode` of the class with its generic parameters passed through from // Build rawType, a `ts.TypeNode` of the class with its generic parameters passed through from
// the definition without any type bounds. For example, if the class is // the definition without any type bounds. For example, if the class is
// `FooDirective<T extends Bar>`, its rawType would be `FooDirective<T>`. // `FooDirective<T extends Bar>`, its rawType would be `FooDirective<T>`.
const rawTypeArgs = node.typeParameters !== undefined ? const rawTypeArgs = node.typeParameters !== undefined ?
node.typeParameters.map(param => ts.createTypeReferenceNode(param.name, undefined)) : node.typeParameters.map(param => ts.createTypeReferenceNode(param.name, undefined)) :
undefined; undefined;
const rawType: ts.TypeNode = ts.createTypeReferenceNode(node.name !, rawTypeArgs); const rawType: ts.TypeNode = ts.createTypeReferenceNode(node.name, rawTypeArgs);
// initType is the type of 'init', the single argument to the type constructor method. // initType is the type of 'init', the single argument to the type constructor method.
// If the Directive has any inputs, outputs, or queries, its initType will be: // If the Directive has any inputs, outputs, or queries, its initType will be:

View File

@ -11,7 +11,7 @@ import * as ts from 'typescript';
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ReferenceEmitter} from '../../imports'; import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ReferenceEmitter} from '../../imports';
import {LogicalFileSystem} from '../../path'; import {LogicalFileSystem} from '../../path';
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript'; import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
import {getRootDirs} from '../../util/src/typescript'; import {getRootDirs, isNamedClassDeclaration} from '../../util/src/typescript';
import {TypeCheckContext} from '../src/context'; import {TypeCheckContext} from '../src/context';
import {TypeCheckProgramHost} from '../src/host'; import {TypeCheckProgramHost} from '../src/host';
@ -47,7 +47,7 @@ TestClass.ngTypeCtor({value: 'test'});
new LogicalProjectStrategy(checker, logicalFs), new LogicalProjectStrategy(checker, logicalFs),
]); ]);
const ctx = new TypeCheckContext(emitter); const ctx = new TypeCheckContext(emitter);
const TestClass = getDeclaration(program, 'main.ts', 'TestClass', ts.isClassDeclaration); const TestClass = getDeclaration(program, 'main.ts', 'TestClass', isNamedClassDeclaration);
ctx.addTypeCtor(program.getSourceFile('main.ts') !, TestClass, { ctx.addTypeCtor(program.getSourceFile('main.ts') !, TestClass, {
fnName: 'ngTypeCtor', fnName: 'ngTypeCtor',
body: true, body: true,

View File

@ -60,6 +60,11 @@ export function isDeclaration(node: ts.Node): node is ts.Declaration {
ts.isFunctionDeclaration(node) || ts.isVariableDeclaration(node); ts.isFunctionDeclaration(node) || ts.isVariableDeclaration(node);
} }
export function isNamedClassDeclaration(node: ts.Node): node is ts.ClassDeclaration&
{name: ts.Identifier} {
return ts.isClassDeclaration(node) && (node.name !== undefined);
}
export function isExported(node: ts.Declaration): boolean { export function isExported(node: ts.Declaration): boolean {
let topLevel: ts.Node = node; let topLevel: ts.Node = node;
if (ts.isVariableDeclaration(node) && ts.isVariableDeclarationList(node.parent)) { if (ts.isVariableDeclaration(node) && ts.isVariableDeclarationList(node.parent)) {