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:
Tobias Bosch 2017-03-15 15:50:30 -07:00 committed by Chuck Jazdzewski
parent c0e05e6f03
commit 492153a986
24 changed files with 299 additions and 123 deletions

View File

@ -0,0 +1,2 @@
<div>
<span [title]="createError()"></span></div>

View File

@ -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'); }
}

View File

@ -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 {

View File

@ -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';

View File

@ -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];
}

View File

@ -17,7 +17,8 @@
"baseUrl": ".",
// Prevent scanning up the directory tree for types
"typeRoots": ["node_modules/@types"],
"noUnusedLocals": true
"noUnusedLocals": true,
"sourceMap": true
},
"files": [

View File

@ -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'}]
},
};

View File

@ -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);
}

View File

@ -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));
}
}

View File

@ -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};
}

View File

@ -11,4 +11,6 @@ export interface AotCompilerOptions {
i18nFormat?: string;
translations?: string;
enableLegacyTemplate?: boolean;
/** preamble for all generated source files */
genFilePreamble?: string;
}

View File

@ -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`;
}

View File

@ -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);

View File

@ -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');
}
}

View File

@ -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);
}

View File

@ -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');
}
}

View File

@ -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); });
}

View File

@ -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);

View File

@ -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'});
});
});

View File

@ -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'));
});
});
}

View File

@ -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'});
});
});

View File

@ -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'));
});
});
}

View File

@ -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]);
}));

View File

@ -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
)