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
*/
import * as ts from 'typescript';
import {Decorator} from '../../../src/ngtsc/reflection';
import {ClassDeclaration, Decorator} from '../../../src/ngtsc/reflection';
/**
* 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.
*/
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 {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 {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.
* @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)) {
return declaration.name && this.checker.getSymbolAtLocation(declaration.name);
return declaration.name && this.checker.getSymbolAtLocation(declaration.name) as ClassSymbol;
}
if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
declaration = declaration.initializer;
}
if (ts.isClassExpression(declaration)) {
return declaration.name && this.checker.getSymbolAtLocation(declaration.name);
return declaration.name && this.checker.getSymbolAtLocation(declaration.name) as ClassSymbol;
}
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) {
const decorators = this.getDecoratorsOfSymbol(symbol);
if (decorators && decorators.length) {

View File

@ -8,7 +8,7 @@
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 {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.
*/
isClass(node: ts.Node): node is ts.NamedDeclaration {
isClass(node: ts.Node): node is ClassDeclaration {
return super.isClass(node) || !!this.getClassSymbol(node);
}
@ -74,7 +74,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
* expression inside the IIFE.
* @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);
if (symbol) return symbol;
@ -85,7 +85,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
const innerClassIdentifier = getReturnIdentifier(iifeBody);
if (!innerClassIdentifier) return undefined;
return this.checker.getSymbolAtLocation(innerClassIdentifier);
return this.checker.getSymbolAtLocation(innerClassIdentifier) as ClassSymbol;
}
const outerClassNode = getClassDeclarationFromInnerFunctionDeclaration(node);

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {ReflectionHost} from '../../../src/ngtsc/reflection';
import {ClassSymbol, ReflectionHost} from '../../../src/ngtsc/reflection';
import {DecoratedClass} from './decorated_class';
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
* 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

View File

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

View File

@ -8,7 +8,7 @@
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 {Esm5ReflectionHost} from '../../src/host/esm5_host';
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)', () => {
const mockNode = {} as ts.Node;
const mockSymbol = {} as ts.Symbol;
const mockSymbol = {} as ClassSymbol;
superGetClassSymbolSpy.and.returnValue(mockSymbol);
const host = new Esm5ReflectionHost(false, {} as any);
@ -1590,7 +1590,7 @@ describe('Esm5ReflectionHost', () => {
const innerNode = (((outerNode.initializer as ts.ParenthesizedExpression)
.expression as ts.CallExpression)
.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) !.valueDeclaration).toBe(innerNode);

View File

@ -7,10 +7,9 @@
*/
import {R3BaseRefMetaData, compileBaseDefFromMetadata} from '@angular/compiler';
import * as ts from 'typescript';
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 {isAngularDecorator} from './util';
@ -33,7 +32,7 @@ export class BaseDefDecoratorHandler implements
readonly precedence = HandlerPrecedence.WEAK;
detect(node: ts.ClassDeclaration, decorators: Decorator[]|null):
detect(node: ClassDeclaration, decorators: Decorator[]|null):
DetectResult<R3BaseRefDecoratorDetection>|undefined {
if (containsNgTopLevelDecorator(decorators, this.isCore)) {
// 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> {
const analysis: R3BaseRefMetaData = {};
if (metadata.inputs) {
@ -114,7 +113,7 @@ export class BaseDefDecoratorHandler implements
return {analysis};
}
compile(node: ts.Declaration, analysis: R3BaseRefMetaData): CompileResult[]|CompileResult {
compile(node: ClassDeclaration, analysis: R3BaseRefMetaData): CompileResult[]|CompileResult {
const {expression, type} = compileBaseDefFromMetadata(analysis);
return {

View File

@ -14,7 +14,7 @@ import {CycleAnalyzer} from '../../cycles';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {DefaultImportRecorder, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
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 {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
import {TypeCheckContext} from '../../typecheck';
@ -60,7 +60,7 @@ export class ComponentDecoratorHandler implements
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) {
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
// the resourceLoader. This is the only time async operations are allowed for a component.
// 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 meta = this._resolveLiteral(decorator);
this.literalCache.delete(decorator);
@ -213,7 +213,7 @@ export class ComponentDecoratorHandler implements
const ref = new Reference(node);
this.scopeRegistry.registerDirective({
ref,
name: node.name !.text,
name: node.name.text,
selector: metadata.selector,
exportAs: metadata.exportAs,
inputs: metadata.inputs,
@ -297,7 +297,7 @@ export class ComponentDecoratorHandler implements
return output;
}
typeCheck(ctx: TypeCheckContext, node: ts.Declaration, meta: ComponentHandlerData): void {
typeCheck(ctx: TypeCheckContext, node: ClassDeclaration, meta: ComponentHandlerData): void {
if (!ts.isClassDeclaration(node)) {
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();
// Check whether this component was registered with an NgModule. If so, it should be compiled
// under that module's compilation scope.
@ -345,9 +345,9 @@ export class ComponentDecoratorHandler implements
if (!cycleDetected) {
const wrapDirectivesAndPipesInClosure =
directives.some(
dir => isExpressionForwardReference(dir.expression, node.name !, context)) ||
dir => isExpressionForwardReference(dir.expression, node.name, context)) ||
Array.from(pipes.values())
.some(pipe => isExpressionForwardReference(pipe, node.name !, context));
.some(pipe => isExpressionForwardReference(pipe, node.name, context));
metadata.directives = directives;
metadata.pipes = pipes;
metadata.wrapDirectivesAndPipesInClosure = wrapDirectivesAndPipesInClosure;
@ -374,7 +374,7 @@ export class ComponentDecoratorHandler implements
return {};
}
compile(node: ts.ClassDeclaration, analysis: ComponentHandlerData, pool: ConstantPool):
compile(node: ClassDeclaration, analysis: ComponentHandlerData, pool: ConstantPool):
CompileResult {
const res = compileComponentFromMetadata(analysis.meta, pool, makeBindingParser());

View File

@ -12,7 +12,7 @@ import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {DefaultImportRecorder, Reference} from '../../imports';
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 {extractDirectiveGuards} from '../../scope/src/util';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
@ -35,7 +35,7 @@ export class DirectiveDecoratorHandler implements
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) {
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(
node, decorator, this.reflector, this.evaluator, this.defaultImportRecorder, this.isCore);
const analysis = directiveResult && directiveResult.metadata;
@ -61,7 +61,7 @@ export class DirectiveDecoratorHandler implements
const ref = new Reference(node);
this.scopeRegistry.registerDirective({
ref,
name: node.name !.text,
name: node.name.text,
selector: analysis.selector,
exportAs: analysis.exportAs,
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 {
const res = compileDirectiveFromMetadata(analysis.meta, pool, makeBindingParser());
const statements = res.statements;
@ -104,7 +104,7 @@ export class DirectiveDecoratorHandler implements
* Helper function to extract metadata from a `Directive` or `Component`.
*/
export function extractDirectiveMetadata(
clazz: ts.ClassDeclaration, decorator: Decorator, reflector: ReflectionHost,
clazz: ClassDeclaration, decorator: Decorator, reflector: ReflectionHost,
evaluator: PartialEvaluator, defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
defaultSelector: string | null = null): {
decorator: Map<string, ts.Expression>,
@ -189,7 +189,7 @@ export function extractDirectiveMetadata(
selector = resolved === '' ? defaultSelector : resolved;
}
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);
@ -217,14 +217,14 @@ export function extractDirectiveMetadata(
// Detect if the component inherits from another class
const usesInheritance = reflector.hasBaseClass(clazz);
const metadata: R3DirectiveMetadata = {
name: clazz.name !.text,
name: clazz.name.text,
deps: getValidConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore), host,
lifecycle: {
usesOnChanges,
},
inputs: {...inputsFromMeta, ...inputsFromFields},
outputs: {...outputsFromMeta, ...outputsFromFields}, queries, viewQueries, selector,
type: new WrappedNodeExpr(clazz.name !),
type: new WrappedNodeExpr(clazz.name),
typeArgumentCount: reflector.getGenericArityOfClass(clazz) || 0,
typeSourceSpan: null !, usesInheritance, exportAs, providers
};

View File

@ -11,7 +11,7 @@ import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
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 {generateSetClassMetadataCall} from './metadata';
@ -33,7 +33,7 @@ export class InjectableDecoratorHandler implements
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) {
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 {
analysis: {
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 statements = res.statements;
if (analysis.metadataStmt !== null) {
@ -81,13 +81,9 @@ export class InjectableDecoratorHandler implements
* A `null` return value indicates this is @Injectable has invalid data.
*/
function extractInjectableMetadata(
clazz: ts.ClassDeclaration, decorator: Decorator, reflector: ReflectionHost,
clazz: ClassDeclaration, decorator: Decorator, reflector: ReflectionHost,
defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
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 type = new WrappedNodeExpr(clazz.name);
const typeArgumentCount = reflector.getGenericArityOfClass(clazz) || 0;

View File

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

View File

@ -12,7 +12,7 @@ import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {DefaultImportRecorder, Reference, ReferenceEmitter} from '../../imports';
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 {LocalModuleScopeRegistry} from '../../scope';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
@ -26,7 +26,7 @@ export interface NgModuleAnalysis {
ngModuleDef: R3NgModuleMetadata;
ngInjectorDef: R3InjectorMetadata;
metadataStmt: Statement|null;
declarations: Reference<ts.Declaration>[];
declarations: Reference<ClassDeclaration>[];
}
/**
@ -44,7 +44,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
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) {
return undefined;
}
@ -59,8 +59,8 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
}
}
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<NgModuleAnalysis> {
const name = node.name !.text;
analyze(node: ClassDeclaration, decorator: Decorator): AnalysisOutput<NgModuleAnalysis> {
const name = node.name.text;
if (decorator.args === null || decorator.args.length > 1) {
throw new FatalDiagnosticError(
ErrorCode.DECORATOR_ARITY_WRONG, decorator.node,
@ -90,20 +90,20 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
]);
// Extract the module declarations, imports, and exports.
let declarationRefs: Reference<ts.Declaration>[] = [];
let declarationRefs: Reference<ClassDeclaration>[] = [];
if (ngModule.has('declarations')) {
const expr = ngModule.get('declarations') !;
const declarationMeta = this.evaluator.evaluate(expr, forwardRefResolver);
declarationRefs = this.resolveTypeList(expr, declarationMeta, name, 'declarations');
}
let importRefs: Reference<ts.Declaration>[] = [];
let importRefs: Reference<ClassDeclaration>[] = [];
let rawImports: ts.Expression|null = null;
if (ngModule.has('imports')) {
rawImports = ngModule.get('imports') !;
const importsMeta = this.evaluator.evaluate(rawImports, moduleResolvers);
importRefs = this.resolveTypeList(rawImports, importsMeta, name, 'imports');
}
let exportRefs: Reference<ts.Declaration>[] = [];
let exportRefs: Reference<ClassDeclaration>[] = [];
let rawExports: ts.Expression|null = null;
if (ngModule.has('exports')) {
rawExports = ngModule.get('exports') !;
@ -111,7 +111,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
exportRefs = this.resolveTypeList(rawExports, exportsMeta, name, 'exports');
this.referencesRegistry.add(node, ...exportRefs);
}
let bootstrapRefs: Reference<ts.Declaration>[] = [];
let bootstrapRefs: Reference<ClassDeclaration>[] = [];
if (ngModule.has('bootstrap')) {
const expr = ngModule.get('bootstrap') !;
const bootstrapMeta = this.evaluator.evaluate(expr, forwardRefResolver);
@ -146,7 +146,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
exports.some(isForwardReference);
const ngModuleDef: R3NgModuleMetadata = {
type: new WrappedNodeExpr(node.name !),
type: new WrappedNodeExpr(node.name),
bootstrap,
declarations,
exports,
@ -176,7 +176,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
const ngInjectorDef: R3InjectorMetadata = {
name,
type: new WrappedNodeExpr(node.name !),
type: new WrappedNodeExpr(node.name),
deps: getValidConstructorDependencies(
node, this.reflector, this.defaultImportRecorder, this.isCore),
providers,
@ -191,11 +191,11 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
metadataStmt: generateSetClassMetadataCall(
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 diagnostics = this.scopeRegistry.getDiagnosticsOfModule(node) || undefined;
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 ngModuleDef = compileNgModule(analysis.ngModuleDef);
const ngModuleStatements = ngModuleDef.additionalStatements;
@ -218,8 +218,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
const context = getSourceFile(node);
for (const decl of analysis.declarations) {
if (this.scopeRegistry.getRequiresRemoteScope(decl.node)) {
const scope =
this.scopeRegistry.getScopeOfModule(ts.getOriginalNode(node) as ts.Declaration);
const scope = this.scopeRegistry.getScopeOfModule(ts.getOriginalNode(node) as typeof node);
if (scope === null) {
continue;
}
@ -340,13 +339,19 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
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.
*/
private resolveTypeList(
expr: ts.Node, resolvedList: ResolvedValue, className: string,
arrayName: string): Reference<ts.Declaration>[] {
const refList: Reference<ts.Declaration>[] = [];
arrayName: string): Reference<ClassDeclaration>[] {
const refList: Reference<ClassDeclaration>[] = [];
if (!Array.isArray(resolvedList)) {
throw new FatalDiagnosticError(
ErrorCode.VALUE_HAS_WRONG_TYPE, expr,
@ -364,7 +369,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
// Recurse into nested arrays.
refList.push(...this.resolveTypeList(expr, entry, className, arrayName));
} else if (isDeclarationReference(entry)) {
if (!this.reflector.isClass(entry.node)) {
if (!this.isClassDeclarationReference(entry)) {
throw new FatalDiagnosticError(
ErrorCode.VALUE_HAS_WRONG_TYPE, entry.node,
`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
*/
import {LiteralExpr, R3PipeMetadata, Statement, WrappedNodeExpr, compilePipeFromMetadata} from '@angular/compiler';
import {R3PipeMetadata, Statement, WrappedNodeExpr, compilePipeFromMetadata} from '@angular/compiler';
import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {DefaultImportRecorder, Reference} from '../../imports';
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 {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
@ -32,7 +32,7 @@ export class PipeDecoratorHandler implements DecoratorHandler<PipeHandlerData, D
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) {
return undefined;
}
@ -47,11 +47,7 @@ export class PipeDecoratorHandler implements DecoratorHandler<PipeHandlerData, D
}
}
analyze(clazz: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<PipeHandlerData> {
if (clazz.name === undefined) {
throw new FatalDiagnosticError(
ErrorCode.DECORATOR_ON_ANONYMOUS_CLASS, clazz, `@Pipes must have names`);
}
analyze(clazz: ClassDeclaration, decorator: Decorator): AnalysisOutput<PipeHandlerData> {
const name = clazz.name.text;
const type = new WrappedNodeExpr(clazz.name);
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 statements = res.statements;
if (analysis.metadataStmt !== null) {

View File

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

View File

@ -15,6 +15,7 @@ import {PartialEvaluator} from '../../partial_evaluator';
import {TypeScriptReflectionHost} from '../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
import {isNamedClassDeclaration} from '../../util/src/typescript';
import {ResourceLoader} from '../src/api';
import {ComponentDecoratorHandler} from '../src/component';
@ -56,7 +57,7 @@ describe('ComponentDecoratorHandler', () => {
const handler = new ComponentDecoratorHandler(
reflectionHost, evaluator, scopeRegistry, false, new NoopResourceLoader(), [''], false,
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));
if (detected === undefined) {
return fail('Failed to recognize @Component');

View File

@ -13,6 +13,7 @@ import {PartialEvaluator} from '../../partial_evaluator';
import {TypeScriptReflectionHost} from '../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
import {isNamedClassDeclaration} from '../../util/src/typescript';
import {DirectiveDecoratorHandler} from '../src/directive';
@ -47,7 +48,7 @@ describe('DirectiveDecoratorHandler', () => {
reflectionHost, evaluator, scopeRegistry, NOOP_DEFAULT_IMPORT_RECORDER, false);
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));
if (detected === undefined) {

View File

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

View File

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

View File

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

View File

@ -42,6 +42,29 @@ export interface Decorator {
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.
*/
@ -452,7 +475,7 @@ export interface ReflectionHost {
/**
* 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.

View File

@ -8,7 +8,7 @@
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';
/**
@ -133,9 +133,10 @@ export class TypeScriptReflectionHost implements ReflectionHost {
return map;
}
isClass(node: ts.Node): node is ts.NamedDeclaration {
isClass(node: ts.Node): node is ClassDeclaration {
// 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 {

View File

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

View File

@ -9,13 +9,13 @@
import * as ts from 'typescript';
import {AliasGenerator, Reference} from '../../imports';
import {ReflectionHost} from '../../reflection';
import {ClassDeclaration, ReflectionHost} from '../../reflection';
import {ExportScope, ScopeDirective, ScopePipe} from './api';
import {extractDirectiveGuards, extractReferencesFromType, readStringArrayType, readStringMapType, readStringType} from './util';
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.
*/
private cache = new Map<ts.ClassDeclaration, ExportScope|null>();
private cache = new Map<ClassDeclaration, ExportScope|null>();
constructor(
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
* 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 sourceFile = clazz.getSourceFile();
if (!sourceFile.isDeclarationFile) {
@ -64,7 +64,7 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
return null;
}
const declarations = new Set<ts.Declaration>();
const declarations = new Set<ClassDeclaration>();
for (const declRef of meta.declarations) {
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.
*/
private readScopeDirectiveFromClassWithDef(ref: Reference<ts.ClassDeclaration>): ScopeDirective
private readScopeDirectiveFromClassWithDef(ref: Reference<ClassDeclaration>): ScopeDirective
|null {
const clazz = ref.node;
const def = this.reflector.getMembersOfClass(clazz).find(
@ -193,7 +193,7 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
return {
ref,
name: clazz.name !.text,
name: clazz.name.text,
isComponent: def.name === 'ngComponentDef', selector,
exportAs: readStringArrayType(def.type.typeArguments[2]),
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.
*/
private readScopePipeFromClassWithDef(ref: Reference<ts.ClassDeclaration>): ScopePipe|null {
private readScopePipeFromClassWithDef(ref: Reference<ClassDeclaration>): ScopePipe|null {
const def = this.reflector.getMembersOfClass(ref.node).find(
field => field.isStatic && field.name === 'ngPipeDef');
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.
*/
interface RawDependencyMetadata {
declarations: Reference<ts.ClassDeclaration>[];
imports: Reference<ts.ClassDeclaration>[];
exports: Reference<ts.ClassDeclaration>[];
declarations: Reference<ClassDeclaration>[];
imports: Reference<ClassDeclaration>[];
exports: Reference<ClassDeclaration>[];
}

View File

@ -11,15 +11,16 @@ import * as ts from 'typescript';
import {ErrorCode, makeDiagnostic} from '../../diagnostics';
import {AliasGenerator, Reexport, Reference, ReferenceEmitter} from '../../imports';
import {ClassDeclaration} from '../../reflection';
import {identifierOfNode, nodeNameForError} from '../../util/src/typescript';
import {ExportScope, ScopeData, ScopeDirective, ScopePipe} from './api';
import {DtsModuleScopeResolver} from './dependency';
export interface LocalNgModuleData {
declarations: Reference<ts.Declaration>[];
imports: Reference<ts.Declaration>[];
exports: Reference<ts.Declaration>[];
declarations: Reference<ClassDeclaration>[];
imports: Reference<ClassDeclaration>[];
exports: Reference<ClassDeclaration>[];
}
export interface LocalModuleScope extends ExportScope {
@ -57,17 +58,17 @@ export class LocalModuleScopeRegistry {
/**
* 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.
*/
private directiveData = new Map<ts.Declaration, ScopeDirective>();
private directiveData = new Map<ClassDeclaration, ScopeDirective>();
/**
* 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.
@ -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
* 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.
@ -84,7 +85,7 @@ export class LocalModuleScopeRegistry {
* A value of `undefined` indicates the scope was invalid and produced errors (therefore,
* 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".
@ -94,12 +95,12 @@ export class LocalModuleScopeRegistry {
* around cyclic import issues). This is not used in calculation of `LocalModuleScope`s, but is
* 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.
*/
private scopeErrors = new Map<ts.Declaration, ts.Diagnostic[]>();
private scopeErrors = new Map<ClassDeclaration, ts.Diagnostic[]>();
constructor(
private dependencyScopeReader: DtsModuleScopeResolver, private refEmitter: ReferenceEmitter,
@ -108,7 +109,7 @@ export class LocalModuleScopeRegistry {
/**
* Add an NgModule's data to the registry.
*/
registerNgModule(clazz: ts.Declaration, data: LocalNgModuleData): void {
registerNgModule(clazz: ClassDeclaration, data: LocalNgModuleData): void {
this.assertCollecting();
this.ngModuleData.set(clazz, data);
for (const decl of data.declarations) {
@ -126,7 +127,7 @@ export class LocalModuleScopeRegistry {
this.pipeData.set(pipe.ref.node, pipe);
}
getScopeForComponent(clazz: ts.ClassDeclaration): LocalModuleScope|null {
getScopeForComponent(clazz: ClassDeclaration): LocalModuleScope|null {
if (!this.declarationToModule.has(clazz)) {
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
* available or the scope contains errors.
*/
getScopeOfModule(clazz: ts.Declaration): LocalModuleScope|null {
getScopeOfModule(clazz: ClassDeclaration): LocalModuleScope|null {
const scope = this.getScopeOfModuleInternal(clazz);
// Translate undefined -> 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
* 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
// before, this will be a no-op due to the scope cache.
this.getScopeOfModule(clazz);
@ -167,7 +168,7 @@ export class LocalModuleScopeRegistry {
* Implementation of `getScopeOfModule` which differentiates between no scope being available
* (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.
this.sealed = true;
@ -218,8 +219,7 @@ export class LocalModuleScopeRegistry {
for (const decl of ngModule.declarations) {
if (this.directiveData.has(decl.node)) {
const directive = this.directiveData.get(decl.node) !;
compilationDirectives.set(
decl.node, {...directive, ref: decl as Reference<ts.ClassDeclaration>});
compilationDirectives.set(decl.node, {...directive, ref: decl});
} else if (this.pipeData.has(decl.node)) {
const pipe = this.pipeData.get(decl.node) !;
compilationPipes.set(decl.node, {...pipe, ref: decl});
@ -303,7 +303,7 @@ export class LocalModuleScopeRegistry {
let reexports: Reexport[]|null = null;
if (this.aliasGenerator !== null) {
reexports = [];
const addReexport = (ref: Reference<ts.Declaration>) => {
const addReexport = (ref: Reference<ClassDeclaration>) => {
if (!declared.has(ref.node) && ref.node.getSourceFile() !== sourceFile) {
const exportName = this.aliasGenerator !.aliasSymbolName(ref.node, sourceFile);
if (ref.alias && ref.alias instanceof ExternalExpr) {
@ -362,12 +362,14 @@ export class LocalModuleScopeRegistry {
/**
* 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.
*/
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.
@ -380,8 +382,8 @@ export class LocalModuleScopeRegistry {
* array parameter.
*/
private getExportedScope(
ref: Reference<ts.Declaration>, diagnostics: ts.Diagnostic[], ownerForErrors: ts.Declaration,
type: 'import'|'export'): ExportScope|null|undefined {
ref: Reference<ClassDeclaration>, diagnostics: ts.Diagnostic[],
ownerForErrors: ts.Declaration, type: 'import'|'export'): ExportScope|null|undefined {
if (ref.node.getSourceFile().isDeclarationFile) {
// The NgModule is declared in a .d.ts file. Resolve it with the `DependencyScopeReader`.
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`));
return undefined;
}
return this.dependencyScopeReader.resolve(ref as Reference<ts.ClassDeclaration>);
return this.dependencyScopeReader.resolve(ref);
} else {
// The NgModule is declared locally in the current program. Resolve it from the registry.
return this.getScopeOfModuleInternal(ref.node);

View File

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

View File

@ -10,7 +10,7 @@ import {ExternalExpr, ExternalReference} from '@angular/compiler';
import * as ts from 'typescript';
import {AliasGenerator, FileToModuleHost, Reference} from '../../imports';
import {TypeScriptReflectionHost} from '../../reflection';
import {ClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
import {makeProgram} from '../../testing/in_memory_typescript';
import {ExportScope} from '../src/api';
import {MetadataDtsModuleScopeResolver} from '../src/dependency';
@ -42,7 +42,7 @@ export declare type PipeMeta<A, B> = never;
*/
function makeTestEnv(
modules: {[module: string]: string}, aliasGenerator: AliasGenerator | null = null): {
refs: {[name: string]: Reference<ts.ClassDeclaration>},
refs: {[name: string]: Reference<ClassDeclaration>},
resolver: MetadataDtsModuleScopeResolver,
} {
// Map the modules object to an array of files for `makeProgram`.
@ -123,7 +123,7 @@ describe('MetadataDtsModuleScopeResolver', () => {
export declare class Dir {
static ngDirectiveDef: DirectiveMeta<Dir, '[dir]', never, never, never, never>;
}
export declare class ModuleA {
static ngModuleDef: ModuleMeta<ModuleA, [typeof Dir], never, [typeof Dir]>;
}
@ -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 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 !));
}
function getAlias(ref: Reference<ts.ClassDeclaration>): ExternalReference|null {
function getAlias(ref: Reference<ClassDeclaration>): ExternalReference|null {
if (ref.alias === null) {
return null;
} else {

View File

@ -9,16 +9,17 @@
import * as ts from 'typescript';
import {Reference, ReferenceEmitter} from '../../imports';
import {ClassDeclaration} from '../../reflection';
import {ScopeData, ScopeDirective, ScopePipe} from '../src/api';
import {DtsModuleScopeResolver} from '../src/dependency';
import {LocalModuleScopeRegistry} from '../src/local';
function registerFakeRefs(registry: LocalModuleScopeRegistry):
{[name: string]: Reference<ts.ClassDeclaration>} {
const get = (target: {}, name: string): Reference<ts.ClassDeclaration> => {
{[name: string]: Reference<ClassDeclaration>} {
const get = (target: {}, name: string): Reference<ClassDeclaration> => {
const sf = ts.createSourceFile(
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);
if (name.startsWith('Dir') || name.startsWith('Cmp')) {
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 !;
return {
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 !;
return {ref, name};
}
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 pipes = scopeData.pipes.map(pipe => pipe.ref);
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 {Reexport} from '../../imports';
import {Decorator} from '../../reflection';
import {ClassDeclaration, Decorator} from '../../reflection';
import {TypeCheckContext} from '../../typecheck';
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
* 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
* 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
* if successful, or an array of diagnostic messages if the analysis fails or the decorator
* 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.
@ -83,15 +83,15 @@ export interface DecoratorHandler<A, M> {
* `DecoratorHandler` a chance to leverage information from the whole compilation unit to enhance
* 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
* initialization code to be generated.
*/
compile(node: ts.Declaration, analysis: A, constantPool: ConstantPool): CompileResult
compile(node: ClassDeclaration, analysis: A, constantPool: ConstantPool): CompileResult
|CompileResult[];
}

View File

@ -6,14 +6,14 @@
* 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 {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {ImportRewriter} from '../../imports';
import {ReflectionHost, reflectNameOfDeclaration} from '../../reflection';
import {ClassDeclaration, ReflectionHost, reflectNameOfDeclaration} from '../../reflection';
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 {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
* 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.
@ -84,7 +84,7 @@ export class IvyCompilation {
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.
const classDecorators = this.reflector.getDecoratorsOfDeclaration(node);
let ivyClass: IvyClass|null = null;
@ -169,7 +169,7 @@ export class IvyCompilation {
private analyze(sf: ts.SourceFile, preanalyze: boolean): Promise<void>|undefined {
const promises: Promise<void>[] = [];
const analyzeClass = (node: ts.Declaration): void => {
const analyzeClass = (node: ClassDeclaration): void => {
const ivyClass = this.detectHandlersForClass(node);
// 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 => {
// Process nodes recursively, and look for class declarations with decorators.
if (ts.isClassDeclaration(node)) {
if (isNamedClassDeclaration(node)) {
analyzeClass(node);
}
ts.forEachChild(node, visit);
@ -291,8 +291,8 @@ export class IvyCompilation {
*/
compileIvyFieldFor(node: ts.Declaration, constantPool: ConstantPool): CompileResult[]|undefined {
// Look to see whether the original node was analyzed. If not, there's nothing to do.
const original = ts.getOriginalNode(node) as ts.Declaration;
if (!this.ivyClasses.has(original)) {
const original = ts.getOriginalNode(node) as typeof node;
if (!isNamedClassDeclaration(original) || !this.ivyClasses.has(original)) {
return undefined;
}
@ -305,7 +305,8 @@ export class IvyCompilation {
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)) {
res.push(compileMatchRes);
} else {
@ -327,9 +328,9 @@ export class IvyCompilation {
* Lookup the `ts.Decorator` which triggered transformation of a particular class declaration.
*/
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;
}
const ivyClass = this.ivyClasses.get(original) !;

View File

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

View File

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

View File

@ -10,6 +10,7 @@ import {BoundTarget} from '@angular/compiler';
import * as ts from 'typescript';
import {NoopImportRewriter, ReferenceEmitter} from '../../imports';
import {ClassDeclaration} from '../../reflection';
import {ImportManager} from '../../translator';
import {TypeCheckBlockMetadata, TypeCheckableDirectiveMeta, TypeCtorMetadata} from './api';
@ -42,16 +43,12 @@ export class TypeCheckContext {
* @param template AST nodes of the template being recorded.
* @param matcher `SelectorMatcher` which tracks directives that are in scope for this template.
*/
addTemplate(node: ts.ClassDeclaration, boundTarget: BoundTarget<TypeCheckableDirectiveMeta>):
void {
// Only write TCBs for named classes.
if (node.name === undefined) {
throw new Error(`Assertion: class must be named`);
}
addTemplate(
node: ClassDeclaration<ts.ClassDeclaration>,
boundTarget: BoundTarget<TypeCheckableDirectiveMeta>): void {
// Get all of the directives used in the template and record type constructors for all of them.
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.
this.addTypeCtor(dirNode.getSourceFile(), dirNode, {
fnName: 'ngTypeCtor',
@ -77,7 +74,9 @@ export class TypeCheckContext {
/**
* 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.
if (!this.opMap.has(sf)) {
this.opMap.set(sf, []);
@ -136,7 +135,8 @@ export class TypeCheckContext {
}
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)) {
this.opMap.set(sf, []);
}
@ -152,7 +152,7 @@ interface Op {
/**
* 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.
@ -170,7 +170,9 @@ interface Op {
* A type check block operation which produces type check code for a particular component.
*/
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.
@ -188,7 +190,8 @@ class TcbOp implements Op {
* A type constructor operation which produces type constructor code for a particular directive.
*/
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.

View File

@ -10,6 +10,7 @@ import {AST, BindingType, BoundTarget, ImplicitReceiver, PropertyRead, TmplAstBo
import * as ts from 'typescript';
import {NOOP_DEFAULT_IMPORT_RECORDER, Reference, ReferenceEmitter} from '../../imports';
import {ClassDeclaration} from '../../reflection';
import {ImportManager, translateExpression} from '../../translator';
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.
*/
export function generateTypeCheckBlock(
node: ts.ClassDeclaration, meta: TypeCheckBlockMetadata, importManager: ImportManager,
refEmitter: ReferenceEmitter): ts.FunctionDeclaration {
node: ClassDeclaration<ts.ClassDeclaration>, meta: TypeCheckBlockMetadata,
importManager: ImportManager, refEmitter: ReferenceEmitter): ts.FunctionDeclaration {
const tcb = new Context(meta.boundTarget, node.getSourceFile(), importManager, refEmitter);
const scope = new Scope(tcb);
tcbProcessNodes(meta.boundTarget.target.template !, tcb, scope);

View File

@ -8,6 +8,7 @@
import * as ts from 'typescript';
import {ClassDeclaration} from '../../reflection';
import {TypeCtorMetadata} from './api';
/**
@ -29,19 +30,20 @@ import {TypeCtorMetadata} from './api';
*
* 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.
* @returns a `ts.MethodDeclaration` for the type constructor.
*/
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
// the definition without any type bounds. For example, if the class is
// `FooDirective<T extends Bar>`, its rawType would be `FooDirective<T>`.
const rawTypeArgs = node.typeParameters !== undefined ?
node.typeParameters.map(param => ts.createTypeReferenceNode(param.name, 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.
// 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 {LogicalFileSystem} from '../../path';
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 {TypeCheckProgramHost} from '../src/host';
@ -47,7 +47,7 @@ TestClass.ngTypeCtor({value: 'test'});
new LogicalProjectStrategy(checker, logicalFs),
]);
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, {
fnName: 'ngTypeCtor',
body: true,

View File

@ -60,6 +60,11 @@ export function isDeclaration(node: ts.Node): node is ts.Declaration {
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 {
let topLevel: ts.Node = node;
if (ts.isVariableDeclaration(node) && ts.isVariableDeclarationList(node.parent)) {