Skip to content

Commit 07a07e3

Browse files
petebacondarwinAndrewKushnir
authored andcommitted
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
1 parent dda3f49 commit 07a07e3

File tree

2 files changed

+58
-2
lines changed

2 files changed

+58
-2
lines changed

packages/compiler-cli/src/ngtsc/sourcemaps/src/source_file_loader.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,16 @@ export class SourceFileLoader {
102102
* whose path is indicated in a comment or implied from the name of the source file itself.
103103
*/
104104
private loadSourceMap(sourcePath: AbsoluteFsPath, contents: string): MapAndPath|null {
105-
const inline = commentRegex.exec(contents);
105+
// Only consider a source-map comment from the last non-empty line of the file, in case there
106+
// are embedded source-map comments elsewhere in the file (as can be the case with bundlers like
107+
// webpack).
108+
const lastLine = this.getLastNonEmptyLine(contents);
109+
const inline = commentRegex.exec(lastLine);
106110
if (inline !== null) {
107111
return {map: fromComment(inline.pop()!).sourcemap, mapPath: null};
108112
}
109113

110-
const external = mapFileCommentRegex.exec(contents);
114+
const external = mapFileCommentRegex.exec(lastLine);
111115
if (external) {
112116
try {
113117
const fileName = external[1] || external[2];
@@ -175,6 +179,20 @@ export class SourceFileLoader {
175179
this.currentPaths.push(path);
176180
}
177181

182+
private getLastNonEmptyLine(contents: string): string {
183+
let trailingWhitespaceIndex = contents.length - 1;
184+
while (trailingWhitespaceIndex > 0 &&
185+
(contents[trailingWhitespaceIndex] === '\n' ||
186+
contents[trailingWhitespaceIndex] === '\r')) {
187+
trailingWhitespaceIndex--;
188+
}
189+
let lastRealLineIndex = contents.lastIndexOf('\n', trailingWhitespaceIndex - 1);
190+
if (lastRealLineIndex === -1) {
191+
lastRealLineIndex = 0;
192+
}
193+
return contents.substr(lastRealLineIndex + 1);
194+
}
195+
178196
/**
179197
* Replace any matched URL schemes with their corresponding path held in the schemeMap.
180198
*

packages/compiler-cli/src/ngtsc/sourcemaps/test/source_file_loader_spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,44 @@ runInEachFileSystem(() => {
6363
expect(sourceFile.rawMap).toEqual(sourceMap);
6464
});
6565

66+
it('should only read source-map comments from the last line of a file', () => {
67+
fs.ensureDir(_('/foo/src'));
68+
const sourceMap = createRawSourceMap({file: 'index.js'});
69+
fs.writeFile(_('/foo/src/external.js.map'), JSON.stringify(sourceMap));
70+
const sourceFile = registry.loadSourceFile(_('/foo/src/index.js'), [
71+
'some content',
72+
'//# sourceMappingURL=bad.js.map',
73+
'some more content',
74+
'//# sourceMappingURL=external.js.map',
75+
].join('\n'));
76+
if (sourceFile === null) {
77+
return fail('Expected source file to be defined');
78+
}
79+
expect(sourceFile.rawMap).toEqual(sourceMap);
80+
});
81+
82+
for (const eolMarker of ['\n', '\r\n']) {
83+
it(`should only read source-map comments from the last non-blank line of a file [EOL marker: ${
84+
eolMarker === '\n' ? '\\n' : '\\r\\n'}]`,
85+
() => {
86+
fs.ensureDir(_('/foo/src'));
87+
const sourceMap = createRawSourceMap({file: 'index.js'});
88+
fs.writeFile(_('/foo/src/external.js.map'), JSON.stringify(sourceMap));
89+
const sourceFile = registry.loadSourceFile(_('/foo/src/index.js'), [
90+
'some content',
91+
'//# sourceMappingURL=bad.js.map',
92+
'some more content',
93+
'//# sourceMappingURL=external.js.map',
94+
'',
95+
'',
96+
].join(eolMarker));
97+
if (sourceFile === null) {
98+
return fail('Expected source file to be defined');
99+
}
100+
expect(sourceFile.rawMap).toEqual(sourceMap);
101+
});
102+
}
103+
66104
it('should handle a missing external source map', () => {
67105
fs.ensureDir(_('/foo/src'));
68106
const sourceFile = registry.loadSourceFile(

0 commit comments

Comments
 (0)