feat: introduce source maps for templates (#15011)
The main use case for the generated source maps is to give errors a meaningful context in terms of the original source that the user wrote. Related changes that are included in this commit: * renamed virtual folders used for jit: * ng://<module type>/module.ngfactory.js * ng://<module type>/<comp type>.ngfactory.js * ng://<module type>/<comp type>.html (for inline templates) * error logging: * all errors that happen in templates are logged from the place of the nearest element. * instead of logging error messages and stacks separately, we log the actual error. This is needed so that browsers apply source maps to the stack correctly. * error type and error is logged as one log entry. Note that long-stack-trace zone has a bug that disables source maps for stack traces, see https://github.com/angular/zone.js/issues/661. BREAKING CHANGE: - DebugNode.source no more returns the source location of a node. Closes 14013
This commit is contained in:

committed by
Chuck Jazdzewski

parent
1c1085b140
commit
cdc882bd36
@ -6,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', () => {
|
||||
|
38
packages/compiler/test/output/source_map_util.ts
Normal file
38
packages/compiler/test/output/source_map_util.ts
Normal file
@ -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', () => {
|
||||
|
Reference in New Issue
Block a user