diff --git a/packages/compiler-cli/src/ngtsc/sourcemaps/src/source_file_loader.ts b/packages/compiler-cli/src/ngtsc/sourcemaps/src/source_file_loader.ts index f51e903b92..395a1ad62e 100644 --- a/packages/compiler-cli/src/ngtsc/sourcemaps/src/source_file_loader.ts +++ b/packages/compiler-cli/src/ngtsc/sourcemaps/src/source_file_loader.ts @@ -102,12 +102,16 @@ export class SourceFileLoader { * whose path is indicated in a comment or implied from the name of the source file itself. */ private loadSourceMap(sourcePath: AbsoluteFsPath, contents: string): MapAndPath|null { - const inline = commentRegex.exec(contents); + // Only consider a source-map comment from the last non-empty line of the file, in case there + // are embedded source-map comments elsewhere in the file (as can be the case with bundlers like + // webpack). + const lastLine = this.getLastNonEmptyLine(contents); + const inline = commentRegex.exec(lastLine); if (inline !== null) { return {map: fromComment(inline.pop()!).sourcemap, mapPath: null}; } - const external = mapFileCommentRegex.exec(contents); + const external = mapFileCommentRegex.exec(lastLine); if (external) { try { const fileName = external[1] || external[2]; @@ -175,6 +179,20 @@ export class SourceFileLoader { this.currentPaths.push(path); } + private getLastNonEmptyLine(contents: string): string { + let trailingWhitespaceIndex = contents.length - 1; + while (trailingWhitespaceIndex > 0 && + (contents[trailingWhitespaceIndex] === '\n' || + contents[trailingWhitespaceIndex] === '\r')) { + trailingWhitespaceIndex--; + } + let lastRealLineIndex = contents.lastIndexOf('\n', trailingWhitespaceIndex - 1); + if (lastRealLineIndex === -1) { + lastRealLineIndex = 0; + } + return contents.substr(lastRealLineIndex + 1); + } + /** * Replace any matched URL schemes with their corresponding path held in the schemeMap. * diff --git a/packages/compiler-cli/src/ngtsc/sourcemaps/test/source_file_loader_spec.ts b/packages/compiler-cli/src/ngtsc/sourcemaps/test/source_file_loader_spec.ts index 72a9201e32..9686355a5e 100644 --- a/packages/compiler-cli/src/ngtsc/sourcemaps/test/source_file_loader_spec.ts +++ b/packages/compiler-cli/src/ngtsc/sourcemaps/test/source_file_loader_spec.ts @@ -63,6 +63,44 @@ runInEachFileSystem(() => { expect(sourceFile.rawMap).toEqual(sourceMap); }); + it('should only read source-map comments from the last line of a file', () => { + fs.ensureDir(_('/foo/src')); + const sourceMap = createRawSourceMap({file: 'index.js'}); + fs.writeFile(_('/foo/src/external.js.map'), JSON.stringify(sourceMap)); + const sourceFile = registry.loadSourceFile(_('/foo/src/index.js'), [ + 'some content', + '//# sourceMappingURL=bad.js.map', + 'some more content', + '//# sourceMappingURL=external.js.map', + ].join('\n')); + if (sourceFile === null) { + return fail('Expected source file to be defined'); + } + expect(sourceFile.rawMap).toEqual(sourceMap); + }); + + for (const eolMarker of ['\n', '\r\n']) { + it(`should only read source-map comments from the last non-blank line of a file [EOL marker: ${ + eolMarker === '\n' ? '\\n' : '\\r\\n'}]`, + () => { + fs.ensureDir(_('/foo/src')); + const sourceMap = createRawSourceMap({file: 'index.js'}); + fs.writeFile(_('/foo/src/external.js.map'), JSON.stringify(sourceMap)); + const sourceFile = registry.loadSourceFile(_('/foo/src/index.js'), [ + 'some content', + '//# sourceMappingURL=bad.js.map', + 'some more content', + '//# sourceMappingURL=external.js.map', + '', + '', + ].join(eolMarker)); + if (sourceFile === null) { + return fail('Expected source file to be defined'); + } + expect(sourceFile.rawMap).toEqual(sourceMap); + }); + } + it('should handle a missing external source map', () => { fs.ensureDir(_('/foo/src')); const sourceFile = registry.loadSourceFile(