diff --git a/packages/compiler-cli/BUILD.bazel b/packages/compiler-cli/BUILD.bazel index d88a524840..6b63722291 100644 --- a/packages/compiler-cli/BUILD.bazel +++ b/packages/compiler-cli/BUILD.bazel @@ -30,6 +30,7 @@ ts_library( "//packages/compiler-cli/src/ngtsc/factories", "//packages/compiler-cli/src/ngtsc/metadata", "//packages/compiler-cli/src/ngtsc/transform", + "//packages/compiler-cli/src/ngtsc/typecheck", ], ) diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts index 0b76cc9ec1..2ea9bb325c 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts @@ -6,26 +6,33 @@ * found in the LICENSE file at https://angular.io/license */ -import {ConstantPool, Expression, R3ComponentMetadata, R3DirectiveMetadata, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler'; +import {ConstantPool, CssSelector, Expression, R3ComponentMetadata, R3DirectiveMetadata, SelectorMatcher, TmplAstNode, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler'; import * as path from 'path'; import * as ts from 'typescript'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; import {Decorator, ReflectionHost} from '../../host'; -import {filterToMembersWithDecorator, reflectObjectLiteral, staticallyResolve} from '../../metadata'; +import {AbsoluteReference, Reference, ResolvedReference, filterToMembersWithDecorator, reflectObjectLiteral, staticallyResolve} from '../../metadata'; import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform'; +import {TypeCheckContext, TypeCheckableDirectiveMeta} from '../../typecheck'; import {ResourceLoader} from './api'; import {extractDirectiveMetadata, extractQueriesFromDecorator, parseFieldArrayValue, queriesFromFields} from './directive'; -import {SelectorScopeRegistry} from './selector_scope'; -import {isAngularCore, unwrapExpression} from './util'; +import {ScopeDirective, SelectorScopeRegistry} from './selector_scope'; +import {extractDirectiveGuards, isAngularCore, unwrapExpression} from './util'; const EMPTY_MAP = new Map(); +export interface ComponentHandlerData { + meta: R3ComponentMetadata; + parsedTemplate: TmplAstNode[]; +} + /** * `DecoratorHandler` which handles the `@Component` annotation. */ -export class ComponentDecoratorHandler implements DecoratorHandler { +export class ComponentDecoratorHandler implements + DecoratorHandler { constructor( private checker: ts.TypeChecker, private reflector: ReflectionHost, private scopeRegistry: SelectorScopeRegistry, private isCore: boolean, @@ -59,7 +66,7 @@ export class ComponentDecoratorHandler implements DecoratorHandler { + analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput { const meta = this._resolveLiteral(decorator); this.literalCache.delete(decorator); @@ -134,13 +141,17 @@ export class ComponentDecoratorHandler implements DecoratorHandler query.propertyName), - isComponent: true, + isComponent: true, ...extractDirectiveGuards(node, this.reflector), }); } @@ -181,26 +192,41 @@ export class ComponentDecoratorHandler implements DecoratorHandler>(); + if (scope !== null) { + scope.directives.forEach( + (meta, selector) => { matcher.addSelectables(CssSelector.parse(selector), meta); }); + ctx.addTemplate(node as ts.ClassDeclaration, meta.parsedTemplate, matcher); + } + } + + compile(node: ts.ClassDeclaration, analysis: ComponentHandlerData, pool: ConstantPool): CompileResult { // Check whether this component was registered with an NgModule. If so, it should be compiled // under that module's compilation scope. const scope = this.scopeRegistry.lookupCompilationScope(node); + let metadata = analysis.meta; if (scope !== null) { // Replace the empty components and directives from the analyze() step with a fully expanded // scope. This is possible now because during compile() the whole compilation unit has been @@ -209,10 +235,10 @@ export class ComponentDecoratorHandler implements DecoratorHandler(); scope.directives.forEach((meta, selector) => directives.set(selector, meta.directive)); const wrapDirectivesInClosure: boolean = !!containsForwardDecls; - analysis = {...analysis, directives, pipes, wrapDirectivesInClosure}; + metadata = {...metadata, directives, pipes, wrapDirectivesInClosure}; } - const res = compileComponentFromMetadata(analysis, pool, makeBindingParser()); + const res = compileComponentFromMetadata(metadata, pool, makeBindingParser()); return { name: 'ngComponentDef', initializer: res.expression, diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts index dea96ccaa7..b47568b7f1 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts @@ -11,11 +11,11 @@ import * as ts from 'typescript'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; import {ClassMember, ClassMemberKind, Decorator, Import, ReflectionHost} from '../../host'; -import {Reference, filterToMembersWithDecorator, reflectObjectLiteral, staticallyResolve} from '../../metadata'; +import {Reference, ResolvedReference, filterToMembersWithDecorator, reflectObjectLiteral, staticallyResolve} from '../../metadata'; import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform'; import {SelectorScopeRegistry} from './selector_scope'; -import {getConstructorDependencies, isAngularCore, unwrapExpression, unwrapForwardRef} from './util'; +import {extractDirectiveGuards, getConstructorDependencies, isAngularCore, unwrapExpression, unwrapForwardRef} from './util'; const EMPTY_OBJECT: {[key: string]: string} = {}; @@ -40,13 +40,17 @@ export class DirectiveDecoratorHandler implements DecoratorHandler query.propertyName), - isComponent: false, + isComponent: false, ...extractDirectiveGuards(node, this.reflector), }); } diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/util.ts b/packages/compiler-cli/src/ngtsc/annotations/src/util.ts index 68cde9b25f..427fffa52f 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/util.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/util.ts @@ -10,7 +10,7 @@ import {Expression, R3DependencyMetadata, R3Reference, R3ResolvedDependencyType, import * as ts from 'typescript'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; -import {Decorator, ReflectionHost} from '../../host'; +import {ClassMemberKind, Decorator, ReflectionHost} from '../../host'; import {AbsoluteReference, ImportMode, Reference} from '../../metadata'; export function getConstructorDependencies( @@ -176,3 +176,20 @@ export function forwardRefResolver( } return expandForwardRef(args[0]); } + +export function extractDirectiveGuards(node: ts.Declaration, reflector: ReflectionHost): { + ngTemplateGuards: string[], + hasNgTemplateContextGuard: boolean, +} { + const methods = nodeStaticMethodNames(node, reflector); + const ngTemplateGuards = methods.filter(method => method.startsWith('ngTemplateGuard_')) + .map(method => method.split('_', 2)[1]); + const hasNgTemplateContextGuard = methods.some(name => name === 'ngTemplateContextGuard'); + return {hasNgTemplateContextGuard, ngTemplateGuards}; +} + +function nodeStaticMethodNames(node: ts.Declaration, reflector: ReflectionHost): string[] { + return reflector.getMembersOfClass(node) + .filter(member => member.kind === ClassMemberKind.Method && member.isStatic) + .map(member => member.name); +} diff --git a/packages/compiler-cli/src/ngtsc/program.ts b/packages/compiler-cli/src/ngtsc/program.ts index 8d311901d7..e3e35d65f6 100644 --- a/packages/compiler-cli/src/ngtsc/program.ts +++ b/packages/compiler-cli/src/ngtsc/program.ts @@ -19,6 +19,7 @@ import {FactoryGenerator, FactoryInfo, GeneratedFactoryHostWrapper, generatedFac import {TypeScriptReflectionHost} from './metadata'; import {FileResourceLoader, HostResourceLoader} from './resource_loader'; import {IvyCompilation, ivyTransformFactory} from './transform'; +import {TypeCheckContext, TypeCheckProgramHost} from './typecheck'; export class NgtscProgram implements api.Program { private tsProgram: ts.Program; @@ -103,7 +104,13 @@ export class NgtscProgram implements api.Program { fileName?: string|undefined, cancellationToken?: ts.CancellationToken| undefined): ReadonlyArray { const compilation = this.ensureAnalyzed(); - return compilation.diagnostics; + const diagnostics = [...compilation.diagnostics]; + if (!!this.options.fullTemplateTypeCheck) { + const ctx = new TypeCheckContext(); + compilation.typeCheck(ctx); + diagnostics.push(...this.compileTypeCheckProgram(ctx)); + } + return diagnostics; } async loadNgStructureAsync(): Promise { @@ -183,6 +190,17 @@ export class NgtscProgram implements api.Program { return emitResult; } + private compileTypeCheckProgram(ctx: TypeCheckContext): ReadonlyArray { + const host = new TypeCheckProgramHost(this.tsProgram, this.host, ctx); + const auxProgram = ts.createProgram({ + host, + rootNames: this.tsProgram.getRootFileNames(), + oldProgram: this.tsProgram, + options: this.options, + }); + return auxProgram.getSemanticDiagnostics(); + } + private makeCompilation(): IvyCompilation { const checker = this.tsProgram.getTypeChecker(); const scopeRegistry = new SelectorScopeRegistry(checker, this.reflector); diff --git a/packages/compiler-cli/src/ngtsc/transform/BUILD.bazel b/packages/compiler-cli/src/ngtsc/transform/BUILD.bazel index 74a7ef8b0c..f1851cddb0 100644 --- a/packages/compiler-cli/src/ngtsc/transform/BUILD.bazel +++ b/packages/compiler-cli/src/ngtsc/transform/BUILD.bazel @@ -15,6 +15,7 @@ ts_library( "//packages/compiler-cli/src/ngtsc/host", "//packages/compiler-cli/src/ngtsc/metadata", "//packages/compiler-cli/src/ngtsc/translator", + "//packages/compiler-cli/src/ngtsc/typecheck", "//packages/compiler-cli/src/ngtsc/util", ], ) diff --git a/packages/compiler-cli/src/ngtsc/transform/src/api.ts b/packages/compiler-cli/src/ngtsc/transform/src/api.ts index e4d995583c..59a3182350 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/api.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/api.ts @@ -10,6 +10,7 @@ import {ConstantPool, Expression, Statement, Type} from '@angular/compiler'; import * as ts from 'typescript'; import {Decorator} from '../../host'; +import {TypeCheckContext} from '../../typecheck'; /** @@ -43,6 +44,8 @@ export interface DecoratorHandler { */ analyze(node: ts.Declaration, metadata: M): AnalysisOutput; + typeCheck?(ctx: TypeCheckContext, node: ts.Declaration, metadata: A): void; + /** * Generate a description of the field which should be added to the class, including any * initialization code to be generated. @@ -60,6 +63,7 @@ export interface AnalysisOutput { analysis?: A; diagnostics?: ts.Diagnostic[]; factorySymbolName?: string; + typeCheck?: boolean; } /** diff --git a/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts b/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts index e8f7ca2ec0..f42e828a43 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts @@ -12,10 +12,12 @@ import * as ts from 'typescript'; import {FatalDiagnosticError} from '../../diagnostics'; import {Decorator, ReflectionHost} from '../../host'; import {reflectNameOfDeclaration} from '../../metadata/src/reflector'; +import {TypeCheckContext} from '../../typecheck'; import {AnalysisOutput, CompileResult, DecoratorHandler} from './api'; import {DtsFileTransformer} from './declaration'; + /** * Record of an adapter which decided to emit a static field, and the analysis it performed to * prepare for that operation. @@ -38,6 +40,7 @@ export class IvyCompilation { * information recorded about them for later compilation. */ private analysis = new Map>(); + private typeCheckMap = new Map>(); /** * Tracks factory information which needs to be generated. @@ -107,6 +110,9 @@ export class IvyCompilation { analysis: analysis.analysis, metadata: metadata, }); + if (!!analysis.typeCheck) { + this.typeCheckMap.set(node, adapter); + } } if (analysis.diagnostics !== undefined) { @@ -156,6 +162,14 @@ export class IvyCompilation { } } + typeCheck(context: TypeCheckContext): void { + this.typeCheckMap.forEach((handler, node) => { + if (handler.typeCheck !== undefined) { + handler.typeCheck(context, node, this.analysis.get(node) !.analysis); + } + }); + } + /** * Perform a compilation operation on the given class declaration and return instructions to an * AST transformer if any are available.