Skip to content

Commit

Permalink
fix(@ngtools/webpack): diagnose generated files and resolve sourcemaps
Browse files Browse the repository at this point in the history
Generated files were not diagnosed in AOT which led to errors not being shown properly. Also,
in order to show the proper error source we now resolve the sourcemap of any errors we find.

Fixes #5264
Fixed #4538
  • Loading branch information
hansl committed Mar 24, 2017
1 parent 5c15933 commit 5acf10b
Showing 1 changed file with 47 additions and 5 deletions.
52 changes: 47 additions & 5 deletions packages/@ngtools/webpack/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import * as SourceMap from 'source-map';

const {__NGTOOLS_PRIVATE_API_2} = require('@angular/compiler-cli');
const ContextElementDependency = require('webpack/lib/dependencies/ContextElementDependency');
Expand Down Expand Up @@ -36,6 +37,9 @@ export interface AotPluginOptions {
}


const inlineSourceMapRe = /\/\/# sourceMappingURL=data:application\/json;base64,([\s\S]+)$/;


export class AotPlugin implements Tapable {
private _options: AotPluginOptions;

Expand Down Expand Up @@ -342,16 +346,40 @@ export class AotPlugin implements Tapable {
});
}

private _translateSourceMap(sourceText: string, fileName: string,
{line, character}: {line: number, character: number}) {
const match = sourceText.match(inlineSourceMapRe);

if (!match) {
return {line, character, fileName};
}

// On any error, return line and character.
try {
const sourceMapJson = JSON.parse(Buffer.from(match[1], 'base64').toString());
const consumer = new SourceMap.SourceMapConsumer(sourceMapJson);

const original = consumer.originalPositionFor({ line, column: character });
return {
line: original.line,
character: original.column,
fileName: original.source || fileName
};
} catch (e) {
return {line, character, fileName};
}
}

diagnose(fileName: string) {
if (this._diagnoseFiles[fileName]) {
return;
}
this._diagnoseFiles[fileName] = true;

const sourceFile = this._program.getSourceFile(fileName);
if (!sourceFile) {
return;
}
if (!sourceFile) {
return;
}

const diagnostics: ts.Diagnostic[] = []
.concat(
Expand All @@ -364,9 +392,14 @@ export class AotPlugin implements Tapable {
if (diagnostics.length > 0) {
const message = diagnostics
.map(diagnostic => {
const {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
const position = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);

const sourceText = diagnostic.file.getFullText();
let {line, character, fileName} = this._translateSourceMap(sourceText,
diagnostic.file.fileName, position);

const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
return `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message})`;
return `${fileName} (${line + 1},${character + 1}): ${message}`;
})
.join('\n');
this._compilation.errors.push(message);
Expand Down Expand Up @@ -404,6 +437,15 @@ export class AotPlugin implements Tapable {
});
})
.then(() => {
// Get the ngfactory that were created by the previous step, and add them to the root
// file path (if those files exists).
const newRootFilePath = this._compilerHost.getChangedFilePaths()
.filter(x => x.match(/\.ngfactory\.ts$/));
// Remove files that don't exist anymore, and add new files.
this._rootFilePath = this._rootFilePath
.filter(x => this._compilerHost.fileExists(x))
.concat(newRootFilePath);

// Create a new Program, based on the old one. This will trigger a resolution of all
// transitive modules, which include files that might just have been generated.
// This needs to happen after the code generator has been created for generated files
Expand Down

0 comments on commit 5acf10b

Please sign in to comment.