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:
parent
1c1085b140
commit
cdc882bd36
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 {CompilerConfig} from '../config';
|
||||||
import {Identifiers, createIdentifier, createIdentifierToken} from '../identifiers';
|
import {Identifiers, createIdentifier, createIdentifierToken} from '../identifiers';
|
||||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||||
|
@ -189,7 +189,7 @@ export class AotCompiler {
|
||||||
|
|
||||||
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
|
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
|
||||||
compMeta, compMeta.template.template, directives, pipes, ngModule.schemas,
|
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 stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
|
||||||
const viewResult =
|
const viewResult =
|
||||||
this._viewCompiler.compileComponent(compMeta, parsedTemplate, stylesExpr, usedPipes);
|
this._viewCompiler.compileComponent(compMeta, parsedTemplate, stylesExpr, usedPipes);
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ChangeDetectionStrategy, ComponentFactory, RendererType2, SchemaMetadata, Type, ViewEncapsulation, ɵLifecycleHooks, ɵreflector, ɵstringify as stringify} from '@angular/core';
|
import {ChangeDetectionStrategy, ComponentFactory, RendererType2, SchemaMetadata, Type, ViewEncapsulation, ɵLifecycleHooks, ɵreflector, ɵstringify as stringify} from '@angular/core';
|
||||||
|
|
||||||
import {StaticSymbol} from './aot/static_symbol';
|
import {StaticSymbol} from './aot/static_symbol';
|
||||||
import {CssSelector} from './selector';
|
import {CssSelector} from './selector';
|
||||||
import {splitAtColon} from './util';
|
import {splitAtColon} from './util';
|
||||||
|
@ -257,6 +258,7 @@ export class CompileTemplateMetadata {
|
||||||
encapsulation: ViewEncapsulation;
|
encapsulation: ViewEncapsulation;
|
||||||
template: string;
|
template: string;
|
||||||
templateUrl: string;
|
templateUrl: string;
|
||||||
|
isInline: boolean;
|
||||||
styles: string[];
|
styles: string[];
|
||||||
styleUrls: string[];
|
styleUrls: string[];
|
||||||
externalStylesheets: CompileStylesheetMetadata[];
|
externalStylesheets: CompileStylesheetMetadata[];
|
||||||
|
@ -265,7 +267,7 @@ export class CompileTemplateMetadata {
|
||||||
interpolation: [string, string];
|
interpolation: [string, string];
|
||||||
constructor(
|
constructor(
|
||||||
{encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets, animations,
|
{encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets, animations,
|
||||||
ngContentSelectors, interpolation}: {
|
ngContentSelectors, interpolation, isInline}: {
|
||||||
encapsulation?: ViewEncapsulation,
|
encapsulation?: ViewEncapsulation,
|
||||||
template?: string,
|
template?: string,
|
||||||
templateUrl?: string,
|
templateUrl?: string,
|
||||||
|
@ -275,6 +277,7 @@ export class CompileTemplateMetadata {
|
||||||
ngContentSelectors?: string[],
|
ngContentSelectors?: string[],
|
||||||
animations?: any[],
|
animations?: any[],
|
||||||
interpolation?: [string, string],
|
interpolation?: [string, string],
|
||||||
|
isInline?: boolean
|
||||||
} = {}) {
|
} = {}) {
|
||||||
this.encapsulation = encapsulation;
|
this.encapsulation = encapsulation;
|
||||||
this.template = template;
|
this.template = template;
|
||||||
|
@ -288,6 +291,7 @@ export class CompileTemplateMetadata {
|
||||||
throw new Error(`'interpolation' should have a start and an end symbol.`);
|
throw new Error(`'interpolation' should have a start and an end symbol.`);
|
||||||
}
|
}
|
||||||
this.interpolation = interpolation;
|
this.interpolation = interpolation;
|
||||||
|
this.isInline = isInline;
|
||||||
}
|
}
|
||||||
|
|
||||||
toSummary(): CompileTemplateSummary {
|
toSummary(): CompileTemplateSummary {
|
||||||
|
@ -524,7 +528,8 @@ export function createHostComponentMeta(
|
||||||
styles: [],
|
styles: [],
|
||||||
styleUrls: [],
|
styleUrls: [],
|
||||||
ngContentSelectors: [],
|
ngContentSelectors: [],
|
||||||
animations: []
|
animations: [],
|
||||||
|
isInline: true,
|
||||||
}),
|
}),
|
||||||
changeDetection: ChangeDetectionStrategy.Default,
|
changeDetection: ChangeDetectionStrategy.Default,
|
||||||
inputs: [],
|
inputs: [],
|
||||||
|
@ -752,3 +757,41 @@ export function flatten<T>(list: Array<T|T[]>): T[] {
|
||||||
return (<T[]>flat).concat(flatItem);
|
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`;
|
||||||
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ export {CompilerConfig} from './config';
|
||||||
export * from './compile_metadata';
|
export * from './compile_metadata';
|
||||||
export * from './aot/compiler_factory';
|
export * from './aot/compiler_factory';
|
||||||
export * from './aot/compiler';
|
export * from './aot/compiler';
|
||||||
|
export * from './aot/generated_file';
|
||||||
export * from './aot/compiler_options';
|
export * from './aot/compiler_options';
|
||||||
export * from './aot/compiler_host';
|
export * from './aot/compiler_host';
|
||||||
export * from './aot/static_reflector';
|
export * from './aot/static_reflector';
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ViewEncapsulation, ɵstringify as stringify} from '@angular/core';
|
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 {CompilerConfig} from './config';
|
||||||
import {CompilerInjectable} from './injectable';
|
import {CompilerInjectable} from './injectable';
|
||||||
import * as html from './ml_parser/ast';
|
import * as html from './ml_parser/ast';
|
||||||
|
@ -20,6 +20,7 @@ import {UrlResolver} from './url_resolver';
|
||||||
import {SyncAsyncResult, syntaxError} from './util';
|
import {SyncAsyncResult, syntaxError} from './util';
|
||||||
|
|
||||||
export interface PrenormalizedTemplateMetadata {
|
export interface PrenormalizedTemplateMetadata {
|
||||||
|
ngModuleType: any;
|
||||||
componentType: any;
|
componentType: any;
|
||||||
moduleUrl: string;
|
moduleUrl: string;
|
||||||
template?: string;
|
template?: string;
|
||||||
|
@ -104,20 +105,25 @@ export class DirectiveNormalizer {
|
||||||
}
|
}
|
||||||
|
|
||||||
normalizeLoadedTemplate(
|
normalizeLoadedTemplate(
|
||||||
prenomData: PrenormalizedTemplateMetadata, template: string,
|
prenormData: PrenormalizedTemplateMetadata, template: string,
|
||||||
templateAbsUrl: string): CompileTemplateMetadata {
|
templateAbsUrl: string): CompileTemplateMetadata {
|
||||||
const interpolationConfig = InterpolationConfig.fromArray(prenomData.interpolation);
|
const isInline = !!prenormData.template;
|
||||||
|
const interpolationConfig = InterpolationConfig.fromArray(prenormData.interpolation);
|
||||||
const rootNodesAndErrors = this._htmlParser.parse(
|
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) {
|
if (rootNodesAndErrors.errors.length > 0) {
|
||||||
const errorString = rootNodesAndErrors.errors.join('\n');
|
const errorString = rootNodesAndErrors.errors.join('\n');
|
||||||
throw syntaxError(`Template parse errors:\n${errorString}`);
|
throw syntaxError(`Template parse errors:\n${errorString}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
|
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
|
||||||
styles: prenomData.styles,
|
styles: prenormData.styles,
|
||||||
styleUrls: prenomData.styleUrls,
|
styleUrls: prenormData.styleUrls,
|
||||||
moduleUrl: prenomData.moduleUrl
|
moduleUrl: prenormData.moduleUrl
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const visitor = new TemplatePreparseVisitor();
|
const visitor = new TemplatePreparseVisitor();
|
||||||
|
@ -125,7 +131,7 @@ export class DirectiveNormalizer {
|
||||||
const templateStyles = this.normalizeStylesheet(new CompileStylesheetMetadata(
|
const templateStyles = this.normalizeStylesheet(new CompileStylesheetMetadata(
|
||||||
{styles: visitor.styles, styleUrls: visitor.styleUrls, moduleUrl: templateAbsUrl}));
|
{styles: visitor.styles, styleUrls: visitor.styleUrls, moduleUrl: templateAbsUrl}));
|
||||||
|
|
||||||
let encapsulation = prenomData.encapsulation;
|
let encapsulation = prenormData.encapsulation;
|
||||||
if (encapsulation == null) {
|
if (encapsulation == null) {
|
||||||
encapsulation = this._config.defaultEncapsulation;
|
encapsulation = this._config.defaultEncapsulation;
|
||||||
}
|
}
|
||||||
|
@ -143,8 +149,8 @@ export class DirectiveNormalizer {
|
||||||
template,
|
template,
|
||||||
templateUrl: templateAbsUrl, styles, styleUrls,
|
templateUrl: templateAbsUrl, styles, styleUrls,
|
||||||
ngContentSelectors: visitor.ngContentSelectors,
|
ngContentSelectors: visitor.ngContentSelectors,
|
||||||
animations: prenomData.animations,
|
animations: prenormData.animations,
|
||||||
interpolation: prenomData.interpolation,
|
interpolation: prenormData.interpolation, isInline
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +166,8 @@ export class DirectiveNormalizer {
|
||||||
externalStylesheets: externalStylesheets,
|
externalStylesheets: externalStylesheets,
|
||||||
ngContentSelectors: templateMeta.ngContentSelectors,
|
ngContentSelectors: templateMeta.ngContentSelectors,
|
||||||
animations: templateMeta.animations,
|
animations: templateMeta.animations,
|
||||||
interpolation: templateMeta.interpolation
|
interpolation: templateMeta.interpolation,
|
||||||
|
isInline: templateMeta.isInline,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -210,7 +210,9 @@ enum Endian {
|
||||||
Big,
|
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 = '';
|
let encoded: string = '';
|
||||||
|
|
||||||
for (let index = 0; index < str.length; index++) {
|
for (let index = 0; index < str.length; index++) {
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import {Compiler, ComponentFactory, Inject, Injector, ModuleWithComponentFactories, NgModuleFactory, Type, ɵgetComponentViewDefinitionFactory as getComponentViewDefinitionFactory, ɵstringify as stringify} from '@angular/core';
|
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 {CompilerConfig} from '../config';
|
||||||
import {CompilerInjectable} from '../injectable';
|
import {CompilerInjectable} from '../injectable';
|
||||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||||
|
@ -38,6 +38,7 @@ export class JitCompiler implements Compiler {
|
||||||
private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>();
|
private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>();
|
||||||
private _compiledDirectiveWrapperCache = new Map<Type<any>, Type<any>>();
|
private _compiledDirectiveWrapperCache = new Map<Type<any>, Type<any>>();
|
||||||
private _compiledNgModuleCache = new Map<Type<any>, NgModuleFactory<any>>();
|
private _compiledNgModuleCache = new Map<Type<any>, NgModuleFactory<any>>();
|
||||||
|
private _sharedStylesheetCount = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _injector: Injector, private _metadataResolver: CompileMetadataResolver,
|
private _injector: Injector, private _metadataResolver: CompileMetadataResolver,
|
||||||
|
@ -128,7 +129,7 @@ export class JitCompiler implements Compiler {
|
||||||
interpretStatements(compileResult.statements, [compileResult.ngModuleFactoryVar])[0];
|
interpretStatements(compileResult.statements, [compileResult.ngModuleFactoryVar])[0];
|
||||||
} else {
|
} else {
|
||||||
ngModuleFactory = jitStatements(
|
ngModuleFactory = jitStatements(
|
||||||
`/${identifierName(moduleMeta.type)}/module.ngfactory.js`, compileResult.statements,
|
ngModuleJitUrl(moduleMeta), compileResult.statements,
|
||||||
[compileResult.ngModuleFactoryVar])[0];
|
[compileResult.ngModuleFactoryVar])[0];
|
||||||
}
|
}
|
||||||
this._compiledNgModuleCache.set(moduleMeta.type.reference, ngModuleFactory);
|
this._compiledNgModuleCache.set(moduleMeta.type.reference, ngModuleFactory);
|
||||||
|
@ -251,7 +252,7 @@ export class JitCompiler implements Compiler {
|
||||||
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
|
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
|
||||||
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
|
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
|
||||||
compMeta, compMeta.template.template, directives, pipes, template.ngModule.schemas,
|
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(
|
const compileResult = this._viewCompiler.compileComponent(
|
||||||
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
|
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
|
||||||
usedPipes);
|
usedPipes);
|
||||||
|
@ -263,10 +264,9 @@ export class JitCompiler implements Compiler {
|
||||||
[viewClass, rendererType] = interpretStatements(
|
[viewClass, rendererType] = interpretStatements(
|
||||||
statements, [compileResult.viewClassVar, compileResult.rendererTypeVar]);
|
statements, [compileResult.viewClassVar, compileResult.rendererTypeVar]);
|
||||||
} else {
|
} else {
|
||||||
const sourceUrl =
|
|
||||||
`/${identifierName(template.ngModule.type)}/${identifierName(template.compType)}/${template.isHost?'host':'component'}.ngfactory.js`;
|
|
||||||
[viewClass, rendererType] = jitStatements(
|
[viewClass, rendererType] = jitStatements(
|
||||||
sourceUrl, statements, [compileResult.viewClassVar, compileResult.rendererTypeVar]);
|
templateJitUrl(template.ngModule.type, template.compMeta), statements,
|
||||||
|
[compileResult.viewClassVar, compileResult.rendererTypeVar]);
|
||||||
}
|
}
|
||||||
template.compiled(viewClass, rendererType);
|
template.compiled(viewClass, rendererType);
|
||||||
}
|
}
|
||||||
|
@ -289,7 +289,8 @@ export class JitCompiler implements Compiler {
|
||||||
return interpretStatements(result.statements, [result.stylesVar])[0];
|
return interpretStatements(result.statements, [result.stylesVar])[0];
|
||||||
} else {
|
} else {
|
||||||
return jitStatements(
|
return jitStatements(
|
||||||
`/${result.meta.moduleUrl}.ngstyle.js`, result.statements, [result.stylesVar])[0];
|
sharedStylesheetJitUrl(result.meta, this._sharedStylesheetCount++), result.statements,
|
||||||
|
[result.stylesVar])[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,7 +155,8 @@ export class CompileMetadataResolver {
|
||||||
return typeSummary && typeSummary.summaryKind === kind ? typeSummary : null;
|
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)) {
|
if (this._directiveCache.has(directiveType)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -191,6 +192,7 @@ export class CompileMetadataResolver {
|
||||||
|
|
||||||
if (metadata.isComponent) {
|
if (metadata.isComponent) {
|
||||||
const templateMeta = this._directiveNormalizer.normalizeTemplate({
|
const templateMeta = this._directiveNormalizer.normalizeTemplate({
|
||||||
|
ngModuleType,
|
||||||
componentType: directiveType,
|
componentType: directiveType,
|
||||||
moduleUrl: componentModuleUrl(this._reflector, directiveType, annotation),
|
moduleUrl: componentModuleUrl(this._reflector, directiveType, annotation),
|
||||||
encapsulation: metadata.template.encapsulation,
|
encapsulation: metadata.template.encapsulation,
|
||||||
|
@ -249,7 +251,8 @@ export class CompileMetadataResolver {
|
||||||
styles: dirMeta.styles,
|
styles: dirMeta.styles,
|
||||||
styleUrls: dirMeta.styleUrls,
|
styleUrls: dirMeta.styleUrls,
|
||||||
animations: animations,
|
animations: animations,
|
||||||
interpolation: dirMeta.interpolation
|
interpolation: dirMeta.interpolation,
|
||||||
|
isInline: !!dirMeta.template
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,7 +381,7 @@ export class CompileMetadataResolver {
|
||||||
const loading: Promise<any>[] = [];
|
const loading: Promise<any>[] = [];
|
||||||
if (ngModule) {
|
if (ngModule) {
|
||||||
ngModule.declaredDirectives.forEach((id) => {
|
ngModule.declaredDirectives.forEach((id) => {
|
||||||
const promise = this._loadDirectiveMetadata(id.reference, isSync);
|
const promise = this._loadDirectiveMetadata(moduleType, id.reference, isSync);
|
||||||
if (promise) {
|
if (promise) {
|
||||||
loading.push(promise);
|
loading.push(promise);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {CompilerInjectable} from './injectable';
|
||||||
import {ClassBuilder, createClassStmt} from './output/class_builder';
|
import {ClassBuilder, createClassStmt} from './output/class_builder';
|
||||||
import * as o from './output/output_ast';
|
import * as o from './output/output_ast';
|
||||||
import {convertValueToOutputAst} from './output/value_util';
|
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 {NgModuleProviderAnalyzer} from './provider_analyzer';
|
||||||
import {ProviderAst} from './template_parser/template_ast';
|
import {ProviderAst} from './template_parser/template_ast';
|
||||||
|
|
||||||
|
@ -37,14 +37,7 @@ export class NgModuleCompileResult {
|
||||||
export class NgModuleCompiler {
|
export class NgModuleCompiler {
|
||||||
compile(ngModuleMeta: CompileNgModuleMetadata, extraProviders: CompileProviderMetadata[]):
|
compile(ngModuleMeta: CompileNgModuleMetadata, extraProviders: CompileProviderMetadata[]):
|
||||||
NgModuleCompileResult {
|
NgModuleCompileResult {
|
||||||
const moduleUrl = identifierModuleUrl(ngModuleMeta.type);
|
const sourceSpan = typeSourceSpan('NgModule', 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 deps: ComponentFactoryDependency[] = [];
|
const deps: ComponentFactoryDependency[] = [];
|
||||||
const bootstrapComponentFactories: CompileIdentifierMetadata[] = [];
|
const bootstrapComponentFactories: CompileIdentifierMetadata[] = [];
|
||||||
const entryComponentFactories =
|
const entryComponentFactories =
|
||||||
|
|
|
@ -420,7 +420,12 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex
|
||||||
ctx.print(ast, `}`, useNewLine);
|
ctx.print(ast, `}`, useNewLine);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
visitCommaExpr(ast: o.CommaExpr, ctx: EmitterVisitorContext): any {
|
||||||
|
ctx.print(ast, '(');
|
||||||
|
this.visitAllExpressions(ast.parts, ctx, ',');
|
||||||
|
ctx.print(ast, ')');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
visitAllExpressions(
|
visitAllExpressions(
|
||||||
expressions: o.Expression[], ctx: EmitterVisitorContext, separator: string,
|
expressions: o.Expression[], ctx: EmitterVisitorContext, separator: string,
|
||||||
newLine: boolean = false): void {
|
newLine: boolean = false): void {
|
||||||
|
|
|
@ -34,11 +34,12 @@ export class JavaScriptEmitter implements OutputEmitter {
|
||||||
srcParts.push(ctx.toSource());
|
srcParts.push(ctx.toSource());
|
||||||
|
|
||||||
const prefixLines = converter.importsWithPrefixes.size;
|
const prefixLines = converter.importsWithPrefixes.size;
|
||||||
const sm = ctx.toSourceMapGenerator(null, prefixLines).toJsComment();
|
const sm = ctx.toSourceMapGenerator(genFilePath, prefixLines).toJsComment();
|
||||||
if (sm) {
|
if (sm) {
|
||||||
srcParts.push(sm);
|
srcParts.push(sm);
|
||||||
}
|
}
|
||||||
|
// always add a newline at the end, as some tools have bugs without it.
|
||||||
|
srcParts.push('');
|
||||||
return srcParts.join('\n');
|
return srcParts.join('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
export interface ExpressionVisitor {
|
||||||
visitReadVarExpr(ast: ReadVarExpr, context: any): any;
|
visitReadVarExpr(ast: ReadVarExpr, context: any): any;
|
||||||
visitWriteVarExpr(expr: WriteVarExpr, context: any): any;
|
visitWriteVarExpr(expr: WriteVarExpr, context: any): any;
|
||||||
|
@ -485,6 +494,7 @@ export interface ExpressionVisitor {
|
||||||
visitReadKeyExpr(ast: ReadKeyExpr, context: any): any;
|
visitReadKeyExpr(ast: ReadKeyExpr, context: any): any;
|
||||||
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): any;
|
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): any;
|
||||||
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any;
|
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any;
|
||||||
|
visitCommaExpr(ast: CommaExpr, context: any): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const THIS_EXPR = new ReadVarExpr(BuiltinVar.This);
|
export const THIS_EXPR = new ReadVarExpr(BuiltinVar.This);
|
||||||
|
@ -653,88 +663,121 @@ export interface StatementVisitor {
|
||||||
visitCommentStmt(stmt: CommentStmt, context: any): any;
|
visitCommentStmt(stmt: CommentStmt, context: any): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExpressionTransformer implements StatementVisitor, ExpressionVisitor {
|
export class AstTransformer implements StatementVisitor, ExpressionVisitor {
|
||||||
visitReadVarExpr(ast: ReadVarExpr, context: any): any { return ast; }
|
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 {
|
visitWriteVarExpr(expr: WriteVarExpr, context: any): any {
|
||||||
return new WriteVarExpr(
|
return this.transformExpr(
|
||||||
expr.name, expr.value.visitExpression(this, context), expr.type, expr.sourceSpan);
|
new WriteVarExpr(
|
||||||
|
expr.name, expr.value.visitExpression(this, context), expr.type, expr.sourceSpan),
|
||||||
|
context);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitWriteKeyExpr(expr: WriteKeyExpr, context: any): any {
|
visitWriteKeyExpr(expr: WriteKeyExpr, context: any): any {
|
||||||
return new WriteKeyExpr(
|
return this.transformExpr(
|
||||||
expr.receiver.visitExpression(this, context), expr.index.visitExpression(this, context),
|
new WriteKeyExpr(
|
||||||
expr.value.visitExpression(this, context), expr.type, expr.sourceSpan);
|
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 {
|
visitWritePropExpr(expr: WritePropExpr, context: any): any {
|
||||||
return new WritePropExpr(
|
return this.transformExpr(
|
||||||
expr.receiver.visitExpression(this, context), expr.name,
|
new WritePropExpr(
|
||||||
expr.value.visitExpression(this, context), expr.type, expr.sourceSpan);
|
expr.receiver.visitExpression(this, context), expr.name,
|
||||||
|
expr.value.visitExpression(this, context), expr.type, expr.sourceSpan),
|
||||||
|
context);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): any {
|
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): any {
|
||||||
const method = ast.builtin || ast.name;
|
const method = ast.builtin || ast.name;
|
||||||
return new InvokeMethodExpr(
|
return this.transformExpr(
|
||||||
ast.receiver.visitExpression(this, context), method,
|
new InvokeMethodExpr(
|
||||||
this.visitAllExpressions(ast.args, context), ast.type, ast.sourceSpan);
|
ast.receiver.visitExpression(this, context), method,
|
||||||
|
this.visitAllExpressions(ast.args, context), ast.type, ast.sourceSpan),
|
||||||
|
context);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: any): any {
|
visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: any): any {
|
||||||
return new InvokeFunctionExpr(
|
return this.transformExpr(
|
||||||
ast.fn.visitExpression(this, context), this.visitAllExpressions(ast.args, context),
|
new InvokeFunctionExpr(
|
||||||
ast.type, ast.sourceSpan);
|
ast.fn.visitExpression(this, context), this.visitAllExpressions(ast.args, context),
|
||||||
|
ast.type, ast.sourceSpan),
|
||||||
|
context);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitInstantiateExpr(ast: InstantiateExpr, context: any): any {
|
visitInstantiateExpr(ast: InstantiateExpr, context: any): any {
|
||||||
return new InstantiateExpr(
|
return this.transformExpr(
|
||||||
ast.classExpr.visitExpression(this, context), this.visitAllExpressions(ast.args, context),
|
new InstantiateExpr(
|
||||||
ast.type, ast.sourceSpan);
|
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 {
|
visitConditionalExpr(ast: ConditionalExpr, context: any): any {
|
||||||
return new ConditionalExpr(
|
return this.transformExpr(
|
||||||
ast.condition.visitExpression(this, context), ast.trueCase.visitExpression(this, context),
|
new ConditionalExpr(
|
||||||
ast.falseCase.visitExpression(this, context), ast.type, ast.sourceSpan);
|
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 {
|
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 {
|
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 {
|
visitFunctionExpr(ast: FunctionExpr, context: any): any {
|
||||||
// Don't descend into nested functions
|
return this.transformExpr(
|
||||||
return ast;
|
new FunctionExpr(
|
||||||
|
ast.params, this.visitAllStatements(ast.statements, context), ast.type, ast.sourceSpan),
|
||||||
|
context);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): any {
|
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): any {
|
||||||
return new BinaryOperatorExpr(
|
return this.transformExpr(
|
||||||
ast.operator, ast.lhs.visitExpression(this, context),
|
new BinaryOperatorExpr(
|
||||||
ast.rhs.visitExpression(this, context), ast.type, ast.sourceSpan);
|
ast.operator, ast.lhs.visitExpression(this, context),
|
||||||
|
ast.rhs.visitExpression(this, context), ast.type, ast.sourceSpan),
|
||||||
|
context);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitReadPropExpr(ast: ReadPropExpr, context: any): any {
|
visitReadPropExpr(ast: ReadPropExpr, context: any): any {
|
||||||
return new ReadPropExpr(
|
return this.transformExpr(
|
||||||
ast.receiver.visitExpression(this, context), ast.name, ast.type, ast.sourceSpan);
|
new ReadPropExpr(
|
||||||
|
ast.receiver.visitExpression(this, context), ast.name, ast.type, ast.sourceSpan),
|
||||||
|
context);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitReadKeyExpr(ast: ReadKeyExpr, context: any): any {
|
visitReadKeyExpr(ast: ReadKeyExpr, context: any): any {
|
||||||
return new ReadKeyExpr(
|
return this.transformExpr(
|
||||||
ast.receiver.visitExpression(this, context), ast.index.visitExpression(this, context),
|
new ReadKeyExpr(
|
||||||
ast.type, ast.sourceSpan);
|
ast.receiver.visitExpression(this, context), ast.index.visitExpression(this, context),
|
||||||
|
ast.type, ast.sourceSpan),
|
||||||
|
context);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): any {
|
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): any {
|
||||||
return new LiteralArrayExpr(
|
return this.transformExpr(
|
||||||
this.visitAllExpressions(ast.entries, context), ast.type, ast.sourceSpan);
|
new LiteralArrayExpr(
|
||||||
|
this.visitAllExpressions(ast.entries, context), ast.type, ast.sourceSpan),
|
||||||
|
context);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any {
|
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any {
|
||||||
|
@ -742,53 +785,88 @@ export class ExpressionTransformer implements StatementVisitor, ExpressionVisito
|
||||||
(entry): LiteralMapEntry => new LiteralMapEntry(
|
(entry): LiteralMapEntry => new LiteralMapEntry(
|
||||||
entry.key, entry.value.visitExpression(this, context), entry.quoted, ));
|
entry.key, entry.value.visitExpression(this, context), entry.quoted, ));
|
||||||
const mapType = new MapType(ast.valueType);
|
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[] {
|
visitAllExpressions(exprs: Expression[], context: any): Expression[] {
|
||||||
return exprs.map(expr => expr.visitExpression(this, context));
|
return exprs.map(expr => expr.visitExpression(this, context));
|
||||||
}
|
}
|
||||||
|
|
||||||
visitDeclareVarStmt(stmt: DeclareVarStmt, context: any): any {
|
visitDeclareVarStmt(stmt: DeclareVarStmt, context: any): any {
|
||||||
return new DeclareVarStmt(
|
return this.transformStmt(
|
||||||
stmt.name, stmt.value.visitExpression(this, context), stmt.type, stmt.modifiers,
|
new DeclareVarStmt(
|
||||||
stmt.sourceSpan);
|
stmt.name, stmt.value.visitExpression(this, context), stmt.type, stmt.modifiers,
|
||||||
|
stmt.sourceSpan),
|
||||||
|
context);
|
||||||
}
|
}
|
||||||
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any): any {
|
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any): any {
|
||||||
// Don't descend into nested functions
|
return this.transformStmt(
|
||||||
return stmt;
|
new DeclareFunctionStmt(
|
||||||
|
stmt.name, stmt.params, this.visitAllStatements(stmt.statements, context), stmt.type,
|
||||||
|
stmt.modifiers, stmt.sourceSpan),
|
||||||
|
context);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitExpressionStmt(stmt: ExpressionStatement, context: any): any {
|
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 {
|
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 {
|
visitDeclareClassStmt(stmt: ClassStmt, context: any): any {
|
||||||
// Don't descend into nested functions
|
const parent = stmt.parent.visitExpression(this, context);
|
||||||
return stmt;
|
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 {
|
visitIfStmt(stmt: IfStmt, context: any): any {
|
||||||
return new IfStmt(
|
return this.transformStmt(
|
||||||
stmt.condition.visitExpression(this, context),
|
new IfStmt(
|
||||||
this.visitAllStatements(stmt.trueCase, context),
|
stmt.condition.visitExpression(this, context),
|
||||||
this.visitAllStatements(stmt.falseCase, context), stmt.sourceSpan);
|
this.visitAllStatements(stmt.trueCase, context),
|
||||||
|
this.visitAllStatements(stmt.falseCase, context), stmt.sourceSpan),
|
||||||
|
context);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitTryCatchStmt(stmt: TryCatchStmt, context: any): any {
|
visitTryCatchStmt(stmt: TryCatchStmt, context: any): any {
|
||||||
return new TryCatchStmt(
|
return this.transformStmt(
|
||||||
this.visitAllStatements(stmt.bodyStmts, context),
|
new TryCatchStmt(
|
||||||
this.visitAllStatements(stmt.catchStmts, context), stmt.sourceSpan);
|
this.visitAllStatements(stmt.bodyStmts, context),
|
||||||
|
this.visitAllStatements(stmt.catchStmts, context), stmt.sourceSpan),
|
||||||
|
context);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitThrowStmt(stmt: ThrowStmt, context: any): any {
|
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[] {
|
visitAllStatements(stmts: Statement[], context: any): Statement[] {
|
||||||
return stmts.map(stmt => stmt.visitStatement(this, context));
|
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; }
|
visitReadVarExpr(ast: ReadVarExpr, context: any): any { return ast; }
|
||||||
visitWriteVarExpr(expr: WriteVarExpr, context: any): any {
|
visitWriteVarExpr(expr: WriteVarExpr, context: any): any {
|
||||||
expr.value.visitExpression(this, context);
|
expr.value.visitExpression(this, context);
|
||||||
|
@ -844,7 +922,10 @@ export class RecursiveExpressionVisitor implements StatementVisitor, ExpressionV
|
||||||
ast.value.visitExpression(this, context);
|
ast.value.visitExpression(this, context);
|
||||||
return ast;
|
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 {
|
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): any {
|
||||||
ast.lhs.visitExpression(this, context);
|
ast.lhs.visitExpression(this, context);
|
||||||
ast.rhs.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));
|
ast.entries.forEach((entry) => entry.value.visitExpression(this, context));
|
||||||
return ast;
|
return ast;
|
||||||
}
|
}
|
||||||
|
visitCommaExpr(ast: CommaExpr, context: any): any {
|
||||||
|
this.visitAllExpressions(ast.parts, context);
|
||||||
|
}
|
||||||
visitAllExpressions(exprs: Expression[], context: any): void {
|
visitAllExpressions(exprs: Expression[], context: any): void {
|
||||||
exprs.forEach(expr => expr.visitExpression(this, context));
|
exprs.forEach(expr => expr.visitExpression(this, context));
|
||||||
}
|
}
|
||||||
|
@ -876,7 +960,7 @@ export class RecursiveExpressionVisitor implements StatementVisitor, ExpressionV
|
||||||
return stmt;
|
return stmt;
|
||||||
}
|
}
|
||||||
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any): any {
|
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any): any {
|
||||||
// Don't descend into nested functions
|
this.visitAllStatements(stmt.statements, context);
|
||||||
return stmt;
|
return stmt;
|
||||||
}
|
}
|
||||||
visitExpressionStmt(stmt: ExpressionStatement, context: any): any {
|
visitExpressionStmt(stmt: ExpressionStatement, context: any): any {
|
||||||
|
@ -888,7 +972,12 @@ export class RecursiveExpressionVisitor implements StatementVisitor, ExpressionV
|
||||||
return stmt;
|
return stmt;
|
||||||
}
|
}
|
||||||
visitDeclareClassStmt(stmt: ClassStmt, context: any): any {
|
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;
|
return stmt;
|
||||||
}
|
}
|
||||||
visitIfStmt(stmt: IfStmt, context: any): any {
|
visitIfStmt(stmt: IfStmt, context: any): any {
|
||||||
|
@ -912,30 +1001,48 @@ export class RecursiveExpressionVisitor implements StatementVisitor, ExpressionV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function replaceVarInExpression(
|
export function applySourceSpanToStatementIfNeeded(
|
||||||
varName: string, newValue: Expression, expression: Expression): Expression {
|
stmt: Statement, sourceSpan: ParseSourceSpan): Statement {
|
||||||
const transformer = new _ReplaceVariableTransformer(varName, newValue);
|
if (!sourceSpan) {
|
||||||
return expression.visitExpression(transformer, null);
|
return stmt;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
const transformer = new _ApplySourceSpanTransformer(sourceSpan);
|
||||||
|
return stmt.visitStatement(transformer, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findReadVarNames(stmts: Statement[]): Set<string> {
|
export function applySourceSpanToExpressionIfNeeded(
|
||||||
const finder = new _VariableFinder();
|
expr: Expression, sourceSpan: ParseSourceSpan): Expression {
|
||||||
finder.visitAllStatements(stmts, null);
|
if (!sourceSpan) {
|
||||||
return finder.varNames;
|
return expr;
|
||||||
|
}
|
||||||
|
const transformer = new _ApplySourceSpanTransformer(sourceSpan);
|
||||||
|
return expr.visitExpression(transformer, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _VariableFinder extends RecursiveExpressionVisitor {
|
class _ApplySourceSpanTransformer extends AstTransformer {
|
||||||
varNames = new Set<string>();
|
constructor(private sourceSpan: ParseSourceSpan) { super(); }
|
||||||
visitReadVarExpr(ast: ReadVarExpr, context: any): any {
|
private _clone(obj: any): any {
|
||||||
this.varNames.add(ast.name);
|
const clone = Object.create(obj.constructor.prototype);
|
||||||
return null;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -304,7 +304,10 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor {
|
||||||
(entry) => (result as any)[entry.key] = entry.value.visitExpression(this, ctx));
|
(entry) => (result as any)[entry.key] = entry.value.visitExpression(this, ctx));
|
||||||
return result;
|
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 {
|
visitAllExpressions(expressions: o.Expression[], ctx: _ExecutionContext): any {
|
||||||
return expressions.map((expr) => expr.visitExpression(this, ctx));
|
return expressions.map((expr) => expr.visitExpression(this, ctx));
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {isDevMode} from '@angular/core';
|
||||||
import {identifierName} from '../compile_metadata';
|
import {identifierName} from '../compile_metadata';
|
||||||
|
|
||||||
import {EmitterVisitorContext} from './abstract_emitter';
|
import {EmitterVisitorContext} from './abstract_emitter';
|
||||||
|
@ -14,14 +15,23 @@ import * as o from './output_ast';
|
||||||
|
|
||||||
function evalExpression(
|
function evalExpression(
|
||||||
sourceUrl: string, ctx: EmitterVisitorContext, vars: {[key: string]: any}): any {
|
sourceUrl: string, ctx: EmitterVisitorContext, vars: {[key: string]: any}): any {
|
||||||
const fnBody =
|
let fnBody = `${ctx.toSource()}\n//# sourceURL=${sourceUrl}`;
|
||||||
`${ctx.toSource()}\n//# sourceURL=${sourceUrl}\n${ctx.toSourceMapGenerator().toJsComment()}`;
|
|
||||||
const fnArgNames: string[] = [];
|
const fnArgNames: string[] = [];
|
||||||
const fnArgValues: any[] = [];
|
const fnArgValues: any[] = [];
|
||||||
for (const argName in vars) {
|
for (const argName in vars) {
|
||||||
fnArgNames.push(argName);
|
fnArgNames.push(argName);
|
||||||
fnArgValues.push(vars[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);
|
return new Function(...fnArgNames.concat(fnBody))(...fnArgValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {utf8Encode} from '../i18n/digest';
|
||||||
|
|
||||||
// https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
|
// https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
|
||||||
const VERSION = 3;
|
const VERSION = 3;
|
||||||
|
|
||||||
|
@ -143,7 +145,7 @@ export class SourceMapGenerator {
|
||||||
|
|
||||||
export function toBase64String(value: string): string {
|
export function toBase64String(value: string): string {
|
||||||
let b64 = '';
|
let b64 = '';
|
||||||
|
value = utf8Encode(value);
|
||||||
for (let i = 0; i < value.length;) {
|
for (let i = 0; i < value.length;) {
|
||||||
const i1 = value.charCodeAt(i++);
|
const i1 = value.charCodeAt(i++);
|
||||||
const i2 = value.charCodeAt(i++);
|
const i2 = value.charCodeAt(i++);
|
||||||
|
|
|
@ -70,10 +70,12 @@ export class TypeScriptEmitter implements OutputEmitter {
|
||||||
srcParts.push(ctx.toSource());
|
srcParts.push(ctx.toSource());
|
||||||
|
|
||||||
const prefixLines = converter.reexports.size + converter.importsWithPrefixes.size;
|
const prefixLines = converter.reexports.size + converter.importsWithPrefixes.size;
|
||||||
const sm = ctx.toSourceMapGenerator(null, prefixLines).toJsComment();
|
const sm = ctx.toSourceMapGenerator(genFilePath, prefixLines).toJsComment();
|
||||||
if (sm) {
|
if (sm) {
|
||||||
srcParts.push(sm);
|
srcParts.push(sm);
|
||||||
}
|
}
|
||||||
|
// always add a newline at the end, as some tools have bugs without it.
|
||||||
|
srcParts.push('');
|
||||||
|
|
||||||
return srcParts.join('\n');
|
return srcParts.join('\n');
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import * as chars from './chars';
|
import * as chars from './chars';
|
||||||
|
import {CompileIdentifierMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
|
||||||
|
|
||||||
export class ParseLocation {
|
export class ParseLocation {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -124,3 +125,13 @@ export class ParseError {
|
||||||
return `${this.msg}${contextStr}: ${this.span.start}${details}`;
|
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));
|
||||||
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ export class ProviderViewContext {
|
||||||
viewProviders: Map<any, boolean>;
|
viewProviders: Map<any, boolean>;
|
||||||
errors: ProviderError[] = [];
|
errors: ProviderError[] = [];
|
||||||
|
|
||||||
constructor(public component: CompileDirectiveMetadata, public sourceSpan: ParseSourceSpan) {
|
constructor(public component: CompileDirectiveMetadata) {
|
||||||
this.viewQueries = _getViewQueries(component);
|
this.viewQueries = _getViewQueries(component);
|
||||||
this.viewProviders = new Map<any, boolean>();
|
this.viewProviders = new Map<any, boolean>();
|
||||||
component.viewProviders.forEach((provider) => {
|
component.viewProviders.forEach((provider) => {
|
||||||
|
|
|
@ -132,8 +132,7 @@ export class TemplateParser {
|
||||||
if (htmlAstWithErrors.rootNodes.length > 0) {
|
if (htmlAstWithErrors.rootNodes.length > 0) {
|
||||||
const uniqDirectives = removeSummaryDuplicates(directives);
|
const uniqDirectives = removeSummaryDuplicates(directives);
|
||||||
const uniqPipes = removeSummaryDuplicates(pipes);
|
const uniqPipes = removeSummaryDuplicates(pipes);
|
||||||
const providerViewContext =
|
const providerViewContext = new ProviderViewContext(component);
|
||||||
new ProviderViewContext(component, htmlAstWithErrors.rootNodes[0].sourceSpan);
|
|
||||||
let interpolationConfig: InterpolationConfig;
|
let interpolationConfig: InterpolationConfig;
|
||||||
if (component.template && component.template.interpolation) {
|
if (component.template && component.template.interpolation) {
|
||||||
interpolationConfig = {
|
interpolationConfig = {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {Identifiers, createIdentifier, createIdentifierToken, resolveIdentifier}
|
||||||
import {CompilerInjectable} from '../injectable';
|
import {CompilerInjectable} from '../injectable';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {convertValueToOutputAst} from '../output/value_util';
|
import {convertValueToOutputAst} from '../output/value_util';
|
||||||
|
import {ParseSourceSpan} from '../parse_util';
|
||||||
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
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';
|
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 {
|
interface UpdateExpression {
|
||||||
nodeIndex: number;
|
context: o.Expression;
|
||||||
expressions: {context: o.Expression, value: AST}[];
|
sourceSpan: ParseSourceSpan;
|
||||||
|
value: AST;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LOG_VAR = o.variable('log');
|
||||||
const VIEW_VAR = o.variable('view');
|
const VIEW_VAR = o.variable('view');
|
||||||
const CHECK_VAR = o.variable('check');
|
const CHECK_VAR = o.variable('check');
|
||||||
const COMP_VAR = o.variable('comp');
|
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 EVENT_NAME_VAR = o.variable('eventName');
|
||||||
const ALLOW_DEFAULT_VAR = o.variable(`allowDefault`);
|
const ALLOW_DEFAULT_VAR = o.variable(`allowDefault`);
|
||||||
|
|
||||||
class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverterFactory {
|
class ViewBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
private compType: o.Type;
|
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);
|
private purePipeNodeIndices: {[pipeName: string]: number} = Object.create(null);
|
||||||
// Need Object.create so that we don't have builtin values...
|
// Need Object.create so that we don't have builtin values...
|
||||||
private refNodeIndices: {[refName: string]: number} = Object.create(null);
|
private refNodeIndices: {[refName: string]: number} = Object.create(null);
|
||||||
private variables: VariableAst[] = [];
|
private variables: VariableAst[] = [];
|
||||||
private children: ViewBuilder[] = [];
|
private children: ViewBuilder[] = [];
|
||||||
private updateDirectivesExpressions: UpdateExpression[] = [];
|
|
||||||
private updateRendererExpressions: UpdateExpression[] = [];
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private parent: ViewBuilder, private component: CompileDirectiveMetadata,
|
private parent: ViewBuilder, private component: CompileDirectiveMetadata,
|
||||||
|
@ -125,7 +131,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
if (!this.parent) {
|
if (!this.parent) {
|
||||||
this.usedPipes.forEach((pipe) => {
|
this.usedPipes.forEach((pipe) => {
|
||||||
if (pipe.pure) {
|
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 {
|
} else {
|
||||||
flags |= NodeFlags.DynamicQuery;
|
flags |= NodeFlags.DynamicQuery;
|
||||||
}
|
}
|
||||||
this.nodeDefs.push(() => o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([
|
this.nodes.push(() => ({
|
||||||
o.literal(flags), o.literal(queryId),
|
sourceSpan: null,
|
||||||
new o.LiteralMapExpr([new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))])
|
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);
|
templateVisitAll(this, astNodes);
|
||||||
|
@ -153,17 +163,23 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
(this.parent && needsAdditionalRootNode(astNodes[astNodes.length - 1]))) {
|
(this.parent && needsAdditionalRootNode(astNodes[astNodes.length - 1]))) {
|
||||||
// if the view is empty, or an embedded view has a view container as last root nde,
|
// if the view is empty, or an embedded view has a view container as last root nde,
|
||||||
// create an additional root node.
|
// create an additional root node.
|
||||||
this.nodeDefs.push(() => o.importExpr(createIdentifier(Identifiers.anchorDef)).callFn([
|
this.nodes.push(() => ({
|
||||||
o.literal(NodeFlags.None), o.NULL_EXPR, o.NULL_EXPR, o.literal(0)
|
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[] {
|
build(targetStatements: o.Statement[] = []): o.Statement[] {
|
||||||
this.children.forEach((child) => child.build(targetStatements));
|
this.children.forEach((child) => child.build(targetStatements));
|
||||||
|
|
||||||
const updateDirectivesFn = this._createUpdateFn(this.updateDirectivesExpressions);
|
const {updateRendererStmts, updateDirectivesStmts, nodeDefExprs} =
|
||||||
const updateRendererFn = this._createUpdateFn(this.updateRendererExpressions);
|
this._createNodeExpressions();
|
||||||
|
|
||||||
|
const updateRendererFn = this._createUpdateFn(updateRendererStmts);
|
||||||
|
const updateDirectivesFn = this._createUpdateFn(updateDirectivesStmts);
|
||||||
|
|
||||||
|
|
||||||
let viewFlags = ViewFlags.None;
|
let viewFlags = ViewFlags.None;
|
||||||
|
@ -171,10 +187,10 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
viewFlags |= ViewFlags.OnPush;
|
viewFlags |= ViewFlags.OnPush;
|
||||||
}
|
}
|
||||||
const viewFactory = new o.DeclareFunctionStmt(
|
const viewFactory = new o.DeclareFunctionStmt(
|
||||||
this.viewName, [],
|
this.viewName, [new o.FnParam(LOG_VAR.name)],
|
||||||
[new o.ReturnStatement(o.importExpr(createIdentifier(Identifiers.viewDef)).callFn([
|
[new o.ReturnStatement(o.importExpr(createIdentifier(Identifiers.viewDef)).callFn([
|
||||||
o.literal(viewFlags),
|
o.literal(viewFlags),
|
||||||
o.literalArr(this.nodeDefs.map(nd => nd())),
|
o.literalArr(nodeDefExprs),
|
||||||
updateDirectivesFn,
|
updateDirectivesFn,
|
||||||
updateRendererFn,
|
updateRendererFn,
|
||||||
]))],
|
]))],
|
||||||
|
@ -184,20 +200,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
return targetStatements;
|
return targetStatements;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createUpdateFn(expressions: UpdateExpression[]): o.Expression {
|
private _createUpdateFn(updateStmts: o.Statement[]): 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());
|
|
||||||
});
|
|
||||||
let updateFn: o.Expression;
|
let updateFn: o.Expression;
|
||||||
if (updateStmts.length > 0) {
|
if (updateStmts.length > 0) {
|
||||||
const preStmts: o.Statement[] = [];
|
const preStmts: o.Statement[] = [];
|
||||||
|
@ -218,40 +221,50 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
|
|
||||||
visitNgContent(ast: NgContentAst, context: any): any {
|
visitNgContent(ast: NgContentAst, context: any): any {
|
||||||
// ngContentDef(ngContentIndex: number, index: number): NodeDef;
|
// ngContentDef(ngContentIndex: number, index: number): NodeDef;
|
||||||
this.nodeDefs.push(() => o.importExpr(createIdentifier(Identifiers.ngContentDef)).callFn([
|
this.nodes.push(() => ({
|
||||||
o.literal(ast.ngContentIndex), o.literal(ast.index)
|
sourceSpan: ast.sourceSpan,
|
||||||
]));
|
nodeDef: o.importExpr(createIdentifier(Identifiers.ngContentDef)).callFn([
|
||||||
|
o.literal(ast.ngContentIndex), o.literal(ast.index)
|
||||||
|
])
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
visitText(ast: TextAst, context: any): any {
|
visitText(ast: TextAst, context: any): any {
|
||||||
// textDef(ngContentIndex: number, constants: string[]): NodeDef;
|
// textDef(ngContentIndex: number, constants: string[]): NodeDef;
|
||||||
this.nodeDefs.push(() => o.importExpr(createIdentifier(Identifiers.textDef)).callFn([
|
this.nodes.push(() => ({
|
||||||
o.literal(ast.ngContentIndex), o.literalArr([o.literal(ast.value)])
|
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 {
|
visitBoundText(ast: BoundTextAst, context: any): any {
|
||||||
const nodeIndex = this.nodeDefs.length;
|
const nodeIndex = this.nodes.length;
|
||||||
// reserve the space in the nodeDefs array
|
// reserve the space in the nodeDefs array
|
||||||
this.nodeDefs.push(null);
|
this.nodes.push(null);
|
||||||
|
|
||||||
const astWithSource = <ASTWithSource>ast.value;
|
const astWithSource = <ASTWithSource>ast.value;
|
||||||
const inter = <Interpolation>astWithSource.ast;
|
const inter = <Interpolation>astWithSource.ast;
|
||||||
|
|
||||||
this._addUpdateExpressions(
|
const updateRendererExpressions = inter.expressions.map(
|
||||||
nodeIndex, inter.expressions.map((expr) => { return {context: COMP_VAR, value: expr}; }),
|
(expr) => this._preprocessUpdateExpression(
|
||||||
this.updateRendererExpressions);
|
{sourceSpan: ast.sourceSpan, context: COMP_VAR, value: expr}));
|
||||||
|
|
||||||
// textDef(ngContentIndex: number, constants: string[]): NodeDef;
|
// textDef(ngContentIndex: number, constants: string[]): NodeDef;
|
||||||
this.nodeDefs[nodeIndex] = () => o.importExpr(createIdentifier(Identifiers.textDef)).callFn([
|
this.nodes[nodeIndex] = () => ({
|
||||||
o.literal(ast.ngContentIndex), o.literalArr(inter.strings.map(s => o.literal(s)))
|
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 {
|
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
|
||||||
const nodeIndex = this.nodeDefs.length;
|
const nodeIndex = this.nodes.length;
|
||||||
// reserve the space in the nodeDefs array
|
// reserve the space in the nodeDefs array
|
||||||
this.nodeDefs.push(null);
|
this.nodes.push(null);
|
||||||
|
|
||||||
const {flags, queryMatchesExpr, hostEvents} = this._visitElementOrTemplate(nodeIndex, ast);
|
const {flags, queryMatchesExpr, hostEvents} = this._visitElementOrTemplate(nodeIndex, ast);
|
||||||
|
|
||||||
|
@ -259,27 +272,29 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
this.children.push(childVisitor);
|
this.children.push(childVisitor);
|
||||||
childVisitor.visitAll(ast.variables, ast.children);
|
childVisitor.visitAll(ast.variables, ast.children);
|
||||||
|
|
||||||
const childCount = this.nodeDefs.length - nodeIndex - 1;
|
const childCount = this.nodes.length - nodeIndex - 1;
|
||||||
|
|
||||||
// anchorDef(
|
// anchorDef(
|
||||||
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
|
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
|
||||||
// childCount: number, handleEventFn?: ElementHandleEventFn, templateFactory?:
|
// childCount: number, handleEventFn?: ElementHandleEventFn, templateFactory?:
|
||||||
// ViewDefinitionFactory): NodeDef;
|
// ViewDefinitionFactory): NodeDef;
|
||||||
const nodeDef = () => o.importExpr(createIdentifier(Identifiers.anchorDef)).callFn([
|
this.nodes[nodeIndex] = () => ({
|
||||||
o.literal(flags),
|
sourceSpan: ast.sourceSpan,
|
||||||
queryMatchesExpr,
|
nodeDef: o.importExpr(createIdentifier(Identifiers.anchorDef)).callFn([
|
||||||
o.literal(ast.ngContentIndex),
|
o.literal(flags),
|
||||||
o.literal(childCount),
|
queryMatchesExpr,
|
||||||
this._createElementHandleEventFn(nodeIndex, hostEvents),
|
o.literal(ast.ngContentIndex),
|
||||||
o.variable(childVisitor.viewName),
|
o.literal(childCount),
|
||||||
]);
|
this._createElementHandleEventFn(nodeIndex, hostEvents),
|
||||||
this.nodeDefs[nodeIndex] = nodeDef;
|
o.variable(childVisitor.viewName),
|
||||||
|
])
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
visitElement(ast: ElementAst, context: any): any {
|
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
|
// reserve the space in the nodeDefs array so we can add children
|
||||||
this.nodeDefs.push(null);
|
this.nodes.push(null);
|
||||||
|
|
||||||
let elName = ast.name;
|
let elName = ast.name;
|
||||||
if (ast.name === NG_CONTAINER_TAG) {
|
if (ast.name === NG_CONTAINER_TAG) {
|
||||||
|
@ -291,18 +306,25 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
this._visitElementOrTemplate(nodeIndex, ast);
|
this._visitElementOrTemplate(nodeIndex, ast);
|
||||||
|
|
||||||
let inputDefs: o.Expression[] = [];
|
let inputDefs: o.Expression[] = [];
|
||||||
|
let updateRendererExpressions: UpdateExpression[] = [];
|
||||||
let outputDefs: o.Expression[] = [];
|
let outputDefs: o.Expression[] = [];
|
||||||
if (elName) {
|
if (elName) {
|
||||||
const hostBindings = ast.inputs
|
const hostBindings = ast.inputs
|
||||||
.map((inputAst) => ({
|
.map((inputAst) => ({
|
||||||
context: COMP_VAR as o.Expression,
|
context: COMP_VAR as o.Expression,
|
||||||
value: inputAst.value,
|
inputAst,
|
||||||
bindingDef: elementBindingDef(inputAst, null),
|
dirAst: null,
|
||||||
}))
|
}))
|
||||||
.concat(dirHostBindings);
|
.concat(dirHostBindings);
|
||||||
if (hostBindings.length) {
|
if (hostBindings.length) {
|
||||||
this._addUpdateExpressions(nodeIndex, hostBindings, this.updateRendererExpressions);
|
updateRendererExpressions =
|
||||||
inputDefs = hostBindings.map(entry => entry.bindingDef);
|
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(
|
outputDefs = usedEvents.map(
|
||||||
([target, eventName]) => o.literalArr([o.literal(target), o.literal(eventName)]));
|
([target, eventName]) => o.literalArr([o.literal(target), o.literal(eventName)]));
|
||||||
|
@ -310,7 +332,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
|
|
||||||
templateVisitAll(this, ast.children);
|
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);
|
const compAst = ast.directives.find(dirAst => dirAst.directive.isComponent);
|
||||||
let compRendererType = o.NULL_EXPR;
|
let compRendererType = o.NULL_EXPR;
|
||||||
|
@ -331,15 +353,23 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
// outputs?: ([OutputType.ElementOutput | OutputType.DirectiveHostOutput, string, string])[],
|
// outputs?: ([OutputType.ElementOutput | OutputType.DirectiveHostOutput, string, string])[],
|
||||||
// handleEvent?: ElementHandleEventFn,
|
// handleEvent?: ElementHandleEventFn,
|
||||||
// componentView?: () => ViewDefinition, componentRendererType?: RendererType2): NodeDef;
|
// componentView?: () => ViewDefinition, componentRendererType?: RendererType2): NodeDef;
|
||||||
const nodeDef = () => o.importExpr(createIdentifier(Identifiers.elementDef)).callFn([
|
this.nodes[nodeIndex] = () => ({
|
||||||
o.literal(flags), queryMatchesExpr, o.literal(ast.ngContentIndex), o.literal(childCount),
|
sourceSpan: ast.sourceSpan,
|
||||||
o.literal(elName), elName ? fixedAttrsDef(ast) : o.NULL_EXPR,
|
nodeDef: o.importExpr(createIdentifier(Identifiers.elementDef)).callFn([
|
||||||
inputDefs.length ? o.literalArr(inputDefs) : o.NULL_EXPR,
|
o.literal(flags),
|
||||||
outputDefs.length ? o.literalArr(outputDefs) : o.NULL_EXPR,
|
queryMatchesExpr,
|
||||||
this._createElementHandleEventFn(nodeIndex, hostEvents), compView, compRendererType
|
o.literal(ast.ngContentIndex),
|
||||||
]);
|
o.literal(childCount),
|
||||||
|
o.literal(elName),
|
||||||
this.nodeDefs[nodeIndex] = nodeDef;
|
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: {
|
private _visitElementOrTemplate(nodeIndex: number, ast: {
|
||||||
|
@ -353,7 +383,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
flags: NodeFlags,
|
flags: NodeFlags,
|
||||||
usedEvents: [string, string][],
|
usedEvents: [string, string][],
|
||||||
queryMatchesExpr: o.Expression,
|
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}[],
|
hostEvents: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[],
|
||||||
} {
|
} {
|
||||||
let flags = NodeFlags.None;
|
let flags = NodeFlags.None;
|
||||||
|
@ -371,7 +402,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
usedEvents.set(elementEventFullName(target, name), [target, name]);
|
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 hostEvents: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[] = [];
|
||||||
const componentFactoryResolverProvider = createComponentFactoryResolver(ast.directives);
|
const componentFactoryResolverProvider = createComponentFactoryResolver(ast.directives);
|
||||||
if (componentFactoryResolverProvider) {
|
if (componentFactoryResolverProvider) {
|
||||||
|
@ -441,12 +473,13 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
providerAst: ProviderAst, dirAst: DirectiveAst, directiveIndex: number,
|
providerAst: ProviderAst, dirAst: DirectiveAst, directiveIndex: number,
|
||||||
elementNodeIndex: number, refs: ReferenceAst[], queryMatches: QueryMatch[],
|
elementNodeIndex: number, refs: ReferenceAst[], queryMatches: QueryMatch[],
|
||||||
usedEvents: Map<string, any>, queryIds: StaticAndDynamicQueryIds): {
|
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}[]
|
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
|
// 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) => {
|
dirAst.directive.queries.forEach((query, queryIndex) => {
|
||||||
let flags = NodeFlags.TypeContentQuery;
|
let flags = NodeFlags.TypeContentQuery;
|
||||||
|
@ -459,17 +492,21 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
flags |= NodeFlags.DynamicQuery;
|
flags |= NodeFlags.DynamicQuery;
|
||||||
}
|
}
|
||||||
const bindingType = query.first ? QueryBindingType.First : QueryBindingType.All;
|
const bindingType = query.first ? QueryBindingType.First : QueryBindingType.All;
|
||||||
this.nodeDefs.push(() => o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([
|
this.nodes.push(() => ({
|
||||||
o.literal(flags), o.literal(queryId),
|
sourceSpan: dirAst.sourceSpan,
|
||||||
new o.LiteralMapExpr([new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))])
|
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,
|
// Note: the operation below might also create new nodeDefs,
|
||||||
// but we don't want them to be a child of a directive,
|
// but we don't want them to be a child of a directive,
|
||||||
// as they might be a provider/pipe on their own.
|
// as they might be a provider/pipe on their own.
|
||||||
// I.e. we only allow queries as children of directives nodes.
|
// 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} =
|
let {flags, queryMatchExprs, providerExpr, depsExpr} =
|
||||||
this._visitProviderOrDirective(providerAst, queryMatches);
|
this._visitProviderOrDirective(providerAst, queryMatches);
|
||||||
|
@ -501,22 +538,21 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
outputDefs.push(new o.LiteralMapEntry(propName, o.literal(eventName), false));
|
outputDefs.push(new o.LiteralMapEntry(propName, o.literal(eventName), false));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
let updateDirectiveExpressions: UpdateExpression[] = [];
|
||||||
if (dirAst.inputs.length || (flags & (NodeFlags.DoCheck | NodeFlags.OnInit)) > 0) {
|
if (dirAst.inputs.length || (flags & (NodeFlags.DoCheck | NodeFlags.OnInit)) > 0) {
|
||||||
this._addUpdateExpressions(
|
updateDirectiveExpressions = dirAst.inputs.map(
|
||||||
nodeIndex,
|
(input) => this._preprocessUpdateExpression(
|
||||||
dirAst.inputs.map((input) => { return {context: COMP_VAR, value: input.value}; }),
|
{sourceSpan: input.sourceSpan, context: COMP_VAR, value: input.value}));
|
||||||
this.updateDirectivesExpressions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const dirContextExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
|
const dirContextExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
|
||||||
VIEW_VAR, o.literal(nodeIndex)
|
VIEW_VAR, o.literal(nodeIndex)
|
||||||
]);
|
]);
|
||||||
const hostBindings =
|
const hostBindings = dirAst.hostProperties.map((inputAst) => ({
|
||||||
dirAst.hostProperties.map((hostBindingAst) => ({
|
context: dirContextExpr,
|
||||||
value: (<ASTWithSource>hostBindingAst.value).ast,
|
dirAst,
|
||||||
context: dirContextExpr,
|
inputAst,
|
||||||
bindingDef: elementBindingDef(hostBindingAst, dirAst),
|
}));
|
||||||
}));
|
|
||||||
const hostEvents = dirAst.hostEvents.map((hostEventAst) => ({
|
const hostEvents = dirAst.hostEvents.map((hostEventAst) => ({
|
||||||
context: dirContextExpr,
|
context: dirContextExpr,
|
||||||
eventAst: hostEventAst, dirAst,
|
eventAst: hostEventAst, dirAst,
|
||||||
|
@ -528,21 +564,24 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
// any,
|
// any,
|
||||||
// deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]},
|
// deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]},
|
||||||
// outputs?: {[name: string]: string}, component?: () => ViewDefinition): NodeDef;
|
// outputs?: {[name: string]: string}, component?: () => ViewDefinition): NodeDef;
|
||||||
const nodeDef = () => o.importExpr(createIdentifier(Identifiers.directiveDef)).callFn([
|
this.nodes[nodeIndex] = () => ({
|
||||||
o.literal(flags), queryMatchExprs.length ? o.literalArr(queryMatchExprs) : o.NULL_EXPR,
|
sourceSpan: dirAst.sourceSpan,
|
||||||
o.literal(childCount), providerExpr, depsExpr,
|
nodeDef: o.importExpr(createIdentifier(Identifiers.directiveDef)).callFn([
|
||||||
inputDefs.length ? new o.LiteralMapExpr(inputDefs) : o.NULL_EXPR,
|
o.literal(flags), queryMatchExprs.length ? o.literalArr(queryMatchExprs) : o.NULL_EXPR,
|
||||||
outputDefs.length ? new o.LiteralMapExpr(outputDefs) : o.NULL_EXPR
|
o.literal(childCount), providerExpr, depsExpr,
|
||||||
]);
|
inputDefs.length ? new o.LiteralMapExpr(inputDefs) : o.NULL_EXPR,
|
||||||
this.nodeDefs[nodeIndex] = nodeDef;
|
outputDefs.length ? new o.LiteralMapExpr(outputDefs) : o.NULL_EXPR
|
||||||
|
]),
|
||||||
|
updateDirectives: updateDirectiveExpressions,
|
||||||
|
});
|
||||||
|
|
||||||
return {hostBindings, hostEvents};
|
return {hostBindings, hostEvents};
|
||||||
}
|
}
|
||||||
|
|
||||||
private _visitProvider(providerAst: ProviderAst, queryMatches: QueryMatch[]): void {
|
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
|
// 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} =
|
const {flags, queryMatchExprs, providerExpr, depsExpr} =
|
||||||
this._visitProviderOrDirective(providerAst, queryMatches);
|
this._visitProviderOrDirective(providerAst, queryMatches);
|
||||||
|
@ -550,11 +589,13 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
// providerDef(
|
// providerDef(
|
||||||
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], token:any,
|
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], token:any,
|
||||||
// value: any, deps: ([DepFlags, any] | any)[]): NodeDef;
|
// value: any, deps: ([DepFlags, any] | any)[]): NodeDef;
|
||||||
const nodeDef = () => o.importExpr(createIdentifier(Identifiers.providerDef)).callFn([
|
this.nodes[nodeIndex] = () => ({
|
||||||
o.literal(flags), queryMatchExprs.length ? o.literalArr(queryMatchExprs) : o.NULL_EXPR,
|
sourceSpan: providerAst.sourceSpan,
|
||||||
tokenExpr(providerAst.token), providerExpr, depsExpr
|
nodeDef: o.importExpr(createIdentifier(Identifiers.providerDef)).callFn([
|
||||||
]);
|
o.literal(flags), queryMatchExprs.length ? o.literalArr(queryMatchExprs) : o.NULL_EXPR,
|
||||||
this.nodeDefs[nodeIndex] = nodeDef;
|
tokenExpr(providerAst.token), providerExpr, depsExpr
|
||||||
|
])
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _visitProviderOrDirective(providerAst: ProviderAst, queryMatches: QueryMatch[]): {
|
private _visitProviderOrDirective(providerAst: ProviderAst, queryMatches: QueryMatch[]): {
|
||||||
|
@ -615,43 +656,50 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
createLiteralArrayConverter(argCount: number): BuiltinConverter {
|
createLiteralArrayConverter(sourceSpan: ParseSourceSpan, argCount: number): BuiltinConverter {
|
||||||
if (argCount === 0) {
|
if (argCount === 0) {
|
||||||
const valueExpr = o.importExpr(createIdentifier(Identifiers.EMPTY_ARRAY));
|
const valueExpr = o.importExpr(createIdentifier(Identifiers.EMPTY_ARRAY));
|
||||||
return () => valueExpr;
|
return () => valueExpr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeIndex = this.nodeDefs.length;
|
const nodeIndex = this.nodes.length;
|
||||||
// pureArrayDef(argCount: number): NodeDef;
|
// pureArrayDef(argCount: number): NodeDef;
|
||||||
const nodeDef = () =>
|
this.nodes.push(
|
||||||
o.importExpr(createIdentifier(Identifiers.pureArrayDef)).callFn([o.literal(argCount)]);
|
() => ({
|
||||||
this.nodeDefs.push(nodeDef);
|
sourceSpan,
|
||||||
|
nodeDef:
|
||||||
|
o.importExpr(createIdentifier(Identifiers.pureArrayDef)).callFn([o.literal(argCount)])
|
||||||
|
}));
|
||||||
|
|
||||||
return (args: o.Expression[]) => callCheckStmt(nodeIndex, args);
|
return (args: o.Expression[]) => callCheckStmt(nodeIndex, args);
|
||||||
}
|
}
|
||||||
createLiteralMapConverter(keys: string[]): BuiltinConverter {
|
createLiteralMapConverter(sourceSpan: ParseSourceSpan, keys: string[]): BuiltinConverter {
|
||||||
if (keys.length === 0) {
|
if (keys.length === 0) {
|
||||||
const valueExpr = o.importExpr(createIdentifier(Identifiers.EMPTY_MAP));
|
const valueExpr = o.importExpr(createIdentifier(Identifiers.EMPTY_MAP));
|
||||||
return () => valueExpr;
|
return () => valueExpr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeIndex = this.nodeDefs.length;
|
const nodeIndex = this.nodes.length;
|
||||||
// function pureObjectDef(propertyNames: string[]): NodeDef
|
// function pureObjectDef(propertyNames: string[]): NodeDef
|
||||||
const nodeDef = () =>
|
this.nodes.push(() => ({
|
||||||
o.importExpr(createIdentifier(Identifiers.pureObjectDef)).callFn([o.literalArr(
|
sourceSpan,
|
||||||
keys.map(key => o.literal(key)))]);
|
nodeDef: o.importExpr(createIdentifier(Identifiers.pureObjectDef))
|
||||||
this.nodeDefs.push(nodeDef);
|
.callFn([o.literalArr(keys.map(key => o.literal(key)))])
|
||||||
|
}));
|
||||||
|
|
||||||
return (args: o.Expression[]) => callCheckStmt(nodeIndex, args);
|
return (args: o.Expression[]) => callCheckStmt(nodeIndex, args);
|
||||||
}
|
}
|
||||||
createPipeConverter(name: string, argCount: number): BuiltinConverter {
|
createPipeConverter(sourceSpan: ParseSourceSpan, name: string, argCount: number):
|
||||||
const pipe = this._findPipe(name);
|
BuiltinConverter {
|
||||||
|
const pipe = this.usedPipes.find((pipeSummary) => pipeSummary.name === name);
|
||||||
if (pipe.pure) {
|
if (pipe.pure) {
|
||||||
const nodeIndex = this.nodeDefs.length;
|
const nodeIndex = this.nodes.length;
|
||||||
// function purePipeDef(argCount: number): NodeDef;
|
// function purePipeDef(argCount: number): NodeDef;
|
||||||
const nodeDef = () =>
|
this.nodes.push(() => ({
|
||||||
o.importExpr(createIdentifier(Identifiers.purePipeDef)).callFn([o.literal(argCount)]);
|
sourceSpan,
|
||||||
this.nodeDefs.push(nodeDef);
|
nodeDef: o.importExpr(createIdentifier(Identifiers.purePipeDef))
|
||||||
|
.callFn([o.literal(argCount)])
|
||||||
|
}));
|
||||||
|
|
||||||
// find underlying pipe in the component view
|
// find underlying pipe in the component view
|
||||||
let compViewExpr: o.Expression = VIEW_VAR;
|
let compViewExpr: o.Expression = VIEW_VAR;
|
||||||
|
@ -669,7 +717,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
return (args: o.Expression[]) =>
|
return (args: o.Expression[]) =>
|
||||||
callUnwrapValue(callCheckStmt(nodeIndex, [pipeValueExpr].concat(args)));
|
callUnwrapValue(callCheckStmt(nodeIndex, [pipeValueExpr].concat(args)));
|
||||||
} else {
|
} else {
|
||||||
const nodeIndex = this._createPipe(pipe);
|
const nodeIndex = this._createPipe(sourceSpan, pipe);
|
||||||
const nodeValueExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
|
const nodeValueExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
|
||||||
VIEW_VAR, o.literal(nodeIndex)
|
VIEW_VAR, o.literal(nodeIndex)
|
||||||
]);
|
]);
|
||||||
|
@ -678,12 +726,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _findPipe(name: string): CompilePipeSummary {
|
private _createPipe(sourceSpan: ParseSourceSpan, pipe: CompilePipeSummary): number {
|
||||||
return this.usedPipes.find((pipeSummary) => pipeSummary.name === name);
|
const nodeIndex = this.nodes.length;
|
||||||
}
|
|
||||||
|
|
||||||
private _createPipe(pipe: CompilePipeSummary): number {
|
|
||||||
const nodeIndex = this.nodeDefs.length;
|
|
||||||
let flags = NodeFlags.None;
|
let flags = NodeFlags.None;
|
||||||
pipe.type.lifecycleHooks.forEach((lifecycleHook) => {
|
pipe.type.lifecycleHooks.forEach((lifecycleHook) => {
|
||||||
// for pipes, we only support ngOnDestroy
|
// for pipes, we only support ngOnDestroy
|
||||||
|
@ -695,24 +739,76 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
const depExprs = pipe.type.diDeps.map(depDef);
|
const depExprs = pipe.type.diDeps.map(depDef);
|
||||||
// function pipeDef(
|
// function pipeDef(
|
||||||
// flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | any)[]): NodeDef
|
// flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | any)[]): NodeDef
|
||||||
const nodeDef = () => o.importExpr(createIdentifier(Identifiers.pipeDef)).callFn([
|
this.nodes.push(() => ({
|
||||||
o.literal(flags), o.importExpr(pipe.type), o.literalArr(depExprs)
|
sourceSpan,
|
||||||
]);
|
nodeDef: o.importExpr(createIdentifier(Identifiers.pipeDef)).callFn([
|
||||||
this.nodeDefs.push(nodeDef);
|
o.literal(flags), o.importExpr(pipe.type), o.literalArr(depExprs)
|
||||||
|
])
|
||||||
|
}));
|
||||||
return nodeIndex;
|
return nodeIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attention: This might create new nodeDefs (for pipes and literal arrays and literal maps)!
|
// Attention: This might create new nodeDefs (for pipes and literal arrays and literal maps)!
|
||||||
private _addUpdateExpressions(
|
private _preprocessUpdateExpression(expression: UpdateExpression): UpdateExpression {
|
||||||
nodeIndex: number, expressions: {context: o.Expression, value: AST}[],
|
return {
|
||||||
target: UpdateExpression[]) {
|
sourceSpan: expression.sourceSpan,
|
||||||
const transformedExpressions = expressions.map(({context, value}) => {
|
context: expression.context,
|
||||||
if (value instanceof ASTWithSource) {
|
value: convertPropertyBindingBuiltins(
|
||||||
value = value.ast;
|
{
|
||||||
|
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(
|
private _createElementHandleEventFn(
|
||||||
|
@ -723,18 +819,17 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
|
||||||
handlers.forEach(({context, eventAst, dirAst}) => {
|
handlers.forEach(({context, eventAst, dirAst}) => {
|
||||||
const bindingId = `${handleEventBindingCount++}`;
|
const bindingId = `${handleEventBindingCount++}`;
|
||||||
const nameResolver = context === COMP_VAR ? this : null;
|
const nameResolver = context === COMP_VAR ? this : null;
|
||||||
const expression =
|
|
||||||
eventAst.handler instanceof ASTWithSource ? eventAst.handler.ast : eventAst.handler;
|
|
||||||
const {stmts, allowDefault} =
|
const {stmts, allowDefault} =
|
||||||
convertActionBinding(nameResolver, context, expression, bindingId);
|
convertActionBinding(nameResolver, context, eventAst.handler, bindingId);
|
||||||
const trueStmts = stmts;
|
const trueStmts = stmts;
|
||||||
if (allowDefault) {
|
if (allowDefault) {
|
||||||
trueStmts.push(ALLOW_DEFAULT_VAR.set(allowDefault.and(ALLOW_DEFAULT_VAR)).toStmt());
|
trueStmts.push(ALLOW_DEFAULT_VAR.set(allowDefault.and(ALLOW_DEFAULT_VAR)).toStmt());
|
||||||
}
|
}
|
||||||
const {target: eventTarget, name: eventName} = elementEventNameAndTarget(eventAst, dirAst);
|
const {target: eventTarget, name: eventName} = elementEventNameAndTarget(eventAst, dirAst);
|
||||||
const fullEventName = elementEventFullName(eventTarget, eventName);
|
const fullEventName = elementEventFullName(eventTarget, eventName);
|
||||||
handleEventStmts.push(
|
handleEventStmts.push(o.applySourceSpanToStatementIfNeeded(
|
||||||
new o.IfStmt(o.literal(fullEventName).identical(EVENT_NAME_VAR), trueStmts));
|
new o.IfStmt(o.literal(fullEventName).identical(EVENT_NAME_VAR), trueStmts),
|
||||||
|
eventAst.sourceSpan));
|
||||||
});
|
});
|
||||||
let handleEventFn: o.Expression;
|
let handleEventFn: o.Expression;
|
||||||
if (handleEventStmts.length > 0) {
|
if (handleEventStmts.length > 0) {
|
||||||
|
|
|
@ -6,12 +6,15 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AotCompiler, AotCompilerHost, AotCompilerOptions, createAotCompiler} from '@angular/compiler';
|
import {AotCompiler, AotCompilerHost, AotCompilerOptions, GeneratedFile, createAotCompiler} from '@angular/compiler';
|
||||||
import {RenderComponentType, ɵReflectionCapabilities as ReflectionCapabilities, ɵreflector as reflector} from '@angular/core';
|
import {RenderComponentType, ɵReflectionCapabilities as ReflectionCapabilities, ɵreflector as reflector} from '@angular/core';
|
||||||
import {async} from '@angular/core/testing';
|
import {async, fakeAsync, tick} from '@angular/core/testing';
|
||||||
import {MetadataBundler, MetadataCollector, ModuleMetadata, privateEntriesToIndex} from '@angular/tsc-wrapped';
|
import {MetadataBundler, MetadataCollector, ModuleMetadata, privateEntriesToIndex} from '@angular/tsc-wrapped';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockData, MockMetadataBundlerHost, settings} from './test_util';
|
|
||||||
|
import {extractSourceMap, originalPositionFor} from '../output/source_map_util';
|
||||||
|
|
||||||
|
import {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, MockMetadataBundlerHost, settings} from './test_util';
|
||||||
|
|
||||||
const DTS = /\.d\.ts$/;
|
const DTS = /\.d\.ts$/;
|
||||||
|
|
||||||
|
@ -39,6 +42,9 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
angularFiles = emittingHost.written;
|
angularFiles = emittingHost.written;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Restore reflector since AoT compiler will update it with a new static reflector
|
||||||
|
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
|
||||||
|
|
||||||
describe('Quickstart', () => {
|
describe('Quickstart', () => {
|
||||||
let host: MockCompilerHost;
|
let host: MockCompilerHost;
|
||||||
let aotHost: MockAotCompilerHost;
|
let aotHost: MockAotCompilerHost;
|
||||||
|
@ -48,9 +54,6 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
aotHost = new MockAotCompilerHost(host);
|
aotHost = new MockAotCompilerHost(host);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Restore reflector since AoT compiler will update it with a new static reflector
|
|
||||||
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
|
|
||||||
|
|
||||||
it('should compile',
|
it('should compile',
|
||||||
async(() => compile(host, aotHost, expectNoDiagnostics).then(generatedFiles => {
|
async(() => compile(host, aotHost, expectNoDiagnostics).then(generatedFiles => {
|
||||||
expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
|
expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||||
|
@ -67,6 +70,155 @@ describe('compiler (unbundled Angular)', () => {
|
||||||
.toBeDefined();
|
.toBeDefined();
|
||||||
})));
|
})));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('aot source mapping', () => {
|
||||||
|
const componentPath = '/app/app.component.ts';
|
||||||
|
|
||||||
|
let rootDir: MockDirectory;
|
||||||
|
let appDir: MockDirectory;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
appDir = {
|
||||||
|
'app.module.ts': `
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [ AppComponent ],
|
||||||
|
bootstrap: [ AppComponent ]
|
||||||
|
})
|
||||||
|
export class AppModule { }
|
||||||
|
`
|
||||||
|
};
|
||||||
|
rootDir = {'app': appDir};
|
||||||
|
});
|
||||||
|
|
||||||
|
function compileApp(): GeneratedFile {
|
||||||
|
const host = new MockCompilerHost(['/app/app.module.ts'], rootDir, angularFiles);
|
||||||
|
const aotHost = new MockAotCompilerHost(host);
|
||||||
|
let result: GeneratedFile[];
|
||||||
|
let error: Error;
|
||||||
|
compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics)
|
||||||
|
.then((files) => result = files, (err) => error = err);
|
||||||
|
tick();
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return result.find(genFile => genFile.srcFileUrl === componentPath);
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findLineAndColumn(file: string, token: string): {line: number, column: number} {
|
||||||
|
const index = file.indexOf(token);
|
||||||
|
if (index === -1) {
|
||||||
|
return {line: null, column: null};
|
||||||
|
}
|
||||||
|
const linesUntilToken = file.slice(0, index).split('\n');
|
||||||
|
const line = linesUntilToken.length;
|
||||||
|
const column = linesUntilToken[linesUntilToken.length - 1].length;
|
||||||
|
return {line, column};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createComponentSource(componentDecorator: string) {
|
||||||
|
return `
|
||||||
|
import { NgModule, Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
${componentDecorator}
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
someMethod() {}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('inline templates', () => {
|
||||||
|
const templateUrl = componentPath;
|
||||||
|
|
||||||
|
function templateDecorator(template: string) { return `template: \`${template}\`,`; }
|
||||||
|
|
||||||
|
declareTests({templateUrl, templateDecorator});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('external templates', () => {
|
||||||
|
const templateUrl = '/app/app.component.html';
|
||||||
|
|
||||||
|
function templateDecorator(template: string) {
|
||||||
|
appDir['app.component.html'] = template;
|
||||||
|
return `templateUrl: 'app.component.html',`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declareTests({templateUrl, templateDecorator});
|
||||||
|
});
|
||||||
|
|
||||||
|
function declareTests(
|
||||||
|
{templateUrl, templateDecorator}:
|
||||||
|
{templateUrl: string, templateDecorator: (template: string) => string}) {
|
||||||
|
it('should use the right source url in html parse errors', fakeAsync(() => {
|
||||||
|
appDir['app.component.ts'] =
|
||||||
|
createComponentSource(templateDecorator('<div>\n </error>'));
|
||||||
|
|
||||||
|
expect(() => compileApp())
|
||||||
|
.toThrowError(new RegExp(`Template parse errors[\\s\\S]*${templateUrl}@1:2`));
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should use the right source url in template parse errors', fakeAsync(() => {
|
||||||
|
appDir['app.component.ts'] = createComponentSource(
|
||||||
|
templateDecorator('<div>\n <div unknown="{{ctxProp}}"></div>'));
|
||||||
|
|
||||||
|
expect(() => compileApp())
|
||||||
|
.toThrowError(new RegExp(`Template parse errors[\\s\\S]*${templateUrl}@1:7`));
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should create a sourceMap for the template', fakeAsync(() => {
|
||||||
|
const template = 'Hello World!';
|
||||||
|
|
||||||
|
appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
|
||||||
|
|
||||||
|
const genFile = compileApp();
|
||||||
|
const sourceMap = extractSourceMap(genFile.source);
|
||||||
|
expect(sourceMap.file).toEqual(genFile.genFileUrl);
|
||||||
|
// the generated file contains the host view and the component view.
|
||||||
|
// we are only interested in the component view.
|
||||||
|
const sourceIndex = sourceMap.sources.indexOf(templateUrl);
|
||||||
|
expect(sourceMap.sourcesContent[sourceIndex]).toEqual(template);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should map elements correctly to the source', fakeAsync(() => {
|
||||||
|
const template = '<div>\n <span></span></div>';
|
||||||
|
|
||||||
|
appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
|
||||||
|
|
||||||
|
const genFile = compileApp();
|
||||||
|
const sourceMap = extractSourceMap(genFile.source);
|
||||||
|
expect(originalPositionFor(sourceMap, findLineAndColumn(genFile.source, `'span'`)))
|
||||||
|
.toEqual({line: 2, column: 3, source: templateUrl});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should map bindings correctly to the source', fakeAsync(() => {
|
||||||
|
const template = `<div>\n <span [title]="someMethod()"></span></div>`;
|
||||||
|
|
||||||
|
appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
|
||||||
|
|
||||||
|
const genFile = compileApp();
|
||||||
|
const sourceMap = extractSourceMap(genFile.source);
|
||||||
|
expect(originalPositionFor(sourceMap, findLineAndColumn(genFile.source, `someMethod()`)))
|
||||||
|
.toEqual({line: 2, column: 9, source: templateUrl});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should map events correctly to the source', fakeAsync(() => {
|
||||||
|
const template = `<div>\n <span (click)="someMethod()"></span></div>`;
|
||||||
|
|
||||||
|
appDir['app.component.ts'] = createComponentSource(templateDecorator(template));
|
||||||
|
|
||||||
|
const genFile = compileApp();
|
||||||
|
const sourceMap = extractSourceMap(genFile.source);
|
||||||
|
expect(originalPositionFor(sourceMap, findLineAndColumn(genFile.source, `someMethod()`)))
|
||||||
|
.toEqual({line: 2, column: 9, source: templateUrl});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('compiler (bundled Angular)', () => {
|
describe('compiler (bundled Angular)', () => {
|
||||||
|
|
|
@ -28,6 +28,7 @@ export function main() {
|
||||||
it('should throw if no template was specified',
|
it('should throw if no template was specified',
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
expect(() => normalizer.normalizeTemplate({
|
expect(() => normalizer.normalizeTemplate({
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
})).toThrowError('No template specified for component SomeComp');
|
})).toThrowError('No template specified for component SomeComp');
|
||||||
|
@ -35,6 +36,7 @@ export function main() {
|
||||||
it('should throw if template is not a string',
|
it('should throw if template is not a string',
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
expect(() => normalizer.normalizeTemplate({
|
expect(() => normalizer.normalizeTemplate({
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
template: <any>{}
|
template: <any>{}
|
||||||
|
@ -43,6 +45,7 @@ export function main() {
|
||||||
it('should throw if templateUrl is not a string',
|
it('should throw if templateUrl is not a string',
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
expect(() => normalizer.normalizeTemplate({
|
expect(() => normalizer.normalizeTemplate({
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
templateUrl: <any>{}
|
templateUrl: <any>{}
|
||||||
|
@ -54,6 +57,7 @@ export function main() {
|
||||||
it('should store the template',
|
it('should store the template',
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeTemplateSync({
|
const template = normalizer.normalizeTemplateSync({
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -64,11 +68,13 @@ export function main() {
|
||||||
});
|
});
|
||||||
expect(template.template).toEqual('a');
|
expect(template.template).toEqual('a');
|
||||||
expect(template.templateUrl).toEqual('package:some/module/a.js');
|
expect(template.templateUrl).toEqual('package:some/module/a.js');
|
||||||
|
expect(template.isInline).toBe(true);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should resolve styles on the annotation against the moduleUrl',
|
it('should resolve styles on the annotation against the moduleUrl',
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeTemplateSync({
|
const template = normalizer.normalizeTemplateSync({
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -83,6 +89,7 @@ export function main() {
|
||||||
it('should resolve styles in the template against the moduleUrl',
|
it('should resolve styles in the template against the moduleUrl',
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeTemplateSync({
|
const template = normalizer.normalizeTemplateSync({
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -97,6 +104,7 @@ export function main() {
|
||||||
it('should use ViewEncapsulation.Emulated by default',
|
it('should use ViewEncapsulation.Emulated by default',
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeTemplateSync({
|
const template = normalizer.normalizeTemplateSync({
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -114,6 +122,7 @@ export function main() {
|
||||||
(config: CompilerConfig, normalizer: DirectiveNormalizer) => {
|
(config: CompilerConfig, normalizer: DirectiveNormalizer) => {
|
||||||
config.defaultEncapsulation = ViewEncapsulation.None;
|
config.defaultEncapsulation = ViewEncapsulation.None;
|
||||||
const template = normalizer.normalizeTemplateSync({
|
const template = normalizer.normalizeTemplateSync({
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -136,6 +145,7 @@ export function main() {
|
||||||
resourceLoader.expect('package:some/module/sometplurl.html', 'a');
|
resourceLoader.expect('package:some/module/sometplurl.html', 'a');
|
||||||
normalizer
|
normalizer
|
||||||
.normalizeTemplateAsync({
|
.normalizeTemplateAsync({
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -147,6 +157,7 @@ export function main() {
|
||||||
.then((template: CompileTemplateMetadata) => {
|
.then((template: CompileTemplateMetadata) => {
|
||||||
expect(template.template).toEqual('a');
|
expect(template.template).toEqual('a');
|
||||||
expect(template.templateUrl).toEqual('package:some/module/sometplurl.html');
|
expect(template.templateUrl).toEqual('package:some/module/sometplurl.html');
|
||||||
|
expect(template.isInline).toBe(false);
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
resourceLoader.flush();
|
resourceLoader.flush();
|
||||||
|
@ -160,6 +171,7 @@ export function main() {
|
||||||
resourceLoader.expect('package:some/module/tpl/sometplurl.html', '');
|
resourceLoader.expect('package:some/module/tpl/sometplurl.html', '');
|
||||||
normalizer
|
normalizer
|
||||||
.normalizeTemplateAsync({
|
.normalizeTemplateAsync({
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -184,6 +196,7 @@ export function main() {
|
||||||
'package:some/module/tpl/sometplurl.html', '<style>@import test.css</style>');
|
'package:some/module/tpl/sometplurl.html', '<style>@import test.css</style>');
|
||||||
normalizer
|
normalizer
|
||||||
.normalizeTemplateAsync({
|
.normalizeTemplateAsync({
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -271,6 +284,7 @@ export function main() {
|
||||||
resourceLoader: MockResourceLoader) => {
|
resourceLoader: MockResourceLoader) => {
|
||||||
resourceLoader.expect('package:some/module/cmp.html', 'a');
|
resourceLoader.expect('package:some/module/cmp.html', 'a');
|
||||||
const prenormMeta = {
|
const prenormMeta = {
|
||||||
|
ngModuleType: null as any,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
templateUrl: 'cmp.html',
|
templateUrl: 'cmp.html',
|
||||||
|
@ -297,6 +311,7 @@ export function main() {
|
||||||
const viewEncapsulation = ViewEncapsulation.Native;
|
const viewEncapsulation = ViewEncapsulation.Native;
|
||||||
const template = normalizer.normalizeLoadedTemplate(
|
const template = normalizer.normalizeLoadedTemplate(
|
||||||
{
|
{
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: viewEncapsulation,
|
encapsulation: viewEncapsulation,
|
||||||
|
@ -311,6 +326,7 @@ export function main() {
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeLoadedTemplate(
|
const template = normalizer.normalizeLoadedTemplate(
|
||||||
{
|
{
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -325,6 +341,7 @@ export function main() {
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeLoadedTemplate(
|
const template = normalizer.normalizeLoadedTemplate(
|
||||||
{
|
{
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -339,6 +356,7 @@ export function main() {
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeLoadedTemplate(
|
const template = normalizer.normalizeLoadedTemplate(
|
||||||
{
|
{
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -354,6 +372,7 @@ export function main() {
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeLoadedTemplate(
|
const template = normalizer.normalizeLoadedTemplate(
|
||||||
{
|
{
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -368,6 +387,7 @@ export function main() {
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeLoadedTemplate(
|
const template = normalizer.normalizeLoadedTemplate(
|
||||||
{
|
{
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -382,6 +402,7 @@ export function main() {
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeLoadedTemplate(
|
const template = normalizer.normalizeLoadedTemplate(
|
||||||
{
|
{
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -396,6 +417,7 @@ export function main() {
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeLoadedTemplate(
|
const template = normalizer.normalizeLoadedTemplate(
|
||||||
{
|
{
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -410,6 +432,7 @@ export function main() {
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeLoadedTemplate(
|
const template = normalizer.normalizeLoadedTemplate(
|
||||||
{
|
{
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -424,6 +447,7 @@ export function main() {
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeLoadedTemplate(
|
const template = normalizer.normalizeLoadedTemplate(
|
||||||
{
|
{
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -438,6 +462,7 @@ export function main() {
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeLoadedTemplate(
|
const template = normalizer.normalizeLoadedTemplate(
|
||||||
{
|
{
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -453,6 +478,7 @@ export function main() {
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeLoadedTemplate(
|
const template = normalizer.normalizeLoadedTemplate(
|
||||||
{
|
{
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -467,6 +493,7 @@ export function main() {
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeLoadedTemplate(
|
const template = normalizer.normalizeLoadedTemplate(
|
||||||
{
|
{
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -482,6 +509,7 @@ export function main() {
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeLoadedTemplate(
|
const template = normalizer.normalizeLoadedTemplate(
|
||||||
{
|
{
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_HTTP_MODULE_URL,
|
moduleUrl: SOME_HTTP_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -497,6 +525,7 @@ export function main() {
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeLoadedTemplate(
|
const template = normalizer.normalizeLoadedTemplate(
|
||||||
{
|
{
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: ViewEncapsulation.Emulated,
|
encapsulation: ViewEncapsulation.Emulated,
|
||||||
|
@ -511,6 +540,7 @@ export function main() {
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeLoadedTemplate(
|
const template = normalizer.normalizeLoadedTemplate(
|
||||||
{
|
{
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
@ -526,6 +556,7 @@ export function main() {
|
||||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||||
const template = normalizer.normalizeLoadedTemplate(
|
const template = normalizer.normalizeLoadedTemplate(
|
||||||
{
|
{
|
||||||
|
ngModuleType: null,
|
||||||
componentType: SomeComp,
|
componentType: SomeComp,
|
||||||
moduleUrl: SOME_MODULE_URL,
|
moduleUrl: SOME_MODULE_URL,
|
||||||
encapsulation: null,
|
encapsulation: null,
|
||||||
|
|
|
@ -9,10 +9,7 @@
|
||||||
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler';
|
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler';
|
||||||
import {EmitterVisitorContext} from '@angular/compiler/src/output/abstract_emitter';
|
import {EmitterVisitorContext} from '@angular/compiler/src/output/abstract_emitter';
|
||||||
import {SourceMap} from '@angular/compiler/src/output/source_map';
|
import {SourceMap} from '@angular/compiler/src/output/source_map';
|
||||||
|
import {extractSourceMap, originalPositionFor} from './source_map_util';
|
||||||
const SourceMapConsumer = require('source-map').SourceMapConsumer;
|
|
||||||
const b64 = require('base64-js');
|
|
||||||
|
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('AbstractEmitter', () => {
|
describe('AbstractEmitter', () => {
|
||||||
|
@ -47,12 +44,10 @@ export function main() {
|
||||||
ctx.print(createSourceSpan(fileA, 0), 'fileA-0');
|
ctx.print(createSourceSpan(fileA, 0), 'fileA-0');
|
||||||
|
|
||||||
const sm = ctx.toSourceMapGenerator(null, 10).toJSON();
|
const sm = ctx.toSourceMapGenerator(null, 10).toJSON();
|
||||||
const smc = new SourceMapConsumer(sm);
|
expect(originalPositionFor(sm, {line: 11, column: 0})).toEqual({
|
||||||
expect(smc.originalPositionFor({line: 11, column: 0})).toEqual({
|
|
||||||
line: 1,
|
line: 1,
|
||||||
column: 0,
|
column: 0,
|
||||||
source: 'a.js',
|
source: 'a.js',
|
||||||
name: null,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -109,9 +104,8 @@ function expectMap(
|
||||||
ctx: EmitterVisitorContext, genLine: number, genCol: number, source: string = null,
|
ctx: EmitterVisitorContext, genLine: number, genCol: number, source: string = null,
|
||||||
srcLine: number = null, srcCol: number = null) {
|
srcLine: number = null, srcCol: number = null) {
|
||||||
const sm = ctx.toSourceMapGenerator().toJSON();
|
const sm = ctx.toSourceMapGenerator().toJSON();
|
||||||
const smc = new SourceMapConsumer(sm);
|
|
||||||
const genPosition = {line: genLine + 1, column: genCol};
|
const genPosition = {line: genLine + 1, column: genCol};
|
||||||
const origPosition = smc.originalPositionFor(genPosition);
|
const origPosition = originalPositionFor(sm, genPosition);
|
||||||
expect(origPosition.source).toEqual(source);
|
expect(origPosition.source).toEqual(source);
|
||||||
expect(origPosition.line).toEqual(srcLine === null ? null : srcLine + 1);
|
expect(origPosition.line).toEqual(srcLine === null ? null : srcLine + 1);
|
||||||
expect(origPosition.column).toEqual(srcCol);
|
expect(origPosition.column).toEqual(srcCol);
|
||||||
|
@ -134,15 +128,3 @@ function createSourceSpan(file: ParseSourceFile, idx: number) {
|
||||||
const sourceSpan = new ParseSourceSpan(start, end);
|
const sourceSpan = new ParseSourceSpan(start, end);
|
||||||
return {sourceSpan};
|
return {sourceSpan};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractSourceMap(source: string): SourceMap {
|
|
||||||
let idx = source.lastIndexOf('\n//#');
|
|
||||||
if (idx == -1) return null;
|
|
||||||
const smComment = source.slice(idx).trim();
|
|
||||||
const smB64 = smComment.split('sourceMappingURL=data:application/json;base64,')[1];
|
|
||||||
return smB64 ? JSON.parse(decodeB64String(smB64)) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function decodeB64String(s: string): string {
|
|
||||||
return b64.toByteArray(s).reduce((s: string, c: number) => s + String.fromCharCode(c), '');
|
|
||||||
}
|
|
|
@ -33,7 +33,10 @@ export function main() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stripSourceMap(source: string): string {
|
export function stripSourceMapAndNewLine(source: string): string {
|
||||||
|
if (source.endsWith('\n')) {
|
||||||
|
source = source.substring(0, source.length - 1);
|
||||||
|
}
|
||||||
const smi = source.lastIndexOf('\n//#');
|
const smi = source.lastIndexOf('\n//#');
|
||||||
if (smi == -1) return source;
|
if (smi == -1) return source;
|
||||||
return source.slice(0, smi);
|
return source.slice(0, smi);
|
||||||
|
|
|
@ -14,9 +14,7 @@ import {ImportResolver} from '@angular/compiler/src/output/path_util';
|
||||||
import {SourceMap} from '@angular/compiler/src/output/source_map';
|
import {SourceMap} from '@angular/compiler/src/output/source_map';
|
||||||
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler/src/parse_util';
|
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler/src/parse_util';
|
||||||
|
|
||||||
import {extractSourceMap} from './abstract_emitter_node_only_spec';
|
import {extractSourceMap, originalPositionFor} from './source_map_util';
|
||||||
|
|
||||||
const SourceMapConsumer = require('source-map').SourceMapConsumer;
|
|
||||||
|
|
||||||
const someModuleUrl = 'somePackage/somePath';
|
const someModuleUrl = 'somePackage/somePath';
|
||||||
|
|
||||||
|
@ -54,12 +52,11 @@ export function main() {
|
||||||
const sourceSpan = new ParseSourceSpan(startLocation, endLocation);
|
const sourceSpan = new ParseSourceSpan(startLocation, endLocation);
|
||||||
const someVar = o.variable('someVar', null, sourceSpan);
|
const someVar = o.variable('someVar', null, sourceSpan);
|
||||||
const sm = emitSourceMap(someVar.toStmt());
|
const sm = emitSourceMap(someVar.toStmt());
|
||||||
const smc = new SourceMapConsumer(sm);
|
|
||||||
|
|
||||||
expect(sm.sources).toEqual(['in.js']);
|
expect(sm.sources).toEqual(['in.js']);
|
||||||
expect(sm.sourcesContent).toEqual([';;;var']);
|
expect(sm.sourcesContent).toEqual([';;;var']);
|
||||||
expect(smc.originalPositionFor({line: 1, column: 0}))
|
expect(originalPositionFor(sm, {line: 1, column: 0}))
|
||||||
.toEqual({line: 1, column: 3, source: 'in.js', name: null});
|
.toEqual({line: 1, column: 3, source: 'in.js'});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter';
|
||||||
import * as o from '@angular/compiler/src/output/output_ast';
|
import * as o from '@angular/compiler/src/output/output_ast';
|
||||||
import {ImportResolver} from '@angular/compiler/src/output/path_util';
|
import {ImportResolver} from '@angular/compiler/src/output/path_util';
|
||||||
|
|
||||||
import {stripSourceMap} from './abstract_emitter_spec';
|
import {stripSourceMapAndNewLine} from './abstract_emitter_spec';
|
||||||
|
|
||||||
const someModuleUrl = 'somePackage/somePath';
|
const someModuleUrl = 'somePackage/somePath';
|
||||||
const anotherModuleUrl = 'somePackage/someOtherPath';
|
const anotherModuleUrl = 'somePackage/someOtherPath';
|
||||||
|
@ -50,7 +50,7 @@ export function main() {
|
||||||
|
|
||||||
function emitStmt(stmt: o.Statement, exportedVars: string[] = null): string {
|
function emitStmt(stmt: o.Statement, exportedVars: string[] = null): string {
|
||||||
const source = emitter.emitStatements(someModuleUrl, [stmt], exportedVars || []);
|
const source = emitter.emitStatements(someModuleUrl, [stmt], exportedVars || []);
|
||||||
return stripSourceMap(source);
|
return stripSourceMapAndNewLine(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should declare variables', () => {
|
it('should declare variables', () => {
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/**
|
||||||
|
* @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 {SourceMap} from '@angular/compiler/src/output/source_map';
|
||||||
|
const b64 = require('base64-js');
|
||||||
|
const SourceMapConsumer = require('source-map').SourceMapConsumer;
|
||||||
|
|
||||||
|
export interface SourceLocation {
|
||||||
|
line: number;
|
||||||
|
column: number;
|
||||||
|
source: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function originalPositionFor(
|
||||||
|
sourceMap: SourceMap, genPosition: {line: number, column: number}): SourceLocation {
|
||||||
|
const smc = new SourceMapConsumer(sourceMap);
|
||||||
|
// Note: We don't return the original object as it also contains a `name` property
|
||||||
|
// which is always null and we don't want to include that in our assertions...
|
||||||
|
const {line, column, source} = smc.originalPositionFor(genPosition);
|
||||||
|
return {line, column, source};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractSourceMap(source: string): SourceMap {
|
||||||
|
let idx = source.lastIndexOf('\n//#');
|
||||||
|
if (idx == -1) return null;
|
||||||
|
const smComment = source.slice(idx).trim();
|
||||||
|
const smB64 = smComment.split('sourceMappingURL=data:application/json;base64,')[1];
|
||||||
|
return smB64 ? JSON.parse(decodeB64String(smB64)) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeB64String(s: string): string {
|
||||||
|
return b64.toByteArray(s).reduce((s: string, c: number) => s + String.fromCharCode(c), '');
|
||||||
|
}
|
|
@ -14,9 +14,7 @@ import {SourceMap} from '@angular/compiler/src/output/source_map';
|
||||||
import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter';
|
import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter';
|
||||||
import {ParseSourceSpan} from '@angular/compiler/src/parse_util';
|
import {ParseSourceSpan} from '@angular/compiler/src/parse_util';
|
||||||
|
|
||||||
import {extractSourceMap} from './abstract_emitter_node_only_spec';
|
import {extractSourceMap, originalPositionFor} from './source_map_util';
|
||||||
|
|
||||||
const SourceMapConsumer = require('source-map').SourceMapConsumer;
|
|
||||||
|
|
||||||
const someModuleUrl = 'somePackage/somePath';
|
const someModuleUrl = 'somePackage/somePath';
|
||||||
|
|
||||||
|
@ -59,12 +57,11 @@ export function main() {
|
||||||
const sourceSpan = new ParseSourceSpan(startLocation, endLocation);
|
const sourceSpan = new ParseSourceSpan(startLocation, endLocation);
|
||||||
const someVar = o.variable('someVar', null, sourceSpan);
|
const someVar = o.variable('someVar', null, sourceSpan);
|
||||||
const sm = emitSourceMap(someVar.toStmt());
|
const sm = emitSourceMap(someVar.toStmt());
|
||||||
const smc = new SourceMapConsumer(sm);
|
|
||||||
|
|
||||||
expect(sm.sources).toEqual(['in.js']);
|
expect(sm.sources).toEqual(['in.js']);
|
||||||
expect(sm.sourcesContent).toEqual([';;;var']);
|
expect(sm.sourcesContent).toEqual([';;;var']);
|
||||||
expect(smc.originalPositionFor({line: 1, column: 0}))
|
expect(originalPositionFor(sm, {line: 1, column: 0}))
|
||||||
.toEqual({line: 1, column: 3, source: 'in.js', name: null});
|
.toEqual({line: 1, column: 3, source: 'in.js'});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,7 @@ import * as o from '@angular/compiler/src/output/output_ast';
|
||||||
import {ImportResolver} from '@angular/compiler/src/output/path_util';
|
import {ImportResolver} from '@angular/compiler/src/output/path_util';
|
||||||
import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter';
|
import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter';
|
||||||
|
|
||||||
import {stripSourceMap} from './abstract_emitter_spec';
|
import {stripSourceMapAndNewLine} from './abstract_emitter_spec';
|
||||||
|
|
||||||
const someModuleUrl = 'somePackage/somePath';
|
const someModuleUrl = 'somePackage/somePath';
|
||||||
const anotherModuleUrl = 'somePackage/someOtherPath';
|
const anotherModuleUrl = 'somePackage/someOtherPath';
|
||||||
|
@ -52,7 +52,7 @@ export function main() {
|
||||||
function emitStmt(stmt: o.Statement | o.Statement[], exportedVars: string[] = null): string {
|
function emitStmt(stmt: o.Statement | o.Statement[], exportedVars: string[] = null): string {
|
||||||
const stmts = Array.isArray(stmt) ? stmt : [stmt];
|
const stmts = Array.isArray(stmt) ? stmt : [stmt];
|
||||||
const source = emitter.emitStatements(someModuleUrl, stmts, exportedVars || []);
|
const source = emitter.emitStatements(someModuleUrl, stmts, exportedVars || []);
|
||||||
return stripSourceMap(source);
|
return stripSourceMapAndNewLine(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should declare variables', () => {
|
it('should declare variables', () => {
|
||||||
|
|
|
@ -23,6 +23,8 @@ export class MockResourceLoader extends ResourceLoader {
|
||||||
return request.getPromise();
|
return request.getPromise();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasPendingRequests() { return !!this._requests.length; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an expectation for the given URL. Incoming requests will be checked against
|
* Add an expectation for the given URL. Incoming requests will be checked against
|
||||||
* the next expectation (in FIFO order). The `verifyNoOutstandingExpectations` method
|
* the next expectation (in FIFO order). The `verifyNoOutstandingExpectations` method
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Injector} from '../di';
|
import {Injector} from '../di';
|
||||||
import {RenderDebugInfo} from '../render/api';
|
import {DebugContext} from '../view/index';
|
||||||
|
|
||||||
export class EventListener { constructor(public name: string, public callback: Function){}; }
|
export class EventListener { constructor(public name: string, public callback: Function){}; }
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ export class DebugNode {
|
||||||
listeners: EventListener[];
|
listeners: EventListener[];
|
||||||
parent: DebugElement;
|
parent: DebugElement;
|
||||||
|
|
||||||
constructor(nativeNode: any, parent: DebugNode, private _debugInfo: RenderDebugInfo) {
|
constructor(nativeNode: any, parent: DebugNode, private _debugContext: DebugContext) {
|
||||||
this.nativeNode = nativeNode;
|
this.nativeNode = nativeNode;
|
||||||
if (parent && parent instanceof DebugElement) {
|
if (parent && parent instanceof DebugElement) {
|
||||||
parent.addChild(this);
|
parent.addChild(this);
|
||||||
|
@ -29,19 +29,24 @@ export class DebugNode {
|
||||||
this.listeners = [];
|
this.listeners = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
get injector(): Injector { return this._debugInfo ? this._debugInfo.injector : null; }
|
get injector(): Injector { return this._debugContext ? this._debugContext.injector : null; }
|
||||||
|
|
||||||
get componentInstance(): any { return this._debugInfo ? this._debugInfo.component : null; }
|
get componentInstance(): any { return this._debugContext ? this._debugContext.component : null; }
|
||||||
|
|
||||||
get context(): any { return this._debugInfo ? this._debugInfo.context : null; }
|
get context(): any { return this._debugContext ? this._debugContext.context : null; }
|
||||||
|
|
||||||
get references(): {[key: string]: any} {
|
get references(): {[key: string]: any} {
|
||||||
return this._debugInfo ? this._debugInfo.references : null;
|
return this._debugContext ? this._debugContext.references : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get providerTokens(): any[] { return this._debugInfo ? this._debugInfo.providerTokens : null; }
|
get providerTokens(): any[] {
|
||||||
|
return this._debugContext ? this._debugContext.providerTokens : null;
|
||||||
|
}
|
||||||
|
|
||||||
get source(): string { return this._debugInfo ? this._debugInfo.source : null; }
|
/**
|
||||||
|
* @deprecated since v4
|
||||||
|
*/
|
||||||
|
get source(): string { return 'Deprecated since v4'; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,8 +61,8 @@ export class DebugElement extends DebugNode {
|
||||||
childNodes: DebugNode[];
|
childNodes: DebugNode[];
|
||||||
nativeElement: any;
|
nativeElement: any;
|
||||||
|
|
||||||
constructor(nativeNode: any, parent: any, _debugInfo: RenderDebugInfo) {
|
constructor(nativeNode: any, parent: any, _debugContext: DebugContext) {
|
||||||
super(nativeNode, parent, _debugInfo);
|
super(nativeNode, parent, _debugContext);
|
||||||
this.properties = {};
|
this.properties = {};
|
||||||
this.attributes = {};
|
this.attributes = {};
|
||||||
this.classes = {};
|
this.classes = {};
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ERROR_ORIGINAL_ERROR, getDebugContext, getOriginalError} from './errors';
|
import {ERROR_ORIGINAL_ERROR, getDebugContext, getErrorLogger, getOriginalError} from './errors';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,36 +50,23 @@ export class ErrorHandler {
|
||||||
constructor(rethrowError: boolean = false) { this.rethrowError = rethrowError; }
|
constructor(rethrowError: boolean = false) { this.rethrowError = rethrowError; }
|
||||||
|
|
||||||
handleError(error: any): void {
|
handleError(error: any): void {
|
||||||
this._console.error(`EXCEPTION: ${this._extractMessage(error)}`);
|
const originalError = this._findOriginalError(error);
|
||||||
|
const context = this._findContext(error);
|
||||||
|
// Note: Browser consoles show the place from where console.error was called.
|
||||||
|
// We can use this to give users additional information about the error.
|
||||||
|
const errorLogger = getErrorLogger(error);
|
||||||
|
|
||||||
if (error instanceof Error) {
|
errorLogger(this._console, `ERROR`, error);
|
||||||
const originalError = this._findOriginalError(error);
|
if (originalError) {
|
||||||
const originalStack = this._findOriginalStack(error);
|
errorLogger(this._console, `ORIGINAL ERROR`, originalError);
|
||||||
const context = this._findContext(error);
|
}
|
||||||
|
if (context) {
|
||||||
if (originalError) {
|
errorLogger(this._console, 'ERROR CONTEXT', context);
|
||||||
this._console.error(`ORIGINAL EXCEPTION: ${this._extractMessage(originalError)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (originalStack) {
|
|
||||||
this._console.error('ORIGINAL STACKTRACE:');
|
|
||||||
this._console.error(originalStack);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context) {
|
|
||||||
this._console.error('ERROR CONTEXT:');
|
|
||||||
this._console.error(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.rethrowError) throw error;
|
if (this.rethrowError) throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
_extractMessage(error: any): string {
|
|
||||||
return error instanceof Error ? error.message : error.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_findContext(error: any): any {
|
_findContext(error: any): any {
|
||||||
if (error) {
|
if (error) {
|
||||||
|
@ -98,20 +86,6 @@ export class ErrorHandler {
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
_findOriginalStack(error: Error): string {
|
|
||||||
let e: any = error;
|
|
||||||
let stack: string = e.stack;
|
|
||||||
while (e instanceof Error && getOriginalError(e)) {
|
|
||||||
e = getOriginalError(e);
|
|
||||||
if (e instanceof Error && e.stack) {
|
|
||||||
stack = e.stack;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stack;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function wrappedError(message: string, originalError: any): Error {
|
export function wrappedError(message: string, originalError: any): Error {
|
||||||
|
|
|
@ -12,6 +12,7 @@ export const ERROR_TYPE = 'ngType';
|
||||||
export const ERROR_COMPONENT_TYPE = 'ngComponentType';
|
export const ERROR_COMPONENT_TYPE = 'ngComponentType';
|
||||||
export const ERROR_DEBUG_CONTEXT = 'ngDebugContext';
|
export const ERROR_DEBUG_CONTEXT = 'ngDebugContext';
|
||||||
export const ERROR_ORIGINAL_ERROR = 'ngOriginalError';
|
export const ERROR_ORIGINAL_ERROR = 'ngOriginalError';
|
||||||
|
export const ERROR_LOGGER = 'ngErrorLogger';
|
||||||
|
|
||||||
|
|
||||||
export function getType(error: Error): Function {
|
export function getType(error: Error): Function {
|
||||||
|
@ -25,3 +26,12 @@ export function getDebugContext(error: Error): DebugContext {
|
||||||
export function getOriginalError(error: Error): Error {
|
export function getOriginalError(error: Error): Error {
|
||||||
return (error as any)[ERROR_ORIGINAL_ERROR];
|
return (error as any)[ERROR_ORIGINAL_ERROR];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getErrorLogger(error: Error): (console: Console, ...values: any[]) => void {
|
||||||
|
return (error as any)[ERROR_LOGGER] || defaultErrorLogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function defaultErrorLogger(console: Console, ...values: any[]) {
|
||||||
|
(<any>console.error)(...values);
|
||||||
|
}
|
|
@ -6,14 +6,11 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {isDevMode} from '../application_ref';
|
|
||||||
import {Renderer2, RendererType2} from '../render/api';
|
import {Renderer2, RendererType2} from '../render/api';
|
||||||
import {SecurityContext} from '../security';
|
import {SecurityContext} from '../security';
|
||||||
|
|
||||||
import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementHandleEventFn, NodeData, NodeDef, NodeFlags, OutputDef, OutputType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, asElementData, asProviderData} from './types';
|
import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementHandleEventFn, NodeData, NodeDef, NodeFlags, OutputDef, OutputType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, asElementData, asProviderData} from './types';
|
||||||
import {checkAndUpdateBinding, dispatchEvent, elementEventFullName, filterQueryId, getParentRenderElement, resolveViewDefinition, sliceErrorStack, splitMatchedQueriesDsl, splitNamespace} from './util';
|
import {NOOP, checkAndUpdateBinding, dispatchEvent, elementEventFullName, filterQueryId, getParentRenderElement, resolveViewDefinition, splitMatchedQueriesDsl, splitNamespace} from './util';
|
||||||
|
|
||||||
const NOOP: any = () => {};
|
|
||||||
|
|
||||||
export function anchorDef(
|
export function anchorDef(
|
||||||
flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
|
flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
|
||||||
|
@ -24,8 +21,6 @@ export function anchorDef(
|
||||||
}
|
}
|
||||||
flags |= NodeFlags.TypeElement;
|
flags |= NodeFlags.TypeElement;
|
||||||
const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl);
|
const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl);
|
||||||
// skip the call to sliceErrorStack itself + the call to this function.
|
|
||||||
const source = isDevMode() ? sliceErrorStack(2, 3) : '';
|
|
||||||
const template = templateFactory ? resolveViewDefinition(templateFactory) : null;
|
const template = templateFactory ? resolveViewDefinition(templateFactory) : null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -45,7 +40,7 @@ export function anchorDef(
|
||||||
element: {
|
element: {
|
||||||
ns: undefined,
|
ns: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
attrs: undefined, template, source,
|
attrs: undefined, template,
|
||||||
componentProvider: undefined,
|
componentProvider: undefined,
|
||||||
componentView: undefined,
|
componentView: undefined,
|
||||||
componentRendererType: undefined,
|
componentRendererType: undefined,
|
||||||
|
@ -71,12 +66,10 @@ export function elementDef(
|
||||||
string, SecurityContext
|
string, SecurityContext
|
||||||
])[],
|
])[],
|
||||||
outputs?: ([string, string])[], handleEvent?: ElementHandleEventFn,
|
outputs?: ([string, string])[], handleEvent?: ElementHandleEventFn,
|
||||||
componentView?: () => ViewDefinition, componentRendererType?: RendererType2): NodeDef {
|
componentView?: ViewDefinitionFactory, componentRendererType?: RendererType2): NodeDef {
|
||||||
if (!handleEvent) {
|
if (!handleEvent) {
|
||||||
handleEvent = NOOP;
|
handleEvent = NOOP;
|
||||||
}
|
}
|
||||||
// skip the call to sliceErrorStack itself + the call to this function.
|
|
||||||
const source = isDevMode() ? sliceErrorStack(2, 3) : '';
|
|
||||||
const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl);
|
const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl);
|
||||||
let ns: string;
|
let ns: string;
|
||||||
let name: string;
|
let name: string;
|
||||||
|
@ -146,7 +139,6 @@ export function elementDef(
|
||||||
ns,
|
ns,
|
||||||
name,
|
name,
|
||||||
attrs,
|
attrs,
|
||||||
source,
|
|
||||||
template: undefined,
|
template: undefined,
|
||||||
// will bet set by the view definition
|
// will bet set by the view definition
|
||||||
componentProvider: undefined, componentView, componentRendererType,
|
componentProvider: undefined, componentView, componentRendererType,
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ERROR_DEBUG_CONTEXT, ERROR_ORIGINAL_ERROR, getDebugContext} from '../errors';
|
import {ERROR_DEBUG_CONTEXT, ERROR_LOGGER, getDebugContext} from '../errors';
|
||||||
import {DebugContext, ViewState} from './types';
|
import {DebugContext, ViewState} from './types';
|
||||||
|
|
||||||
export function expressionChangedAfterItHasBeenCheckedError(
|
export function expressionChangedAfterItHasBeenCheckedError(
|
||||||
|
@ -21,19 +21,27 @@ export function expressionChangedAfterItHasBeenCheckedError(
|
||||||
return viewDebugError(msg, context);
|
return viewDebugError(msg, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function viewWrappedDebugError(originalError: any, context: DebugContext): Error {
|
export function viewWrappedDebugError(err: any, context: DebugContext): Error {
|
||||||
const err = viewDebugError(originalError.message, context);
|
if (!(err instanceof Error)) {
|
||||||
(err as any)[ERROR_ORIGINAL_ERROR] = originalError;
|
// errors that are not Error instances don't have a stack,
|
||||||
|
// so it is ok to wrap them into a new Error object...
|
||||||
|
err = new Error(err.toString());
|
||||||
|
}
|
||||||
|
_addDebugContext(err, context);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function viewDebugError(msg: string, context: DebugContext): Error {
|
export function viewDebugError(msg: string, context: DebugContext): Error {
|
||||||
const err = new Error(msg);
|
const err = new Error(msg);
|
||||||
(err as any)[ERROR_DEBUG_CONTEXT] = context;
|
_addDebugContext(err, context);
|
||||||
err.stack = context.source;
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _addDebugContext(err: Error, context: DebugContext) {
|
||||||
|
(err as any)[ERROR_DEBUG_CONTEXT] = context;
|
||||||
|
(err as any)[ERROR_LOGGER] = context.logError.bind(context);
|
||||||
|
}
|
||||||
|
|
||||||
export function isViewDebugError(err: Error): boolean {
|
export function isViewDebugError(err: Error): boolean {
|
||||||
return !!getDebugContext(err);
|
return !!getDebugContext(err);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@ import {isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './err
|
||||||
import {resolveDep} from './provider';
|
import {resolveDep} from './provider';
|
||||||
import {dirtyParentQueries, getQueryValue} from './query';
|
import {dirtyParentQueries, getQueryValue} from './query';
|
||||||
import {createInjector} from './refs';
|
import {createInjector} from './refs';
|
||||||
import {ArgumentType, BindingType, CheckType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData, asPureExpressionData} from './types';
|
import {ArgumentType, BindingType, CheckType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, NodeLogger, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData, asPureExpressionData} from './types';
|
||||||
import {checkBinding, isComponentView, renderNode, viewParentEl} from './util';
|
import {NOOP, checkBinding, isComponentView, renderNode, viewParentEl} from './util';
|
||||||
import {checkAndUpdateNode, checkAndUpdateView, checkNoChangesNode, checkNoChangesView, createEmbeddedView, createRootView, destroyView} from './view';
|
import {checkAndUpdateNode, checkAndUpdateView, checkNoChangesNode, checkNoChangesView, createEmbeddedView, createRootView, destroyView} from './view';
|
||||||
|
|
||||||
let initialized = false;
|
let initialized = false;
|
||||||
|
@ -357,13 +357,6 @@ class DebugContext_ implements DebugContext {
|
||||||
}
|
}
|
||||||
return references;
|
return references;
|
||||||
}
|
}
|
||||||
get source(): string {
|
|
||||||
if (this.nodeDef.flags & NodeFlags.TypeText) {
|
|
||||||
return this.nodeDef.text.source;
|
|
||||||
} else {
|
|
||||||
return this.elDef.element.source;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
get componentRenderElement() {
|
get componentRenderElement() {
|
||||||
const elData = findHostElement(this.elOrCompView);
|
const elData = findHostElement(this.elOrCompView);
|
||||||
return elData ? elData.renderElement : undefined;
|
return elData ? elData.renderElement : undefined;
|
||||||
|
@ -372,6 +365,31 @@ class DebugContext_ implements DebugContext {
|
||||||
return this.nodeDef.flags & NodeFlags.TypeText ? renderNode(this.view, this.nodeDef) :
|
return this.nodeDef.flags & NodeFlags.TypeText ? renderNode(this.view, this.nodeDef) :
|
||||||
renderNode(this.elView, this.elDef);
|
renderNode(this.elView, this.elDef);
|
||||||
}
|
}
|
||||||
|
logError(console: Console, ...values: any[]) {
|
||||||
|
let logViewFactory: ViewDefinitionFactory;
|
||||||
|
let logNodeIndex: number;
|
||||||
|
if (this.nodeDef.flags & NodeFlags.TypeText) {
|
||||||
|
logViewFactory = this.view.def.factory;
|
||||||
|
logNodeIndex = this.nodeDef.index;
|
||||||
|
} else {
|
||||||
|
logViewFactory = this.elView.def.factory;
|
||||||
|
logNodeIndex = this.elDef.index;
|
||||||
|
}
|
||||||
|
let currNodeIndex = -1;
|
||||||
|
let nodeLogger: NodeLogger = () => {
|
||||||
|
currNodeIndex++;
|
||||||
|
if (currNodeIndex === logNodeIndex) {
|
||||||
|
return console.error.bind(console, ...values);
|
||||||
|
} else {
|
||||||
|
return NOOP;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
logViewFactory(nodeLogger);
|
||||||
|
if (currNodeIndex < logNodeIndex) {
|
||||||
|
console.error('Illegal state: the ViewDefinitionFactory did not call the logger!');
|
||||||
|
(<any>console.error)(...values);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function findHostElement(view: ViewData): ElementData {
|
function findHostElement(view: ViewData): ElementData {
|
||||||
|
|
|
@ -6,15 +6,12 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {isDevMode} from '../application_ref';
|
|
||||||
import {looseIdentical} from '../util';
|
import {looseIdentical} from '../util';
|
||||||
|
|
||||||
import {BindingDef, BindingType, DebugContext, NodeData, NodeDef, NodeFlags, RootData, Services, TextData, ViewData, ViewFlags, asElementData, asTextData} from './types';
|
import {BindingDef, BindingType, DebugContext, NodeData, NodeDef, NodeFlags, RootData, Services, TextData, ViewData, ViewFlags, asElementData, asTextData} from './types';
|
||||||
import {checkAndUpdateBinding, getParentRenderElement, sliceErrorStack} from './util';
|
import {checkAndUpdateBinding, getParentRenderElement} from './util';
|
||||||
|
|
||||||
export function textDef(ngContentIndex: number, constants: string[]): NodeDef {
|
export function textDef(ngContentIndex: number, constants: string[]): NodeDef {
|
||||||
// skip the call to sliceErrorStack itself + the call to this function.
|
|
||||||
const source = isDevMode() ? sliceErrorStack(2, 3) : '';
|
|
||||||
const bindings: BindingDef[] = new Array(constants.length - 1);
|
const bindings: BindingDef[] = new Array(constants.length - 1);
|
||||||
for (let i = 1; i < constants.length; i++) {
|
for (let i = 1; i < constants.length; i++) {
|
||||||
bindings[i - 1] = {
|
bindings[i - 1] = {
|
||||||
|
@ -46,7 +43,7 @@ export function textDef(ngContentIndex: number, constants: string[]): NodeDef {
|
||||||
outputs: [],
|
outputs: [],
|
||||||
element: undefined,
|
element: undefined,
|
||||||
provider: undefined,
|
provider: undefined,
|
||||||
text: {prefix: constants[0], source},
|
text: {prefix: constants[0]},
|
||||||
query: undefined,
|
query: undefined,
|
||||||
ngContent: undefined
|
ngContent: undefined
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {Sanitizer, SecurityContext} from '../security';
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
|
|
||||||
export interface ViewDefinition {
|
export interface ViewDefinition {
|
||||||
|
factory: ViewDefinitionFactory;
|
||||||
flags: ViewFlags;
|
flags: ViewFlags;
|
||||||
updateDirectives: ViewUpdateFn;
|
updateDirectives: ViewUpdateFn;
|
||||||
updateRenderer: ViewUpdateFn;
|
updateRenderer: ViewUpdateFn;
|
||||||
|
@ -45,9 +46,22 @@ export interface ViewDefinition {
|
||||||
nodeMatchedQueries: number;
|
nodeMatchedQueries: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ViewDefinitionFactory = () => ViewDefinition;
|
/**
|
||||||
|
* Factory for ViewDefinitions.
|
||||||
|
* We use a function so we can reexeute it in case an error happens and use the given logger
|
||||||
|
* function to log the error from the definition of the node, which is shown in all browser
|
||||||
|
* logs.
|
||||||
|
*/
|
||||||
|
export interface ViewDefinitionFactory { (logger: NodeLogger): ViewDefinition; }
|
||||||
|
|
||||||
export type ViewUpdateFn = (check: NodeCheckFn, view: ViewData) => void;
|
/**
|
||||||
|
* Function to call console.error at the right source location. This is an indirection
|
||||||
|
* via another function as browser will log the location that actually called
|
||||||
|
* `console.error`.
|
||||||
|
*/
|
||||||
|
export interface NodeLogger { (): () => void; }
|
||||||
|
|
||||||
|
export interface ViewUpdateFn { (check: NodeCheckFn, view: ViewData): void; }
|
||||||
|
|
||||||
// helper functions to create an overloaded function type.
|
// helper functions to create an overloaded function type.
|
||||||
export interface NodeCheckFn {
|
export interface NodeCheckFn {
|
||||||
|
@ -57,11 +71,12 @@ export interface NodeCheckFn {
|
||||||
v3?: any, v4?: any, v5?: any, v6?: any, v7?: any, v8?: any, v9?: any): any;
|
v3?: any, v4?: any, v5?: any, v6?: any, v7?: any, v8?: any, v9?: any): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ViewHandleEventFn =
|
|
||||||
(view: ViewData, nodeIndex: number, eventName: string, event: any) => boolean;
|
|
||||||
|
|
||||||
export const enum ArgumentType {Inline, Dynamic}
|
export const enum ArgumentType {Inline, Dynamic}
|
||||||
|
|
||||||
|
export interface ViewHandleEventFn {
|
||||||
|
(view: ViewData, nodeIndex: number, eventName: string, event: any): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bitmask for ViewDefintion.flags.
|
* Bitmask for ViewDefintion.flags.
|
||||||
*/
|
*/
|
||||||
|
@ -221,11 +236,10 @@ export interface ElementDef {
|
||||||
* that are located on this element.
|
* that are located on this element.
|
||||||
*/
|
*/
|
||||||
allProviders: {[tokenKey: string]: NodeDef};
|
allProviders: {[tokenKey: string]: NodeDef};
|
||||||
source: string;
|
|
||||||
handleEvent: ElementHandleEventFn;
|
handleEvent: ElementHandleEventFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ElementHandleEventFn = (view: ViewData, eventName: string, event: any) => boolean;
|
export interface ElementHandleEventFn { (view: ViewData, eventName: string, event: any): boolean; }
|
||||||
|
|
||||||
export interface ProviderDef {
|
export interface ProviderDef {
|
||||||
token: any;
|
token: any;
|
||||||
|
@ -250,10 +264,7 @@ export const enum DepFlags {
|
||||||
Value = 2 << 2,
|
Value = 2 << 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TextDef {
|
export interface TextDef { prefix: string; }
|
||||||
prefix: string;
|
|
||||||
source: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface QueryDef {
|
export interface QueryDef {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -318,7 +329,7 @@ export const enum ViewState {
|
||||||
Destroyed = 1 << 3
|
Destroyed = 1 << 3
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DisposableFn = () => void;
|
export interface DisposableFn { (): void; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Node instance data.
|
* Node instance data.
|
||||||
|
@ -428,9 +439,9 @@ export abstract class DebugContext {
|
||||||
abstract get providerTokens(): any[];
|
abstract get providerTokens(): any[];
|
||||||
abstract get references(): {[key: string]: any};
|
abstract get references(): {[key: string]: any};
|
||||||
abstract get context(): any;
|
abstract get context(): any;
|
||||||
abstract get source(): string;
|
|
||||||
abstract get componentRenderElement(): any;
|
abstract get componentRenderElement(): any;
|
||||||
abstract get renderNode(): any;
|
abstract get renderNode(): any;
|
||||||
|
abstract logError(console: Console, ...values: any[]): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {isDevMode} from '../application_ref';
|
|
||||||
import {WrappedValue, devModeEqual} from '../change_detection/change_detection';
|
import {WrappedValue, devModeEqual} from '../change_detection/change_detection';
|
||||||
import {SimpleChange} from '../change_detection/change_detection_util';
|
import {SimpleChange} from '../change_detection/change_detection_util';
|
||||||
import {Injector} from '../di';
|
import {Injector} from '../di';
|
||||||
|
@ -18,7 +17,9 @@ import {Renderer, RendererType2} from '../render/api';
|
||||||
import {looseIdentical, stringify} from '../util';
|
import {looseIdentical, stringify} from '../util';
|
||||||
|
|
||||||
import {expressionChangedAfterItHasBeenCheckedError, isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './errors';
|
import {expressionChangedAfterItHasBeenCheckedError, isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './errors';
|
||||||
import {DebugContext, ElementData, NodeData, NodeDef, NodeFlags, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asProviderData, asTextData} from './types';
|
import {DebugContext, ElementData, NodeData, NodeDef, NodeFlags, NodeLogger, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asProviderData, asTextData} from './types';
|
||||||
|
|
||||||
|
export const NOOP: any = () => {};
|
||||||
|
|
||||||
const _tokenKeyCache = new Map<any, string>();
|
const _tokenKeyCache = new Map<any, string>();
|
||||||
|
|
||||||
|
@ -194,29 +195,13 @@ const VIEW_DEFINITION_CACHE = new WeakMap<any, ViewDefinition>();
|
||||||
export function resolveViewDefinition(factory: ViewDefinitionFactory): ViewDefinition {
|
export function resolveViewDefinition(factory: ViewDefinitionFactory): ViewDefinition {
|
||||||
let value: ViewDefinition = VIEW_DEFINITION_CACHE.get(factory);
|
let value: ViewDefinition = VIEW_DEFINITION_CACHE.get(factory);
|
||||||
if (!value) {
|
if (!value) {
|
||||||
value = factory();
|
value = factory(() => NOOP);
|
||||||
|
value.factory = factory;
|
||||||
VIEW_DEFINITION_CACHE.set(factory, value);
|
VIEW_DEFINITION_CACHE.set(factory, value);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sliceErrorStack(start: number, end: number): string {
|
|
||||||
let err: any;
|
|
||||||
try {
|
|
||||||
throw new Error();
|
|
||||||
} catch (e) {
|
|
||||||
err = e;
|
|
||||||
}
|
|
||||||
const stack = err.stack || '';
|
|
||||||
const lines = stack.split('\n');
|
|
||||||
if (lines[0].startsWith('Error')) {
|
|
||||||
// Chrome always adds the message to the stack as well...
|
|
||||||
start++;
|
|
||||||
end++;
|
|
||||||
}
|
|
||||||
return lines.slice(start, end).join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function rootRenderNodes(view: ViewData): any[] {
|
export function rootRenderNodes(view: ViewData): any[] {
|
||||||
const renderNodes: any[] = [];
|
const renderNodes: any[] = [];
|
||||||
visitRootRenderNodes(view, RenderNodeAction.Collect, undefined, undefined, renderNodes);
|
visitRootRenderNodes(view, RenderNodeAction.Collect, undefined, undefined, renderNodes);
|
||||||
|
|
|
@ -18,9 +18,7 @@ import {checkAndUpdateQuery, createQuery, queryDef} from './query';
|
||||||
import {createTemplateData, createViewContainerData} from './refs';
|
import {createTemplateData, createViewContainerData} from './refs';
|
||||||
import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text';
|
import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text';
|
||||||
import {ArgumentType, CheckType, ElementData, ElementDef, NodeData, NodeDef, NodeFlags, ProviderData, ProviderDef, RootData, Services, TextDef, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList, asTextData} from './types';
|
import {ArgumentType, CheckType, ElementData, ElementDef, NodeData, NodeDef, NodeFlags, ProviderData, ProviderDef, RootData, Services, TextDef, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList, asTextData} from './types';
|
||||||
import {checkBindingNoChanges, isComponentView, resolveViewDefinition, viewParentEl} from './util';
|
import {NOOP, checkBindingNoChanges, isComponentView, resolveViewDefinition, viewParentEl} from './util';
|
||||||
|
|
||||||
const NOOP = (): any => undefined;
|
|
||||||
|
|
||||||
export function viewDef(
|
export function viewDef(
|
||||||
flags: ViewFlags, nodes: NodeDef[], updateDirectives?: ViewUpdateFn,
|
flags: ViewFlags, nodes: NodeDef[], updateDirectives?: ViewUpdateFn,
|
||||||
|
@ -137,6 +135,8 @@ export function viewDef(
|
||||||
const handleEvent: ViewHandleEventFn = (view, nodeIndex, eventName, event) =>
|
const handleEvent: ViewHandleEventFn = (view, nodeIndex, eventName, event) =>
|
||||||
nodes[nodeIndex].element.handleEvent(view, eventName, event);
|
nodes[nodeIndex].element.handleEvent(view, eventName, event);
|
||||||
return {
|
return {
|
||||||
|
// Will be filled later...
|
||||||
|
factory: undefined,
|
||||||
nodeFlags: viewNodeFlags,
|
nodeFlags: viewNodeFlags,
|
||||||
rootNodeFlags: viewRootNodeFlags,
|
rootNodeFlags: viewRootNodeFlags,
|
||||||
nodeMatchedQueries: viewMatchedQueries, flags,
|
nodeMatchedQueries: viewMatchedQueries, flags,
|
||||||
|
|
|
@ -172,7 +172,7 @@ export function main() {
|
||||||
]))
|
]))
|
||||||
.then(() => expect(false).toBe(true), (e) => {
|
.then(() => expect(false).toBe(true), (e) => {
|
||||||
expect(e).toBe('Test');
|
expect(e).toBe('Test');
|
||||||
expect(mockConsole.res).toEqual(['EXCEPTION: Test']);
|
expect(mockConsole.res[0].join('#')).toEqual('ERROR#Test');
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -213,7 +213,7 @@ export function main() {
|
||||||
const expectedErrMsg =
|
const expectedErrMsg =
|
||||||
`The module MyModule was bootstrapped, but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. Please define one of these.`;
|
`The module MyModule was bootstrapped, but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. Please define one of these.`;
|
||||||
expect(e.message).toEqual(expectedErrMsg);
|
expect(e.message).toEqual(expectedErrMsg);
|
||||||
expect(mockConsole.res[0]).toEqual('EXCEPTION: ' + expectedErrMsg);
|
expect(mockConsole.res[0].join('#')).toEqual('ERROR#Error: ' + expectedErrMsg);
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -269,7 +269,7 @@ export function main() {
|
||||||
defaultPlatform.bootstrapModuleFactory(moduleFactory)
|
defaultPlatform.bootstrapModuleFactory(moduleFactory)
|
||||||
.then(() => expect(false).toBe(true), (e) => {
|
.then(() => expect(false).toBe(true), (e) => {
|
||||||
expect(e).toBe('Test');
|
expect(e).toBe('Test');
|
||||||
expect(mockConsole.res).toEqual(['EXCEPTION: Test']);
|
expect(mockConsole.res[0].join('#')).toEqual('ERROR#Test');
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
@ -509,7 +509,7 @@ export function main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockConsole {
|
class MockConsole {
|
||||||
res: any[] = [];
|
res: any[][] = [];
|
||||||
log(s: any): void { this.res.push(s); }
|
log(...args: any[]): void { this.res.push(args); }
|
||||||
error(s: any): void { this.res.push(s); }
|
error(...args: any[]): void { this.res.push(args); }
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,13 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ERROR_DEBUG_CONTEXT, ERROR_TYPE} from '@angular/core/src/errors';
|
import {ERROR_DEBUG_CONTEXT, ERROR_LOGGER, ERROR_TYPE} from '@angular/core/src/errors';
|
||||||
|
|
||||||
import {ErrorHandler, wrappedError} from '../src/error_handler';
|
import {ErrorHandler, wrappedError} from '../src/error_handler';
|
||||||
|
|
||||||
class MockConsole {
|
class MockConsole {
|
||||||
res: any[] = [];
|
res: any[][] = [];
|
||||||
error(s: any): void { this.res.push(s); }
|
error(...s: any[]): void { this.res.push(s); }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
|
@ -21,15 +21,7 @@ export function main() {
|
||||||
const errorHandler = new ErrorHandler(false);
|
const errorHandler = new ErrorHandler(false);
|
||||||
errorHandler._console = logger as any;
|
errorHandler._console = logger as any;
|
||||||
errorHandler.handleError(error);
|
errorHandler.handleError(error);
|
||||||
return logger.res.join('\n');
|
return logger.res.map(line => line.join('#')).join('\n');
|
||||||
}
|
|
||||||
|
|
||||||
function getStack(error: Error): string {
|
|
||||||
try {
|
|
||||||
throw error;
|
|
||||||
} catch (e) {
|
|
||||||
return e.stack;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('ErrorHandler', () => {
|
describe('ErrorHandler', () => {
|
||||||
|
@ -38,33 +30,15 @@ export function main() {
|
||||||
expect(e).toContain('message!');
|
expect(e).toContain('message!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should output stackTrace', () => {
|
|
||||||
const error = new Error('message!');
|
|
||||||
const stack = getStack(error);
|
|
||||||
if (stack) {
|
|
||||||
const e = errorToString(error);
|
|
||||||
expect(e).toContain(stack);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('context', () => {
|
describe('context', () => {
|
||||||
it('should print nested context', () => {
|
it('should print nested context', () => {
|
||||||
const cause = new Error('message!');
|
const cause = new Error('message!');
|
||||||
const stack = getStack(cause);
|
|
||||||
const context = { source: 'context!', toString() { return 'Context'; } } as any;
|
const context = { source: 'context!', toString() { return 'Context'; } } as any;
|
||||||
const original = viewWrappedError(cause, context);
|
const original = debugError(cause, context);
|
||||||
const e = errorToString(wrappedError('message', original));
|
const e = errorToString(wrappedError('message', original));
|
||||||
expect(e).toEqual(
|
expect(e).toEqual(`ERROR#Error: message caused by: Error in context! caused by: message!
|
||||||
stack ? `EXCEPTION: message caused by: Error in context! caused by: message!
|
ORIGINAL ERROR#Error: message!
|
||||||
ORIGINAL EXCEPTION: message!
|
ERROR CONTEXT#Context`);
|
||||||
ORIGINAL STACKTRACE:
|
|
||||||
${stack}
|
|
||||||
ERROR CONTEXT:
|
|
||||||
Context` :
|
|
||||||
`EXCEPTION: message caused by: Error in context! caused by: message!
|
|
||||||
ORIGINAL EXCEPTION: message!
|
|
||||||
ERROR CONTEXT:
|
|
||||||
Context`);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -84,23 +58,25 @@ Context`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('original stack', () => {
|
it('should use the error logger on the error', () => {
|
||||||
it('should print original stack if available', () => {
|
const err = new Error('test');
|
||||||
const realOriginal = new Error('inner');
|
const console = new MockConsole();
|
||||||
const stack = getStack(realOriginal);
|
const errorHandler = new ErrorHandler(false);
|
||||||
if (stack) {
|
errorHandler._console = console as any;
|
||||||
const original = wrappedError('wrapped', realOriginal);
|
const logger = jasmine.createSpy('logger');
|
||||||
const e = errorToString(wrappedError('wrappedwrapped', original));
|
(err as any)[ERROR_LOGGER] = logger;
|
||||||
expect(e).toContain(stack);
|
|
||||||
}
|
errorHandler.handleError(err);
|
||||||
});
|
|
||||||
|
expect(console.res).toEqual([]);
|
||||||
|
expect(logger).toHaveBeenCalledWith(console, 'ERROR', err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function viewWrappedError(originalError: any, context: any): Error {
|
function debugError(originalError: any, context: any): Error {
|
||||||
const error = wrappedError(`Error in ${context.source}`, originalError);
|
const error = wrappedError(`Error in ${context.source}`, originalError);
|
||||||
(error as any)[ERROR_DEBUG_CONTEXT] = context;
|
(error as any)[ERROR_DEBUG_CONTEXT] = context;
|
||||||
(error as any)[ERROR_TYPE] = viewWrappedError;
|
(error as any)[ERROR_TYPE] = debugError;
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1414,8 +1414,8 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||||
TestBed.createComponent(MyComp);
|
TestBed.createComponent(MyComp);
|
||||||
throw 'Should throw';
|
throw 'Should throw';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
expect(e.message).toEqual(
|
expect(e.message).toMatch(
|
||||||
`Template parse errors:\nCan't bind to 'unknown' since it isn't a known property of 'div'. ("<div [ERROR ->]unknown="{{ctxProp}}"></div>"): MyComp@0:5`);
|
/Template parse errors:\nCan't bind to 'unknown' since it isn't a known property of 'div'. \("<div \[ERROR ->\]unknown="{{ctxProp}}"><\/div>"\): .*MyComp.html@0:5/);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,222 @@
|
||||||
|
/**
|
||||||
|
* @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 {ResourceLoader} from '@angular/compiler';
|
||||||
|
import {SourceMap} from '@angular/compiler/src/output/source_map';
|
||||||
|
import {extractSourceMap, originalPositionFor} from '@angular/compiler/test/output/source_map_util';
|
||||||
|
import {MockResourceLoader} from '@angular/compiler/testing/src/resource_loader_mock';
|
||||||
|
import {Component, Directive, ɵglobal} from '@angular/core';
|
||||||
|
import {getErrorLogger} from '@angular/core/src/errors';
|
||||||
|
import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('jit source mapping', () => {
|
||||||
|
let jitSpy: jasmine.Spy;
|
||||||
|
let resourceLoader: MockResourceLoader;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jitSpy = spyOn(ɵglobal, 'Function').and.callThrough();
|
||||||
|
resourceLoader = new MockResourceLoader();
|
||||||
|
TestBed.configureCompiler({providers: [{provide: ResourceLoader, useValue: resourceLoader}]});
|
||||||
|
});
|
||||||
|
|
||||||
|
function getErrorLoggerStack(e: Error): string {
|
||||||
|
let logStack: string;
|
||||||
|
getErrorLogger(e)(<any>{error: () => logStack = new Error().stack}, e.message);
|
||||||
|
return logStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSourceMap(genFile: string): SourceMap {
|
||||||
|
const jitSources = jitSpy.calls.all().map((call) => call.args[call.args.length - 1]);
|
||||||
|
return jitSources.map(source => extractSourceMap(source))
|
||||||
|
.find(map => map && map.file === genFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSourcePositionForStack(stack: string):
|
||||||
|
{source: string, line: number, column: number} {
|
||||||
|
const ngFactoryLocations =
|
||||||
|
stack
|
||||||
|
.split('\n')
|
||||||
|
// e.g. at View_MyComp_0 (ng:///DynamicTestModule/MyComp.ngfactory.js:153:40)
|
||||||
|
.map(line => /\((.*\.ngfactory\.js):(\d+):(\d+)/.exec(line))
|
||||||
|
.filter(match => !!match)
|
||||||
|
.map(match => ({
|
||||||
|
file: match[1],
|
||||||
|
line: parseInt(match[2], 10),
|
||||||
|
column: parseInt(match[3], 10)
|
||||||
|
}));
|
||||||
|
const ngFactoryLocation = ngFactoryLocations[0];
|
||||||
|
|
||||||
|
const sourceMap = getSourceMap(ngFactoryLocation.file);
|
||||||
|
return originalPositionFor(
|
||||||
|
sourceMap, {line: ngFactoryLocation.line, column: ngFactoryLocation.column});
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileAndCreateComponent(comType: any) {
|
||||||
|
TestBed.configureTestingModule({declarations: [comType]});
|
||||||
|
|
||||||
|
let error: any;
|
||||||
|
TestBed.compileComponents().catch((e) => error = e);
|
||||||
|
if (resourceLoader.hasPendingRequests()) {
|
||||||
|
resourceLoader.flush();
|
||||||
|
}
|
||||||
|
tick();
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return TestBed.createComponent(comType);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('inline templates', () => {
|
||||||
|
const templateUrl = 'ng:///DynamicTestModule/MyComp.html';
|
||||||
|
|
||||||
|
function templateDecorator(template: string) { return {template}; }
|
||||||
|
|
||||||
|
declareTests({templateUrl, templateDecorator});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('external templates', () => {
|
||||||
|
const templateUrl = 'http://localhost:1234:some/url.html';
|
||||||
|
|
||||||
|
function templateDecorator(template: string) {
|
||||||
|
resourceLoader.expect(templateUrl, template);
|
||||||
|
return {templateUrl};
|
||||||
|
}
|
||||||
|
|
||||||
|
declareTests({templateUrl, templateDecorator});
|
||||||
|
});
|
||||||
|
|
||||||
|
function declareTests({templateUrl, templateDecorator}: {
|
||||||
|
templateUrl: string,
|
||||||
|
templateDecorator: (template: string) => { [key: string]: any }
|
||||||
|
}) {
|
||||||
|
it('should use the right source url in html parse errors', fakeAsync(() => {
|
||||||
|
@Component({...templateDecorator('<div>\n </error>')})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => compileAndCreateComponent(MyComp))
|
||||||
|
.toThrowError(new RegExp(
|
||||||
|
`Template parse errors[\\s\\S]*${templateUrl.replace('$', '\\$')}@1:2`));
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should use the right source url in template parse errors', fakeAsync(() => {
|
||||||
|
@Component({...templateDecorator('<div>\n <div unknown="{{ctxProp}}"></div>')})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => compileAndCreateComponent(MyComp))
|
||||||
|
.toThrowError(new RegExp(
|
||||||
|
`Template parse errors[\\s\\S]*${templateUrl.replace('$', '\\$')}@1:7`));
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should create a sourceMap for templates', fakeAsync(() => {
|
||||||
|
const template = `Hello World!`;
|
||||||
|
|
||||||
|
@Component({...templateDecorator(template)})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
|
||||||
|
compileAndCreateComponent(MyComp);
|
||||||
|
|
||||||
|
const sourceMap = getSourceMap('ng:///DynamicTestModule/MyComp.ngfactory.js');
|
||||||
|
expect(sourceMap.sources).toEqual([templateUrl]);
|
||||||
|
expect(sourceMap.sourcesContent).toEqual([template]);
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should report source location for di errors', fakeAsync(() => {
|
||||||
|
const template = `<div>\n <div someDir></div></div>`;
|
||||||
|
|
||||||
|
@Component({...templateDecorator(template)})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: '[someDir]'})
|
||||||
|
class SomeDir {
|
||||||
|
constructor() { throw new Error('Test'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [SomeDir]});
|
||||||
|
let error: any;
|
||||||
|
try {
|
||||||
|
compileAndCreateComponent(MyComp);
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
// The error should be logged from the element
|
||||||
|
expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({
|
||||||
|
line: 2,
|
||||||
|
column: 4,
|
||||||
|
source: templateUrl,
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should report source location for binding errors', fakeAsync(() => {
|
||||||
|
const template = `<div>\n <span [title]="createError()"></span></div>`;
|
||||||
|
|
||||||
|
@Component({...templateDecorator(template)})
|
||||||
|
class MyComp {
|
||||||
|
createError() { throw new Error('Test'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
const comp = compileAndCreateComponent(MyComp);
|
||||||
|
|
||||||
|
let error: any;
|
||||||
|
try {
|
||||||
|
comp.detectChanges();
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
// the stack should point to the binding
|
||||||
|
expect(getSourcePositionForStack(error.stack)).toEqual({
|
||||||
|
line: 2,
|
||||||
|
column: 12,
|
||||||
|
source: templateUrl,
|
||||||
|
});
|
||||||
|
// The error should be logged from the element
|
||||||
|
expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({
|
||||||
|
line: 2,
|
||||||
|
column: 4,
|
||||||
|
source: templateUrl,
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should report source location for event errors', fakeAsync(() => {
|
||||||
|
const template = `<div>\n <span (click)="createError()"></span></div>`;
|
||||||
|
|
||||||
|
@Component({...templateDecorator(template)})
|
||||||
|
class MyComp {
|
||||||
|
createError() { throw new Error('Test'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
const comp = compileAndCreateComponent(MyComp);
|
||||||
|
|
||||||
|
let error: any;
|
||||||
|
try {
|
||||||
|
comp.debugElement.children[0].children[0].triggerEventHandler('click', 'EVENT');
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
// the stack should point to the binding
|
||||||
|
expect(getSourcePositionForStack(error.stack)).toEqual({
|
||||||
|
line: 2,
|
||||||
|
column: 12,
|
||||||
|
source: templateUrl,
|
||||||
|
});
|
||||||
|
// The error should be logged from the element
|
||||||
|
expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({
|
||||||
|
line: 2,
|
||||||
|
column: 4,
|
||||||
|
source: templateUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -470,7 +470,7 @@ export function main() {
|
||||||
TestBed.configureTestingModule({declarations: [CycleDirective]});
|
TestBed.configureTestingModule({declarations: [CycleDirective]});
|
||||||
expect(() => createComponent('<div cycleDirective></div>'))
|
expect(() => createComponent('<div cycleDirective></div>'))
|
||||||
.toThrowError(
|
.toThrowError(
|
||||||
'Template parse errors:\nCannot instantiate cyclic dependency! CycleDirective ("[ERROR ->]<div cycleDirective></div>"): TestComp@0:0');
|
/Template parse errors:\nCannot instantiate cyclic dependency! CycleDirective \("\[ERROR ->\]<div cycleDirective><\/div>"\): .*TestComp.html@0:0/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not instantiate a directive in a view that has a host dependency on providers' +
|
it('should not instantiate a directive in a view that has a host dependency on providers' +
|
||||||
|
@ -485,7 +485,7 @@ export function main() {
|
||||||
|
|
||||||
expect(() => createComponent('<div simpleComponent></div>'))
|
expect(() => createComponent('<div simpleComponent></div>'))
|
||||||
.toThrowError(
|
.toThrowError(
|
||||||
`Template parse errors:\nNo provider for service ("[ERROR ->]<div needsServiceFromHost><div>"): SimpleComponent@0:0`);
|
/Template parse errors:\nNo provider for service \("\[ERROR ->\]<div needsServiceFromHost><div>"\): .*SimpleComponent.html@0:0/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not instantiate a directive in a view that has a host dependency on providers' +
|
it('should not instantiate a directive in a view that has a host dependency on providers' +
|
||||||
|
@ -501,7 +501,7 @@ export function main() {
|
||||||
|
|
||||||
expect(() => createComponent('<div simpleComponent someOtherDirective></div>'))
|
expect(() => createComponent('<div simpleComponent someOtherDirective></div>'))
|
||||||
.toThrowError(
|
.toThrowError(
|
||||||
`Template parse errors:\nNo provider for service ("[ERROR ->]<div needsServiceFromHost><div>"): SimpleComponent@0:0`);
|
/Template parse errors:\nNo provider for service \("\[ERROR ->\]<div needsServiceFromHost><div>"\): .*SimpleComponent.html@0:0/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not instantiate a directive in a view that has a self dependency on a parent directive',
|
it('should not instantiate a directive in a view that has a self dependency on a parent directive',
|
||||||
|
@ -512,7 +512,7 @@ export function main() {
|
||||||
() =>
|
() =>
|
||||||
createComponent('<div simpleDirective><div needsDirectiveFromSelf></div></div>'))
|
createComponent('<div simpleDirective><div needsDirectiveFromSelf></div></div>'))
|
||||||
.toThrowError(
|
.toThrowError(
|
||||||
`Template parse errors:\nNo provider for SimpleDirective ("<div simpleDirective>[ERROR ->]<div needsDirectiveFromSelf></div></div>"): TestComp@0:21`);
|
/Template parse errors:\nNo provider for SimpleDirective \("<div simpleDirective>\[ERROR ->\]<div needsDirectiveFromSelf><\/div><\/div>"\): .*TestComp.html@0:21/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should instantiate directives that depend on other directives', fakeAsync(() => {
|
it('should instantiate directives that depend on other directives', fakeAsync(() => {
|
||||||
|
@ -560,7 +560,7 @@ export function main() {
|
||||||
SimpleComponent, {set: {template: '<div needsDirectiveFromHost></div>'}});
|
SimpleComponent, {set: {template: '<div needsDirectiveFromHost></div>'}});
|
||||||
expect(() => createComponent('<div simpleComponent simpleDirective></div>'))
|
expect(() => createComponent('<div simpleComponent simpleDirective></div>'))
|
||||||
.toThrowError(
|
.toThrowError(
|
||||||
`Template parse errors:\nNo provider for SimpleDirective ("[ERROR ->]<div needsDirectiveFromHost></div>"): SimpleComponent@0:0`);
|
/Template parse errors:\nNo provider for SimpleDirective \("\[ERROR ->\]<div needsDirectiveFromHost><\/div>"\): .*SimpleComponent.html@0:0/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,6 @@ export function main() {
|
||||||
expect(debugCtx.component).toBe(compView.component);
|
expect(debugCtx.component).toBe(compView.component);
|
||||||
expect(debugCtx.context).toBe(compView.context);
|
expect(debugCtx.context).toBe(compView.context);
|
||||||
expect(debugCtx.providerTokens).toEqual([AService]);
|
expect(debugCtx.providerTokens).toEqual([AService]);
|
||||||
expect(debugCtx.source).toBeTruthy();
|
|
||||||
expect(debugCtx.references['ref'].nativeElement)
|
expect(debugCtx.references['ref'].nativeElement)
|
||||||
.toBe(asElementData(compView, 0).renderElement);
|
.toBe(asElementData(compView, 0).renderElement);
|
||||||
});
|
});
|
||||||
|
@ -74,7 +73,6 @@ export function main() {
|
||||||
expect(debugCtx.injector.get(AComp)).toBe(compView.component);
|
expect(debugCtx.injector.get(AComp)).toBe(compView.component);
|
||||||
expect(debugCtx.component).toBe(compView.component);
|
expect(debugCtx.component).toBe(compView.component);
|
||||||
expect(debugCtx.context).toBe(compView.context);
|
expect(debugCtx.context).toBe(compView.context);
|
||||||
expect(debugCtx.source).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should provide data for other nodes based on the nearest element parent', () => {
|
it('should provide data for other nodes based on the nearest element parent', () => {
|
||||||
|
@ -85,6 +83,7 @@ export function main() {
|
||||||
|
|
||||||
expect(debugCtx.renderNode).toBe(asElementData(compView, 0).renderElement);
|
expect(debugCtx.renderNode).toBe(asElementData(compView, 0).renderElement);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,8 +98,8 @@ class HelloCmpUsingCustomElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockConsole {
|
class MockConsole {
|
||||||
res: any[] = [];
|
res: any[][] = [];
|
||||||
error(s: any): void { this.res.push(s); }
|
error(...s: any[]): void { this.res.push(s); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -208,8 +208,8 @@ export function main() {
|
||||||
const refPromise =
|
const refPromise =
|
||||||
bootstrap(NonExistentComp, [{provide: ErrorHandler, useValue: errorHandler}]);
|
bootstrap(NonExistentComp, [{provide: ErrorHandler, useValue: errorHandler}]);
|
||||||
refPromise.then(null, (reason) => {
|
refPromise.then(null, (reason) => {
|
||||||
expect(logger.res.join(''))
|
expect(logger.res[0].join('#'))
|
||||||
.toContain('The selector "non-existent" did not match any elements');
|
.toContain('ERROR#Error: The selector "non-existent" did not match any elements');
|
||||||
async.done();
|
async.done();
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1537,24 +1537,21 @@ export function main() {
|
||||||
const elementC = html(`<ng2-c></ng2-c>`);
|
const elementC = html(`<ng2-c></ng2-c>`);
|
||||||
|
|
||||||
bootstrap(platformBrowserDynamic(), Ng2Module, elementA, ng1Module).then(() => {
|
bootstrap(platformBrowserDynamic(), Ng2Module, elementA, ng1Module).then(() => {
|
||||||
expect(mockExceptionHandler).toHaveBeenCalledWith(jasmine.objectContaining({
|
expect(mockExceptionHandler)
|
||||||
ngOriginalError: new Error(
|
.toHaveBeenCalledWith(new Error(
|
||||||
'Unable to find required \'iDoNotExist\' in upgraded directive \'ng1A\'.')
|
'Unable to find required \'iDoNotExist\' in upgraded directive \'ng1A\'.'));
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
bootstrap(platformBrowserDynamic(), Ng2Module, elementB, ng1Module).then(() => {
|
bootstrap(platformBrowserDynamic(), Ng2Module, elementB, ng1Module).then(() => {
|
||||||
expect(mockExceptionHandler).toHaveBeenCalledWith(jasmine.objectContaining({
|
expect(mockExceptionHandler)
|
||||||
ngOriginalError: new Error(
|
.toHaveBeenCalledWith(new Error(
|
||||||
'Unable to find required \'^iDoNotExist\' in upgraded directive \'ng1B\'.')
|
'Unable to find required \'^iDoNotExist\' in upgraded directive \'ng1B\'.'));
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
bootstrap(platformBrowserDynamic(), Ng2Module, elementC, ng1Module).then(() => {
|
bootstrap(platformBrowserDynamic(), Ng2Module, elementC, ng1Module).then(() => {
|
||||||
expect(mockExceptionHandler).toHaveBeenCalledWith(jasmine.objectContaining({
|
expect(mockExceptionHandler)
|
||||||
ngOriginalError: new Error(
|
.toHaveBeenCalledWith(new Error(
|
||||||
'Unable to find required \'^^iDoNotExist\' in upgraded directive \'ng1C\'.')
|
'Unable to find required \'^^iDoNotExist\' in upgraded directive \'ng1C\'.'));
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -302,7 +302,7 @@ export declare class DebugElement extends DebugNode {
|
||||||
styles: {
|
styles: {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
};
|
};
|
||||||
constructor(nativeNode: any, parent: any, _debugInfo: RenderDebugInfo);
|
constructor(nativeNode: any, parent: any, _debugContext: DebugContext);
|
||||||
addChild(child: DebugNode): void;
|
addChild(child: DebugNode): void;
|
||||||
insertBefore(refChild: DebugNode, newChild: DebugNode): void;
|
insertBefore(refChild: DebugNode, newChild: DebugNode): void;
|
||||||
insertChildrenAfter(child: DebugNode, newChildren: DebugNode[]): void;
|
insertChildrenAfter(child: DebugNode, newChildren: DebugNode[]): void;
|
||||||
|
@ -325,8 +325,8 @@ export declare class DebugNode {
|
||||||
readonly references: {
|
readonly references: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
readonly source: string;
|
/** @deprecated */ readonly source: string;
|
||||||
constructor(nativeNode: any, parent: DebugNode, _debugInfo: RenderDebugInfo);
|
constructor(nativeNode: any, parent: DebugNode, _debugContext: DebugContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
|
|
Loading…
Reference in New Issue