feat(compiler): reuse the TypeScript typecheck for template typechecking. (#19152)
This speeds up the compilation process significantly. Also introduces a new option `fullTemplateTypeCheck` to do more checks in templates: - check expressions inside of templatized content (e.g. inside of `<div *ngIf>`). - check the arguments of calls to the `transform` function of pipes - check references to directives that were exposed as variables via `exportAs` PR Close #19152
This commit is contained in:

committed by
Matias Niemelä

parent
554fe65690
commit
996c7c2dde
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompileProviderMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, createHostComponentMeta, flatten, identifierName, sourceUrl, templateSourceUrl} from '../compile_metadata';
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, createHostComponentMeta, flatten, identifierName, sourceUrl, templateSourceUrl} from '../compile_metadata';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {MessageBundle} from '../i18n/message_bundle';
|
||||
import {Identifiers, createTokenForExternalReference} from '../identifiers';
|
||||
@ -19,8 +19,10 @@ import * as o from '../output/output_ast';
|
||||
import {ParseError} from '../parse_util';
|
||||
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||
import {SummaryResolver} from '../summary_resolver';
|
||||
import {TemplateAst} from '../template_parser/template_ast';
|
||||
import {TemplateParser} from '../template_parser/template_parser';
|
||||
import {OutputContext, syntaxError} from '../util';
|
||||
import {TypeCheckCompiler} from '../view_compiler/type_check_compiler';
|
||||
import {ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
|
||||
|
||||
import {AotCompilerHost} from './compiler_host';
|
||||
@ -32,11 +34,15 @@ import {createForJitStub, serializeSummaries} from './summary_serializer';
|
||||
import {ngfactoryFilePath, splitTypescriptSuffix, summaryFileName, summaryForJitFileName, summaryForJitName} from './util';
|
||||
|
||||
export class AotCompiler {
|
||||
private _templateAstCache =
|
||||
new Map<StaticSymbol, {template: TemplateAst[], pipes: CompilePipeSummary[]}>();
|
||||
|
||||
constructor(
|
||||
private _config: CompilerConfig, private _host: AotCompilerHost,
|
||||
private _reflector: StaticReflector, private _metadataResolver: CompileMetadataResolver,
|
||||
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
|
||||
private _viewCompiler: ViewCompiler, private _ngModuleCompiler: NgModuleCompiler,
|
||||
private _htmlParser: HtmlParser, private _templateParser: TemplateParser,
|
||||
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
|
||||
private _typeCheckCompiler: TypeCheckCompiler, private _ngModuleCompiler: NgModuleCompiler,
|
||||
private _outputEmitter: OutputEmitter,
|
||||
private _summaryResolver: SummaryResolver<StaticSymbol>, private _localeId: string|null,
|
||||
private _translationFormat: string|null, private _enableSummariesForJit: boolean|null,
|
||||
@ -66,9 +72,10 @@ export class AotCompiler {
|
||||
}
|
||||
|
||||
emitAllStubs(analyzeResult: NgAnalyzedModules): GeneratedFile[] {
|
||||
const {files} = analyzeResult;
|
||||
const {files, ngModuleByPipeOrDirective} = analyzeResult;
|
||||
const sourceModules = files.map(
|
||||
file => this._compileStubFile(file.srcUrl, file.directives, file.pipes, file.ngModules));
|
||||
file => this._compileStubFile(
|
||||
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes, file.ngModules));
|
||||
return flatten(sourceModules);
|
||||
}
|
||||
|
||||
@ -112,7 +119,8 @@ export class AotCompiler {
|
||||
}
|
||||
|
||||
private _compileStubFile(
|
||||
srcFileUrl: string, directives: StaticSymbol[], pipes: StaticSymbol[],
|
||||
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||
directives: StaticSymbol[], pipes: StaticSymbol[],
|
||||
ngModules: StaticSymbol[]): GeneratedFile[] {
|
||||
const fileSuffix = splitTypescriptSuffix(srcFileUrl, true)[1];
|
||||
const generatedFiles: GeneratedFile[] = [];
|
||||
@ -130,10 +138,17 @@ export class AotCompiler {
|
||||
// the generated code)
|
||||
directives.forEach((dirType) => {
|
||||
const compMeta = this._metadataResolver.getDirectiveMetadata(<any>dirType);
|
||||
|
||||
if (!compMeta.isComponent) {
|
||||
return;
|
||||
}
|
||||
const ngModule = ngModuleByPipeOrDirective.get(dirType);
|
||||
if (!ngModule) {
|
||||
throw new Error(
|
||||
`Internal Error: cannot determine the module for component ${identifierName(compMeta.type)}!`);
|
||||
}
|
||||
this._compileComponentTypeCheckBlock(
|
||||
ngFactoryOutputCtx, compMeta, ngModule, ngModule.transitiveModule.directives);
|
||||
|
||||
// Note: compMeta is a component and therefore template is non null.
|
||||
compMeta.template !.externalStylesheets.forEach((stylesheetMeta) => {
|
||||
const styleContext = this._createOutputContext(_stylesModuleUrl(
|
||||
@ -285,7 +300,8 @@ export class AotCompiler {
|
||||
ngModule: CompileNgModuleMetadata, fileSuffix: string): void {
|
||||
const hostType = this._metadataResolver.getHostComponentType(compMeta.type.reference);
|
||||
const hostMeta = createHostComponentMeta(
|
||||
hostType, compMeta, this._metadataResolver.getHostComponentViewClass(hostType));
|
||||
hostType, compMeta, this._metadataResolver.getHostComponentViewClass(hostType),
|
||||
this._htmlParser);
|
||||
const hostViewFactoryVar =
|
||||
this._compileComponent(outputCtx, hostMeta, ngModule, [compMeta.type], null, fileSuffix)
|
||||
.viewClassVar;
|
||||
@ -320,19 +336,32 @@ export class AotCompiler {
|
||||
[o.StmtModifier.Final, o.StmtModifier.Exported]));
|
||||
}
|
||||
|
||||
private _compileComponent(
|
||||
outputCtx: OutputContext, compMeta: CompileDirectiveMetadata,
|
||||
ngModule: CompileNgModuleMetadata, directiveIdentifiers: CompileIdentifierMetadata[],
|
||||
componentStyles: CompiledStylesheet|null, fileSuffix: string): ViewCompileResult {
|
||||
private _parseTemplate(
|
||||
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata,
|
||||
directiveIdentifiers: CompileIdentifierMetadata[]):
|
||||
{template: TemplateAst[], pipes: CompilePipeSummary[]} {
|
||||
let result = this._templateAstCache.get(compMeta.type.reference);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
const preserveWhitespaces = compMeta !.template !.preserveWhitespaces;
|
||||
const directives =
|
||||
directiveIdentifiers.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference));
|
||||
const pipes = ngModule.transitiveModule.pipes.map(
|
||||
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
|
||||
|
||||
const preserveWhitespaces = compMeta !.template !.preserveWhitespaces;
|
||||
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
|
||||
compMeta, compMeta.template !.template !, directives, pipes, ngModule.schemas,
|
||||
result = this._templateParser.parse(
|
||||
compMeta, compMeta.template !.htmlAst !, directives, pipes, ngModule.schemas,
|
||||
templateSourceUrl(ngModule.type, compMeta, compMeta.template !), preserveWhitespaces);
|
||||
this._templateAstCache.set(compMeta.type.reference, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private _compileComponent(
|
||||
outputCtx: OutputContext, compMeta: CompileDirectiveMetadata,
|
||||
ngModule: CompileNgModuleMetadata, directiveIdentifiers: CompileIdentifierMetadata[],
|
||||
componentStyles: CompiledStylesheet|null, fileSuffix: string): ViewCompileResult {
|
||||
const {template: parsedTemplate, pipes: usedPipes} =
|
||||
this._parseTemplate(compMeta, ngModule, directiveIdentifiers);
|
||||
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
|
||||
const viewResult = this._viewCompiler.compileComponent(
|
||||
outputCtx, compMeta, parsedTemplate, stylesExpr, usedPipes);
|
||||
@ -344,6 +373,14 @@ export class AotCompiler {
|
||||
return viewResult;
|
||||
}
|
||||
|
||||
private _compileComponentTypeCheckBlock(
|
||||
outputCtx: OutputContext, compMeta: CompileDirectiveMetadata,
|
||||
ngModule: CompileNgModuleMetadata, directiveIdentifiers: CompileIdentifierMetadata[]) {
|
||||
const {template: parsedTemplate, pipes: usedPipes} =
|
||||
this._parseTemplate(compMeta, ngModule, directiveIdentifiers);
|
||||
this._typeCheckCompiler.compileComponent(outputCtx, compMeta, parsedTemplate, usedPipes);
|
||||
}
|
||||
|
||||
private _createOutputContext(genFilePath: string): OutputContext {
|
||||
const importExpr = (symbol: StaticSymbol, typeParams: o.Type[] | null = null) => {
|
||||
if (!(symbol instanceof StaticSymbol)) {
|
||||
|
@ -24,6 +24,7 @@ import {StyleCompiler} from '../style_compiler';
|
||||
import {TemplateParser} from '../template_parser/template_parser';
|
||||
import {UrlResolver} from '../url_resolver';
|
||||
import {syntaxError} from '../util';
|
||||
import {TypeCheckCompiler} from '../view_compiler/type_check_compiler';
|
||||
import {ViewCompiler} from '../view_compiler/view_compiler';
|
||||
|
||||
import {AotCompiler} from './compiler';
|
||||
@ -81,9 +82,11 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
|
||||
console, symbolCache, staticReflector);
|
||||
// TODO(vicb): do not pass options.i18nFormat here
|
||||
const viewCompiler = new ViewCompiler(config, staticReflector, elementSchemaRegistry);
|
||||
const typeCheckCompiler = new TypeCheckCompiler(options, staticReflector);
|
||||
const compiler = new AotCompiler(
|
||||
config, compilerHost, staticReflector, resolver, tmplParser, new StyleCompiler(urlResolver),
|
||||
viewCompiler, new NgModuleCompiler(staticReflector), new TypeScriptEmitter(), summaryResolver,
|
||||
config, compilerHost, staticReflector, resolver, htmlParser, tmplParser,
|
||||
new StyleCompiler(urlResolver), viewCompiler, typeCheckCompiler,
|
||||
new NgModuleCompiler(staticReflector), new TypeScriptEmitter(), summaryResolver,
|
||||
options.locale || null, options.i18nFormat || null, options.enableSummariesForJit || null,
|
||||
symbolResolver);
|
||||
return {compiler, reflector: staticReflector};
|
||||
|
@ -16,4 +16,5 @@ export interface AotCompilerOptions {
|
||||
enableLegacyTemplate?: boolean;
|
||||
enableSummariesForJit?: boolean;
|
||||
preserveWhitespaces?: boolean;
|
||||
fullTemplateTypeCheck?: boolean;
|
||||
}
|
||||
|
@ -9,6 +9,9 @@
|
||||
import {StaticSymbol} from './aot/static_symbol';
|
||||
import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from './core';
|
||||
import {LifecycleHooks} from './lifecycle_reflector';
|
||||
import * as html from './ml_parser/ast';
|
||||
import {HtmlParser} from './ml_parser/html_parser';
|
||||
import {ParseTreeResult as HtmlParseTreeResult} from './ml_parser/parser';
|
||||
import {CssSelector} from './selector';
|
||||
import {splitAtColon, stringify} from './util';
|
||||
|
||||
@ -244,6 +247,7 @@ export class CompileTemplateMetadata {
|
||||
encapsulation: ViewEncapsulation|null;
|
||||
template: string|null;
|
||||
templateUrl: string|null;
|
||||
htmlAst: HtmlParseTreeResult|null;
|
||||
isInline: boolean;
|
||||
styles: string[];
|
||||
styleUrls: string[];
|
||||
@ -252,11 +256,13 @@ export class CompileTemplateMetadata {
|
||||
ngContentSelectors: string[];
|
||||
interpolation: [string, string]|null;
|
||||
preserveWhitespaces: boolean;
|
||||
constructor({encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets,
|
||||
animations, ngContentSelectors, interpolation, isInline, preserveWhitespaces}: {
|
||||
constructor({encapsulation, template, templateUrl, htmlAst, styles, styleUrls,
|
||||
externalStylesheets, animations, ngContentSelectors, interpolation, isInline,
|
||||
preserveWhitespaces}: {
|
||||
encapsulation: ViewEncapsulation | null,
|
||||
template: string|null,
|
||||
templateUrl: string|null,
|
||||
htmlAst: HtmlParseTreeResult|null,
|
||||
styles: string[],
|
||||
styleUrls: string[],
|
||||
externalStylesheets: CompileStylesheetMetadata[],
|
||||
@ -269,6 +275,7 @@ export class CompileTemplateMetadata {
|
||||
this.encapsulation = encapsulation;
|
||||
this.template = template;
|
||||
this.templateUrl = templateUrl;
|
||||
this.htmlAst = htmlAst;
|
||||
this.styles = _normalizeArray(styles);
|
||||
this.styleUrls = _normalizeArray(styleUrls);
|
||||
this.externalStylesheets = _normalizeArray(externalStylesheets);
|
||||
@ -503,15 +510,18 @@ export class CompileDirectiveMetadata {
|
||||
*/
|
||||
export function createHostComponentMeta(
|
||||
hostTypeReference: any, compMeta: CompileDirectiveMetadata,
|
||||
hostViewType: StaticSymbol | ProxyClass): CompileDirectiveMetadata {
|
||||
hostViewType: StaticSymbol | ProxyClass, htmlParser: HtmlParser): CompileDirectiveMetadata {
|
||||
const template = CssSelector.parse(compMeta.selector !)[0].getMatchingElementTemplate();
|
||||
const templateUrl = '';
|
||||
const htmlAst = htmlParser.parse(template, templateUrl);
|
||||
return CompileDirectiveMetadata.create({
|
||||
isHost: true,
|
||||
type: {reference: hostTypeReference, diDeps: [], lifecycleHooks: []},
|
||||
template: new CompileTemplateMetadata({
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
template: template,
|
||||
templateUrl: '',
|
||||
template,
|
||||
templateUrl,
|
||||
htmlAst,
|
||||
styles: [],
|
||||
styleUrls: [],
|
||||
ngContentSelectors: [],
|
||||
|
@ -30,7 +30,8 @@ export class CompilerConfig {
|
||||
jitDevMode?: boolean,
|
||||
missingTranslation?: MissingTranslationStrategy,
|
||||
enableLegacyTemplate?: boolean,
|
||||
preserveWhitespaces?: boolean
|
||||
preserveWhitespaces?: boolean,
|
||||
fullTemplateTypeCheck?: boolean
|
||||
} = {}) {
|
||||
this.defaultEncapsulation = defaultEncapsulation;
|
||||
this.useJit = !!useJit;
|
||||
|
@ -150,7 +150,8 @@ export class DirectiveNormalizer {
|
||||
return new CompileTemplateMetadata({
|
||||
encapsulation,
|
||||
template,
|
||||
templateUrl: templateAbsUrl, styles, styleUrls,
|
||||
templateUrl: templateAbsUrl,
|
||||
htmlAst: rootNodesAndErrors, styles, styleUrls,
|
||||
ngContentSelectors: visitor.ngContentSelectors,
|
||||
animations: prenormData.animations,
|
||||
interpolation: prenormData.interpolation, isInline,
|
||||
@ -168,6 +169,7 @@ export class DirectiveNormalizer {
|
||||
encapsulation: templateMeta.encapsulation,
|
||||
template: templateMeta.template,
|
||||
templateUrl: templateMeta.templateUrl,
|
||||
htmlAst: templateMeta.htmlAst,
|
||||
styles: templateMeta.styles,
|
||||
styleUrls: templateMeta.styleUrls,
|
||||
externalStylesheets: externalStylesheets,
|
||||
|
@ -11,6 +11,7 @@ import {CompileReflector} from '../compile_reflector';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {Type} from '../core';
|
||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||
import {HtmlParser} from '../ml_parser/html_parser';
|
||||
import {NgModuleCompiler} from '../ng_module_compiler';
|
||||
import * as ir from '../output/output_ast';
|
||||
import {interpretStatements} from '../output/output_interpreter';
|
||||
@ -43,11 +44,11 @@ export class JitCompiler {
|
||||
private _sharedStylesheetCount = 0;
|
||||
|
||||
constructor(
|
||||
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
|
||||
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
|
||||
private _ngModuleCompiler: NgModuleCompiler, private _summaryResolver: SummaryResolver<Type>,
|
||||
private _reflector: CompileReflector, private _compilerConfig: CompilerConfig,
|
||||
private _console: Console,
|
||||
private _metadataResolver: CompileMetadataResolver, private _htmlParser: HtmlParser,
|
||||
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
|
||||
private _viewCompiler: ViewCompiler, private _ngModuleCompiler: NgModuleCompiler,
|
||||
private _summaryResolver: SummaryResolver<Type>, private _reflector: CompileReflector,
|
||||
private _compilerConfig: CompilerConfig, private _console: Console,
|
||||
private getExtraNgModuleProviders: (ngModule: any) => CompileProviderMetadata[]) {}
|
||||
|
||||
compileModuleSync(moduleType: Type): object {
|
||||
@ -228,7 +229,7 @@ export class JitCompiler {
|
||||
|
||||
const hostClass = this._metadataResolver.getHostComponentType(compType);
|
||||
const hostMeta = createHostComponentMeta(
|
||||
hostClass, compMeta, (compMeta.componentFactory as any).viewDefFactory);
|
||||
hostClass, compMeta, (compMeta.componentFactory as any).viewDefFactory, this._htmlParser);
|
||||
compiledTemplate =
|
||||
new CompiledTemplate(true, compMeta.type, hostMeta, ngModule, [compMeta.type]);
|
||||
this._compiledHostTemplateCache.set(compType, compiledTemplate);
|
||||
|
@ -261,6 +261,7 @@ export class CompileMetadataResolver {
|
||||
encapsulation: noUndefined(compMeta.encapsulation),
|
||||
template: noUndefined(compMeta.template),
|
||||
templateUrl: noUndefined(compMeta.templateUrl),
|
||||
htmlAst: null,
|
||||
styles: compMeta.styles || [],
|
||||
styleUrls: compMeta.styleUrls || [],
|
||||
animations: animations || [],
|
||||
|
@ -1141,8 +1141,8 @@ export function importType(
|
||||
}
|
||||
|
||||
export function expressionType(
|
||||
expr: Expression, typeModifiers: TypeModifier[] | null = null): ExpressionType|null {
|
||||
return expr != null ? new ExpressionType(expr, typeModifiers) ! : null;
|
||||
expr: Expression, typeModifiers: TypeModifier[] | null = null): ExpressionType {
|
||||
return new ExpressionType(expr, typeModifiers);
|
||||
}
|
||||
|
||||
export function literalArr(
|
||||
|
@ -101,8 +101,9 @@ export class TemplateParser {
|
||||
public transforms: TemplateAstVisitor[]) {}
|
||||
|
||||
parse(
|
||||
component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveSummary[],
|
||||
pipes: CompilePipeSummary[], schemas: SchemaMetadata[], templateUrl: string,
|
||||
component: CompileDirectiveMetadata, template: string|ParseTreeResult,
|
||||
directives: CompileDirectiveSummary[], pipes: CompilePipeSummary[], schemas: SchemaMetadata[],
|
||||
templateUrl: string,
|
||||
preserveWhitespaces: boolean): {template: TemplateAst[], pipes: CompilePipeSummary[]} {
|
||||
const result = this.tryParse(
|
||||
component, template, directives, pipes, schemas, templateUrl, preserveWhitespaces);
|
||||
@ -126,11 +127,13 @@ export class TemplateParser {
|
||||
}
|
||||
|
||||
tryParse(
|
||||
component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveSummary[],
|
||||
pipes: CompilePipeSummary[], schemas: SchemaMetadata[], templateUrl: string,
|
||||
preserveWhitespaces: boolean): TemplateParseResult {
|
||||
let htmlParseResult = this._htmlParser !.parse(
|
||||
template, templateUrl, true, this.getInterpolationConfig(component));
|
||||
component: CompileDirectiveMetadata, template: string|ParseTreeResult,
|
||||
directives: CompileDirectiveSummary[], pipes: CompilePipeSummary[], schemas: SchemaMetadata[],
|
||||
templateUrl: string, preserveWhitespaces: boolean): TemplateParseResult {
|
||||
let htmlParseResult = typeof template === 'string' ?
|
||||
this._htmlParser !.parse(
|
||||
template, templateUrl, true, this.getInterpolationConfig(component)) :
|
||||
template;
|
||||
|
||||
if (!preserveWhitespaces) {
|
||||
htmlParseResult = removeWhitespaces(htmlParseResult);
|
||||
|
283
packages/compiler/src/view_compiler/type_check_compiler.ts
Normal file
283
packages/compiler/src/view_compiler/type_check_compiler.ts
Normal file
@ -0,0 +1,283 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AotCompilerOptions} from '../aot/compiler_options';
|
||||
import {StaticReflector} from '../aot/static_reflector';
|
||||
import {StaticSymbol} from '../aot/static_symbol';
|
||||
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompilePipeSummary, viewClassName} from '../compile_metadata';
|
||||
import {BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
||||
import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast';
|
||||
import {Identifiers} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {convertValueToOutputAst} from '../output/value_util';
|
||||
import {ParseSourceSpan} from '../parse_util';
|
||||
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAst, ProviderAstType, QueryMatch, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
|
||||
import {OutputContext} from '../util';
|
||||
|
||||
|
||||
/**
|
||||
* Generates code that is used to type check templates.
|
||||
*/
|
||||
export class TypeCheckCompiler {
|
||||
constructor(private options: AotCompilerOptions, private reflector: StaticReflector) {}
|
||||
|
||||
compileComponent(
|
||||
outputCtx: OutputContext, component: CompileDirectiveMetadata, template: TemplateAst[],
|
||||
usedPipes: CompilePipeSummary[]): void {
|
||||
const pipes = new Map<string, StaticSymbol>();
|
||||
usedPipes.forEach(p => pipes.set(p.name, p.type.reference));
|
||||
let embeddedViewCount = 0;
|
||||
const viewBuilderFactory = (parent: ViewBuilder | null): ViewBuilder => {
|
||||
const embeddedViewIndex = embeddedViewCount++;
|
||||
return new ViewBuilder(
|
||||
this.options, this.reflector, outputCtx, parent, component.type.reference,
|
||||
embeddedViewIndex, pipes, viewBuilderFactory);
|
||||
};
|
||||
|
||||
const visitor = viewBuilderFactory(null);
|
||||
visitor.visitAll([], template);
|
||||
|
||||
outputCtx.statements.push(...visitor.build());
|
||||
}
|
||||
}
|
||||
|
||||
interface ViewBuilderFactory {
|
||||
(parent: ViewBuilder): ViewBuilder;
|
||||
}
|
||||
|
||||
// Note: This is used as key in Map and should therefore be
|
||||
// unique per value.
|
||||
type OutputVarType = o.BuiltinTypeName | StaticSymbol;
|
||||
|
||||
interface Expression {
|
||||
context: OutputVarType;
|
||||
sourceSpan: ParseSourceSpan;
|
||||
value: AST;
|
||||
}
|
||||
|
||||
class ViewBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
private outputVarTypes = new Map<string, OutputVarType>();
|
||||
private outputVarNames = new Map<OutputVarType, string>();
|
||||
private refOutputVars = new Map<string, OutputVarType>();
|
||||
private variables: VariableAst[] = [];
|
||||
private children: ViewBuilder[] = [];
|
||||
private updates: Expression[] = [];
|
||||
private actions: Expression[] = [];
|
||||
|
||||
constructor(
|
||||
private options: AotCompilerOptions, private reflector: StaticReflector,
|
||||
private outputCtx: OutputContext, private parent: ViewBuilder|null,
|
||||
private component: StaticSymbol, private embeddedViewIndex: number,
|
||||
private pipes: Map<string, StaticSymbol>, private viewBuilderFactory: ViewBuilderFactory) {}
|
||||
|
||||
private getOrAddOutputVar(type: o.BuiltinTypeName|StaticSymbol): string {
|
||||
let varName = this.outputVarNames.get(type);
|
||||
if (!varName) {
|
||||
varName = `_v${this.outputVarNames.size}`;
|
||||
this.outputVarNames.set(type, varName);
|
||||
this.outputVarTypes.set(varName, type);
|
||||
}
|
||||
return varName;
|
||||
}
|
||||
|
||||
visitAll(variables: VariableAst[], astNodes: TemplateAst[]) {
|
||||
this.variables = variables;
|
||||
templateVisitAll(this, astNodes);
|
||||
}
|
||||
|
||||
build(targetStatements: o.Statement[] = []): o.Statement[] {
|
||||
this.children.forEach((child) => child.build(targetStatements));
|
||||
|
||||
const viewStmts: o.Statement[] = [];
|
||||
let bindingCount = 0;
|
||||
this.updates.forEach((expression) => {
|
||||
const {sourceSpan, context, value} = this.preprocessUpdateExpression(expression);
|
||||
const bindingId = `${bindingCount++}`;
|
||||
const nameResolver = context === this.component ? this : null;
|
||||
const {stmts, currValExpr} = convertPropertyBinding(
|
||||
nameResolver, o.variable(this.getOrAddOutputVar(context)), value, bindingId);
|
||||
stmts.push(new o.ExpressionStatement(currValExpr));
|
||||
viewStmts.push(...stmts.map(
|
||||
(stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
|
||||
});
|
||||
|
||||
this.actions.forEach(({sourceSpan, context, value}) => {
|
||||
const bindingId = `${bindingCount++}`;
|
||||
const nameResolver = context === this.component ? this : null;
|
||||
const {stmts} = convertActionBinding(
|
||||
nameResolver, o.variable(this.getOrAddOutputVar(context)), value, bindingId);
|
||||
viewStmts.push(...stmts.map(
|
||||
(stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
|
||||
});
|
||||
|
||||
const viewName = `_View_${this.component.name}_${this.embeddedViewIndex}`;
|
||||
const params: o.FnParam[] = [];
|
||||
this.outputVarNames.forEach((varName, varType) => {
|
||||
const outputType = varType instanceof StaticSymbol ?
|
||||
o.expressionType(this.outputCtx.importExpr(varType)) :
|
||||
new o.BuiltinType(varType);
|
||||
params.push(new o.FnParam(varName, outputType));
|
||||
});
|
||||
|
||||
const viewFactory = new o.DeclareFunctionStmt(viewName, params, viewStmts);
|
||||
targetStatements.push(viewFactory);
|
||||
return targetStatements;
|
||||
}
|
||||
|
||||
visitBoundText(ast: BoundTextAst, context: any): any {
|
||||
const astWithSource = <ASTWithSource>ast.value;
|
||||
const inter = <Interpolation>astWithSource.ast;
|
||||
|
||||
inter.expressions.forEach(
|
||||
(expr) =>
|
||||
this.updates.push({context: this.component, value: expr, sourceSpan: ast.sourceSpan}));
|
||||
}
|
||||
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
|
||||
this.visitElementOrTemplate(ast);
|
||||
// Note: The old view compiler used to use an `any` type
|
||||
// for the context in any embedded view.
|
||||
// We keep this behaivor behind a flag for now.
|
||||
if (this.options.fullTemplateTypeCheck) {
|
||||
const childVisitor = this.viewBuilderFactory(this);
|
||||
this.children.push(childVisitor);
|
||||
childVisitor.visitAll(ast.variables, ast.children);
|
||||
}
|
||||
}
|
||||
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
this.visitElementOrTemplate(ast);
|
||||
|
||||
let inputDefs: o.Expression[] = [];
|
||||
let updateRendererExpressions: Expression[] = [];
|
||||
let outputDefs: o.Expression[] = [];
|
||||
ast.inputs.forEach((inputAst) => {
|
||||
this.updates.push(
|
||||
{context: this.component, value: inputAst.value, sourceSpan: inputAst.sourceSpan});
|
||||
});
|
||||
|
||||
templateVisitAll(this, ast.children);
|
||||
}
|
||||
|
||||
private visitElementOrTemplate(ast: {
|
||||
outputs: BoundEventAst[],
|
||||
directives: DirectiveAst[],
|
||||
references: ReferenceAst[],
|
||||
}) {
|
||||
ast.directives.forEach((dirAst) => { this.visitDirective(dirAst); });
|
||||
|
||||
ast.references.forEach((ref) => {
|
||||
let outputVarType: OutputVarType = null !;
|
||||
// Note: The old view compiler used to use an `any` type
|
||||
// for directives exposed via `exportAs`.
|
||||
// We keep this behaivor behind a flag for now.
|
||||
if (ref.value && ref.value.identifier && this.options.fullTemplateTypeCheck) {
|
||||
outputVarType = ref.value.identifier.reference;
|
||||
} else {
|
||||
outputVarType = o.BuiltinTypeName.Dynamic;
|
||||
}
|
||||
this.refOutputVars.set(ref.name, outputVarType);
|
||||
});
|
||||
ast.outputs.forEach((outputAst) => {
|
||||
this.actions.push(
|
||||
{context: this.component, value: outputAst.handler, sourceSpan: outputAst.sourceSpan});
|
||||
});
|
||||
}
|
||||
|
||||
visitDirective(dirAst: DirectiveAst) {
|
||||
const dirType = dirAst.directive.type.reference;
|
||||
dirAst.inputs.forEach(
|
||||
(input) => this.updates.push(
|
||||
{context: this.component, value: input.value, sourceSpan: input.sourceSpan}));
|
||||
// Note: The old view compiler used to use an `any` type
|
||||
// for expressions in host properties / events.
|
||||
// We keep this behaivor behind a flag for now.
|
||||
if (this.options.fullTemplateTypeCheck) {
|
||||
dirAst.hostProperties.forEach(
|
||||
(inputAst) => this.updates.push(
|
||||
{context: dirType, value: inputAst.value, sourceSpan: inputAst.sourceSpan}));
|
||||
dirAst.hostEvents.forEach((hostEventAst) => this.actions.push({
|
||||
context: dirType,
|
||||
value: hostEventAst.handler,
|
||||
sourceSpan: hostEventAst.sourceSpan
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
getLocal(name: string): o.Expression|null {
|
||||
if (name == EventHandlerVars.event.name) {
|
||||
return o.variable(this.getOrAddOutputVar(o.BuiltinTypeName.Dynamic));
|
||||
}
|
||||
for (let currBuilder: ViewBuilder|null = this; currBuilder; currBuilder = currBuilder.parent) {
|
||||
let outputVarType: OutputVarType|undefined;
|
||||
// check references
|
||||
outputVarType = currBuilder.refOutputVars.get(name);
|
||||
if (outputVarType == null) {
|
||||
// check variables
|
||||
const varAst = currBuilder.variables.find((varAst) => varAst.name === name);
|
||||
if (varAst) {
|
||||
outputVarType = o.BuiltinTypeName.Dynamic;
|
||||
}
|
||||
}
|
||||
if (outputVarType != null) {
|
||||
return o.variable(this.getOrAddOutputVar(outputVarType));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private pipeOutputVar(name: string): string {
|
||||
const pipe = this.pipes.get(name);
|
||||
if (!pipe) {
|
||||
throw new Error(
|
||||
`Illegal State: Could not find pipe ${name} in template of ${this.component}`);
|
||||
}
|
||||
return this.getOrAddOutputVar(pipe);
|
||||
}
|
||||
|
||||
private preprocessUpdateExpression(expression: Expression): Expression {
|
||||
return {
|
||||
sourceSpan: expression.sourceSpan,
|
||||
context: expression.context,
|
||||
value: convertPropertyBindingBuiltins(
|
||||
{
|
||||
createLiteralArrayConverter: (argCount: number) => (args: o.Expression[]) =>
|
||||
o.literalArr(args),
|
||||
createLiteralMapConverter: (keys: {key: string, quoted: boolean}[]) =>
|
||||
(values: o.Expression[]) => {
|
||||
const entries = keys.map((k, i) => ({
|
||||
key: k.key,
|
||||
value: values[i],
|
||||
quoted: k.quoted,
|
||||
}));
|
||||
return o.literalMap(entries);
|
||||
},
|
||||
createPipeConverter: (name: string, argCount: number) => (args: o.Expression[]) => {
|
||||
// Note: The old view compiler used to use an `any` type
|
||||
// for pipe calls.
|
||||
// We keep this behaivor behind a flag for now.
|
||||
if (this.options.fullTemplateTypeCheck) {
|
||||
return o.variable(this.pipeOutputVar(name)).callMethod('transform', args);
|
||||
} else {
|
||||
return o.variable(this.getOrAddOutputVar(o.BuiltinTypeName.Dynamic));
|
||||
}
|
||||
},
|
||||
},
|
||||
expression.value)
|
||||
};
|
||||
}
|
||||
|
||||
visitNgContent(ast: NgContentAst, context: any): any {}
|
||||
visitText(ast: TextAst, context: any): any {}
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {}
|
||||
visitReference(ast: ReferenceAst, context: any): any {}
|
||||
visitVariable(ast: VariableAst, context: any): any {}
|
||||
visitEvent(ast: BoundEventAst, context: any): any {}
|
||||
visitElementProperty(ast: BoundElementPropertyAst, context: any): any {}
|
||||
visitAttr(ast: AttrAst, context: any): any {}
|
||||
}
|
Reference in New Issue
Block a user