From bb6a3632f6e7a3cf4abd21ee4ce23f9b1df68c0b Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 20 Mar 2019 12:10:57 +0200 Subject: [PATCH] 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 --- .../ngcc/src/host/decorated_class.ts | 5 +- .../ngcc/src/host/esm2015_host.ts | 10 ++-- .../compiler-cli/ngcc/src/host/esm5_host.ts | 8 ++-- .../compiler-cli/ngcc/src/host/ngcc_host.ts | 4 +- .../ngcc/src/rendering/renderer.ts | 2 +- .../ngcc/test/host/esm5_host_spec.ts | 6 +-- .../src/ngtsc/annotations/src/base_def.ts | 9 ++-- .../src/ngtsc/annotations/src/component.ts | 20 ++++---- .../src/ngtsc/annotations/src/directive.ts | 18 +++---- .../src/ngtsc/annotations/src/injectable.ts | 14 ++---- .../src/ngtsc/annotations/src/metadata.ts | 2 +- .../src/ngtsc/annotations/src/ng_module.ts | 43 +++++++++-------- .../src/ngtsc/annotations/src/pipe.ts | 14 ++---- .../src/ngtsc/annotations/src/util.ts | 8 ++-- .../ngtsc/annotations/test/component_spec.ts | 3 +- .../ngtsc/annotations/test/directive_spec.ts | 3 +- .../ngtsc/annotations/test/ng_module_spec.ts | 3 +- .../src/ngtsc/imports/BUILD.bazel | 1 + .../src/ngtsc/imports/src/alias.ts | 11 ++--- .../src/ngtsc/reflection/src/host.ts | 25 +++++++++- .../src/ngtsc/reflection/src/typescript.ts | 7 +-- .../compiler-cli/src/ngtsc/scope/src/api.ts | 5 +- .../src/ngtsc/scope/src/dependency.ts | 22 ++++----- .../compiler-cli/src/ngtsc/scope/src/local.ts | 48 ++++++++++--------- .../compiler-cli/src/ngtsc/scope/src/util.ts | 10 ++-- .../src/ngtsc/scope/test/dependency_spec.ts | 12 ++--- .../src/ngtsc/scope/test/local_spec.ts | 15 +++--- .../src/ngtsc/transform/src/api.ts | 14 +++--- .../src/ngtsc/transform/src/compilation.ts | 25 +++++----- .../src/ngtsc/typecheck/BUILD.bazel | 1 + .../src/ngtsc/typecheck/src/api.ts | 4 +- .../src/ngtsc/typecheck/src/context.ts | 29 ++++++----- .../ngtsc/typecheck/src/type_check_block.ts | 5 +- .../ngtsc/typecheck/src/type_constructor.ts | 8 ++-- .../typecheck/test/type_constructor_spec.ts | 4 +- .../src/ngtsc/util/src/typescript.ts | 5 ++ 36 files changed, 229 insertions(+), 194 deletions(-) diff --git a/packages/compiler-cli/ngcc/src/host/decorated_class.ts b/packages/compiler-cli/ngcc/src/host/decorated_class.ts index 2047d5af72..addfa5d3d2 100644 --- a/packages/compiler-cli/ngcc/src/host/decorated_class.ts +++ b/packages/compiler-cli/ngcc/src/host/decorated_class.ts @@ -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[]) {} } diff --git a/packages/compiler-cli/ngcc/src/host/esm2015_host.ts b/packages/compiler-cli/ngcc/src/host/esm2015_host.ts index 8e304b6983..0d9a24a352 100644 --- a/packages/compiler-cli/ngcc/src/host/esm2015_host.ts +++ b/packages/compiler-cli/ngcc/src/host/esm2015_host.ts @@ -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) { diff --git a/packages/compiler-cli/ngcc/src/host/esm5_host.ts b/packages/compiler-cli/ngcc/src/host/esm5_host.ts index 846bc45dac..1e11de66f2 100644 --- a/packages/compiler-cli/ngcc/src/host/esm5_host.ts +++ b/packages/compiler-cli/ngcc/src/host/esm5_host.ts @@ -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); diff --git a/packages/compiler-cli/ngcc/src/host/ngcc_host.ts b/packages/compiler-cli/ngcc/src/host/ngcc_host.ts index 3b932c3c9c..1ddfe6dd54 100644 --- a/packages/compiler-cli/ngcc/src/host/ngcc_host.ts +++ b/packages/compiler-cli/ngcc/src/host/ngcc_host.ts @@ -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 diff --git a/packages/compiler-cli/ngcc/src/rendering/renderer.ts b/packages/compiler-cli/ngcc/src/rendering/renderer.ts index 8a054abed9..2345588b56 100644 --- a/packages/compiler-cli/ngcc/src/rendering/renderer.ts +++ b/packages/compiler-cli/ngcc/src/rendering/renderer.ts @@ -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 = diff --git a/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts b/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts index 9fb1cfbdd9..a01684d411 100644 --- a/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts @@ -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); diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/base_def.ts b/packages/compiler-cli/src/ngtsc/annotations/src/base_def.ts index fd79d60cba..93298e7770 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/base_def.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/base_def.ts @@ -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|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 { 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 { diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts index 73efd42a6f..a722ae4879 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts @@ -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|undefined { + detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult|undefined { if (!decorators) { return undefined; } @@ -75,7 +75,7 @@ export class ComponentDecoratorHandler implements } } - preanalyze(node: ts.ClassDeclaration, decorator: Decorator): Promise|undefined { + preanalyze(node: ClassDeclaration, decorator: Decorator): Promise|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 { + analyze(node: ClassDeclaration, decorator: Decorator): AnalysisOutput { 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()); diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts index 623db57a42..5afef7c479 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts @@ -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|undefined { + detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult|undefined { if (!decorators) { return undefined; } @@ -50,7 +50,7 @@ export class DirectiveDecoratorHandler implements } } - analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput { + analyze(node: ClassDeclaration, decorator: Decorator): AnalysisOutput { 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, @@ -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 }; diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts b/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts index b020c9316b..31edc42660 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts @@ -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|undefined { + detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult|undefined { if (!decorators) { return undefined; } @@ -48,7 +48,7 @@ export class InjectableDecoratorHandler implements } } - analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput { + analyze(node: ClassDeclaration, decorator: Decorator): AnalysisOutput { 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; diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/metadata.ts b/packages/compiler-cli/src/ngtsc/annotations/src/metadata.ts index a976cd3848..ae8b68753f 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/metadata.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/metadata.ts @@ -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); diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts index 664256c06d..737fd91856 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts @@ -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[]; + declarations: Reference[]; } /** @@ -44,7 +44,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler|undefined { + detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult|undefined { if (!decorators) { return undefined; } @@ -59,8 +59,8 @@ export class NgModuleDecoratorHandler implements DecoratorHandler { - const name = node.name !.text; + analyze(node: ClassDeclaration, decorator: Decorator): AnalysisOutput { + 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[] = []; + let declarationRefs: Reference[] = []; 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[] = []; + let importRefs: Reference[] = []; 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[] = []; + let exportRefs: Reference[] = []; let rawExports: ts.Expression|null = null; if (ngModule.has('exports')) { rawExports = ngModule.get('exports') !; @@ -111,7 +111,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler[] = []; + let bootstrapRefs: Reference[] = []; 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): + ref is Reference { + 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[] { - const refList: Reference[] = []; + arrayName: string): Reference[] { + const refList: Reference[] = []; if (!Array.isArray(resolvedList)) { throw new FatalDiagnosticError( ErrorCode.VALUE_HAS_WRONG_TYPE, expr, @@ -364,7 +369,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler|undefined { + detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult|undefined { if (!decorators) { return undefined; } @@ -47,11 +47,7 @@ export class PipeDecoratorHandler implements DecoratorHandler { - if (clazz.name === undefined) { - throw new FatalDiagnosticError( - ErrorCode.DECORATOR_ON_ANONYMOUS_CLASS, clazz, `@Pipes must have names`); - } + analyze(clazz: ClassDeclaration, decorator: Decorator): AnalysisOutput { const name = clazz.name.text; const type = new WrappedNodeExpr(clazz.name); if (decorator.args === null) { @@ -109,7 +105,7 @@ export class PipeDecoratorHandler implements DecoratorHandler { 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'); diff --git a/packages/compiler-cli/src/ngtsc/annotations/test/directive_spec.ts b/packages/compiler-cli/src/ngtsc/annotations/test/directive_spec.ts index a314de85b6..4e4de13de2 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/test/directive_spec.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/test/directive_spec.ts @@ -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) { diff --git a/packages/compiler-cli/src/ngtsc/annotations/test/ng_module_spec.ts b/packages/compiler-cli/src/ngtsc/annotations/test/ng_module_spec.ts index ad94014bc0..369706d62b 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/test/ng_module_spec.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/test/ng_module_spec.ts @@ -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) { diff --git a/packages/compiler-cli/src/ngtsc/imports/BUILD.bazel b/packages/compiler-cli/src/ngtsc/imports/BUILD.bazel index 48952cb198..dbb06cc8f2 100644 --- a/packages/compiler-cli/src/ngtsc/imports/BUILD.bazel +++ b/packages/compiler-cli/src/ngtsc/imports/BUILD.bazel @@ -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", diff --git a/packages/compiler-cli/src/ngtsc/imports/src/alias.ts b/packages/compiler-cli/src/ngtsc/imports/src/alias.ts index b7231f7faa..4122616f4e 100644 --- a/packages/compiler-cli/src/ngtsc/imports/src/alias.ts +++ b/packages/compiler-cli/src/ngtsc/imports/src/alias.ts @@ -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); diff --git a/packages/compiler-cli/src/ngtsc/reflection/src/host.ts b/packages/compiler-cli/src/ngtsc/reflection/src/host.ts index 3a751d417f..126d583833 100644 --- a/packages/compiler-cli/src/ngtsc/reflection/src/host.ts +++ b/packages/compiler-cli/src/ngtsc/reflection/src/host.ts @@ -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 & {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. diff --git a/packages/compiler-cli/src/ngtsc/reflection/src/typescript.ts b/packages/compiler-cli/src/ngtsc/reflection/src/typescript.ts index f06b084a8e..6ceafc27fb 100644 --- a/packages/compiler-cli/src/ngtsc/reflection/src/typescript.ts +++ b/packages/compiler-cli/src/ngtsc/reflection/src/typescript.ts @@ -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 { diff --git a/packages/compiler-cli/src/ngtsc/scope/src/api.ts b/packages/compiler-cli/src/ngtsc/scope/src/api.ts index deccd78622..4a8a260197 100644 --- a/packages/compiler-cli/src/ngtsc/scope/src/api.ts +++ b/packages/compiler-cli/src/ngtsc/scope/src/api.ts @@ -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; + ref: Reference; name: string; } diff --git a/packages/compiler-cli/src/ngtsc/scope/src/dependency.ts b/packages/compiler-cli/src/ngtsc/scope/src/dependency.ts index 8228c40549..786a3f1465 100644 --- a/packages/compiler-cli/src/ngtsc/scope/src/dependency.ts +++ b/packages/compiler-cli/src/ngtsc/scope/src/dependency.ts @@ -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): ExportScope|null; + resolve(ref: Reference): 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(); + private cache = new Map(); 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): ExportScope|null { + resolve(ref: Reference): 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(); + const declarations = new Set(); 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): ScopeDirective + private readScopeDirectiveFromClassWithDef(ref: Reference): 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): ScopePipe|null { + private readScopePipeFromClassWithDef(ref: Reference): 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[]; - imports: Reference[]; - exports: Reference[]; + declarations: Reference[]; + imports: Reference[]; + exports: Reference[]; } diff --git a/packages/compiler-cli/src/ngtsc/scope/src/local.ts b/packages/compiler-cli/src/ngtsc/scope/src/local.ts index 7ba08761b5..a1ba8fd12e 100644 --- a/packages/compiler-cli/src/ngtsc/scope/src/local.ts +++ b/packages/compiler-cli/src/ngtsc/scope/src/local.ts @@ -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[]; - imports: Reference[]; - exports: Reference[]; + declarations: Reference[]; + imports: Reference[]; + exports: Reference[]; } export interface LocalModuleScope extends ExportScope { @@ -57,17 +58,17 @@ export class LocalModuleScopeRegistry { /** * Metadata for each local NgModule registered. */ - private ngModuleData = new Map(); + private ngModuleData = new Map(); /** * Metadata for each local directive registered. */ - private directiveData = new Map(); + private directiveData = new Map(); /** * Metadata for each local pipe registered. */ - private pipeData = new Map(); + private pipeData = new Map(); /** * 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(); + private declarationToModule = new Map(); /** * 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(); + private cache = new Map(); /** * 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(); + private remoteScoping = new Set(); /** * Tracks errors accumulated in the processing of scopes for each module declaration. */ - private scopeErrors = new Map(); + private scopeErrors = new Map(); 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}); + 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) => { + const addReexport = (ref: Reference) => { 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, diagnostics: ts.Diagnostic[], ownerForErrors: ts.Declaration, - type: 'import'|'export'): ExportScope|null|undefined { + ref: Reference, 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); + 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); diff --git a/packages/compiler-cli/src/ngtsc/scope/src/util.ts b/packages/compiler-cli/src/ngtsc/scope/src/util.ts index fbff69b526..1b05dd628d 100644 --- a/packages/compiler-cli/src/ngtsc/scope/src/util.ts +++ b/packages/compiler-cli/src/ngtsc/scope/src/util.ts @@ -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[] { + resolutionContext: string): Reference[] { 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) { diff --git a/packages/compiler-cli/src/ngtsc/scope/test/dependency_spec.ts b/packages/compiler-cli/src/ngtsc/scope/test/dependency_spec.ts index 5b722a2755..05e53e34ed 100644 --- a/packages/compiler-cli/src/ngtsc/scope/test/dependency_spec.ts +++ b/packages/compiler-cli/src/ngtsc/scope/test/dependency_spec.ts @@ -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 = never; */ function makeTestEnv( modules: {[module: string]: string}, aliasGenerator: AliasGenerator | null = null): { - refs: {[name: string]: Reference}, + refs: {[name: string]: Reference}, 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; } - + export declare class ModuleA { static ngModuleDef: ModuleMeta; } @@ -270,13 +270,13 @@ describe('MetadataDtsModuleScopeResolver', () => { }); }); -function scopeToRefs(scope: ExportScope): Reference[] { +function scopeToRefs(scope: ExportScope): Reference[] { const directives = scope.exported.directives.map(dir => dir.ref); - const pipes = scope.exported.pipes.map(pipe => pipe.ref as Reference); + const pipes = scope.exported.pipes.map(pipe => pipe.ref); return [...directives, ...pipes].sort((a, b) => a.debugName !.localeCompare(b.debugName !)); } -function getAlias(ref: Reference): ExternalReference|null { +function getAlias(ref: Reference): ExternalReference|null { if (ref.alias === null) { return null; } else { diff --git a/packages/compiler-cli/src/ngtsc/scope/test/local_spec.ts b/packages/compiler-cli/src/ngtsc/scope/test/local_spec.ts index 10b39c5d8b..a414fc15d0 100644 --- a/packages/compiler-cli/src/ngtsc/scope/test/local_spec.ts +++ b/packages/compiler-cli/src/ngtsc/scope/test/local_spec.ts @@ -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} { - const get = (target: {}, name: string): Reference => { + {[name: string]: Reference} { + const get = (target: {}, name: string): Reference => { 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): ScopeDirective { +function fakeDirective(ref: Reference): ScopeDirective { const name = ref.debugName !; return { ref, @@ -152,16 +153,16 @@ function fakeDirective(ref: Reference): ScopeDirective { }; } -function fakePipe(ref: Reference): ScopePipe { +function fakePipe(ref: Reference): ScopePipe { const name = ref.debugName !; return {ref, name}; } class MockDtsModuleScopeResolver implements DtsModuleScopeResolver { - resolve(ref: Reference): null { return null; } + resolve(ref: Reference): null { return null; } } -function scopeToRefs(scopeData: ScopeData): Reference[] { +function scopeToRefs(scopeData: ScopeData): Reference[] { 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 !)); diff --git a/packages/compiler-cli/src/ngtsc/transform/src/api.ts b/packages/compiler-cli/src/ngtsc/transform/src/api.ts index ef5c8aa216..76a627818a 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/api.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/api.ts @@ -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 { * 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|undefined; + detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult|undefined; /** @@ -67,14 +67,14 @@ export interface DecoratorHandler { * `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|undefined; + preanalyze?(node: ClassDeclaration, metadata: M): Promise|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; + analyze(node: ClassDeclaration, metadata: M): AnalysisOutput; /** * Perform resolution on the given decorator along with the result of analysis. @@ -83,15 +83,15 @@ export interface DecoratorHandler { * `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[]; } diff --git a/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts b/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts index e3b3416466..db2bf6b539 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts @@ -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(); + private ivyClasses = new Map(); /** * Tracks factory information which needs to be generated. @@ -84,7 +84,7 @@ export class IvyCompilation { analyzeAsync(sf: ts.SourceFile): Promise|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|undefined { const promises: Promise[] = []; - 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) !; diff --git a/packages/compiler-cli/src/ngtsc/typecheck/BUILD.bazel b/packages/compiler-cli/src/ngtsc/typecheck/BUILD.bazel index ded2c9dded..3b5198bf55 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/BUILD.bazel +++ b/packages/compiler-cli/src/ngtsc/typecheck/BUILD.bazel @@ -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", diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/api.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/api.ts index b603836a05..65225aefdd 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/api.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/api.ts @@ -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; + ref: Reference; queries: string[]; ngTemplateGuards: string[]; hasNgTemplateContextGuard: boolean; diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts index 7b5c34c08e..1942b5440b 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts @@ -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): - void { - // Only write TCBs for named classes. - if (node.name === undefined) { - throw new Error(`Assertion: class must be named`); - } - + addTemplate( + node: ClassDeclaration, + boundTarget: BoundTarget): 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; // 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, + 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, + 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; /** * 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, 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, readonly meta: TypeCtorMetadata) {} /** * Type constructor operations are inserted immediately before the end of the directive class. diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts index 270ce22dbb..6d7f89205e 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts @@ -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, 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); diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/type_constructor.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/type_constructor.ts index 33019ac51c..95bb2a2df0 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/type_constructor.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/type_constructor.ts @@ -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. * - * @param node the `ts.ClassDeclaration` for which a type constructor will be generated. + * @param node the `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, 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`, its rawType would be `FooDirective`. 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: diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_constructor_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_constructor_spec.ts index e92a7a8840..4c3abaa7c0 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_constructor_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_constructor_spec.ts @@ -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, diff --git a/packages/compiler-cli/src/ngtsc/util/src/typescript.ts b/packages/compiler-cli/src/ngtsc/util/src/typescript.ts index a63ded5d28..111c3565d4 100644 --- a/packages/compiler-cli/src/ngtsc/util/src/typescript.ts +++ b/packages/compiler-cli/src/ngtsc/util/src/typescript.ts @@ -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)) {