feat: introduce source maps for templates (#15011)

The main use case for the generated source maps is to give
errors a meaningful context in terms of the original source
that the user wrote.

Related changes that are included in this commit:

* renamed virtual folders used for jit:
  * ng://<module type>/module.ngfactory.js
  * ng://<module type>/<comp type>.ngfactory.js
  * ng://<module type>/<comp type>.html (for inline templates)
* error logging:
  * all errors that happen in templates are logged
    from the place of the nearest element.
  * instead of logging error messages and stacks separately,
    we log the actual error. This is needed so that browsers apply
    source maps to the stack correctly.
  * error type and error is logged as one log entry.

Note that long-stack-trace zone has a bug that 
disables source maps for stack traces,
see https://github.com/angular/zone.js/issues/661.

BREAKING CHANGE:

- DebugNode.source no more returns the source location of a node.  

Closes 14013
This commit is contained in:
Tobias Bosch
2017-03-14 09:16:15 -07:00
committed by Chuck Jazdzewski
parent 1c1085b140
commit cdc882bd36
48 changed files with 1196 additions and 515 deletions

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, componentFactoryName, createHostComponentMeta, flatten, identifierName} from '../compile_metadata';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, componentFactoryName, createHostComponentMeta, flatten, identifierName, templateSourceUrl} from '../compile_metadata';
import {CompilerConfig} from '../config';
import {Identifiers, createIdentifier, createIdentifierToken} from '../identifiers';
import {CompileMetadataResolver} from '../metadata_resolver';
@ -189,7 +189,7 @@ export class AotCompiler {
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
compMeta, compMeta.template.template, directives, pipes, ngModule.schemas,
identifierName(compMeta.type));
templateSourceUrl(ngModule.type, compMeta, compMeta.template));
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
const viewResult =
this._viewCompiler.compileComponent(compMeta, parsedTemplate, stylesExpr, usedPipes);

View File

@ -7,6 +7,7 @@
*/
import {ChangeDetectionStrategy, ComponentFactory, RendererType2, SchemaMetadata, Type, ViewEncapsulation, ɵLifecycleHooks, ɵreflector, ɵstringify as stringify} from '@angular/core';
import {StaticSymbol} from './aot/static_symbol';
import {CssSelector} from './selector';
import {splitAtColon} from './util';
@ -257,6 +258,7 @@ export class CompileTemplateMetadata {
encapsulation: ViewEncapsulation;
template: string;
templateUrl: string;
isInline: boolean;
styles: string[];
styleUrls: string[];
externalStylesheets: CompileStylesheetMetadata[];
@ -265,7 +267,7 @@ export class CompileTemplateMetadata {
interpolation: [string, string];
constructor(
{encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets, animations,
ngContentSelectors, interpolation}: {
ngContentSelectors, interpolation, isInline}: {
encapsulation?: ViewEncapsulation,
template?: string,
templateUrl?: string,
@ -275,6 +277,7 @@ export class CompileTemplateMetadata {
ngContentSelectors?: string[],
animations?: any[],
interpolation?: [string, string],
isInline?: boolean
} = {}) {
this.encapsulation = encapsulation;
this.template = template;
@ -288,6 +291,7 @@ export class CompileTemplateMetadata {
throw new Error(`'interpolation' should have a start and an end symbol.`);
}
this.interpolation = interpolation;
this.isInline = isInline;
}
toSummary(): CompileTemplateSummary {
@ -524,7 +528,8 @@ export function createHostComponentMeta(
styles: [],
styleUrls: [],
ngContentSelectors: [],
animations: []
animations: [],
isInline: true,
}),
changeDetection: ChangeDetectionStrategy.Default,
inputs: [],
@ -752,3 +757,41 @@ export function flatten<T>(list: Array<T|T[]>): T[] {
return (<T[]>flat).concat(flatItem);
}, []);
}
/**
* Note: Using `location.origin` as prefix helps displaying them as a hierarchy in chrome.
* It also helps long-stack-trace zone when rewriting stack traces to not break
* source maps (as now all scripts have the same origin).
*/
function ngJitFolder() {
return 'ng://';
}
export function templateSourceUrl(
ngModuleType: CompileIdentifierMetadata, compMeta: {type: CompileIdentifierMetadata},
templateMeta: {isInline: boolean, templateUrl: string}) {
if (templateMeta.isInline) {
if (compMeta.type.reference instanceof StaticSymbol) {
return compMeta.type.reference.filePath;
} else {
return `${ngJitFolder()}/${identifierName(ngModuleType)}/${identifierName(compMeta.type)}.html`;
}
} else {
return templateMeta.templateUrl;
}
}
export function sharedStylesheetJitUrl(meta: CompileStylesheetMetadata, id: number) {
const pathParts = meta.moduleUrl.split(/\/\\/g);
const baseName = pathParts[pathParts.length - 1];
return `${ngJitFolder()}/css/${id}${baseName}.ngstyle.js`;
}
export function ngModuleJitUrl(moduleMeta: CompileNgModuleMetadata): string {
return `${ngJitFolder()}/${identifierName(moduleMeta.type)}/module.ngfactory.js`;
}
export function templateJitUrl(
ngModuleType: CompileIdentifierMetadata, compMeta: CompileDirectiveMetadata): string {
return `${ngJitFolder()}/${identifierName(ngModuleType)}/${identifierName(compMeta.type)}.ngfactory.js`;
}

View File

@ -28,6 +28,7 @@ export {CompilerConfig} from './config';
export * from './compile_metadata';
export * from './aot/compiler_factory';
export * from './aot/compiler';
export * from './aot/generated_file';
export * from './aot/compiler_options';
export * from './aot/compiler_host';
export * from './aot/static_reflector';

View File

@ -7,7 +7,7 @@
*/
import {ViewEncapsulation, ɵstringify as stringify} from '@angular/core';
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata} from './compile_metadata';
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, templateSourceUrl} from './compile_metadata';
import {CompilerConfig} from './config';
import {CompilerInjectable} from './injectable';
import * as html from './ml_parser/ast';
@ -20,6 +20,7 @@ import {UrlResolver} from './url_resolver';
import {SyncAsyncResult, syntaxError} from './util';
export interface PrenormalizedTemplateMetadata {
ngModuleType: any;
componentType: any;
moduleUrl: string;
template?: string;
@ -104,20 +105,25 @@ export class DirectiveNormalizer {
}
normalizeLoadedTemplate(
prenomData: PrenormalizedTemplateMetadata, template: string,
prenormData: PrenormalizedTemplateMetadata, template: string,
templateAbsUrl: string): CompileTemplateMetadata {
const interpolationConfig = InterpolationConfig.fromArray(prenomData.interpolation);
const isInline = !!prenormData.template;
const interpolationConfig = InterpolationConfig.fromArray(prenormData.interpolation);
const rootNodesAndErrors = this._htmlParser.parse(
template, stringify(prenomData.componentType), true, interpolationConfig);
template,
templateSourceUrl(
{reference: prenormData.ngModuleType}, {type: {reference: prenormData.componentType}},
{isInline, templateUrl: templateAbsUrl}),
true, interpolationConfig);
if (rootNodesAndErrors.errors.length > 0) {
const errorString = rootNodesAndErrors.errors.join('\n');
throw syntaxError(`Template parse errors:\n${errorString}`);
}
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
styles: prenomData.styles,
styleUrls: prenomData.styleUrls,
moduleUrl: prenomData.moduleUrl
styles: prenormData.styles,
styleUrls: prenormData.styleUrls,
moduleUrl: prenormData.moduleUrl
}));
const visitor = new TemplatePreparseVisitor();
@ -125,7 +131,7 @@ export class DirectiveNormalizer {
const templateStyles = this.normalizeStylesheet(new CompileStylesheetMetadata(
{styles: visitor.styles, styleUrls: visitor.styleUrls, moduleUrl: templateAbsUrl}));
let encapsulation = prenomData.encapsulation;
let encapsulation = prenormData.encapsulation;
if (encapsulation == null) {
encapsulation = this._config.defaultEncapsulation;
}
@ -143,8 +149,8 @@ export class DirectiveNormalizer {
template,
templateUrl: templateAbsUrl, styles, styleUrls,
ngContentSelectors: visitor.ngContentSelectors,
animations: prenomData.animations,
interpolation: prenomData.interpolation,
animations: prenormData.animations,
interpolation: prenormData.interpolation, isInline
});
}
@ -160,7 +166,8 @@ export class DirectiveNormalizer {
externalStylesheets: externalStylesheets,
ngContentSelectors: templateMeta.ngContentSelectors,
animations: templateMeta.animations,
interpolation: templateMeta.interpolation
interpolation: templateMeta.interpolation,
isInline: templateMeta.isInline,
}));
}

View File

@ -210,7 +210,9 @@ enum Endian {
Big,
}
function utf8Encode(str: string): string {
// TODO(vicb): move this to some shared place, as we also need it
// for SourceMaps.
export function utf8Encode(str: string): string {
let encoded: string = '';
for (let index = 0; index < str.length; index++) {

View File

@ -8,7 +8,7 @@
import {Compiler, ComponentFactory, Inject, Injector, ModuleWithComponentFactories, NgModuleFactory, Type, ɵgetComponentViewDefinitionFactory as getComponentViewDefinitionFactory, ɵstringify as stringify} from '@angular/core';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, ProviderMeta, ProxyClass, createHostComponentMeta, identifierName} from '../compile_metadata';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileStylesheetMetadata, ProviderMeta, ProxyClass, createHostComponentMeta, identifierName, ngModuleJitUrl, sharedStylesheetJitUrl, templateJitUrl, templateSourceUrl} from '../compile_metadata';
import {CompilerConfig} from '../config';
import {CompilerInjectable} from '../injectable';
import {CompileMetadataResolver} from '../metadata_resolver';
@ -38,6 +38,7 @@ export class JitCompiler implements Compiler {
private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>();
private _compiledDirectiveWrapperCache = new Map<Type<any>, Type<any>>();
private _compiledNgModuleCache = new Map<Type<any>, NgModuleFactory<any>>();
private _sharedStylesheetCount = 0;
constructor(
private _injector: Injector, private _metadataResolver: CompileMetadataResolver,
@ -128,7 +129,7 @@ export class JitCompiler implements Compiler {
interpretStatements(compileResult.statements, [compileResult.ngModuleFactoryVar])[0];
} else {
ngModuleFactory = jitStatements(
`/${identifierName(moduleMeta.type)}/module.ngfactory.js`, compileResult.statements,
ngModuleJitUrl(moduleMeta), compileResult.statements,
[compileResult.ngModuleFactoryVar])[0];
}
this._compiledNgModuleCache.set(moduleMeta.type.reference, ngModuleFactory);
@ -251,7 +252,7 @@ export class JitCompiler implements Compiler {
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
compMeta, compMeta.template.template, directives, pipes, template.ngModule.schemas,
identifierName(compMeta.type));
templateSourceUrl(template.ngModule.type, template.compMeta, template.compMeta.template));
const compileResult = this._viewCompiler.compileComponent(
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
usedPipes);
@ -263,10 +264,9 @@ export class JitCompiler implements Compiler {
[viewClass, rendererType] = interpretStatements(
statements, [compileResult.viewClassVar, compileResult.rendererTypeVar]);
} else {
const sourceUrl =
`/${identifierName(template.ngModule.type)}/${identifierName(template.compType)}/${template.isHost?'host':'component'}.ngfactory.js`;
[viewClass, rendererType] = jitStatements(
sourceUrl, statements, [compileResult.viewClassVar, compileResult.rendererTypeVar]);
templateJitUrl(template.ngModule.type, template.compMeta), statements,
[compileResult.viewClassVar, compileResult.rendererTypeVar]);
}
template.compiled(viewClass, rendererType);
}
@ -289,7 +289,8 @@ export class JitCompiler implements Compiler {
return interpretStatements(result.statements, [result.stylesVar])[0];
} else {
return jitStatements(
`/${result.meta.moduleUrl}.ngstyle.js`, result.statements, [result.stylesVar])[0];
sharedStylesheetJitUrl(result.meta, this._sharedStylesheetCount++), result.statements,
[result.stylesVar])[0];
}
}
}

View File

@ -155,7 +155,8 @@ export class CompileMetadataResolver {
return typeSummary && typeSummary.summaryKind === kind ? typeSummary : null;
}
private _loadDirectiveMetadata(directiveType: any, isSync: boolean): Promise<any> {
private _loadDirectiveMetadata(ngModuleType: any, directiveType: any, isSync: boolean):
Promise<any> {
if (this._directiveCache.has(directiveType)) {
return;
}
@ -191,6 +192,7 @@ export class CompileMetadataResolver {
if (metadata.isComponent) {
const templateMeta = this._directiveNormalizer.normalizeTemplate({
ngModuleType,
componentType: directiveType,
moduleUrl: componentModuleUrl(this._reflector, directiveType, annotation),
encapsulation: metadata.template.encapsulation,
@ -249,7 +251,8 @@ export class CompileMetadataResolver {
styles: dirMeta.styles,
styleUrls: dirMeta.styleUrls,
animations: animations,
interpolation: dirMeta.interpolation
interpolation: dirMeta.interpolation,
isInline: !!dirMeta.template
});
}
@ -378,7 +381,7 @@ export class CompileMetadataResolver {
const loading: Promise<any>[] = [];
if (ngModule) {
ngModule.declaredDirectives.forEach((id) => {
const promise = this._loadDirectiveMetadata(id.reference, isSync);
const promise = this._loadDirectiveMetadata(moduleType, id.reference, isSync);
if (promise) {
loading.push(promise);
}

View File

@ -14,7 +14,7 @@ import {CompilerInjectable} from './injectable';
import {ClassBuilder, createClassStmt} from './output/class_builder';
import * as o from './output/output_ast';
import {convertValueToOutputAst} from './output/value_util';
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
import {ParseLocation, ParseSourceFile, ParseSourceSpan, typeSourceSpan} from './parse_util';
import {NgModuleProviderAnalyzer} from './provider_analyzer';
import {ProviderAst} from './template_parser/template_ast';
@ -37,14 +37,7 @@ export class NgModuleCompileResult {
export class NgModuleCompiler {
compile(ngModuleMeta: CompileNgModuleMetadata, extraProviders: CompileProviderMetadata[]):
NgModuleCompileResult {
const moduleUrl = identifierModuleUrl(ngModuleMeta.type);
const sourceFileName = moduleUrl != null ?
`in NgModule ${identifierName(ngModuleMeta.type)} in ${moduleUrl}` :
`in NgModule ${identifierName(ngModuleMeta.type)}`;
const sourceFile = new ParseSourceFile('', sourceFileName);
const sourceSpan = new ParseSourceSpan(
new ParseLocation(sourceFile, null, null, null),
new ParseLocation(sourceFile, null, null, null));
const sourceSpan = typeSourceSpan('NgModule', ngModuleMeta.type);
const deps: ComponentFactoryDependency[] = [];
const bootstrapComponentFactories: CompileIdentifierMetadata[] = [];
const entryComponentFactories =

View File

@ -420,7 +420,12 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex
ctx.print(ast, `}`, useNewLine);
return null;
}
visitCommaExpr(ast: o.CommaExpr, ctx: EmitterVisitorContext): any {
ctx.print(ast, '(');
this.visitAllExpressions(ast.parts, ctx, ',');
ctx.print(ast, ')');
return null;
}
visitAllExpressions(
expressions: o.Expression[], ctx: EmitterVisitorContext, separator: string,
newLine: boolean = false): void {

View File

@ -34,11 +34,12 @@ export class JavaScriptEmitter implements OutputEmitter {
srcParts.push(ctx.toSource());
const prefixLines = converter.importsWithPrefixes.size;
const sm = ctx.toSourceMapGenerator(null, prefixLines).toJsComment();
const sm = ctx.toSourceMapGenerator(genFilePath, prefixLines).toJsComment();
if (sm) {
srcParts.push(sm);
}
// always add a newline at the end, as some tools have bugs without it.
srcParts.push('');
return srcParts.join('\n');
}
}

View File

@ -466,6 +466,15 @@ export class LiteralMapExpr extends Expression {
}
}
export class CommaExpr extends Expression {
constructor(public parts: Expression[], sourceSpan?: ParseSourceSpan) {
super(parts[parts.length - 1].type, sourceSpan);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitCommaExpr(this, context);
}
}
export interface ExpressionVisitor {
visitReadVarExpr(ast: ReadVarExpr, context: any): any;
visitWriteVarExpr(expr: WriteVarExpr, context: any): any;
@ -485,6 +494,7 @@ export interface ExpressionVisitor {
visitReadKeyExpr(ast: ReadKeyExpr, context: any): any;
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): any;
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any;
visitCommaExpr(ast: CommaExpr, context: any): any;
}
export const THIS_EXPR = new ReadVarExpr(BuiltinVar.This);
@ -653,88 +663,121 @@ export interface StatementVisitor {
visitCommentStmt(stmt: CommentStmt, context: any): any;
}
export class ExpressionTransformer implements StatementVisitor, ExpressionVisitor {
visitReadVarExpr(ast: ReadVarExpr, context: any): any { return ast; }
export class AstTransformer implements StatementVisitor, ExpressionVisitor {
transformExpr(expr: Expression, context: any): Expression { return expr; }
transformStmt(stmt: Statement, context: any): Statement { return stmt; }
visitReadVarExpr(ast: ReadVarExpr, context: any): any { return this.transformExpr(ast, context); }
visitWriteVarExpr(expr: WriteVarExpr, context: any): any {
return new WriteVarExpr(
expr.name, expr.value.visitExpression(this, context), expr.type, expr.sourceSpan);
return this.transformExpr(
new WriteVarExpr(
expr.name, expr.value.visitExpression(this, context), expr.type, expr.sourceSpan),
context);
}
visitWriteKeyExpr(expr: WriteKeyExpr, context: any): any {
return new WriteKeyExpr(
expr.receiver.visitExpression(this, context), expr.index.visitExpression(this, context),
expr.value.visitExpression(this, context), expr.type, expr.sourceSpan);
return this.transformExpr(
new WriteKeyExpr(
expr.receiver.visitExpression(this, context), expr.index.visitExpression(this, context),
expr.value.visitExpression(this, context), expr.type, expr.sourceSpan),
context);
}
visitWritePropExpr(expr: WritePropExpr, context: any): any {
return new WritePropExpr(
expr.receiver.visitExpression(this, context), expr.name,
expr.value.visitExpression(this, context), expr.type, expr.sourceSpan);
return this.transformExpr(
new WritePropExpr(
expr.receiver.visitExpression(this, context), expr.name,
expr.value.visitExpression(this, context), expr.type, expr.sourceSpan),
context);
}
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): any {
const method = ast.builtin || ast.name;
return new InvokeMethodExpr(
ast.receiver.visitExpression(this, context), method,
this.visitAllExpressions(ast.args, context), ast.type, ast.sourceSpan);
return this.transformExpr(
new InvokeMethodExpr(
ast.receiver.visitExpression(this, context), method,
this.visitAllExpressions(ast.args, context), ast.type, ast.sourceSpan),
context);
}
visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: any): any {
return new InvokeFunctionExpr(
ast.fn.visitExpression(this, context), this.visitAllExpressions(ast.args, context),
ast.type, ast.sourceSpan);
return this.transformExpr(
new InvokeFunctionExpr(
ast.fn.visitExpression(this, context), this.visitAllExpressions(ast.args, context),
ast.type, ast.sourceSpan),
context);
}
visitInstantiateExpr(ast: InstantiateExpr, context: any): any {
return new InstantiateExpr(
ast.classExpr.visitExpression(this, context), this.visitAllExpressions(ast.args, context),
ast.type, ast.sourceSpan);
return this.transformExpr(
new InstantiateExpr(
ast.classExpr.visitExpression(this, context),
this.visitAllExpressions(ast.args, context), ast.type, ast.sourceSpan),
context);
}
visitLiteralExpr(ast: LiteralExpr, context: any): any { return ast; }
visitLiteralExpr(ast: LiteralExpr, context: any): any { return this.transformExpr(ast, context); }
visitExternalExpr(ast: ExternalExpr, context: any): any { return ast; }
visitExternalExpr(ast: ExternalExpr, context: any): any {
return this.transformExpr(ast, context);
}
visitConditionalExpr(ast: ConditionalExpr, context: any): any {
return new ConditionalExpr(
ast.condition.visitExpression(this, context), ast.trueCase.visitExpression(this, context),
ast.falseCase.visitExpression(this, context), ast.type, ast.sourceSpan);
return this.transformExpr(
new ConditionalExpr(
ast.condition.visitExpression(this, context),
ast.trueCase.visitExpression(this, context),
ast.falseCase.visitExpression(this, context), ast.type, ast.sourceSpan),
context);
}
visitNotExpr(ast: NotExpr, context: any): any {
return new NotExpr(ast.condition.visitExpression(this, context), ast.sourceSpan);
return this.transformExpr(
new NotExpr(ast.condition.visitExpression(this, context), ast.sourceSpan), context);
}
visitCastExpr(ast: CastExpr, context: any): any {
return new CastExpr(ast.value.visitExpression(this, context), context, ast.sourceSpan);
return this.transformExpr(
new CastExpr(ast.value.visitExpression(this, context), ast.type, ast.sourceSpan), context);
}
visitFunctionExpr(ast: FunctionExpr, context: any): any {
// Don't descend into nested functions
return ast;
return this.transformExpr(
new FunctionExpr(
ast.params, this.visitAllStatements(ast.statements, context), ast.type, ast.sourceSpan),
context);
}
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): any {
return new BinaryOperatorExpr(
ast.operator, ast.lhs.visitExpression(this, context),
ast.rhs.visitExpression(this, context), ast.type, ast.sourceSpan);
return this.transformExpr(
new BinaryOperatorExpr(
ast.operator, ast.lhs.visitExpression(this, context),
ast.rhs.visitExpression(this, context), ast.type, ast.sourceSpan),
context);
}
visitReadPropExpr(ast: ReadPropExpr, context: any): any {
return new ReadPropExpr(
ast.receiver.visitExpression(this, context), ast.name, ast.type, ast.sourceSpan);
return this.transformExpr(
new ReadPropExpr(
ast.receiver.visitExpression(this, context), ast.name, ast.type, ast.sourceSpan),
context);
}
visitReadKeyExpr(ast: ReadKeyExpr, context: any): any {
return new ReadKeyExpr(
ast.receiver.visitExpression(this, context), ast.index.visitExpression(this, context),
ast.type, ast.sourceSpan);
return this.transformExpr(
new ReadKeyExpr(
ast.receiver.visitExpression(this, context), ast.index.visitExpression(this, context),
ast.type, ast.sourceSpan),
context);
}
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): any {
return new LiteralArrayExpr(
this.visitAllExpressions(ast.entries, context), ast.type, ast.sourceSpan);
return this.transformExpr(
new LiteralArrayExpr(
this.visitAllExpressions(ast.entries, context), ast.type, ast.sourceSpan),
context);
}
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any {
@ -742,53 +785,88 @@ export class ExpressionTransformer implements StatementVisitor, ExpressionVisito
(entry): LiteralMapEntry => new LiteralMapEntry(
entry.key, entry.value.visitExpression(this, context), entry.quoted, ));
const mapType = new MapType(ast.valueType);
return new LiteralMapExpr(entries, mapType, ast.sourceSpan);
return this.transformExpr(new LiteralMapExpr(entries, mapType, ast.sourceSpan), context);
}
visitCommaExpr(ast: CommaExpr, context: any): any {
return this.transformExpr(
new CommaExpr(this.visitAllExpressions(ast.parts, context), ast.sourceSpan), context);
}
visitAllExpressions(exprs: Expression[], context: any): Expression[] {
return exprs.map(expr => expr.visitExpression(this, context));
}
visitDeclareVarStmt(stmt: DeclareVarStmt, context: any): any {
return new DeclareVarStmt(
stmt.name, stmt.value.visitExpression(this, context), stmt.type, stmt.modifiers,
stmt.sourceSpan);
return this.transformStmt(
new DeclareVarStmt(
stmt.name, stmt.value.visitExpression(this, context), stmt.type, stmt.modifiers,
stmt.sourceSpan),
context);
}
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any): any {
// Don't descend into nested functions
return stmt;
return this.transformStmt(
new DeclareFunctionStmt(
stmt.name, stmt.params, this.visitAllStatements(stmt.statements, context), stmt.type,
stmt.modifiers, stmt.sourceSpan),
context);
}
visitExpressionStmt(stmt: ExpressionStatement, context: any): any {
return new ExpressionStatement(stmt.expr.visitExpression(this, context), stmt.sourceSpan);
return this.transformStmt(
new ExpressionStatement(stmt.expr.visitExpression(this, context), stmt.sourceSpan),
context);
}
visitReturnStmt(stmt: ReturnStatement, context: any): any {
return new ReturnStatement(stmt.value.visitExpression(this, context), stmt.sourceSpan);
return this.transformStmt(
new ReturnStatement(stmt.value.visitExpression(this, context), stmt.sourceSpan), context);
}
visitDeclareClassStmt(stmt: ClassStmt, context: any): any {
// Don't descend into nested functions
return stmt;
const parent = stmt.parent.visitExpression(this, context);
const getters = stmt.getters.map(
getter => new ClassGetter(
getter.name, this.visitAllStatements(getter.body, context), getter.type,
getter.modifiers));
const ctorMethod = stmt.constructorMethod &&
new ClassMethod(stmt.constructorMethod.name, stmt.constructorMethod.params,
this.visitAllStatements(stmt.constructorMethod.body, context),
stmt.constructorMethod.type, stmt.constructorMethod.modifiers);
const methods = stmt.methods.map(
method => new ClassMethod(
method.name, method.params, this.visitAllStatements(method.body, context), method.type,
method.modifiers));
return this.transformStmt(
new ClassStmt(
stmt.name, parent, stmt.fields, getters, ctorMethod, methods, stmt.modifiers,
stmt.sourceSpan),
context);
}
visitIfStmt(stmt: IfStmt, context: any): any {
return new IfStmt(
stmt.condition.visitExpression(this, context),
this.visitAllStatements(stmt.trueCase, context),
this.visitAllStatements(stmt.falseCase, context), stmt.sourceSpan);
return this.transformStmt(
new IfStmt(
stmt.condition.visitExpression(this, context),
this.visitAllStatements(stmt.trueCase, context),
this.visitAllStatements(stmt.falseCase, context), stmt.sourceSpan),
context);
}
visitTryCatchStmt(stmt: TryCatchStmt, context: any): any {
return new TryCatchStmt(
this.visitAllStatements(stmt.bodyStmts, context),
this.visitAllStatements(stmt.catchStmts, context), stmt.sourceSpan);
return this.transformStmt(
new TryCatchStmt(
this.visitAllStatements(stmt.bodyStmts, context),
this.visitAllStatements(stmt.catchStmts, context), stmt.sourceSpan),
context);
}
visitThrowStmt(stmt: ThrowStmt, context: any): any {
return new ThrowStmt(stmt.error.visitExpression(this, context), stmt.sourceSpan);
return this.transformStmt(
new ThrowStmt(stmt.error.visitExpression(this, context), stmt.sourceSpan), context);
}
visitCommentStmt(stmt: CommentStmt, context: any): any { return stmt; }
visitCommentStmt(stmt: CommentStmt, context: any): any {
return this.transformStmt(stmt, context);
}
visitAllStatements(stmts: Statement[], context: any): Statement[] {
return stmts.map(stmt => stmt.visitStatement(this, context));
@ -796,7 +874,7 @@ export class ExpressionTransformer implements StatementVisitor, ExpressionVisito
}
export class RecursiveExpressionVisitor implements StatementVisitor, ExpressionVisitor {
export class RecursiveAstVisitor implements StatementVisitor, ExpressionVisitor {
visitReadVarExpr(ast: ReadVarExpr, context: any): any { return ast; }
visitWriteVarExpr(expr: WriteVarExpr, context: any): any {
expr.value.visitExpression(this, context);
@ -844,7 +922,10 @@ export class RecursiveExpressionVisitor implements StatementVisitor, ExpressionV
ast.value.visitExpression(this, context);
return ast;
}
visitFunctionExpr(ast: FunctionExpr, context: any): any { return ast; }
visitFunctionExpr(ast: FunctionExpr, context: any): any {
this.visitAllStatements(ast.statements, context);
return ast;
}
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): any {
ast.lhs.visitExpression(this, context);
ast.rhs.visitExpression(this, context);
@ -867,6 +948,9 @@ export class RecursiveExpressionVisitor implements StatementVisitor, ExpressionV
ast.entries.forEach((entry) => entry.value.visitExpression(this, context));
return ast;
}
visitCommaExpr(ast: CommaExpr, context: any): any {
this.visitAllExpressions(ast.parts, context);
}
visitAllExpressions(exprs: Expression[], context: any): void {
exprs.forEach(expr => expr.visitExpression(this, context));
}
@ -876,7 +960,7 @@ export class RecursiveExpressionVisitor implements StatementVisitor, ExpressionV
return stmt;
}
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any): any {
// Don't descend into nested functions
this.visitAllStatements(stmt.statements, context);
return stmt;
}
visitExpressionStmt(stmt: ExpressionStatement, context: any): any {
@ -888,7 +972,12 @@ export class RecursiveExpressionVisitor implements StatementVisitor, ExpressionV
return stmt;
}
visitDeclareClassStmt(stmt: ClassStmt, context: any): any {
// Don't descend into nested functions
stmt.parent.visitExpression(this, context);
stmt.getters.forEach(getter => this.visitAllStatements(getter.body, context));
if (stmt.constructorMethod) {
this.visitAllStatements(stmt.constructorMethod.body, context);
}
stmt.methods.forEach(method => this.visitAllStatements(method.body, context));
return stmt;
}
visitIfStmt(stmt: IfStmt, context: any): any {
@ -912,30 +1001,48 @@ export class RecursiveExpressionVisitor implements StatementVisitor, ExpressionV
}
}
export function replaceVarInExpression(
varName: string, newValue: Expression, expression: Expression): Expression {
const transformer = new _ReplaceVariableTransformer(varName, newValue);
return expression.visitExpression(transformer, null);
}
class _ReplaceVariableTransformer extends ExpressionTransformer {
constructor(private _varName: string, private _newValue: Expression) { super(); }
visitReadVarExpr(ast: ReadVarExpr, context: any): any {
return ast.name == this._varName ? this._newValue : ast;
export function applySourceSpanToStatementIfNeeded(
stmt: Statement, sourceSpan: ParseSourceSpan): Statement {
if (!sourceSpan) {
return stmt;
}
const transformer = new _ApplySourceSpanTransformer(sourceSpan);
return stmt.visitStatement(transformer, null);
}
export function findReadVarNames(stmts: Statement[]): Set<string> {
const finder = new _VariableFinder();
finder.visitAllStatements(stmts, null);
return finder.varNames;
export function applySourceSpanToExpressionIfNeeded(
expr: Expression, sourceSpan: ParseSourceSpan): Expression {
if (!sourceSpan) {
return expr;
}
const transformer = new _ApplySourceSpanTransformer(sourceSpan);
return expr.visitExpression(transformer, null);
}
class _VariableFinder extends RecursiveExpressionVisitor {
varNames = new Set<string>();
visitReadVarExpr(ast: ReadVarExpr, context: any): any {
this.varNames.add(ast.name);
return null;
class _ApplySourceSpanTransformer extends AstTransformer {
constructor(private sourceSpan: ParseSourceSpan) { super(); }
private _clone(obj: any): any {
const clone = Object.create(obj.constructor.prototype);
for (let prop in obj) {
clone[prop] = obj[prop];
}
return clone;
}
transformExpr(expr: Expression, context: any): Expression {
if (!expr.sourceSpan) {
expr = this._clone(expr);
expr.sourceSpan = this.sourceSpan;
}
return expr;
}
transformStmt(stmt: Statement, context: any): Statement {
if (!stmt.sourceSpan) {
stmt = this._clone(stmt);
stmt.sourceSpan = this.sourceSpan;
}
return stmt;
}
}

View File

@ -304,7 +304,10 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor {
(entry) => (result as any)[entry.key] = entry.value.visitExpression(this, ctx));
return result;
}
visitCommaExpr(ast: o.CommaExpr, context: any): any {
const values = this.visitAllExpressions(ast.parts, context);
return values[values.length - 1];
}
visitAllExpressions(expressions: o.Expression[], ctx: _ExecutionContext): any {
return expressions.map((expr) => expr.visitExpression(this, ctx));
}

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {isDevMode} from '@angular/core';
import {identifierName} from '../compile_metadata';
import {EmitterVisitorContext} from './abstract_emitter';
@ -14,14 +15,23 @@ import * as o from './output_ast';
function evalExpression(
sourceUrl: string, ctx: EmitterVisitorContext, vars: {[key: string]: any}): any {
const fnBody =
`${ctx.toSource()}\n//# sourceURL=${sourceUrl}\n${ctx.toSourceMapGenerator().toJsComment()}`;
let fnBody = `${ctx.toSource()}\n//# sourceURL=${sourceUrl}`;
const fnArgNames: string[] = [];
const fnArgValues: any[] = [];
for (const argName in vars) {
fnArgNames.push(argName);
fnArgValues.push(vars[argName]);
}
if (isDevMode()) {
// using `new Function(...)` generates a header, 1 line of no arguments, 2 lines otherwise
// E.g. ```
// function anonymous(a,b,c
// /**/) { ... }```
// We don't want to hard code this fact, so we auto detect it via an empty function first.
const emptyFn = new Function(...fnArgNames.concat('return null;')).toString();
const headerLines = emptyFn.slice(0, emptyFn.indexOf('return null;')).split('\n').length - 1;
fnBody += `\n${ctx.toSourceMapGenerator(sourceUrl, headerLines).toJsComment()}`;
}
return new Function(...fnArgNames.concat(fnBody))(...fnArgValues);
}

View File

@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {utf8Encode} from '../i18n/digest';
// https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
const VERSION = 3;
@ -143,7 +145,7 @@ export class SourceMapGenerator {
export function toBase64String(value: string): string {
let b64 = '';
value = utf8Encode(value);
for (let i = 0; i < value.length;) {
const i1 = value.charCodeAt(i++);
const i2 = value.charCodeAt(i++);

View File

@ -70,10 +70,12 @@ export class TypeScriptEmitter implements OutputEmitter {
srcParts.push(ctx.toSource());
const prefixLines = converter.reexports.size + converter.importsWithPrefixes.size;
const sm = ctx.toSourceMapGenerator(null, prefixLines).toJsComment();
const sm = ctx.toSourceMapGenerator(genFilePath, prefixLines).toJsComment();
if (sm) {
srcParts.push(sm);
}
// always add a newline at the end, as some tools have bugs without it.
srcParts.push('');
return srcParts.join('\n');
}

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import * as chars from './chars';
import {CompileIdentifierMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
export class ParseLocation {
constructor(
@ -124,3 +125,13 @@ export class ParseError {
return `${this.msg}${contextStr}: ${this.span.start}${details}`;
}
}
export function typeSourceSpan(kind: string, type: CompileIdentifierMetadata): ParseSourceSpan {
const moduleUrl = identifierModuleUrl(type);
const sourceFileName = moduleUrl != null ? `in ${kind} ${identifierName(type)} in ${moduleUrl}` :
`in ${kind} ${identifierName(type)}`;
const sourceFile = new ParseSourceFile('', sourceFileName);
return new ParseSourceSpan(
new ParseLocation(sourceFile, null, null, null),
new ParseLocation(sourceFile, null, null, null));
}

View File

@ -32,7 +32,7 @@ export class ProviderViewContext {
viewProviders: Map<any, boolean>;
errors: ProviderError[] = [];
constructor(public component: CompileDirectiveMetadata, public sourceSpan: ParseSourceSpan) {
constructor(public component: CompileDirectiveMetadata) {
this.viewQueries = _getViewQueries(component);
this.viewProviders = new Map<any, boolean>();
component.viewProviders.forEach((provider) => {

View File

@ -132,8 +132,7 @@ export class TemplateParser {
if (htmlAstWithErrors.rootNodes.length > 0) {
const uniqDirectives = removeSummaryDuplicates(directives);
const uniqPipes = removeSummaryDuplicates(pipes);
const providerViewContext =
new ProviderViewContext(component, htmlAstWithErrors.rootNodes[0].sourceSpan);
const providerViewContext = new ProviderViewContext(component);
let interpolationConfig: InterpolationConfig;
if (component.template && component.template.interpolation) {
interpolationConfig = {

View File

@ -16,6 +16,7 @@ import {Identifiers, createIdentifier, createIdentifierToken, resolveIdentifier}
import {CompilerInjectable} from '../injectable';
import * as o from '../output/output_ast';
import {convertValueToOutputAst} from '../output/value_util';
import {ParseSourceSpan} from '../parse_util';
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
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';
@ -82,10 +83,12 @@ interface ViewBuilderFactory {
}
interface UpdateExpression {
nodeIndex: number;
expressions: {context: o.Expression, value: AST}[];
context: o.Expression;
sourceSpan: ParseSourceSpan;
value: AST;
}
const LOG_VAR = o.variable('log');
const VIEW_VAR = o.variable('view');
const CHECK_VAR = o.variable('check');
const COMP_VAR = o.variable('comp');
@ -93,16 +96,19 @@ const NODE_INDEX_VAR = o.variable('nodeIndex');
const EVENT_NAME_VAR = o.variable('eventName');
const ALLOW_DEFAULT_VAR = o.variable(`allowDefault`);
class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverterFactory {
class ViewBuilder implements TemplateAstVisitor, LocalResolver {
private compType: o.Type;
private nodeDefs: (() => o.Expression)[] = [];
private nodes: (() => {
sourceSpan: ParseSourceSpan,
nodeDef: o.Expression,
updateDirectives?: UpdateExpression[],
updateRenderer?: UpdateExpression[]
})[] = [];
private purePipeNodeIndices: {[pipeName: string]: number} = Object.create(null);
// Need Object.create so that we don't have builtin values...
private refNodeIndices: {[refName: string]: number} = Object.create(null);
private variables: VariableAst[] = [];
private children: ViewBuilder[] = [];
private updateDirectivesExpressions: UpdateExpression[] = [];
private updateRendererExpressions: UpdateExpression[] = [];
constructor(
private parent: ViewBuilder, private component: CompileDirectiveMetadata,
@ -125,7 +131,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
if (!this.parent) {
this.usedPipes.forEach((pipe) => {
if (pipe.pure) {
this.purePipeNodeIndices[pipe.name] = this._createPipe(pipe);
this.purePipeNodeIndices[pipe.name] = this._createPipe(null, pipe);
}
});
}
@ -142,10 +148,14 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
} else {
flags |= NodeFlags.DynamicQuery;
}
this.nodeDefs.push(() => o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([
o.literal(flags), o.literal(queryId),
new o.LiteralMapExpr([new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))])
]));
this.nodes.push(() => ({
sourceSpan: null,
nodeDef: o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([
o.literal(flags), o.literal(queryId),
new o.LiteralMapExpr(
[new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))])
])
}));
});
}
templateVisitAll(this, astNodes);
@ -153,17 +163,23 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
(this.parent && needsAdditionalRootNode(astNodes[astNodes.length - 1]))) {
// if the view is empty, or an embedded view has a view container as last root nde,
// create an additional root node.
this.nodeDefs.push(() => o.importExpr(createIdentifier(Identifiers.anchorDef)).callFn([
o.literal(NodeFlags.None), o.NULL_EXPR, o.NULL_EXPR, o.literal(0)
]));
this.nodes.push(() => ({
sourceSpan: null,
nodeDef: o.importExpr(createIdentifier(Identifiers.anchorDef)).callFn([
o.literal(NodeFlags.None), o.NULL_EXPR, o.NULL_EXPR, o.literal(0)
])
}));
}
}
build(targetStatements: o.Statement[] = []): o.Statement[] {
this.children.forEach((child) => child.build(targetStatements));
const updateDirectivesFn = this._createUpdateFn(this.updateDirectivesExpressions);
const updateRendererFn = this._createUpdateFn(this.updateRendererExpressions);
const {updateRendererStmts, updateDirectivesStmts, nodeDefExprs} =
this._createNodeExpressions();
const updateRendererFn = this._createUpdateFn(updateRendererStmts);
const updateDirectivesFn = this._createUpdateFn(updateDirectivesStmts);
let viewFlags = ViewFlags.None;
@ -171,10 +187,10 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
viewFlags |= ViewFlags.OnPush;
}
const viewFactory = new o.DeclareFunctionStmt(
this.viewName, [],
this.viewName, [new o.FnParam(LOG_VAR.name)],
[new o.ReturnStatement(o.importExpr(createIdentifier(Identifiers.viewDef)).callFn([
o.literal(viewFlags),
o.literalArr(this.nodeDefs.map(nd => nd())),
o.literalArr(nodeDefExprs),
updateDirectivesFn,
updateRendererFn,
]))],
@ -184,20 +200,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
return targetStatements;
}
private _createUpdateFn(expressions: UpdateExpression[]): o.Expression {
const updateStmts: o.Statement[] = [];
let updateBindingCount = 0;
expressions.forEach(({expressions, nodeIndex}) => {
const exprs = expressions.map(({context, value}) => {
const bindingId = `${updateBindingCount++}`;
const nameResolver = context === COMP_VAR ? this : null;
const {stmts, currValExpr} =
convertPropertyBinding(nameResolver, context, value, bindingId);
updateStmts.push(...stmts);
return currValExpr;
});
updateStmts.push(callCheckStmt(nodeIndex, exprs).toStmt());
});
private _createUpdateFn(updateStmts: o.Statement[]): o.Expression {
let updateFn: o.Expression;
if (updateStmts.length > 0) {
const preStmts: o.Statement[] = [];
@ -218,40 +221,50 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
visitNgContent(ast: NgContentAst, context: any): any {
// ngContentDef(ngContentIndex: number, index: number): NodeDef;
this.nodeDefs.push(() => o.importExpr(createIdentifier(Identifiers.ngContentDef)).callFn([
o.literal(ast.ngContentIndex), o.literal(ast.index)
]));
this.nodes.push(() => ({
sourceSpan: ast.sourceSpan,
nodeDef: o.importExpr(createIdentifier(Identifiers.ngContentDef)).callFn([
o.literal(ast.ngContentIndex), o.literal(ast.index)
])
}));
}
visitText(ast: TextAst, context: any): any {
// textDef(ngContentIndex: number, constants: string[]): NodeDef;
this.nodeDefs.push(() => o.importExpr(createIdentifier(Identifiers.textDef)).callFn([
o.literal(ast.ngContentIndex), o.literalArr([o.literal(ast.value)])
]));
this.nodes.push(() => ({
sourceSpan: ast.sourceSpan,
nodeDef: o.importExpr(createIdentifier(Identifiers.textDef)).callFn([
o.literal(ast.ngContentIndex), o.literalArr([o.literal(ast.value)])
])
}));
}
visitBoundText(ast: BoundTextAst, context: any): any {
const nodeIndex = this.nodeDefs.length;
const nodeIndex = this.nodes.length;
// reserve the space in the nodeDefs array
this.nodeDefs.push(null);
this.nodes.push(null);
const astWithSource = <ASTWithSource>ast.value;
const inter = <Interpolation>astWithSource.ast;
this._addUpdateExpressions(
nodeIndex, inter.expressions.map((expr) => { return {context: COMP_VAR, value: expr}; }),
this.updateRendererExpressions);
const updateRendererExpressions = inter.expressions.map(
(expr) => this._preprocessUpdateExpression(
{sourceSpan: ast.sourceSpan, context: COMP_VAR, value: expr}));
// textDef(ngContentIndex: number, constants: string[]): NodeDef;
this.nodeDefs[nodeIndex] = () => o.importExpr(createIdentifier(Identifiers.textDef)).callFn([
o.literal(ast.ngContentIndex), o.literalArr(inter.strings.map(s => o.literal(s)))
]);
this.nodes[nodeIndex] = () => ({
sourceSpan: ast.sourceSpan,
nodeDef: o.importExpr(createIdentifier(Identifiers.textDef)).callFn([
o.literal(ast.ngContentIndex), o.literalArr(inter.strings.map(s => o.literal(s)))
]),
updateRenderer: updateRendererExpressions
});
}
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
const nodeIndex = this.nodeDefs.length;
const nodeIndex = this.nodes.length;
// reserve the space in the nodeDefs array
this.nodeDefs.push(null);
this.nodes.push(null);
const {flags, queryMatchesExpr, hostEvents} = this._visitElementOrTemplate(nodeIndex, ast);
@ -259,27 +272,29 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
this.children.push(childVisitor);
childVisitor.visitAll(ast.variables, ast.children);
const childCount = this.nodeDefs.length - nodeIndex - 1;
const childCount = this.nodes.length - nodeIndex - 1;
// anchorDef(
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
// childCount: number, handleEventFn?: ElementHandleEventFn, templateFactory?:
// ViewDefinitionFactory): NodeDef;
const nodeDef = () => o.importExpr(createIdentifier(Identifiers.anchorDef)).callFn([
o.literal(flags),
queryMatchesExpr,
o.literal(ast.ngContentIndex),
o.literal(childCount),
this._createElementHandleEventFn(nodeIndex, hostEvents),
o.variable(childVisitor.viewName),
]);
this.nodeDefs[nodeIndex] = nodeDef;
this.nodes[nodeIndex] = () => ({
sourceSpan: ast.sourceSpan,
nodeDef: o.importExpr(createIdentifier(Identifiers.anchorDef)).callFn([
o.literal(flags),
queryMatchesExpr,
o.literal(ast.ngContentIndex),
o.literal(childCount),
this._createElementHandleEventFn(nodeIndex, hostEvents),
o.variable(childVisitor.viewName),
])
});
}
visitElement(ast: ElementAst, context: any): any {
const nodeIndex = this.nodeDefs.length;
const nodeIndex = this.nodes.length;
// reserve the space in the nodeDefs array so we can add children
this.nodeDefs.push(null);
this.nodes.push(null);
let elName = ast.name;
if (ast.name === NG_CONTAINER_TAG) {
@ -291,18 +306,25 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
this._visitElementOrTemplate(nodeIndex, ast);
let inputDefs: o.Expression[] = [];
let updateRendererExpressions: UpdateExpression[] = [];
let outputDefs: o.Expression[] = [];
if (elName) {
const hostBindings = ast.inputs
.map((inputAst) => ({
context: COMP_VAR as o.Expression,
value: inputAst.value,
bindingDef: elementBindingDef(inputAst, null),
inputAst,
dirAst: null,
}))
.concat(dirHostBindings);
if (hostBindings.length) {
this._addUpdateExpressions(nodeIndex, hostBindings, this.updateRendererExpressions);
inputDefs = hostBindings.map(entry => entry.bindingDef);
updateRendererExpressions =
hostBindings.map((hostBinding) => this._preprocessUpdateExpression({
context: hostBinding.context,
sourceSpan: hostBinding.inputAst.sourceSpan,
value: hostBinding.inputAst.value
}));
inputDefs = hostBindings.map(
hostBinding => elementBindingDef(hostBinding.inputAst, hostBinding.dirAst));
}
outputDefs = usedEvents.map(
([target, eventName]) => o.literalArr([o.literal(target), o.literal(eventName)]));
@ -310,7 +332,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
templateVisitAll(this, ast.children);
const childCount = this.nodeDefs.length - nodeIndex - 1;
const childCount = this.nodes.length - nodeIndex - 1;
const compAst = ast.directives.find(dirAst => dirAst.directive.isComponent);
let compRendererType = o.NULL_EXPR;
@ -331,15 +353,23 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
// outputs?: ([OutputType.ElementOutput | OutputType.DirectiveHostOutput, string, string])[],
// handleEvent?: ElementHandleEventFn,
// componentView?: () => ViewDefinition, componentRendererType?: RendererType2): NodeDef;
const nodeDef = () => o.importExpr(createIdentifier(Identifiers.elementDef)).callFn([
o.literal(flags), queryMatchesExpr, o.literal(ast.ngContentIndex), o.literal(childCount),
o.literal(elName), elName ? fixedAttrsDef(ast) : o.NULL_EXPR,
inputDefs.length ? o.literalArr(inputDefs) : o.NULL_EXPR,
outputDefs.length ? o.literalArr(outputDefs) : o.NULL_EXPR,
this._createElementHandleEventFn(nodeIndex, hostEvents), compView, compRendererType
]);
this.nodeDefs[nodeIndex] = nodeDef;
this.nodes[nodeIndex] = () => ({
sourceSpan: ast.sourceSpan,
nodeDef: o.importExpr(createIdentifier(Identifiers.elementDef)).callFn([
o.literal(flags),
queryMatchesExpr,
o.literal(ast.ngContentIndex),
o.literal(childCount),
o.literal(elName),
elName ? fixedAttrsDef(ast) : o.NULL_EXPR,
inputDefs.length ? o.literalArr(inputDefs) : o.NULL_EXPR,
outputDefs.length ? o.literalArr(outputDefs) : o.NULL_EXPR,
this._createElementHandleEventFn(nodeIndex, hostEvents),
compView,
compRendererType,
]),
updateRenderer: updateRendererExpressions
});
}
private _visitElementOrTemplate(nodeIndex: number, ast: {
@ -353,7 +383,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
flags: NodeFlags,
usedEvents: [string, string][],
queryMatchesExpr: o.Expression,
hostBindings: {value: AST, context: o.Expression, bindingDef: o.Expression}[],
hostBindings:
{context: o.Expression, inputAst: BoundElementPropertyAst, dirAst: DirectiveAst}[],
hostEvents: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[],
} {
let flags = NodeFlags.None;
@ -371,7 +402,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
usedEvents.set(elementEventFullName(target, name), [target, name]);
});
});
const hostBindings: {value: AST, context: o.Expression, bindingDef: o.Expression}[] = [];
const hostBindings:
{context: o.Expression, inputAst: BoundElementPropertyAst, dirAst: DirectiveAst}[] = [];
const hostEvents: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[] = [];
const componentFactoryResolverProvider = createComponentFactoryResolver(ast.directives);
if (componentFactoryResolverProvider) {
@ -441,12 +473,13 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
providerAst: ProviderAst, dirAst: DirectiveAst, directiveIndex: number,
elementNodeIndex: number, refs: ReferenceAst[], queryMatches: QueryMatch[],
usedEvents: Map<string, any>, queryIds: StaticAndDynamicQueryIds): {
hostBindings: {value: AST, context: o.Expression, bindingDef: o.Expression}[],
hostBindings:
{context: o.Expression, inputAst: BoundElementPropertyAst, dirAst: DirectiveAst}[],
hostEvents: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[]
} {
const nodeIndex = this.nodeDefs.length;
const nodeIndex = this.nodes.length;
// reserve the space in the nodeDefs array so we can add children
this.nodeDefs.push(null);
this.nodes.push(null);
dirAst.directive.queries.forEach((query, queryIndex) => {
let flags = NodeFlags.TypeContentQuery;
@ -459,17 +492,21 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
flags |= NodeFlags.DynamicQuery;
}
const bindingType = query.first ? QueryBindingType.First : QueryBindingType.All;
this.nodeDefs.push(() => o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([
o.literal(flags), o.literal(queryId),
new o.LiteralMapExpr([new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))])
]));
this.nodes.push(() => ({
sourceSpan: dirAst.sourceSpan,
nodeDef: o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([
o.literal(flags), o.literal(queryId),
new o.LiteralMapExpr(
[new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))])
]),
}));
});
// Note: the operation below might also create new nodeDefs,
// but we don't want them to be a child of a directive,
// as they might be a provider/pipe on their own.
// I.e. we only allow queries as children of directives nodes.
const childCount = this.nodeDefs.length - nodeIndex - 1;
const childCount = this.nodes.length - nodeIndex - 1;
let {flags, queryMatchExprs, providerExpr, depsExpr} =
this._visitProviderOrDirective(providerAst, queryMatches);
@ -501,22 +538,21 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
outputDefs.push(new o.LiteralMapEntry(propName, o.literal(eventName), false));
}
});
let updateDirectiveExpressions: UpdateExpression[] = [];
if (dirAst.inputs.length || (flags & (NodeFlags.DoCheck | NodeFlags.OnInit)) > 0) {
this._addUpdateExpressions(
nodeIndex,
dirAst.inputs.map((input) => { return {context: COMP_VAR, value: input.value}; }),
this.updateDirectivesExpressions);
updateDirectiveExpressions = dirAst.inputs.map(
(input) => this._preprocessUpdateExpression(
{sourceSpan: input.sourceSpan, context: COMP_VAR, value: input.value}));
}
const dirContextExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
VIEW_VAR, o.literal(nodeIndex)
]);
const hostBindings =
dirAst.hostProperties.map((hostBindingAst) => ({
value: (<ASTWithSource>hostBindingAst.value).ast,
context: dirContextExpr,
bindingDef: elementBindingDef(hostBindingAst, dirAst),
}));
const hostBindings = dirAst.hostProperties.map((inputAst) => ({
context: dirContextExpr,
dirAst,
inputAst,
}));
const hostEvents = dirAst.hostEvents.map((hostEventAst) => ({
context: dirContextExpr,
eventAst: hostEventAst, dirAst,
@ -528,21 +564,24 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
// any,
// deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]},
// outputs?: {[name: string]: string}, component?: () => ViewDefinition): NodeDef;
const nodeDef = () => o.importExpr(createIdentifier(Identifiers.directiveDef)).callFn([
o.literal(flags), queryMatchExprs.length ? o.literalArr(queryMatchExprs) : o.NULL_EXPR,
o.literal(childCount), providerExpr, depsExpr,
inputDefs.length ? new o.LiteralMapExpr(inputDefs) : o.NULL_EXPR,
outputDefs.length ? new o.LiteralMapExpr(outputDefs) : o.NULL_EXPR
]);
this.nodeDefs[nodeIndex] = nodeDef;
this.nodes[nodeIndex] = () => ({
sourceSpan: dirAst.sourceSpan,
nodeDef: o.importExpr(createIdentifier(Identifiers.directiveDef)).callFn([
o.literal(flags), queryMatchExprs.length ? o.literalArr(queryMatchExprs) : o.NULL_EXPR,
o.literal(childCount), providerExpr, depsExpr,
inputDefs.length ? new o.LiteralMapExpr(inputDefs) : o.NULL_EXPR,
outputDefs.length ? new o.LiteralMapExpr(outputDefs) : o.NULL_EXPR
]),
updateDirectives: updateDirectiveExpressions,
});
return {hostBindings, hostEvents};
}
private _visitProvider(providerAst: ProviderAst, queryMatches: QueryMatch[]): void {
const nodeIndex = this.nodeDefs.length;
const nodeIndex = this.nodes.length;
// reserve the space in the nodeDefs array so we can add children
this.nodeDefs.push(null);
this.nodes.push(null);
const {flags, queryMatchExprs, providerExpr, depsExpr} =
this._visitProviderOrDirective(providerAst, queryMatches);
@ -550,11 +589,13 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
// providerDef(
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], token:any,
// value: any, deps: ([DepFlags, any] | any)[]): NodeDef;
const nodeDef = () => o.importExpr(createIdentifier(Identifiers.providerDef)).callFn([
o.literal(flags), queryMatchExprs.length ? o.literalArr(queryMatchExprs) : o.NULL_EXPR,
tokenExpr(providerAst.token), providerExpr, depsExpr
]);
this.nodeDefs[nodeIndex] = nodeDef;
this.nodes[nodeIndex] = () => ({
sourceSpan: providerAst.sourceSpan,
nodeDef: o.importExpr(createIdentifier(Identifiers.providerDef)).callFn([
o.literal(flags), queryMatchExprs.length ? o.literalArr(queryMatchExprs) : o.NULL_EXPR,
tokenExpr(providerAst.token), providerExpr, depsExpr
])
});
}
private _visitProviderOrDirective(providerAst: ProviderAst, queryMatches: QueryMatch[]): {
@ -615,43 +656,50 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
return null;
}
createLiteralArrayConverter(argCount: number): BuiltinConverter {
createLiteralArrayConverter(sourceSpan: ParseSourceSpan, argCount: number): BuiltinConverter {
if (argCount === 0) {
const valueExpr = o.importExpr(createIdentifier(Identifiers.EMPTY_ARRAY));
return () => valueExpr;
}
const nodeIndex = this.nodeDefs.length;
const nodeIndex = this.nodes.length;
// pureArrayDef(argCount: number): NodeDef;
const nodeDef = () =>
o.importExpr(createIdentifier(Identifiers.pureArrayDef)).callFn([o.literal(argCount)]);
this.nodeDefs.push(nodeDef);
this.nodes.push(
() => ({
sourceSpan,
nodeDef:
o.importExpr(createIdentifier(Identifiers.pureArrayDef)).callFn([o.literal(argCount)])
}));
return (args: o.Expression[]) => callCheckStmt(nodeIndex, args);
}
createLiteralMapConverter(keys: string[]): BuiltinConverter {
createLiteralMapConverter(sourceSpan: ParseSourceSpan, keys: string[]): BuiltinConverter {
if (keys.length === 0) {
const valueExpr = o.importExpr(createIdentifier(Identifiers.EMPTY_MAP));
return () => valueExpr;
}
const nodeIndex = this.nodeDefs.length;
const nodeIndex = this.nodes.length;
// function pureObjectDef(propertyNames: string[]): NodeDef
const nodeDef = () =>
o.importExpr(createIdentifier(Identifiers.pureObjectDef)).callFn([o.literalArr(
keys.map(key => o.literal(key)))]);
this.nodeDefs.push(nodeDef);
this.nodes.push(() => ({
sourceSpan,
nodeDef: o.importExpr(createIdentifier(Identifiers.pureObjectDef))
.callFn([o.literalArr(keys.map(key => o.literal(key)))])
}));
return (args: o.Expression[]) => callCheckStmt(nodeIndex, args);
}
createPipeConverter(name: string, argCount: number): BuiltinConverter {
const pipe = this._findPipe(name);
createPipeConverter(sourceSpan: ParseSourceSpan, name: string, argCount: number):
BuiltinConverter {
const pipe = this.usedPipes.find((pipeSummary) => pipeSummary.name === name);
if (pipe.pure) {
const nodeIndex = this.nodeDefs.length;
const nodeIndex = this.nodes.length;
// function purePipeDef(argCount: number): NodeDef;
const nodeDef = () =>
o.importExpr(createIdentifier(Identifiers.purePipeDef)).callFn([o.literal(argCount)]);
this.nodeDefs.push(nodeDef);
this.nodes.push(() => ({
sourceSpan,
nodeDef: o.importExpr(createIdentifier(Identifiers.purePipeDef))
.callFn([o.literal(argCount)])
}));
// find underlying pipe in the component view
let compViewExpr: o.Expression = VIEW_VAR;
@ -669,7 +717,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
return (args: o.Expression[]) =>
callUnwrapValue(callCheckStmt(nodeIndex, [pipeValueExpr].concat(args)));
} else {
const nodeIndex = this._createPipe(pipe);
const nodeIndex = this._createPipe(sourceSpan, pipe);
const nodeValueExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
VIEW_VAR, o.literal(nodeIndex)
]);
@ -678,12 +726,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
}
}
private _findPipe(name: string): CompilePipeSummary {
return this.usedPipes.find((pipeSummary) => pipeSummary.name === name);
}
private _createPipe(pipe: CompilePipeSummary): number {
const nodeIndex = this.nodeDefs.length;
private _createPipe(sourceSpan: ParseSourceSpan, pipe: CompilePipeSummary): number {
const nodeIndex = this.nodes.length;
let flags = NodeFlags.None;
pipe.type.lifecycleHooks.forEach((lifecycleHook) => {
// for pipes, we only support ngOnDestroy
@ -695,24 +739,76 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
const depExprs = pipe.type.diDeps.map(depDef);
// function pipeDef(
// flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | any)[]): NodeDef
const nodeDef = () => o.importExpr(createIdentifier(Identifiers.pipeDef)).callFn([
o.literal(flags), o.importExpr(pipe.type), o.literalArr(depExprs)
]);
this.nodeDefs.push(nodeDef);
this.nodes.push(() => ({
sourceSpan,
nodeDef: o.importExpr(createIdentifier(Identifiers.pipeDef)).callFn([
o.literal(flags), o.importExpr(pipe.type), o.literalArr(depExprs)
])
}));
return nodeIndex;
}
// Attention: This might create new nodeDefs (for pipes and literal arrays and literal maps)!
private _addUpdateExpressions(
nodeIndex: number, expressions: {context: o.Expression, value: AST}[],
target: UpdateExpression[]) {
const transformedExpressions = expressions.map(({context, value}) => {
if (value instanceof ASTWithSource) {
value = value.ast;
private _preprocessUpdateExpression(expression: UpdateExpression): UpdateExpression {
return {
sourceSpan: expression.sourceSpan,
context: expression.context,
value: convertPropertyBindingBuiltins(
{
createLiteralArrayConverter: (argCount: number) => this.createLiteralArrayConverter(
expression.sourceSpan, argCount),
createLiteralMapConverter:
(keys: string[]) => this.createLiteralMapConverter(expression.sourceSpan, keys),
createPipeConverter: (name: string, argCount: number) =>
this.createPipeConverter(expression.sourceSpan, name, argCount)
},
expression.value)
};
}
private _createNodeExpressions(): {
updateRendererStmts: o.Statement[],
updateDirectivesStmts: o.Statement[],
nodeDefExprs: o.Expression[]
} {
const self = this;
let updateBindingCount = 0;
const updateRendererStmts: o.Statement[] = [];
const updateDirectivesStmts: o.Statement[] = [];
const nodeDefExprs = this.nodes.map((factory, nodeIndex) => {
const {nodeDef, updateDirectives, updateRenderer, sourceSpan} = factory();
if (updateRenderer) {
updateRendererStmts.push(...createUpdateStatements(nodeIndex, sourceSpan, updateRenderer));
}
return {context, value: convertPropertyBindingBuiltins(this, value)};
if (updateDirectives) {
updateDirectivesStmts.push(
...createUpdateStatements(nodeIndex, sourceSpan, updateDirectives));
}
// We use a comma expression to call the log function before
// the nodeDef function, but still use the result of the nodeDef function
// as the value.
const logWithNodeDef = new o.CommaExpr([LOG_VAR.callFn([]).callFn([]), nodeDef]);
return o.applySourceSpanToExpressionIfNeeded(logWithNodeDef, sourceSpan);
});
target.push({nodeIndex, expressions: transformedExpressions});
return {updateRendererStmts, updateDirectivesStmts, nodeDefExprs};
function createUpdateStatements(
nodeIndex: number, sourceSpan: ParseSourceSpan,
expressions: UpdateExpression[]): o.Statement[] {
const updateStmts: o.Statement[] = [];
const exprs = expressions.map(({sourceSpan, context, value}) => {
const bindingId = `${updateBindingCount++}`;
const nameResolver = context === COMP_VAR ? self : null;
const {stmts, currValExpr} =
convertPropertyBinding(nameResolver, context, value, bindingId);
updateStmts.push(
...stmts.map(stmt => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
return o.applySourceSpanToExpressionIfNeeded(currValExpr, sourceSpan);
});
updateStmts.push(o.applySourceSpanToStatementIfNeeded(
callCheckStmt(nodeIndex, exprs).toStmt(), sourceSpan));
return updateStmts;
}
}
private _createElementHandleEventFn(
@ -723,18 +819,17 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
handlers.forEach(({context, eventAst, dirAst}) => {
const bindingId = `${handleEventBindingCount++}`;
const nameResolver = context === COMP_VAR ? this : null;
const expression =
eventAst.handler instanceof ASTWithSource ? eventAst.handler.ast : eventAst.handler;
const {stmts, allowDefault} =
convertActionBinding(nameResolver, context, expression, bindingId);
convertActionBinding(nameResolver, context, eventAst.handler, bindingId);
const trueStmts = stmts;
if (allowDefault) {
trueStmts.push(ALLOW_DEFAULT_VAR.set(allowDefault.and(ALLOW_DEFAULT_VAR)).toStmt());
}
const {target: eventTarget, name: eventName} = elementEventNameAndTarget(eventAst, dirAst);
const fullEventName = elementEventFullName(eventTarget, eventName);
handleEventStmts.push(
new o.IfStmt(o.literal(fullEventName).identical(EVENT_NAME_VAR), trueStmts));
handleEventStmts.push(o.applySourceSpanToStatementIfNeeded(
new o.IfStmt(o.literal(fullEventName).identical(EVENT_NAME_VAR), trueStmts),
eventAst.sourceSpan));
});
let handleEventFn: o.Expression;
if (handleEventStmts.length > 0) {