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:
committed by
Chuck Jazdzewski
parent
1c1085b140
commit
cdc882bd36
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, componentFactoryName, createHostComponentMeta, flatten, identifierName} from '../compile_metadata';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, componentFactoryName, createHostComponentMeta, flatten, identifierName, templateSourceUrl} from '../compile_metadata';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {Identifiers, createIdentifier, createIdentifierToken} from '../identifiers';
|
||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||
@@ -189,7 +189,7 @@ export class AotCompiler {
|
||||
|
||||
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
|
||||
compMeta, compMeta.template.template, directives, pipes, ngModule.schemas,
|
||||
identifierName(compMeta.type));
|
||||
templateSourceUrl(ngModule.type, compMeta, compMeta.template));
|
||||
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
|
||||
const viewResult =
|
||||
this._viewCompiler.compileComponent(compMeta, parsedTemplate, stylesExpr, usedPipes);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import {ChangeDetectionStrategy, ComponentFactory, RendererType2, SchemaMetadata, Type, ViewEncapsulation, ɵLifecycleHooks, ɵreflector, ɵstringify as stringify} from '@angular/core';
|
||||
|
||||
import {StaticSymbol} from './aot/static_symbol';
|
||||
import {CssSelector} from './selector';
|
||||
import {splitAtColon} from './util';
|
||||
@@ -257,6 +258,7 @@ export class CompileTemplateMetadata {
|
||||
encapsulation: ViewEncapsulation;
|
||||
template: string;
|
||||
templateUrl: string;
|
||||
isInline: boolean;
|
||||
styles: string[];
|
||||
styleUrls: string[];
|
||||
externalStylesheets: CompileStylesheetMetadata[];
|
||||
@@ -265,7 +267,7 @@ export class CompileTemplateMetadata {
|
||||
interpolation: [string, string];
|
||||
constructor(
|
||||
{encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets, animations,
|
||||
ngContentSelectors, interpolation}: {
|
||||
ngContentSelectors, interpolation, isInline}: {
|
||||
encapsulation?: ViewEncapsulation,
|
||||
template?: string,
|
||||
templateUrl?: string,
|
||||
@@ -275,6 +277,7 @@ export class CompileTemplateMetadata {
|
||||
ngContentSelectors?: string[],
|
||||
animations?: any[],
|
||||
interpolation?: [string, string],
|
||||
isInline?: boolean
|
||||
} = {}) {
|
||||
this.encapsulation = encapsulation;
|
||||
this.template = template;
|
||||
@@ -288,6 +291,7 @@ export class CompileTemplateMetadata {
|
||||
throw new Error(`'interpolation' should have a start and an end symbol.`);
|
||||
}
|
||||
this.interpolation = interpolation;
|
||||
this.isInline = isInline;
|
||||
}
|
||||
|
||||
toSummary(): CompileTemplateSummary {
|
||||
@@ -524,7 +528,8 @@ export function createHostComponentMeta(
|
||||
styles: [],
|
||||
styleUrls: [],
|
||||
ngContentSelectors: [],
|
||||
animations: []
|
||||
animations: [],
|
||||
isInline: true,
|
||||
}),
|
||||
changeDetection: ChangeDetectionStrategy.Default,
|
||||
inputs: [],
|
||||
@@ -752,3 +757,41 @@ export function flatten<T>(list: Array<T|T[]>): T[] {
|
||||
return (<T[]>flat).concat(flatItem);
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Using `location.origin` as prefix helps displaying them as a hierarchy in chrome.
|
||||
* It also helps long-stack-trace zone when rewriting stack traces to not break
|
||||
* source maps (as now all scripts have the same origin).
|
||||
*/
|
||||
function ngJitFolder() {
|
||||
return 'ng://';
|
||||
}
|
||||
|
||||
export function templateSourceUrl(
|
||||
ngModuleType: CompileIdentifierMetadata, compMeta: {type: CompileIdentifierMetadata},
|
||||
templateMeta: {isInline: boolean, templateUrl: string}) {
|
||||
if (templateMeta.isInline) {
|
||||
if (compMeta.type.reference instanceof StaticSymbol) {
|
||||
return compMeta.type.reference.filePath;
|
||||
} else {
|
||||
return `${ngJitFolder()}/${identifierName(ngModuleType)}/${identifierName(compMeta.type)}.html`;
|
||||
}
|
||||
} else {
|
||||
return templateMeta.templateUrl;
|
||||
}
|
||||
}
|
||||
|
||||
export function sharedStylesheetJitUrl(meta: CompileStylesheetMetadata, id: number) {
|
||||
const pathParts = meta.moduleUrl.split(/\/\\/g);
|
||||
const baseName = pathParts[pathParts.length - 1];
|
||||
return `${ngJitFolder()}/css/${id}${baseName}.ngstyle.js`;
|
||||
}
|
||||
|
||||
export function ngModuleJitUrl(moduleMeta: CompileNgModuleMetadata): string {
|
||||
return `${ngJitFolder()}/${identifierName(moduleMeta.type)}/module.ngfactory.js`;
|
||||
}
|
||||
|
||||
export function templateJitUrl(
|
||||
ngModuleType: CompileIdentifierMetadata, compMeta: CompileDirectiveMetadata): string {
|
||||
return `${ngJitFolder()}/${identifierName(ngModuleType)}/${identifierName(compMeta.type)}.ngfactory.js`;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ export {CompilerConfig} from './config';
|
||||
export * from './compile_metadata';
|
||||
export * from './aot/compiler_factory';
|
||||
export * from './aot/compiler';
|
||||
export * from './aot/generated_file';
|
||||
export * from './aot/compiler_options';
|
||||
export * from './aot/compiler_host';
|
||||
export * from './aot/static_reflector';
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {ViewEncapsulation, ɵstringify as stringify} from '@angular/core';
|
||||
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata} from './compile_metadata';
|
||||
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, templateSourceUrl} from './compile_metadata';
|
||||
import {CompilerConfig} from './config';
|
||||
import {CompilerInjectable} from './injectable';
|
||||
import * as html from './ml_parser/ast';
|
||||
@@ -20,6 +20,7 @@ import {UrlResolver} from './url_resolver';
|
||||
import {SyncAsyncResult, syntaxError} from './util';
|
||||
|
||||
export interface PrenormalizedTemplateMetadata {
|
||||
ngModuleType: any;
|
||||
componentType: any;
|
||||
moduleUrl: string;
|
||||
template?: string;
|
||||
@@ -104,20 +105,25 @@ export class DirectiveNormalizer {
|
||||
}
|
||||
|
||||
normalizeLoadedTemplate(
|
||||
prenomData: PrenormalizedTemplateMetadata, template: string,
|
||||
prenormData: PrenormalizedTemplateMetadata, template: string,
|
||||
templateAbsUrl: string): CompileTemplateMetadata {
|
||||
const interpolationConfig = InterpolationConfig.fromArray(prenomData.interpolation);
|
||||
const isInline = !!prenormData.template;
|
||||
const interpolationConfig = InterpolationConfig.fromArray(prenormData.interpolation);
|
||||
const rootNodesAndErrors = this._htmlParser.parse(
|
||||
template, stringify(prenomData.componentType), true, interpolationConfig);
|
||||
template,
|
||||
templateSourceUrl(
|
||||
{reference: prenormData.ngModuleType}, {type: {reference: prenormData.componentType}},
|
||||
{isInline, templateUrl: templateAbsUrl}),
|
||||
true, interpolationConfig);
|
||||
if (rootNodesAndErrors.errors.length > 0) {
|
||||
const errorString = rootNodesAndErrors.errors.join('\n');
|
||||
throw syntaxError(`Template parse errors:\n${errorString}`);
|
||||
}
|
||||
|
||||
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
|
||||
styles: prenomData.styles,
|
||||
styleUrls: prenomData.styleUrls,
|
||||
moduleUrl: prenomData.moduleUrl
|
||||
styles: prenormData.styles,
|
||||
styleUrls: prenormData.styleUrls,
|
||||
moduleUrl: prenormData.moduleUrl
|
||||
}));
|
||||
|
||||
const visitor = new TemplatePreparseVisitor();
|
||||
@@ -125,7 +131,7 @@ export class DirectiveNormalizer {
|
||||
const templateStyles = this.normalizeStylesheet(new CompileStylesheetMetadata(
|
||||
{styles: visitor.styles, styleUrls: visitor.styleUrls, moduleUrl: templateAbsUrl}));
|
||||
|
||||
let encapsulation = prenomData.encapsulation;
|
||||
let encapsulation = prenormData.encapsulation;
|
||||
if (encapsulation == null) {
|
||||
encapsulation = this._config.defaultEncapsulation;
|
||||
}
|
||||
@@ -143,8 +149,8 @@ export class DirectiveNormalizer {
|
||||
template,
|
||||
templateUrl: templateAbsUrl, styles, styleUrls,
|
||||
ngContentSelectors: visitor.ngContentSelectors,
|
||||
animations: prenomData.animations,
|
||||
interpolation: prenomData.interpolation,
|
||||
animations: prenormData.animations,
|
||||
interpolation: prenormData.interpolation, isInline
|
||||
});
|
||||
}
|
||||
|
||||
@@ -160,7 +166,8 @@ export class DirectiveNormalizer {
|
||||
externalStylesheets: externalStylesheets,
|
||||
ngContentSelectors: templateMeta.ngContentSelectors,
|
||||
animations: templateMeta.animations,
|
||||
interpolation: templateMeta.interpolation
|
||||
interpolation: templateMeta.interpolation,
|
||||
isInline: templateMeta.isInline,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -210,7 +210,9 @@ enum Endian {
|
||||
Big,
|
||||
}
|
||||
|
||||
function utf8Encode(str: string): string {
|
||||
// TODO(vicb): move this to some shared place, as we also need it
|
||||
// for SourceMaps.
|
||||
export function utf8Encode(str: string): string {
|
||||
let encoded: string = '';
|
||||
|
||||
for (let index = 0; index < str.length; index++) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import {Compiler, ComponentFactory, Inject, Injector, ModuleWithComponentFactories, NgModuleFactory, Type, ɵgetComponentViewDefinitionFactory as getComponentViewDefinitionFactory, ɵstringify as stringify} from '@angular/core';
|
||||
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, ProviderMeta, ProxyClass, createHostComponentMeta, identifierName} from '../compile_metadata';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileStylesheetMetadata, ProviderMeta, ProxyClass, createHostComponentMeta, identifierName, ngModuleJitUrl, sharedStylesheetJitUrl, templateJitUrl, templateSourceUrl} from '../compile_metadata';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {CompilerInjectable} from '../injectable';
|
||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||
@@ -38,6 +38,7 @@ export class JitCompiler implements Compiler {
|
||||
private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>();
|
||||
private _compiledDirectiveWrapperCache = new Map<Type<any>, Type<any>>();
|
||||
private _compiledNgModuleCache = new Map<Type<any>, NgModuleFactory<any>>();
|
||||
private _sharedStylesheetCount = 0;
|
||||
|
||||
constructor(
|
||||
private _injector: Injector, private _metadataResolver: CompileMetadataResolver,
|
||||
@@ -128,7 +129,7 @@ export class JitCompiler implements Compiler {
|
||||
interpretStatements(compileResult.statements, [compileResult.ngModuleFactoryVar])[0];
|
||||
} else {
|
||||
ngModuleFactory = jitStatements(
|
||||
`/${identifierName(moduleMeta.type)}/module.ngfactory.js`, compileResult.statements,
|
||||
ngModuleJitUrl(moduleMeta), compileResult.statements,
|
||||
[compileResult.ngModuleFactoryVar])[0];
|
||||
}
|
||||
this._compiledNgModuleCache.set(moduleMeta.type.reference, ngModuleFactory);
|
||||
@@ -251,7 +252,7 @@ export class JitCompiler implements Compiler {
|
||||
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
|
||||
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
|
||||
compMeta, compMeta.template.template, directives, pipes, template.ngModule.schemas,
|
||||
identifierName(compMeta.type));
|
||||
templateSourceUrl(template.ngModule.type, template.compMeta, template.compMeta.template));
|
||||
const compileResult = this._viewCompiler.compileComponent(
|
||||
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
|
||||
usedPipes);
|
||||
@@ -263,10 +264,9 @@ export class JitCompiler implements Compiler {
|
||||
[viewClass, rendererType] = interpretStatements(
|
||||
statements, [compileResult.viewClassVar, compileResult.rendererTypeVar]);
|
||||
} else {
|
||||
const sourceUrl =
|
||||
`/${identifierName(template.ngModule.type)}/${identifierName(template.compType)}/${template.isHost?'host':'component'}.ngfactory.js`;
|
||||
[viewClass, rendererType] = jitStatements(
|
||||
sourceUrl, statements, [compileResult.viewClassVar, compileResult.rendererTypeVar]);
|
||||
templateJitUrl(template.ngModule.type, template.compMeta), statements,
|
||||
[compileResult.viewClassVar, compileResult.rendererTypeVar]);
|
||||
}
|
||||
template.compiled(viewClass, rendererType);
|
||||
}
|
||||
@@ -289,7 +289,8 @@ export class JitCompiler implements Compiler {
|
||||
return interpretStatements(result.statements, [result.stylesVar])[0];
|
||||
} else {
|
||||
return jitStatements(
|
||||
`/${result.meta.moduleUrl}.ngstyle.js`, result.statements, [result.stylesVar])[0];
|
||||
sharedStylesheetJitUrl(result.meta, this._sharedStylesheetCount++), result.statements,
|
||||
[result.stylesVar])[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +155,8 @@ export class CompileMetadataResolver {
|
||||
return typeSummary && typeSummary.summaryKind === kind ? typeSummary : null;
|
||||
}
|
||||
|
||||
private _loadDirectiveMetadata(directiveType: any, isSync: boolean): Promise<any> {
|
||||
private _loadDirectiveMetadata(ngModuleType: any, directiveType: any, isSync: boolean):
|
||||
Promise<any> {
|
||||
if (this._directiveCache.has(directiveType)) {
|
||||
return;
|
||||
}
|
||||
@@ -191,6 +192,7 @@ export class CompileMetadataResolver {
|
||||
|
||||
if (metadata.isComponent) {
|
||||
const templateMeta = this._directiveNormalizer.normalizeTemplate({
|
||||
ngModuleType,
|
||||
componentType: directiveType,
|
||||
moduleUrl: componentModuleUrl(this._reflector, directiveType, annotation),
|
||||
encapsulation: metadata.template.encapsulation,
|
||||
@@ -249,7 +251,8 @@ export class CompileMetadataResolver {
|
||||
styles: dirMeta.styles,
|
||||
styleUrls: dirMeta.styleUrls,
|
||||
animations: animations,
|
||||
interpolation: dirMeta.interpolation
|
||||
interpolation: dirMeta.interpolation,
|
||||
isInline: !!dirMeta.template
|
||||
});
|
||||
}
|
||||
|
||||
@@ -378,7 +381,7 @@ export class CompileMetadataResolver {
|
||||
const loading: Promise<any>[] = [];
|
||||
if (ngModule) {
|
||||
ngModule.declaredDirectives.forEach((id) => {
|
||||
const promise = this._loadDirectiveMetadata(id.reference, isSync);
|
||||
const promise = this._loadDirectiveMetadata(moduleType, id.reference, isSync);
|
||||
if (promise) {
|
||||
loading.push(promise);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import {CompilerInjectable} from './injectable';
|
||||
import {ClassBuilder, createClassStmt} from './output/class_builder';
|
||||
import * as o from './output/output_ast';
|
||||
import {convertValueToOutputAst} from './output/value_util';
|
||||
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
|
||||
import {ParseLocation, ParseSourceFile, ParseSourceSpan, typeSourceSpan} from './parse_util';
|
||||
import {NgModuleProviderAnalyzer} from './provider_analyzer';
|
||||
import {ProviderAst} from './template_parser/template_ast';
|
||||
|
||||
@@ -37,14 +37,7 @@ export class NgModuleCompileResult {
|
||||
export class NgModuleCompiler {
|
||||
compile(ngModuleMeta: CompileNgModuleMetadata, extraProviders: CompileProviderMetadata[]):
|
||||
NgModuleCompileResult {
|
||||
const moduleUrl = identifierModuleUrl(ngModuleMeta.type);
|
||||
const sourceFileName = moduleUrl != null ?
|
||||
`in NgModule ${identifierName(ngModuleMeta.type)} in ${moduleUrl}` :
|
||||
`in NgModule ${identifierName(ngModuleMeta.type)}`;
|
||||
const sourceFile = new ParseSourceFile('', sourceFileName);
|
||||
const sourceSpan = new ParseSourceSpan(
|
||||
new ParseLocation(sourceFile, null, null, null),
|
||||
new ParseLocation(sourceFile, null, null, null));
|
||||
const sourceSpan = typeSourceSpan('NgModule', ngModuleMeta.type);
|
||||
const deps: ComponentFactoryDependency[] = [];
|
||||
const bootstrapComponentFactories: CompileIdentifierMetadata[] = [];
|
||||
const entryComponentFactories =
|
||||
|
||||
@@ -420,7 +420,12 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex
|
||||
ctx.print(ast, `}`, useNewLine);
|
||||
return null;
|
||||
}
|
||||
|
||||
visitCommaExpr(ast: o.CommaExpr, ctx: EmitterVisitorContext): any {
|
||||
ctx.print(ast, '(');
|
||||
this.visitAllExpressions(ast.parts, ctx, ',');
|
||||
ctx.print(ast, ')');
|
||||
return null;
|
||||
}
|
||||
visitAllExpressions(
|
||||
expressions: o.Expression[], ctx: EmitterVisitorContext, separator: string,
|
||||
newLine: boolean = false): void {
|
||||
|
||||
@@ -34,11 +34,12 @@ export class JavaScriptEmitter implements OutputEmitter {
|
||||
srcParts.push(ctx.toSource());
|
||||
|
||||
const prefixLines = converter.importsWithPrefixes.size;
|
||||
const sm = ctx.toSourceMapGenerator(null, prefixLines).toJsComment();
|
||||
const sm = ctx.toSourceMapGenerator(genFilePath, prefixLines).toJsComment();
|
||||
if (sm) {
|
||||
srcParts.push(sm);
|
||||
}
|
||||
|
||||
// always add a newline at the end, as some tools have bugs without it.
|
||||
srcParts.push('');
|
||||
return srcParts.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -304,7 +304,10 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor {
|
||||
(entry) => (result as any)[entry.key] = entry.value.visitExpression(this, ctx));
|
||||
return result;
|
||||
}
|
||||
|
||||
visitCommaExpr(ast: o.CommaExpr, context: any): any {
|
||||
const values = this.visitAllExpressions(ast.parts, context);
|
||||
return values[values.length - 1];
|
||||
}
|
||||
visitAllExpressions(expressions: o.Expression[], ctx: _ExecutionContext): any {
|
||||
return expressions.map((expr) => expr.visitExpression(this, ctx));
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isDevMode} from '@angular/core';
|
||||
import {identifierName} from '../compile_metadata';
|
||||
|
||||
import {EmitterVisitorContext} from './abstract_emitter';
|
||||
@@ -14,14 +15,23 @@ import * as o from './output_ast';
|
||||
|
||||
function evalExpression(
|
||||
sourceUrl: string, ctx: EmitterVisitorContext, vars: {[key: string]: any}): any {
|
||||
const fnBody =
|
||||
`${ctx.toSource()}\n//# sourceURL=${sourceUrl}\n${ctx.toSourceMapGenerator().toJsComment()}`;
|
||||
let fnBody = `${ctx.toSource()}\n//# sourceURL=${sourceUrl}`;
|
||||
const fnArgNames: string[] = [];
|
||||
const fnArgValues: any[] = [];
|
||||
for (const argName in vars) {
|
||||
fnArgNames.push(argName);
|
||||
fnArgValues.push(vars[argName]);
|
||||
}
|
||||
if (isDevMode()) {
|
||||
// using `new Function(...)` generates a header, 1 line of no arguments, 2 lines otherwise
|
||||
// E.g. ```
|
||||
// function anonymous(a,b,c
|
||||
// /**/) { ... }```
|
||||
// We don't want to hard code this fact, so we auto detect it via an empty function first.
|
||||
const emptyFn = new Function(...fnArgNames.concat('return null;')).toString();
|
||||
const headerLines = emptyFn.slice(0, emptyFn.indexOf('return null;')).split('\n').length - 1;
|
||||
fnBody += `\n${ctx.toSourceMapGenerator(sourceUrl, headerLines).toJsComment()}`;
|
||||
}
|
||||
return new Function(...fnArgNames.concat(fnBody))(...fnArgValues);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {utf8Encode} from '../i18n/digest';
|
||||
|
||||
// https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
|
||||
const VERSION = 3;
|
||||
|
||||
@@ -143,7 +145,7 @@ export class SourceMapGenerator {
|
||||
|
||||
export function toBase64String(value: string): string {
|
||||
let b64 = '';
|
||||
|
||||
value = utf8Encode(value);
|
||||
for (let i = 0; i < value.length;) {
|
||||
const i1 = value.charCodeAt(i++);
|
||||
const i2 = value.charCodeAt(i++);
|
||||
|
||||
@@ -70,10 +70,12 @@ export class TypeScriptEmitter implements OutputEmitter {
|
||||
srcParts.push(ctx.toSource());
|
||||
|
||||
const prefixLines = converter.reexports.size + converter.importsWithPrefixes.size;
|
||||
const sm = ctx.toSourceMapGenerator(null, prefixLines).toJsComment();
|
||||
const sm = ctx.toSourceMapGenerator(genFilePath, prefixLines).toJsComment();
|
||||
if (sm) {
|
||||
srcParts.push(sm);
|
||||
}
|
||||
// always add a newline at the end, as some tools have bugs without it.
|
||||
srcParts.push('');
|
||||
|
||||
return srcParts.join('\n');
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import * as chars from './chars';
|
||||
import {CompileIdentifierMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
|
||||
|
||||
export class ParseLocation {
|
||||
constructor(
|
||||
@@ -124,3 +125,13 @@ export class ParseError {
|
||||
return `${this.msg}${contextStr}: ${this.span.start}${details}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function typeSourceSpan(kind: string, type: CompileIdentifierMetadata): ParseSourceSpan {
|
||||
const moduleUrl = identifierModuleUrl(type);
|
||||
const sourceFileName = moduleUrl != null ? `in ${kind} ${identifierName(type)} in ${moduleUrl}` :
|
||||
`in ${kind} ${identifierName(type)}`;
|
||||
const sourceFile = new ParseSourceFile('', sourceFileName);
|
||||
return new ParseSourceSpan(
|
||||
new ParseLocation(sourceFile, null, null, null),
|
||||
new ParseLocation(sourceFile, null, null, null));
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export class ProviderViewContext {
|
||||
viewProviders: Map<any, boolean>;
|
||||
errors: ProviderError[] = [];
|
||||
|
||||
constructor(public component: CompileDirectiveMetadata, public sourceSpan: ParseSourceSpan) {
|
||||
constructor(public component: CompileDirectiveMetadata) {
|
||||
this.viewQueries = _getViewQueries(component);
|
||||
this.viewProviders = new Map<any, boolean>();
|
||||
component.viewProviders.forEach((provider) => {
|
||||
|
||||
@@ -132,8 +132,7 @@ export class TemplateParser {
|
||||
if (htmlAstWithErrors.rootNodes.length > 0) {
|
||||
const uniqDirectives = removeSummaryDuplicates(directives);
|
||||
const uniqPipes = removeSummaryDuplicates(pipes);
|
||||
const providerViewContext =
|
||||
new ProviderViewContext(component, htmlAstWithErrors.rootNodes[0].sourceSpan);
|
||||
const providerViewContext = new ProviderViewContext(component);
|
||||
let interpolationConfig: InterpolationConfig;
|
||||
if (component.template && component.template.interpolation) {
|
||||
interpolationConfig = {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,12 +6,15 @@
|
||||
* 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 {async} from '@angular/core/testing';
|
||||
import {async, fakeAsync, tick} from '@angular/core/testing';
|
||||
import {MetadataBundler, MetadataCollector, ModuleMetadata, privateEntriesToIndex} from '@angular/tsc-wrapped';
|
||||
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$/;
|
||||
|
||||
@@ -39,6 +42,9 @@ describe('compiler (unbundled Angular)', () => {
|
||||
angularFiles = emittingHost.written;
|
||||
});
|
||||
|
||||
// Restore reflector since AoT compiler will update it with a new static reflector
|
||||
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
|
||||
|
||||
describe('Quickstart', () => {
|
||||
let host: MockCompilerHost;
|
||||
let aotHost: MockAotCompilerHost;
|
||||
@@ -48,9 +54,6 @@ describe('compiler (unbundled Angular)', () => {
|
||||
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',
|
||||
async(() => compile(host, aotHost, expectNoDiagnostics).then(generatedFiles => {
|
||||
expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||
@@ -67,6 +70,155 @@ describe('compiler (unbundled Angular)', () => {
|
||||
.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)', () => {
|
||||
|
||||
@@ -28,6 +28,7 @@ export function main() {
|
||||
it('should throw if no template was specified',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
expect(() => normalizer.normalizeTemplate({
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
})).toThrowError('No template specified for component SomeComp');
|
||||
@@ -35,6 +36,7 @@ export function main() {
|
||||
it('should throw if template is not a string',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
expect(() => normalizer.normalizeTemplate({
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
template: <any>{}
|
||||
@@ -43,6 +45,7 @@ export function main() {
|
||||
it('should throw if templateUrl is not a string',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
expect(() => normalizer.normalizeTemplate({
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
templateUrl: <any>{}
|
||||
@@ -54,6 +57,7 @@ export function main() {
|
||||
it('should store the template',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeTemplateSync({
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -64,11 +68,13 @@ export function main() {
|
||||
});
|
||||
expect(template.template).toEqual('a');
|
||||
expect(template.templateUrl).toEqual('package:some/module/a.js');
|
||||
expect(template.isInline).toBe(true);
|
||||
}));
|
||||
|
||||
it('should resolve styles on the annotation against the moduleUrl',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeTemplateSync({
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -83,6 +89,7 @@ export function main() {
|
||||
it('should resolve styles in the template against the moduleUrl',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeTemplateSync({
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -97,6 +104,7 @@ export function main() {
|
||||
it('should use ViewEncapsulation.Emulated by default',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeTemplateSync({
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -114,6 +122,7 @@ export function main() {
|
||||
(config: CompilerConfig, normalizer: DirectiveNormalizer) => {
|
||||
config.defaultEncapsulation = ViewEncapsulation.None;
|
||||
const template = normalizer.normalizeTemplateSync({
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -136,6 +145,7 @@ export function main() {
|
||||
resourceLoader.expect('package:some/module/sometplurl.html', 'a');
|
||||
normalizer
|
||||
.normalizeTemplateAsync({
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -147,6 +157,7 @@ export function main() {
|
||||
.then((template: CompileTemplateMetadata) => {
|
||||
expect(template.template).toEqual('a');
|
||||
expect(template.templateUrl).toEqual('package:some/module/sometplurl.html');
|
||||
expect(template.isInline).toBe(false);
|
||||
async.done();
|
||||
});
|
||||
resourceLoader.flush();
|
||||
@@ -160,6 +171,7 @@ export function main() {
|
||||
resourceLoader.expect('package:some/module/tpl/sometplurl.html', '');
|
||||
normalizer
|
||||
.normalizeTemplateAsync({
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -184,6 +196,7 @@ export function main() {
|
||||
'package:some/module/tpl/sometplurl.html', '<style>@import test.css</style>');
|
||||
normalizer
|
||||
.normalizeTemplateAsync({
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -271,6 +284,7 @@ export function main() {
|
||||
resourceLoader: MockResourceLoader) => {
|
||||
resourceLoader.expect('package:some/module/cmp.html', 'a');
|
||||
const prenormMeta = {
|
||||
ngModuleType: null as any,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
templateUrl: 'cmp.html',
|
||||
@@ -297,6 +311,7 @@ export function main() {
|
||||
const viewEncapsulation = ViewEncapsulation.Native;
|
||||
const template = normalizer.normalizeLoadedTemplate(
|
||||
{
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: viewEncapsulation,
|
||||
@@ -311,6 +326,7 @@ export function main() {
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeLoadedTemplate(
|
||||
{
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -325,6 +341,7 @@ export function main() {
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeLoadedTemplate(
|
||||
{
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -339,6 +356,7 @@ export function main() {
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeLoadedTemplate(
|
||||
{
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -354,6 +372,7 @@ export function main() {
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeLoadedTemplate(
|
||||
{
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -368,6 +387,7 @@ export function main() {
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeLoadedTemplate(
|
||||
{
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -382,6 +402,7 @@ export function main() {
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeLoadedTemplate(
|
||||
{
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -396,6 +417,7 @@ export function main() {
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeLoadedTemplate(
|
||||
{
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -410,6 +432,7 @@ export function main() {
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeLoadedTemplate(
|
||||
{
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -424,6 +447,7 @@ export function main() {
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeLoadedTemplate(
|
||||
{
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -438,6 +462,7 @@ export function main() {
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeLoadedTemplate(
|
||||
{
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -453,6 +478,7 @@ export function main() {
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeLoadedTemplate(
|
||||
{
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -467,6 +493,7 @@ export function main() {
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeLoadedTemplate(
|
||||
{
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -482,6 +509,7 @@ export function main() {
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeLoadedTemplate(
|
||||
{
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_HTTP_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -497,6 +525,7 @@ export function main() {
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeLoadedTemplate(
|
||||
{
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: ViewEncapsulation.Emulated,
|
||||
@@ -511,6 +540,7 @@ export function main() {
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeLoadedTemplate(
|
||||
{
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
@@ -526,6 +556,7 @@ export function main() {
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
const template = normalizer.normalizeLoadedTemplate(
|
||||
{
|
||||
ngModuleType: null,
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
|
||||
@@ -9,10 +9,7 @@
|
||||
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler';
|
||||
import {EmitterVisitorContext} from '@angular/compiler/src/output/abstract_emitter';
|
||||
import {SourceMap} from '@angular/compiler/src/output/source_map';
|
||||
|
||||
const SourceMapConsumer = require('source-map').SourceMapConsumer;
|
||||
const b64 = require('base64-js');
|
||||
|
||||
import {extractSourceMap, originalPositionFor} from './source_map_util';
|
||||
|
||||
export function main() {
|
||||
describe('AbstractEmitter', () => {
|
||||
@@ -47,12 +44,10 @@ export function main() {
|
||||
ctx.print(createSourceSpan(fileA, 0), 'fileA-0');
|
||||
|
||||
const sm = ctx.toSourceMapGenerator(null, 10).toJSON();
|
||||
const smc = new SourceMapConsumer(sm);
|
||||
expect(smc.originalPositionFor({line: 11, column: 0})).toEqual({
|
||||
expect(originalPositionFor(sm, {line: 11, column: 0})).toEqual({
|
||||
line: 1,
|
||||
column: 0,
|
||||
source: 'a.js',
|
||||
name: null,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -109,9 +104,8 @@ function expectMap(
|
||||
ctx: EmitterVisitorContext, genLine: number, genCol: number, source: string = null,
|
||||
srcLine: number = null, srcCol: number = null) {
|
||||
const sm = ctx.toSourceMapGenerator().toJSON();
|
||||
const smc = new SourceMapConsumer(sm);
|
||||
const genPosition = {line: genLine + 1, column: genCol};
|
||||
const origPosition = smc.originalPositionFor(genPosition);
|
||||
const origPosition = originalPositionFor(sm, genPosition);
|
||||
expect(origPosition.source).toEqual(source);
|
||||
expect(origPosition.line).toEqual(srcLine === null ? null : srcLine + 1);
|
||||
expect(origPosition.column).toEqual(srcCol);
|
||||
@@ -134,15 +128,3 @@ function createSourceSpan(file: ParseSourceFile, idx: number) {
|
||||
const sourceSpan = new ParseSourceSpan(start, end);
|
||||
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//#');
|
||||
if (smi == -1) return source;
|
||||
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 {ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler/src/parse_util';
|
||||
|
||||
import {extractSourceMap} from './abstract_emitter_node_only_spec';
|
||||
|
||||
const SourceMapConsumer = require('source-map').SourceMapConsumer;
|
||||
import {extractSourceMap, originalPositionFor} from './source_map_util';
|
||||
|
||||
const someModuleUrl = 'somePackage/somePath';
|
||||
|
||||
@@ -54,12 +52,11 @@ export function main() {
|
||||
const sourceSpan = new ParseSourceSpan(startLocation, endLocation);
|
||||
const someVar = o.variable('someVar', null, sourceSpan);
|
||||
const sm = emitSourceMap(someVar.toStmt());
|
||||
const smc = new SourceMapConsumer(sm);
|
||||
|
||||
expect(sm.sources).toEqual(['in.js']);
|
||||
expect(sm.sourcesContent).toEqual([';;;var']);
|
||||
expect(smc.originalPositionFor({line: 1, column: 0}))
|
||||
.toEqual({line: 1, column: 3, source: 'in.js', name: null});
|
||||
expect(originalPositionFor(sm, {line: 1, column: 0}))
|
||||
.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 {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 anotherModuleUrl = 'somePackage/someOtherPath';
|
||||
@@ -50,7 +50,7 @@ export function main() {
|
||||
|
||||
function emitStmt(stmt: o.Statement, exportedVars: string[] = null): string {
|
||||
const source = emitter.emitStatements(someModuleUrl, [stmt], exportedVars || []);
|
||||
return stripSourceMap(source);
|
||||
return stripSourceMapAndNewLine(source);
|
||||
}
|
||||
|
||||
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 {ParseSourceSpan} from '@angular/compiler/src/parse_util';
|
||||
|
||||
import {extractSourceMap} from './abstract_emitter_node_only_spec';
|
||||
|
||||
const SourceMapConsumer = require('source-map').SourceMapConsumer;
|
||||
import {extractSourceMap, originalPositionFor} from './source_map_util';
|
||||
|
||||
const someModuleUrl = 'somePackage/somePath';
|
||||
|
||||
@@ -59,12 +57,11 @@ export function main() {
|
||||
const sourceSpan = new ParseSourceSpan(startLocation, endLocation);
|
||||
const someVar = o.variable('someVar', null, sourceSpan);
|
||||
const sm = emitSourceMap(someVar.toStmt());
|
||||
const smc = new SourceMapConsumer(sm);
|
||||
|
||||
expect(sm.sources).toEqual(['in.js']);
|
||||
expect(sm.sourcesContent).toEqual([';;;var']);
|
||||
expect(smc.originalPositionFor({line: 1, column: 0}))
|
||||
.toEqual({line: 1, column: 3, source: 'in.js', name: null});
|
||||
expect(originalPositionFor(sm, {line: 1, column: 0}))
|
||||
.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 {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 anotherModuleUrl = 'somePackage/someOtherPath';
|
||||
@@ -52,7 +52,7 @@ export function main() {
|
||||
function emitStmt(stmt: o.Statement | o.Statement[], exportedVars: string[] = null): string {
|
||||
const stmts = Array.isArray(stmt) ? stmt : [stmt];
|
||||
const source = emitter.emitStatements(someModuleUrl, stmts, exportedVars || []);
|
||||
return stripSourceMap(source);
|
||||
return stripSourceMapAndNewLine(source);
|
||||
}
|
||||
|
||||
it('should declare variables', () => {
|
||||
|
||||
@@ -23,6 +23,8 @@ export class MockResourceLoader extends ResourceLoader {
|
||||
return request.getPromise();
|
||||
}
|
||||
|
||||
hasPendingRequests() { return !!this._requests.length; }
|
||||
|
||||
/**
|
||||
* Add an expectation for the given URL. Incoming requests will be checked against
|
||||
* the next expectation (in FIFO order). The `verifyNoOutstandingExpectations` method
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Injector} from '../di';
|
||||
import {RenderDebugInfo} from '../render/api';
|
||||
import {DebugContext} from '../view/index';
|
||||
|
||||
export class EventListener { constructor(public name: string, public callback: Function){}; }
|
||||
|
||||
@@ -19,7 +19,7 @@ export class DebugNode {
|
||||
listeners: EventListener[];
|
||||
parent: DebugElement;
|
||||
|
||||
constructor(nativeNode: any, parent: DebugNode, private _debugInfo: RenderDebugInfo) {
|
||||
constructor(nativeNode: any, parent: DebugNode, private _debugContext: DebugContext) {
|
||||
this.nativeNode = nativeNode;
|
||||
if (parent && parent instanceof DebugElement) {
|
||||
parent.addChild(this);
|
||||
@@ -29,19 +29,24 @@ export class DebugNode {
|
||||
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} {
|
||||
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[];
|
||||
nativeElement: any;
|
||||
|
||||
constructor(nativeNode: any, parent: any, _debugInfo: RenderDebugInfo) {
|
||||
super(nativeNode, parent, _debugInfo);
|
||||
constructor(nativeNode: any, parent: any, _debugContext: DebugContext) {
|
||||
super(nativeNode, parent, _debugContext);
|
||||
this.properties = {};
|
||||
this.attributes = {};
|
||||
this.classes = {};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user