feat: introduce source maps for templates (#15011)

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

Related changes that are included in this commit:

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

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

BREAKING CHANGE:

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

Closes 14013
This commit is contained in:
Tobias Bosch
2017-03-14 09:16:15 -07:00
committed by Chuck Jazdzewski
parent 1c1085b140
commit cdc882bd36
48 changed files with 1196 additions and 515 deletions
+2 -2
View File
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, componentFactoryName, createHostComponentMeta, flatten, identifierName} from '../compile_metadata';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, componentFactoryName, createHostComponentMeta, flatten, identifierName, templateSourceUrl} from '../compile_metadata';
import {CompilerConfig} from '../config';
import {Identifiers, createIdentifier, createIdentifierToken} from '../identifiers';
import {CompileMetadataResolver} from '../metadata_resolver';
@@ -189,7 +189,7 @@ export class AotCompiler {
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
compMeta, compMeta.template.template, directives, pipes, ngModule.schemas,
identifierName(compMeta.type));
templateSourceUrl(ngModule.type, compMeta, compMeta.template));
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
const viewResult =
this._viewCompiler.compileComponent(compMeta, parsedTemplate, stylesExpr, usedPipes);
+45 -2
View File
@@ -7,6 +7,7 @@
*/
import {ChangeDetectionStrategy, ComponentFactory, RendererType2, SchemaMetadata, Type, ViewEncapsulation, ɵLifecycleHooks, ɵreflector, ɵstringify as stringify} from '@angular/core';
import {StaticSymbol} from './aot/static_symbol';
import {CssSelector} from './selector';
import {splitAtColon} from './util';
@@ -257,6 +258,7 @@ export class CompileTemplateMetadata {
encapsulation: ViewEncapsulation;
template: string;
templateUrl: string;
isInline: boolean;
styles: string[];
styleUrls: string[];
externalStylesheets: CompileStylesheetMetadata[];
@@ -265,7 +267,7 @@ export class CompileTemplateMetadata {
interpolation: [string, string];
constructor(
{encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets, animations,
ngContentSelectors, interpolation}: {
ngContentSelectors, interpolation, isInline}: {
encapsulation?: ViewEncapsulation,
template?: string,
templateUrl?: string,
@@ -275,6 +277,7 @@ export class CompileTemplateMetadata {
ngContentSelectors?: string[],
animations?: any[],
interpolation?: [string, string],
isInline?: boolean
} = {}) {
this.encapsulation = encapsulation;
this.template = template;
@@ -288,6 +291,7 @@ export class CompileTemplateMetadata {
throw new Error(`'interpolation' should have a start and an end symbol.`);
}
this.interpolation = interpolation;
this.isInline = isInline;
}
toSummary(): CompileTemplateSummary {
@@ -524,7 +528,8 @@ export function createHostComponentMeta(
styles: [],
styleUrls: [],
ngContentSelectors: [],
animations: []
animations: [],
isInline: true,
}),
changeDetection: ChangeDetectionStrategy.Default,
inputs: [],
@@ -752,3 +757,41 @@ export function flatten<T>(list: Array<T|T[]>): T[] {
return (<T[]>flat).concat(flatItem);
}, []);
}
/**
* Note: Using `location.origin` as prefix helps displaying them as a hierarchy in chrome.
* It also helps long-stack-trace zone when rewriting stack traces to not break
* source maps (as now all scripts have the same origin).
*/
function ngJitFolder() {
return 'ng://';
}
export function templateSourceUrl(
ngModuleType: CompileIdentifierMetadata, compMeta: {type: CompileIdentifierMetadata},
templateMeta: {isInline: boolean, templateUrl: string}) {
if (templateMeta.isInline) {
if (compMeta.type.reference instanceof StaticSymbol) {
return compMeta.type.reference.filePath;
} else {
return `${ngJitFolder()}/${identifierName(ngModuleType)}/${identifierName(compMeta.type)}.html`;
}
} else {
return templateMeta.templateUrl;
}
}
export function sharedStylesheetJitUrl(meta: CompileStylesheetMetadata, id: number) {
const pathParts = meta.moduleUrl.split(/\/\\/g);
const baseName = pathParts[pathParts.length - 1];
return `${ngJitFolder()}/css/${id}${baseName}.ngstyle.js`;
}
export function ngModuleJitUrl(moduleMeta: CompileNgModuleMetadata): string {
return `${ngJitFolder()}/${identifierName(moduleMeta.type)}/module.ngfactory.js`;
}
export function templateJitUrl(
ngModuleType: CompileIdentifierMetadata, compMeta: CompileDirectiveMetadata): string {
return `${ngJitFolder()}/${identifierName(ngModuleType)}/${identifierName(compMeta.type)}.ngfactory.js`;
}
+1
View File
@@ -28,6 +28,7 @@ export {CompilerConfig} from './config';
export * from './compile_metadata';
export * from './aot/compiler_factory';
export * from './aot/compiler';
export * from './aot/generated_file';
export * from './aot/compiler_options';
export * from './aot/compiler_host';
export * from './aot/static_reflector';
+18 -11
View File
@@ -7,7 +7,7 @@
*/
import {ViewEncapsulation, ɵstringify as stringify} from '@angular/core';
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata} from './compile_metadata';
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, templateSourceUrl} from './compile_metadata';
import {CompilerConfig} from './config';
import {CompilerInjectable} from './injectable';
import * as html from './ml_parser/ast';
@@ -20,6 +20,7 @@ import {UrlResolver} from './url_resolver';
import {SyncAsyncResult, syntaxError} from './util';
export interface PrenormalizedTemplateMetadata {
ngModuleType: any;
componentType: any;
moduleUrl: string;
template?: string;
@@ -104,20 +105,25 @@ export class DirectiveNormalizer {
}
normalizeLoadedTemplate(
prenomData: PrenormalizedTemplateMetadata, template: string,
prenormData: PrenormalizedTemplateMetadata, template: string,
templateAbsUrl: string): CompileTemplateMetadata {
const interpolationConfig = InterpolationConfig.fromArray(prenomData.interpolation);
const isInline = !!prenormData.template;
const interpolationConfig = InterpolationConfig.fromArray(prenormData.interpolation);
const rootNodesAndErrors = this._htmlParser.parse(
template, stringify(prenomData.componentType), true, interpolationConfig);
template,
templateSourceUrl(
{reference: prenormData.ngModuleType}, {type: {reference: prenormData.componentType}},
{isInline, templateUrl: templateAbsUrl}),
true, interpolationConfig);
if (rootNodesAndErrors.errors.length > 0) {
const errorString = rootNodesAndErrors.errors.join('\n');
throw syntaxError(`Template parse errors:\n${errorString}`);
}
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
styles: prenomData.styles,
styleUrls: prenomData.styleUrls,
moduleUrl: prenomData.moduleUrl
styles: prenormData.styles,
styleUrls: prenormData.styleUrls,
moduleUrl: prenormData.moduleUrl
}));
const visitor = new TemplatePreparseVisitor();
@@ -125,7 +131,7 @@ export class DirectiveNormalizer {
const templateStyles = this.normalizeStylesheet(new CompileStylesheetMetadata(
{styles: visitor.styles, styleUrls: visitor.styleUrls, moduleUrl: templateAbsUrl}));
let encapsulation = prenomData.encapsulation;
let encapsulation = prenormData.encapsulation;
if (encapsulation == null) {
encapsulation = this._config.defaultEncapsulation;
}
@@ -143,8 +149,8 @@ export class DirectiveNormalizer {
template,
templateUrl: templateAbsUrl, styles, styleUrls,
ngContentSelectors: visitor.ngContentSelectors,
animations: prenomData.animations,
interpolation: prenomData.interpolation,
animations: prenormData.animations,
interpolation: prenormData.interpolation, isInline
});
}
@@ -160,7 +166,8 @@ export class DirectiveNormalizer {
externalStylesheets: externalStylesheets,
ngContentSelectors: templateMeta.ngContentSelectors,
animations: templateMeta.animations,
interpolation: templateMeta.interpolation
interpolation: templateMeta.interpolation,
isInline: templateMeta.isInline,
}));
}
+3 -1
View File
@@ -210,7 +210,9 @@ enum Endian {
Big,
}
function utf8Encode(str: string): string {
// TODO(vicb): move this to some shared place, as we also need it
// for SourceMaps.
export function utf8Encode(str: string): string {
let encoded: string = '';
for (let index = 0; index < str.length; index++) {
+8 -7
View File
@@ -8,7 +8,7 @@
import {Compiler, ComponentFactory, Inject, Injector, ModuleWithComponentFactories, NgModuleFactory, Type, ɵgetComponentViewDefinitionFactory as getComponentViewDefinitionFactory, ɵstringify as stringify} from '@angular/core';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, ProviderMeta, ProxyClass, createHostComponentMeta, identifierName} from '../compile_metadata';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileStylesheetMetadata, ProviderMeta, ProxyClass, createHostComponentMeta, identifierName, ngModuleJitUrl, sharedStylesheetJitUrl, templateJitUrl, templateSourceUrl} from '../compile_metadata';
import {CompilerConfig} from '../config';
import {CompilerInjectable} from '../injectable';
import {CompileMetadataResolver} from '../metadata_resolver';
@@ -38,6 +38,7 @@ export class JitCompiler implements Compiler {
private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>();
private _compiledDirectiveWrapperCache = new Map<Type<any>, Type<any>>();
private _compiledNgModuleCache = new Map<Type<any>, NgModuleFactory<any>>();
private _sharedStylesheetCount = 0;
constructor(
private _injector: Injector, private _metadataResolver: CompileMetadataResolver,
@@ -128,7 +129,7 @@ export class JitCompiler implements Compiler {
interpretStatements(compileResult.statements, [compileResult.ngModuleFactoryVar])[0];
} else {
ngModuleFactory = jitStatements(
`/${identifierName(moduleMeta.type)}/module.ngfactory.js`, compileResult.statements,
ngModuleJitUrl(moduleMeta), compileResult.statements,
[compileResult.ngModuleFactoryVar])[0];
}
this._compiledNgModuleCache.set(moduleMeta.type.reference, ngModuleFactory);
@@ -251,7 +252,7 @@ export class JitCompiler implements Compiler {
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
compMeta, compMeta.template.template, directives, pipes, template.ngModule.schemas,
identifierName(compMeta.type));
templateSourceUrl(template.ngModule.type, template.compMeta, template.compMeta.template));
const compileResult = this._viewCompiler.compileComponent(
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
usedPipes);
@@ -263,10 +264,9 @@ export class JitCompiler implements Compiler {
[viewClass, rendererType] = interpretStatements(
statements, [compileResult.viewClassVar, compileResult.rendererTypeVar]);
} else {
const sourceUrl =
`/${identifierName(template.ngModule.type)}/${identifierName(template.compType)}/${template.isHost?'host':'component'}.ngfactory.js`;
[viewClass, rendererType] = jitStatements(
sourceUrl, statements, [compileResult.viewClassVar, compileResult.rendererTypeVar]);
templateJitUrl(template.ngModule.type, template.compMeta), statements,
[compileResult.viewClassVar, compileResult.rendererTypeVar]);
}
template.compiled(viewClass, rendererType);
}
@@ -289,7 +289,8 @@ export class JitCompiler implements Compiler {
return interpretStatements(result.statements, [result.stylesVar])[0];
} else {
return jitStatements(
`/${result.meta.moduleUrl}.ngstyle.js`, result.statements, [result.stylesVar])[0];
sharedStylesheetJitUrl(result.meta, this._sharedStylesheetCount++), result.statements,
[result.stylesVar])[0];
}
}
}
+6 -3
View File
@@ -155,7 +155,8 @@ export class CompileMetadataResolver {
return typeSummary && typeSummary.summaryKind === kind ? typeSummary : null;
}
private _loadDirectiveMetadata(directiveType: any, isSync: boolean): Promise<any> {
private _loadDirectiveMetadata(ngModuleType: any, directiveType: any, isSync: boolean):
Promise<any> {
if (this._directiveCache.has(directiveType)) {
return;
}
@@ -191,6 +192,7 @@ export class CompileMetadataResolver {
if (metadata.isComponent) {
const templateMeta = this._directiveNormalizer.normalizeTemplate({
ngModuleType,
componentType: directiveType,
moduleUrl: componentModuleUrl(this._reflector, directiveType, annotation),
encapsulation: metadata.template.encapsulation,
@@ -249,7 +251,8 @@ export class CompileMetadataResolver {
styles: dirMeta.styles,
styleUrls: dirMeta.styleUrls,
animations: animations,
interpolation: dirMeta.interpolation
interpolation: dirMeta.interpolation,
isInline: !!dirMeta.template
});
}
@@ -378,7 +381,7 @@ export class CompileMetadataResolver {
const loading: Promise<any>[] = [];
if (ngModule) {
ngModule.declaredDirectives.forEach((id) => {
const promise = this._loadDirectiveMetadata(id.reference, isSync);
const promise = this._loadDirectiveMetadata(moduleType, id.reference, isSync);
if (promise) {
loading.push(promise);
}
+2 -9
View File
@@ -14,7 +14,7 @@ import {CompilerInjectable} from './injectable';
import {ClassBuilder, createClassStmt} from './output/class_builder';
import * as o from './output/output_ast';
import {convertValueToOutputAst} from './output/value_util';
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
import {ParseLocation, ParseSourceFile, ParseSourceSpan, typeSourceSpan} from './parse_util';
import {NgModuleProviderAnalyzer} from './provider_analyzer';
import {ProviderAst} from './template_parser/template_ast';
@@ -37,14 +37,7 @@ export class NgModuleCompileResult {
export class NgModuleCompiler {
compile(ngModuleMeta: CompileNgModuleMetadata, extraProviders: CompileProviderMetadata[]):
NgModuleCompileResult {
const moduleUrl = identifierModuleUrl(ngModuleMeta.type);
const sourceFileName = moduleUrl != null ?
`in NgModule ${identifierName(ngModuleMeta.type)} in ${moduleUrl}` :
`in NgModule ${identifierName(ngModuleMeta.type)}`;
const sourceFile = new ParseSourceFile('', sourceFileName);
const sourceSpan = new ParseSourceSpan(
new ParseLocation(sourceFile, null, null, null),
new ParseLocation(sourceFile, null, null, null));
const sourceSpan = typeSourceSpan('NgModule', ngModuleMeta.type);
const deps: ComponentFactoryDependency[] = [];
const bootstrapComponentFactories: CompileIdentifierMetadata[] = [];
const entryComponentFactories =
@@ -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 {
+3 -2
View File
@@ -34,11 +34,12 @@ export class JavaScriptEmitter implements OutputEmitter {
srcParts.push(ctx.toSource());
const prefixLines = converter.importsWithPrefixes.size;
const sm = ctx.toSourceMapGenerator(null, prefixLines).toJsComment();
const sm = ctx.toSourceMapGenerator(genFilePath, prefixLines).toJsComment();
if (sm) {
srcParts.push(sm);
}
// always add a newline at the end, as some tools have bugs without it.
srcParts.push('');
return srcParts.join('\n');
}
}
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));
}
+12 -2
View File
@@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {isDevMode} from '@angular/core';
import {identifierName} from '../compile_metadata';
import {EmitterVisitorContext} from './abstract_emitter';
@@ -14,14 +15,23 @@ import * as o from './output_ast';
function evalExpression(
sourceUrl: string, ctx: EmitterVisitorContext, vars: {[key: string]: any}): any {
const fnBody =
`${ctx.toSource()}\n//# sourceURL=${sourceUrl}\n${ctx.toSourceMapGenerator().toJsComment()}`;
let fnBody = `${ctx.toSource()}\n//# sourceURL=${sourceUrl}`;
const fnArgNames: string[] = [];
const fnArgValues: any[] = [];
for (const argName in vars) {
fnArgNames.push(argName);
fnArgValues.push(vars[argName]);
}
if (isDevMode()) {
// using `new Function(...)` generates a header, 1 line of no arguments, 2 lines otherwise
// E.g. ```
// function anonymous(a,b,c
// /**/) { ... }```
// We don't want to hard code this fact, so we auto detect it via an empty function first.
const emptyFn = new Function(...fnArgNames.concat('return null;')).toString();
const headerLines = emptyFn.slice(0, emptyFn.indexOf('return null;')).split('\n').length - 1;
fnBody += `\n${ctx.toSourceMapGenerator(sourceUrl, headerLines).toJsComment()}`;
}
return new Function(...fnArgNames.concat(fnBody))(...fnArgValues);
}
+3 -1
View File
@@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {utf8Encode} from '../i18n/digest';
// https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
const VERSION = 3;
@@ -143,7 +145,7 @@ export class SourceMapGenerator {
export function toBase64String(value: string): string {
let b64 = '';
value = utf8Encode(value);
for (let i = 0; i < value.length;) {
const i1 = value.charCodeAt(i++);
const i2 = value.charCodeAt(i++);
+3 -1
View File
@@ -70,10 +70,12 @@ export class TypeScriptEmitter implements OutputEmitter {
srcParts.push(ctx.toSource());
const prefixLines = converter.reexports.size + converter.importsWithPrefixes.size;
const sm = ctx.toSourceMapGenerator(null, prefixLines).toJsComment();
const sm = ctx.toSourceMapGenerator(genFilePath, prefixLines).toJsComment();
if (sm) {
srcParts.push(sm);
}
// always add a newline at the end, as some tools have bugs without it.
srcParts.push('');
return srcParts.join('\n');
}
+11
View File
@@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import * as chars from './chars';
import {CompileIdentifierMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
export class ParseLocation {
constructor(
@@ -124,3 +125,13 @@ export class ParseError {
return `${this.msg}${contextStr}: ${this.span.start}${details}`;
}
}
export function typeSourceSpan(kind: string, type: CompileIdentifierMetadata): ParseSourceSpan {
const moduleUrl = identifierModuleUrl(type);
const sourceFileName = moduleUrl != null ? `in ${kind} ${identifierName(type)} in ${moduleUrl}` :
`in ${kind} ${identifierName(type)}`;
const sourceFile = new ParseSourceFile('', sourceFileName);
return new ParseSourceSpan(
new ParseLocation(sourceFile, null, null, null),
new ParseLocation(sourceFile, null, null, null));
}
+1 -1
View File
@@ -32,7 +32,7 @@ export class ProviderViewContext {
viewProviders: Map<any, boolean>;
errors: ProviderError[] = [];
constructor(public component: CompileDirectiveMetadata, public sourceSpan: ParseSourceSpan) {
constructor(public component: CompileDirectiveMetadata) {
this.viewQueries = _getViewQueries(component);
this.viewProviders = new Map<any, boolean>();
component.viewProviders.forEach((provider) => {
@@ -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
+158 -6
View File
@@ -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
+15 -10
View File
@@ -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