fix(compiler-cli): only read source-map comment from last line (#32912)

Source-maps can be linked to from a source-file by a comment at
the end of the file.

Previously the `SourceFileLoader` would read
the first comment that matched `//# sourceMappingURL=` but
this is not valid since some bundlers may include embedded
source-files that contain such a comment.

Now we only look for this comment in the last non-empty line
in the file.

PR Close #32912
This commit is contained in:
Pete Bacon Darwin 2020-06-18 12:05:06 +01:00 committed by Andrew Kushnir
parent dda3f49952
commit 07a07e34bc
2 changed files with 58 additions and 2 deletions

View File

@ -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.
*

View File

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