From 5acf10b7aa59a8cedf2e648dc857db2ee896a3cf Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 23 Mar 2017 16:36:56 -0700 Subject: [PATCH] fix(@ngtools/webpack): diagnose generated files and resolve sourcemaps 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 --- packages/@ngtools/webpack/src/plugin.ts | 52 ++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/packages/@ngtools/webpack/src/plugin.ts b/packages/@ngtools/webpack/src/plugin.ts index 60da107cb4..87e729efe4 100644 --- a/packages/@ngtools/webpack/src/plugin.ts +++ b/packages/@ngtools/webpack/src/plugin.ts @@ -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'); @@ -36,6 +37,9 @@ export interface AotPluginOptions { } +const inlineSourceMapRe = /\/\/# sourceMappingURL=data:application\/json;base64,([\s\S]+)$/; + + export class AotPlugin implements Tapable { private _options: AotPluginOptions; @@ -342,6 +346,30 @@ 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; @@ -349,9 +377,9 @@ export class AotPlugin implements Tapable { this._diagnoseFiles[fileName] = true; const sourceFile = this._program.getSourceFile(fileName); - if (!sourceFile) { - return; - } + if (!sourceFile) { + return; + } const diagnostics: ts.Diagnostic[] = [] .concat( @@ -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); @@ -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