fix(compiler): make sourcemaps work in AOT mode
Inlcuded fixes: - include preamble in generated source map - always add a mapping for line/col 0 so that the generated sourcemap is not sparse - use a uniue sourceUrl for inline templates even in the AOT case
This commit is contained in:
parent
c0e05e6f03
commit
492153a986
|
@ -0,0 +1,2 @@
|
|||
<div>
|
||||
<span [title]="createError()"></span></div>
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* @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 {Component} from '@angular/core';
|
||||
|
||||
@Component({selector: 'comp-with-error', templateUrl: 'errors.html'})
|
||||
export class BindingErrorComp {
|
||||
createError() { throw new Error('Test'); }
|
||||
}
|
|
@ -22,6 +22,7 @@ import {BasicComp} from './basic';
|
|||
import {ComponentUsingThirdParty} from './comp_using_3rdp';
|
||||
import {CUSTOM} from './custom_token';
|
||||
import {CompWithAnalyzeEntryComponentsProvider, CompWithEntryComponents} from './entry_components';
|
||||
import {BindingErrorComp} from './errors';
|
||||
import {CompConsumingEvents, CompUsingPipes, CompWithProviders, CompWithReferences, DirPublishingEvents, ModuleUsingCustomElements} from './features';
|
||||
import {CompUsingRootModuleDirectiveAndPipe, SomeDirectiveInRootModule, SomeLibModule, SomePipeInRootModule, SomeService} from './module_fixtures';
|
||||
import {CompWithNgContent, ProjectingComp} from './projection';
|
||||
|
@ -63,6 +64,7 @@ export const SERVER_ANIMATIONS_PROVIDERS: Provider[] = [{
|
|||
SomeDirectiveInRootModule,
|
||||
SomePipeInRootModule,
|
||||
ComponentUsingThirdParty,
|
||||
BindingErrorComp,
|
||||
],
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
|
@ -88,6 +90,7 @@ export const SERVER_ANIMATIONS_PROVIDERS: Provider[] = [{
|
|||
CompWithReferences,
|
||||
ProjectingComp,
|
||||
ComponentUsingThirdParty,
|
||||
BindingErrorComp,
|
||||
]
|
||||
})
|
||||
export class MainModule {
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
require('source-map-support').install();
|
||||
|
||||
import './init';
|
||||
import './animate_spec';
|
||||
import './basic_spec';
|
||||
|
@ -14,3 +16,4 @@ import './i18n_spec';
|
|||
import './ng_module_spec';
|
||||
import './projection_spec';
|
||||
import './query_spec';
|
||||
import './source_map_spec';
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* @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 './init';
|
||||
import {BindingErrorComp} from '../src/errors';
|
||||
import {createComponent} from './util';
|
||||
|
||||
describe('source maps', () => {
|
||||
it('should report source location for binding errors', () => {
|
||||
const comp = createComponent(BindingErrorComp);
|
||||
let error: any;
|
||||
try {
|
||||
comp.detectChanges();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
const sourcePosition = getSourcePositionForStack(error.stack);
|
||||
expect(sourcePosition.line).toBe(2);
|
||||
expect(sourcePosition.column).toBe(13);
|
||||
expect(sourcePosition.source.endsWith('errors.html')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
function getSourcePositionForStack(stack: string): {source: string, line: number, column: number} {
|
||||
const htmlLocations = stack
|
||||
.split('\n')
|
||||
// e.g. at View_MyComp_0 (...html:153:40)
|
||||
.map(line => /\((.*\.html):(\d+):(\d+)/.exec(line))
|
||||
.filter(match => !!match)
|
||||
.map(match => ({
|
||||
source: match[1],
|
||||
line: parseInt(match[2], 10),
|
||||
column: parseInt(match[3], 10)
|
||||
}));
|
||||
return htmlLocations[0];
|
||||
}
|
|
@ -17,7 +17,8 @@
|
|||
"baseUrl": ".",
|
||||
// Prevent scanning up the directory tree for types
|
||||
"typeRoots": ["node_modules/@types"],
|
||||
"noUnusedLocals": true
|
||||
"noUnusedLocals": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
|
||||
"files": [
|
||||
|
|
|
@ -11,5 +11,9 @@ module.exports = {
|
|||
entry: './test/all_spec.js',
|
||||
output: {filename: './all_spec.js'},
|
||||
resolve: {extensions: ['.js']},
|
||||
|
||||
devtool: '#source-map',
|
||||
module: {
|
||||
loaders:
|
||||
[{test: /\.js$/, exclude: /node_modules/, loaders: ['source-map-loader'], enforce: 'pre'}]
|
||||
},
|
||||
};
|
||||
|
|
|
@ -45,7 +45,7 @@ export class CodeGenerator {
|
|||
const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
|
||||
const emitPath = this.ngCompilerHost.calculateEmitPath(generatedModule.genFileUrl);
|
||||
const source = GENERATED_META_FILES.test(emitPath) ? generatedModule.source :
|
||||
PREAMBLE + generatedModule.source;
|
||||
generatedModule.source;
|
||||
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);
|
||||
});
|
||||
});
|
||||
|
@ -76,6 +76,7 @@ export class CodeGenerator {
|
|||
i18nFormat: cliOptions.i18nFormat,
|
||||
locale: cliOptions.locale,
|
||||
enableLegacyTemplate: options.enableLegacyTemplate !== false,
|
||||
genFilePreamble: PREAMBLE,
|
||||
});
|
||||
return new CodeGenerator(options, program, tsCompilerHost, aotCompiler, ngCompilerHost);
|
||||
}
|
||||
|
|
|
@ -33,7 +33,8 @@ export class AotCompiler {
|
|||
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
|
||||
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
|
||||
private _summaryResolver: SummaryResolver<StaticSymbol>, private _localeId: string,
|
||||
private _translationFormat: string, private _symbolResolver: StaticSymbolResolver) {}
|
||||
private _translationFormat: string, private _genFilePreamble: string,
|
||||
private _symbolResolver: StaticSymbolResolver) {}
|
||||
|
||||
clearCache() { this._metadataResolver.clearCache(); }
|
||||
|
||||
|
@ -215,7 +216,8 @@ export class AotCompiler {
|
|||
exportedVars: string[]): GeneratedFile {
|
||||
return new GeneratedFile(
|
||||
srcFileUrl, genFileUrl,
|
||||
this._outputEmitter.emitStatements(genFileUrl, statements, exportedVars));
|
||||
this._outputEmitter.emitStatements(
|
||||
srcFileUrl, genFileUrl, statements, exportedVars, this._genFilePreamble));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -79,6 +79,6 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
|
|||
const compiler = new AotCompiler(
|
||||
config, compilerHost, resolver, tmplParser, new StyleCompiler(urlResolver), viewCompiler,
|
||||
new NgModuleCompiler(), new TypeScriptEmitter(importResolver), summaryResolver,
|
||||
options.locale, options.i18nFormat, symbolResolver);
|
||||
options.locale, options.i18nFormat, options.genFilePreamble, symbolResolver);
|
||||
return {compiler, reflector: staticReflector};
|
||||
}
|
||||
|
|
|
@ -11,4 +11,6 @@ export interface AotCompilerOptions {
|
|||
i18nFormat?: string;
|
||||
translations?: string;
|
||||
enableLegacyTemplate?: boolean;
|
||||
/** preamble for all generated source files */
|
||||
genFilePreamble?: string;
|
||||
}
|
||||
|
|
|
@ -772,7 +772,9 @@ export function templateSourceUrl(
|
|||
templateMeta: {isInline: boolean, templateUrl: string}) {
|
||||
if (templateMeta.isInline) {
|
||||
if (compMeta.type.reference instanceof StaticSymbol) {
|
||||
return compMeta.type.reference.filePath;
|
||||
// Note: a .ts file might contain multiple components with inline templates,
|
||||
// so we need to give them unique urls, as these will be used for sourcemaps.
|
||||
return `${compMeta.type.reference.filePath}#${compMeta.type.reference.name}.html`;
|
||||
} else {
|
||||
return `${ngJitFolder()}/${identifierName(ngModuleType)}/${identifierName(compMeta.type)}.html`;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,9 @@ export const CATCH_ERROR_VAR = o.variable('error');
|
|||
export const CATCH_STACK_VAR = o.variable('stack');
|
||||
|
||||
export abstract class OutputEmitter {
|
||||
abstract emitStatements(moduleUrl: string, stmts: o.Statement[], exportedVars: string[]): string;
|
||||
abstract emitStatements(
|
||||
srcFilePath: string, genFilePath: string, stmts: o.Statement[], exportedVars: string[],
|
||||
preamble?: string): string;
|
||||
}
|
||||
|
||||
class _EmittedLine {
|
||||
|
@ -89,13 +91,24 @@ export class EmitterVisitorContext {
|
|||
.join('\n');
|
||||
}
|
||||
|
||||
toSourceMapGenerator(file: string|null = null, startsAtLine: number = 0): SourceMapGenerator {
|
||||
const map = new SourceMapGenerator(file);
|
||||
toSourceMapGenerator(sourceFilePath: string, genFilePath: string, startsAtLine: number = 0):
|
||||
SourceMapGenerator {
|
||||
const map = new SourceMapGenerator(genFilePath);
|
||||
|
||||
let firstOffsetMapped = false;
|
||||
const mapFirstOffsetIfNeeded = () => {
|
||||
if (!firstOffsetMapped) {
|
||||
map.addSource(sourceFilePath).addMapping(0, sourceFilePath, 0, 0);
|
||||
firstOffsetMapped = true;
|
||||
}
|
||||
};
|
||||
|
||||
for (let i = 0; i < startsAtLine; i++) {
|
||||
map.addLine();
|
||||
mapFirstOffsetIfNeeded();
|
||||
}
|
||||
|
||||
this.sourceLines.forEach(line => {
|
||||
this.sourceLines.forEach((line, lineIdx) => {
|
||||
map.addLine();
|
||||
|
||||
const spans = line.srcSpans;
|
||||
|
@ -107,13 +120,17 @@ export class EmitterVisitorContext {
|
|||
col0 += parts[spanIdx].length;
|
||||
spanIdx++;
|
||||
}
|
||||
if (spanIdx < spans.length && lineIdx === 0 && col0 === 0) {
|
||||
firstOffsetMapped = true;
|
||||
} else {
|
||||
mapFirstOffsetIfNeeded();
|
||||
}
|
||||
|
||||
while (spanIdx < spans.length) {
|
||||
const span = spans[spanIdx];
|
||||
const source = span.start.file;
|
||||
const sourceLine = span.start.line;
|
||||
const sourceCol = span.start.col;
|
||||
|
||||
map.addSource(source.url, source.content)
|
||||
.addMapping(col0, source.url, sourceLine, sourceCol);
|
||||
|
||||
|
|
|
@ -18,29 +18,29 @@ import {ImportResolver} from './path_util';
|
|||
export class JavaScriptEmitter implements OutputEmitter {
|
||||
constructor(private _importResolver: ImportResolver) {}
|
||||
|
||||
emitStatements(genFilePath: string, stmts: o.Statement[], exportedVars: string[]): string {
|
||||
emitStatements(
|
||||
srcFilePath: string, genFilePath: string, stmts: o.Statement[], exportedVars: string[],
|
||||
preamble: string = ''): string {
|
||||
const converter = new JsEmitterVisitor(genFilePath, this._importResolver);
|
||||
const ctx = EmitterVisitorContext.createRoot(exportedVars);
|
||||
converter.visitAllStatements(stmts, ctx);
|
||||
|
||||
const srcParts: string[] = [];
|
||||
const preambleLines = preamble ? preamble.split('\n') : [];
|
||||
converter.importsWithPrefixes.forEach((prefix, importedFilePath) => {
|
||||
// Note: can't write the real word for import as it screws up system.js auto detection...
|
||||
srcParts.push(
|
||||
preambleLines.push(
|
||||
`var ${prefix} = req` +
|
||||
`uire('${this._importResolver.fileNameToModuleName(importedFilePath, genFilePath)}');`);
|
||||
});
|
||||
|
||||
srcParts.push(ctx.toSource());
|
||||
|
||||
const prefixLines = converter.importsWithPrefixes.size;
|
||||
const sm = ctx.toSourceMapGenerator(genFilePath, prefixLines).toJsComment();
|
||||
const sm =
|
||||
ctx.toSourceMapGenerator(srcFilePath, genFilePath, preambleLines.length).toJsComment();
|
||||
const lines = [...preambleLines, ctx.toSource(), sm];
|
||||
if (sm) {
|
||||
srcParts.push(sm);
|
||||
// always add a newline at the end, as some tools have bugs without it.
|
||||
lines.push('');
|
||||
}
|
||||
// always add a newline at the end, as some tools have bugs without it.
|
||||
srcParts.push('');
|
||||
return srcParts.join('\n');
|
||||
return lines.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ function evalExpression(
|
|||
// 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()}`;
|
||||
fnBody += `\n${ctx.toSourceMapGenerator(sourceUrl, sourceUrl, headerLines).toJsComment()}`;
|
||||
}
|
||||
return new Function(...fnArgNames.concat(fnBody))(...fnArgValues);
|
||||
}
|
||||
|
|
|
@ -44,40 +44,38 @@ export function debugOutputAstAsTypeScript(ast: o.Statement | o.Expression | o.T
|
|||
export class TypeScriptEmitter implements OutputEmitter {
|
||||
constructor(private _importResolver: ImportResolver) {}
|
||||
|
||||
emitStatements(genFilePath: string, stmts: o.Statement[], exportedVars: string[]): string {
|
||||
emitStatements(
|
||||
srcFilePath: string, genFilePath: string, stmts: o.Statement[], exportedVars: string[],
|
||||
preamble: string = ''): string {
|
||||
const converter = new _TsEmitterVisitor(genFilePath, this._importResolver);
|
||||
|
||||
const ctx = EmitterVisitorContext.createRoot(exportedVars);
|
||||
|
||||
converter.visitAllStatements(stmts, ctx);
|
||||
|
||||
const srcParts: string[] = [];
|
||||
|
||||
const preambleLines = preamble ? preamble.split('\n') : [];
|
||||
converter.reexports.forEach((reexports, exportedFilePath) => {
|
||||
const reexportsCode =
|
||||
reexports.map(reexport => `${reexport.name} as ${reexport.as}`).join(',');
|
||||
srcParts.push(
|
||||
preambleLines.push(
|
||||
`export {${reexportsCode}} from '${this._importResolver.fileNameToModuleName(exportedFilePath, genFilePath)}';`);
|
||||
});
|
||||
|
||||
converter.importsWithPrefixes.forEach((prefix, importedFilePath) => {
|
||||
// Note: can't write the real word for import as it screws up system.js auto detection...
|
||||
srcParts.push(
|
||||
preambleLines.push(
|
||||
`imp` +
|
||||
`ort * as ${prefix} from '${this._importResolver.fileNameToModuleName(importedFilePath, genFilePath)}';`);
|
||||
});
|
||||
|
||||
srcParts.push(ctx.toSource());
|
||||
|
||||
const prefixLines = converter.reexports.size + converter.importsWithPrefixes.size;
|
||||
const sm = ctx.toSourceMapGenerator(genFilePath, prefixLines).toJsComment();
|
||||
const sm =
|
||||
ctx.toSourceMapGenerator(srcFilePath, genFilePath, preambleLines.length).toJsComment();
|
||||
const lines = [...preambleLines, ctx.toSource(), sm];
|
||||
if (sm) {
|
||||
srcParts.push(sm);
|
||||
// always add a newline at the end, as some tools have bugs without it.
|
||||
lines.push('');
|
||||
}
|
||||
// always add a newline at the end, as some tools have bugs without it.
|
||||
srcParts.push('');
|
||||
|
||||
return srcParts.join('\n');
|
||||
return lines.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import {AotCompiler, AotCompilerHost, AotCompilerOptions, GeneratedFile, createAotCompiler} from '@angular/compiler';
|
||||
import {RenderComponentType, ɵReflectionCapabilities as ReflectionCapabilities, ɵreflector as reflector} from '@angular/core';
|
||||
import {async, fakeAsync, tick} from '@angular/core/testing';
|
||||
import {async} from '@angular/core/testing';
|
||||
import {MetadataBundler, MetadataCollector, ModuleMetadata, privateEntriesToIndex} from '@angular/tsc-wrapped';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
|
@ -94,19 +94,18 @@ describe('compiler (unbundled Angular)', () => {
|
|||
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 compileApp(): Promise<GeneratedFile> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const host = new MockCompilerHost(['/app/app.module.ts'], rootDir, angularFiles);
|
||||
const aotHost = new MockAotCompilerHost(host);
|
||||
let result: GeneratedFile[];
|
||||
let error: Error;
|
||||
resolve(compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics)
|
||||
.then(
|
||||
(files) => files.find(
|
||||
genFile => genFile.srcFileUrl === componentPath &&
|
||||
genFile.genFileUrl.endsWith('.ts'))));
|
||||
});
|
||||
}
|
||||
|
||||
function findLineAndColumn(file: string, token: string): {line: number, column: number} {
|
||||
|
@ -134,7 +133,7 @@ describe('compiler (unbundled Angular)', () => {
|
|||
}
|
||||
|
||||
describe('inline templates', () => {
|
||||
const templateUrl = componentPath;
|
||||
const templateUrl = `${componentPath}#AppComponent.html`;
|
||||
|
||||
function templateDecorator(template: string) { return `template: \`${template}\`,`; }
|
||||
|
||||
|
@ -155,74 +154,96 @@ describe('compiler (unbundled Angular)', () => {
|
|||
function declareTests(
|
||||
{templateUrl, templateDecorator}:
|
||||
{templateUrl: string, templateDecorator: (template: string) => string}) {
|
||||
it('should use the right source url in html parse errors', fakeAsync(() => {
|
||||
it('should use the right source url in html parse errors', async(() => {
|
||||
appDir['app.component.ts'] =
|
||||
createComponentSource(templateDecorator('<div>\n </error>'));
|
||||
|
||||
expect(() => compileApp())
|
||||
.toThrowError(new RegExp(`Template parse errors[\\s\\S]*${templateUrl}@1:2`));
|
||||
expectPromiseToThrow(
|
||||
compileApp(), new RegExp(`Template parse errors[\\s\\S]*${templateUrl}@1:2`));
|
||||
}));
|
||||
|
||||
it('should use the right source url in template parse errors', fakeAsync(() => {
|
||||
it('should use the right source url in template parse errors', async(() => {
|
||||
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`));
|
||||
expectPromiseToThrow(
|
||||
compileApp(), new RegExp(`Template parse errors[\\s\\S]*${templateUrl}@1:7`));
|
||||
}));
|
||||
|
||||
it('should create a sourceMap for the template', fakeAsync(() => {
|
||||
it('should create a sourceMap for the template', async(() => {
|
||||
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);
|
||||
compileApp().then((genFile) => {
|
||||
const sourceMap = extractSourceMap(genFile.source);
|
||||
expect(sourceMap.file).toEqual(genFile.genFileUrl);
|
||||
|
||||
// the generated file contains code that is not mapped to
|
||||
// the template but rather to the original source file (e.g. import statements, ...)
|
||||
const templateIndex = sourceMap.sources.indexOf(templateUrl);
|
||||
expect(sourceMap.sourcesContent[templateIndex]).toEqual(template);
|
||||
|
||||
// for the mapping to the original source file we don't store the source code
|
||||
// as we want to keep whatever TypeScript / ... produced for them.
|
||||
const sourceIndex = sourceMap.sources.indexOf(componentPath);
|
||||
expect(sourceMap.sourcesContent[sourceIndex]).toBe(null);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should map elements correctly to the source', fakeAsync(() => {
|
||||
it('should map elements correctly to the source', async(() => {
|
||||
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});
|
||||
compileApp().then((genFile) => {
|
||||
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(() => {
|
||||
it('should map bindings correctly to the source', async(() => {
|
||||
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});
|
||||
compileApp().then((genFile) => {
|
||||
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(() => {
|
||||
it('should map events correctly to the source', async(() => {
|
||||
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});
|
||||
compileApp().then((genFile) => {
|
||||
const sourceMap = extractSourceMap(genFile.source);
|
||||
expect(
|
||||
originalPositionFor(sourceMap, findLineAndColumn(genFile.source, `someMethod()`)))
|
||||
.toEqual({line: 2, column: 9, source: templateUrl});
|
||||
});
|
||||
}));
|
||||
|
||||
it('should map non template parts to the source file', async(() => {
|
||||
appDir['app.component.ts'] = createComponentSource(templateDecorator('Hello World!'));
|
||||
|
||||
compileApp().then((genFile) => {
|
||||
const sourceMap = extractSourceMap(genFile.source);
|
||||
expect(originalPositionFor(sourceMap, {line: 1, column: 0}))
|
||||
.toEqual({line: 1, column: 0, source: componentPath});
|
||||
});
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
it('should only warn if not all arguments of an @Injectable class can be resolved',
|
||||
fakeAsync(() => {
|
||||
async(() => {
|
||||
const FILES: MockData = {
|
||||
app: {
|
||||
'app.ts': `
|
||||
|
@ -237,17 +258,40 @@ describe('compiler (unbundled Angular)', () => {
|
|||
};
|
||||
const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles);
|
||||
const aotHost = new MockAotCompilerHost(host);
|
||||
let ok = false;
|
||||
const warnSpy = spyOn(console, 'warn');
|
||||
compile(host, aotHost, expectNoDiagnostics).then(() => ok = true);
|
||||
compile(host, aotHost, expectNoDiagnostics).then(() => {
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
`Warning: Can't resolve all parameters for MyService in /app/app.ts: (?). This will become an error in Angular v5.x`);
|
||||
});
|
||||
|
||||
tick();
|
||||
|
||||
expect(ok).toBe(true);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
`Warning: Can't resolve all parameters for MyService in /app/app.ts: (?). This will become an error in Angular v5.x`);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should add the preamble to generated files', async(() => {
|
||||
const FILES: MockData = {
|
||||
app: {
|
||||
'app.ts': `
|
||||
import { NgModule, Component } from '@angular/core';
|
||||
|
||||
@Component({ template: '' })
|
||||
export class AppComponent {}
|
||||
|
||||
@NgModule({ declarations: [ AppComponent ] })
|
||||
export class AppModule { }
|
||||
`
|
||||
}
|
||||
};
|
||||
const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles);
|
||||
const aotHost = new MockAotCompilerHost(host);
|
||||
const genFilePreamble = '/* Hello world! */';
|
||||
compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics, {genFilePreamble})
|
||||
.then((generatedFiles) => {
|
||||
const genFile = generatedFiles.find(
|
||||
gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
|
||||
expect(genFile.source.startsWith(genFilePreamble)).toBe(true);
|
||||
});
|
||||
|
||||
}));
|
||||
});
|
||||
|
||||
describe('compiler (bundled Angular)', () => {
|
||||
|
@ -426,3 +470,8 @@ const FILES: MockData = {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
function expectPromiseToThrow(p: Promise<any>, msg: RegExp) {
|
||||
p.then(
|
||||
() => { throw new Error('Expected to throw'); }, (e) => { expect(e.message).toMatch(msg); });
|
||||
}
|
|
@ -25,7 +25,7 @@ export function main() {
|
|||
ctx.print(createSourceSpan(fileA, 1), 'o1');
|
||||
ctx.print(createSourceSpan(fileB, 0), 'o2');
|
||||
ctx.print(createSourceSpan(fileB, 1), 'o3');
|
||||
const sm = ctx.toSourceMapGenerator('o.js').toJSON();
|
||||
const sm = ctx.toSourceMapGenerator('o.ts', 'o.js').toJSON();
|
||||
expect(sm.sources).toEqual([fileA.url, fileB.url]);
|
||||
expect(sm.sourcesContent).toEqual([fileA.content, fileB.content]);
|
||||
});
|
||||
|
@ -43,7 +43,7 @@ export function main() {
|
|||
it('should be able to shift the content', () => {
|
||||
ctx.print(createSourceSpan(fileA, 0), 'fileA-0');
|
||||
|
||||
const sm = ctx.toSourceMapGenerator(null, 10).toJSON();
|
||||
const sm = ctx.toSourceMapGenerator('o.ts', 'o.js', 10).toJSON();
|
||||
expect(originalPositionFor(sm, {line: 11, column: 0})).toEqual({
|
||||
line: 1,
|
||||
column: 0,
|
||||
|
@ -51,13 +51,23 @@ export function main() {
|
|||
});
|
||||
});
|
||||
|
||||
it('should not map leading segment without span', () => {
|
||||
it('should use the default source file for the first character', () => {
|
||||
ctx.print(null, 'fileA-0');
|
||||
expectMap(ctx, 0, 0, 'o.ts', 0, 0);
|
||||
});
|
||||
|
||||
it('should use an explicit mapping for the first character', () => {
|
||||
ctx.print(createSourceSpan(fileA, 0), 'fileA-0');
|
||||
expectMap(ctx, 0, 0, 'a.js', 0, 0);
|
||||
});
|
||||
|
||||
it('should map leading segment without span', () => {
|
||||
ctx.print(null, '....');
|
||||
ctx.print(createSourceSpan(fileA, 0), 'fileA-0');
|
||||
|
||||
expectMap(ctx, 0, 0);
|
||||
expectMap(ctx, 0, 0, 'o.ts', 0, 0);
|
||||
expectMap(ctx, 0, 4, 'a.js', 0, 0);
|
||||
expect(nbSegmentsPerLine(ctx)).toEqual([1]);
|
||||
expect(nbSegmentsPerLine(ctx)).toEqual([2]);
|
||||
});
|
||||
|
||||
it('should handle indent', () => {
|
||||
|
@ -68,7 +78,7 @@ export function main() {
|
|||
ctx.decIndent();
|
||||
ctx.println(createSourceSpan(fileA, 2), 'fileA-2');
|
||||
|
||||
expectMap(ctx, 0, 0);
|
||||
expectMap(ctx, 0, 0, 'o.ts', 0, 0);
|
||||
expectMap(ctx, 0, 2, 'a.js', 0, 0);
|
||||
expectMap(ctx, 1, 0);
|
||||
expectMap(ctx, 1, 2);
|
||||
|
@ -76,7 +86,7 @@ export function main() {
|
|||
expectMap(ctx, 2, 0);
|
||||
expectMap(ctx, 2, 2, 'a.js', 0, 4);
|
||||
|
||||
expect(nbSegmentsPerLine(ctx)).toEqual([1, 1, 1]);
|
||||
expect(nbSegmentsPerLine(ctx)).toEqual([2, 1, 1]);
|
||||
});
|
||||
|
||||
it('should coalesce identical span', () => {
|
||||
|
@ -103,7 +113,7 @@ export function main() {
|
|||
function expectMap(
|
||||
ctx: EmitterVisitorContext, genLine: number, genCol: number, source: string = null,
|
||||
srcLine: number = null, srcCol: number = null) {
|
||||
const sm = ctx.toSourceMapGenerator().toJSON();
|
||||
const sm = ctx.toSourceMapGenerator('o.ts', 'o.js').toJSON();
|
||||
const genPosition = {line: genLine + 1, column: genCol};
|
||||
const origPosition = originalPositionFor(sm, genPosition);
|
||||
expect(origPosition.source).toEqual(source);
|
||||
|
@ -113,7 +123,7 @@ function expectMap(
|
|||
|
||||
// returns the number of segments per line
|
||||
function nbSegmentsPerLine(ctx: EmitterVisitorContext) {
|
||||
const sm = ctx.toSourceMapGenerator().toJSON();
|
||||
const sm = ctx.toSourceMapGenerator('o.ts', 'o.js').toJSON();
|
||||
const lines = sm.mappings.split(';');
|
||||
return lines.map(l => {
|
||||
const m = l.match(/,/g);
|
||||
|
|
|
@ -16,7 +16,8 @@ import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler
|
|||
|
||||
import {extractSourceMap, originalPositionFor} from './source_map_util';
|
||||
|
||||
const someModuleUrl = 'somePackage/somePath';
|
||||
const someGenFilePath = 'somePackage/someGenFile';
|
||||
const someSourceFilePath = 'somePackage/someSourceFile';
|
||||
|
||||
class SimpleJsImportGenerator implements ImportResolver {
|
||||
fileNameToModuleName(importedUrlStr: string, moduleUrlStr: string): string {
|
||||
|
@ -38,9 +39,11 @@ export function main() {
|
|||
});
|
||||
|
||||
function emitSourceMap(
|
||||
stmt: o.Statement | o.Statement[], exportedVars: string[] = null): SourceMap {
|
||||
stmt: o.Statement | o.Statement[], exportedVars: string[] = null,
|
||||
preamble?: string): SourceMap {
|
||||
const stmts = Array.isArray(stmt) ? stmt : [stmt];
|
||||
const source = emitter.emitStatements(someModuleUrl, stmts, exportedVars || []);
|
||||
const source = emitter.emitStatements(
|
||||
someSourceFilePath, someGenFilePath, stmts, exportedVars || [], preamble);
|
||||
return extractSourceMap(source);
|
||||
}
|
||||
|
||||
|
@ -51,11 +54,11 @@ export function main() {
|
|||
const endLocation = new ParseLocation(source, 7, 0, 6);
|
||||
const sourceSpan = new ParseSourceSpan(startLocation, endLocation);
|
||||
const someVar = o.variable('someVar', null, sourceSpan);
|
||||
const sm = emitSourceMap(someVar.toStmt());
|
||||
const sm = emitSourceMap(someVar.toStmt(), [], '/* MyPreamble \n */');
|
||||
|
||||
expect(sm.sources).toEqual(['in.js']);
|
||||
expect(sm.sourcesContent).toEqual([';;;var']);
|
||||
expect(originalPositionFor(sm, {line: 1, column: 0}))
|
||||
expect(sm.sources).toEqual([someSourceFilePath, 'in.js']);
|
||||
expect(sm.sourcesContent).toEqual([null, ';;;var']);
|
||||
expect(originalPositionFor(sm, {line: 3, column: 0}))
|
||||
.toEqual({line: 1, column: 3, source: 'in.js'});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,11 +14,12 @@ import {ImportResolver} from '@angular/compiler/src/output/path_util';
|
|||
|
||||
import {stripSourceMapAndNewLine} from './abstract_emitter_spec';
|
||||
|
||||
const someModuleUrl = 'somePackage/somePath';
|
||||
const someGenFilePath = 'somePackage/someGenFile';
|
||||
const someSourceFilePath = 'somePackage/someSourceFile';
|
||||
const anotherModuleUrl = 'somePackage/someOtherPath';
|
||||
|
||||
const sameModuleIdentifier: CompileIdentifierMetadata = {
|
||||
reference: new StaticSymbol(someModuleUrl, 'someLocalId', [])
|
||||
reference: new StaticSymbol(someGenFilePath, 'someLocalId', [])
|
||||
};
|
||||
const externalModuleIdentifier: CompileIdentifierMetadata = {
|
||||
reference: new StaticSymbol(anotherModuleUrl, 'someExternalId', [])
|
||||
|
@ -48,8 +49,9 @@ export function main() {
|
|||
someVar = o.variable('someVar');
|
||||
});
|
||||
|
||||
function emitStmt(stmt: o.Statement, exportedVars: string[] = null): string {
|
||||
const source = emitter.emitStatements(someModuleUrl, [stmt], exportedVars || []);
|
||||
function emitStmt(stmt: o.Statement, exportedVars: string[] = null, preamble?: string): string {
|
||||
const source = emitter.emitStatements(
|
||||
someSourceFilePath, someGenFilePath, [stmt], exportedVars || [], preamble);
|
||||
return stripSourceMapAndNewLine(source);
|
||||
}
|
||||
|
||||
|
@ -300,5 +302,11 @@ export function main() {
|
|||
].join('\n'));
|
||||
});
|
||||
});
|
||||
|
||||
it('should support a preamble', () => {
|
||||
expect(emitStmt(o.variable('a').toStmt(), [], '/* SomePreamble */')).toBe([
|
||||
'/* SomePreamble */', 'a;'
|
||||
].join('\n'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -16,7 +16,8 @@ import {ParseSourceSpan} from '@angular/compiler/src/parse_util';
|
|||
|
||||
import {extractSourceMap, originalPositionFor} from './source_map_util';
|
||||
|
||||
const someModuleUrl = 'somePackage/somePath';
|
||||
const someGenFilePath = 'somePackage/someGenFile';
|
||||
const someSourceFilePath = 'somePackage/someSourceFile';
|
||||
|
||||
class SimpleJsImportGenerator implements ImportResolver {
|
||||
fileNameToModuleName(importedUrlStr: string, moduleUrlStr: string): string {
|
||||
|
@ -43,9 +44,11 @@ export function main() {
|
|||
});
|
||||
|
||||
function emitSourceMap(
|
||||
stmt: o.Statement | o.Statement[], exportedVars: string[] = null): SourceMap {
|
||||
stmt: o.Statement | o.Statement[], exportedVars: string[] = null,
|
||||
preamble?: string): SourceMap {
|
||||
const stmts = Array.isArray(stmt) ? stmt : [stmt];
|
||||
const source = emitter.emitStatements(someModuleUrl, stmts, exportedVars || []);
|
||||
const source = emitter.emitStatements(
|
||||
someSourceFilePath, someGenFilePath, stmts, exportedVars || [], preamble);
|
||||
return extractSourceMap(source);
|
||||
}
|
||||
|
||||
|
@ -56,11 +59,11 @@ export function main() {
|
|||
const endLocation = new ParseLocation(source, 7, 0, 6);
|
||||
const sourceSpan = new ParseSourceSpan(startLocation, endLocation);
|
||||
const someVar = o.variable('someVar', null, sourceSpan);
|
||||
const sm = emitSourceMap(someVar.toStmt());
|
||||
const sm = emitSourceMap(someVar.toStmt(), [], '/* MyPreamble \n */');
|
||||
|
||||
expect(sm.sources).toEqual(['in.js']);
|
||||
expect(sm.sourcesContent).toEqual([';;;var']);
|
||||
expect(originalPositionFor(sm, {line: 1, column: 0}))
|
||||
expect(sm.sources).toEqual([someSourceFilePath, 'in.js']);
|
||||
expect(sm.sourcesContent).toEqual([null, ';;;var']);
|
||||
expect(originalPositionFor(sm, {line: 3, column: 0}))
|
||||
.toEqual({line: 1, column: 3, source: 'in.js'});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,11 +14,12 @@ import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter';
|
|||
|
||||
import {stripSourceMapAndNewLine} from './abstract_emitter_spec';
|
||||
|
||||
const someModuleUrl = 'somePackage/somePath';
|
||||
const someGenFilePath = 'somePackage/someGenFile';
|
||||
const someSourceFilePath = 'somePackage/someSourceFile';
|
||||
const anotherModuleUrl = 'somePackage/someOtherPath';
|
||||
|
||||
const sameModuleIdentifier: CompileIdentifierMetadata = {
|
||||
reference: new StaticSymbol(someModuleUrl, 'someLocalId', [])
|
||||
reference: new StaticSymbol(someGenFilePath, 'someLocalId', [])
|
||||
};
|
||||
|
||||
const externalModuleIdentifier: CompileIdentifierMetadata = {
|
||||
|
@ -49,9 +50,12 @@ export function main() {
|
|||
someVar = o.variable('someVar');
|
||||
});
|
||||
|
||||
function emitStmt(stmt: o.Statement | o.Statement[], exportedVars: string[] = null): string {
|
||||
function emitStmt(
|
||||
stmt: o.Statement | o.Statement[], exportedVars: string[] = null,
|
||||
preamble?: string): string {
|
||||
const stmts = Array.isArray(stmt) ? stmt : [stmt];
|
||||
const source = emitter.emitStatements(someModuleUrl, stmts, exportedVars || []);
|
||||
const source = emitter.emitStatements(
|
||||
someSourceFilePath, someGenFilePath, stmts, exportedVars || [], preamble);
|
||||
return stripSourceMapAndNewLine(source);
|
||||
}
|
||||
|
||||
|
@ -459,5 +463,11 @@ export function main() {
|
|||
expect(emitStmt(writeVarExpr.toDeclStmt(new o.MapType(o.INT_TYPE))))
|
||||
.toEqual('var a:{[key: string]:number} = (null as any);');
|
||||
});
|
||||
|
||||
it('should support a preamble', () => {
|
||||
expect(emitStmt(o.variable('a').toStmt(), [], '/* SomePreamble */')).toBe([
|
||||
'/* SomePreamble */', 'a;'
|
||||
].join('\n'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -125,8 +125,10 @@ export function main() {
|
|||
compileAndCreateComponent(MyComp);
|
||||
|
||||
const sourceMap = getSourceMap('ng:///DynamicTestModule/MyComp.ngfactory.js');
|
||||
expect(sourceMap.sources).toEqual([templateUrl]);
|
||||
expect(sourceMap.sourcesContent).toEqual([template]);
|
||||
expect(sourceMap.sources).toEqual([
|
||||
'ng:///DynamicTestModule/MyComp.ngfactory.js', templateUrl
|
||||
]);
|
||||
expect(sourceMap.sourcesContent).toEqual([null, template]);
|
||||
}));
|
||||
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ PKGS=(
|
|||
@types/{node@6.0.38,jasmine@2.2.33}
|
||||
jasmine@2.4.1
|
||||
webpack@2.1.0-beta.21
|
||||
source-map-loader@0.2.0
|
||||
@angular2-material/{core,button}@2.0.0-alpha.8-1
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue