Skip to content

Commit 95b53ee

Browse files
authored
Improve approximation of coarse sourcemap segments (#5985)
* add test * Improve approximation of coarse sourcemap segments * tweak test
1 parent 5dcce11 commit 95b53ee

File tree

4 files changed

+178
-4
lines changed

4 files changed

+178
-4
lines changed

src/utils/collapseSourcemaps.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,22 @@ class Link {
122122

123123
while (searchStart <= searchEnd) {
124124
const m = (searchStart + searchEnd) >> 1;
125-
const segment = segments[m];
125+
let segment = segments[m];
126126

127127
// If a sourcemap does not have sufficient resolution to contain a
128-
// necessary mapping, e.g. because it only contains line information, we
129-
// use the best approximation we could find
128+
// necessary mapping, e.g. because it only contains line information or
129+
// the column is not precise (e.g. the sourcemap is generated by esbuild, segment[0] may be shorter than the location of the first letter),
130+
// we approximate by finding the closest segment whose segment[0] is less than the given column
131+
if (segment[0] !== column && searchStart === searchEnd) {
132+
let approximatedSegmentIndex = 0;
133+
for (let index = 0; index < segments.length; index++) {
134+
if (segments[index][0] > column) {
135+
break;
136+
}
137+
approximatedSegmentIndex = index;
138+
}
139+
segment = segments[approximatedSegmentIndex];
140+
}
130141
if (segment[0] === column || searchStart === searchEnd) {
131142
if (segment.length == 1) return null;
132143
const source = this.sources[segment[1]];

src/utils/getOriginalLocation.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,18 @@ export function getOriginalLocation(
1515
(segment): segment is [number, number, number, number] => segment.length > 1
1616
);
1717
const lastSegment = filteredLine[filteredLine.length - 1];
18-
for (const segment of filteredLine) {
18+
let previousSegment = filteredLine[0];
19+
for (let segment of filteredLine) {
1920
if (segment[0] >= location.column || segment === lastSegment) {
21+
const notMatched = segment[0] !== location.column;
22+
segment = notMatched ? previousSegment : segment;
2023
location = {
2124
column: segment[3],
2225
line: segment[2] + 1
2326
};
2427
continue traceSourcemap;
2528
}
29+
previousSegment = segment;
2630
}
2731
}
2832
throw new Error("Can't resolve original location of error.");
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
const assert = require('node:assert');
2+
const { encode } = require('@jridgewell/sourcemap-codec');
3+
const terser = require('terser');
4+
const { SourceMapConsumer } = require('source-map');
5+
const getLocation = require('../../getLocation');
6+
7+
const originalCode = `
8+
export function App() {
9+
return <div>{'.'}</div>;
10+
}
11+
`;
12+
13+
module.exports = defineTest({
14+
description: 'get correct combined sourcemap in transforming',
15+
formats: ['es'],
16+
options: {
17+
external: ['react/jsx-runtime'],
18+
plugins: [
19+
{
20+
resolveId(id) {
21+
return id;
22+
},
23+
load() {
24+
return {
25+
code: originalCode
26+
};
27+
}
28+
},
29+
{
30+
async transform(code) {
31+
return {
32+
code: `import { jsx } from "react/jsx-runtime";
33+
export function App() {
34+
return /* @__PURE__ */ jsx("div", { children: "." });
35+
}
36+
`,
37+
map: {
38+
mappings: encode([
39+
[[0, 0, 2, 9]],
40+
[
41+
[0, 0, 1, 7],
42+
[16, 0, 1, 16],
43+
[22, 0, 1, 22]
44+
],
45+
[
46+
// coarse segment
47+
[0, 0, 2, 2],
48+
[9, 0, 2, 9],
49+
[29, 0, 2, 10],
50+
[38, 0, 2, 15],
51+
[53, 0, 2, 19]
52+
],
53+
[[0, 0, 3, 0]],
54+
[]
55+
]),
56+
sourcesContent: [code]
57+
}
58+
};
59+
}
60+
},
61+
{
62+
transform(code) {
63+
return terser.minify(code, {
64+
sourceMap: true
65+
});
66+
}
67+
},
68+
{
69+
async transform(code) {
70+
const generatedLoc = getLocation(code, code.indexOf('return'));
71+
const map = this.getCombinedSourcemap();
72+
const smc = await new SourceMapConsumer(map);
73+
const originalLoc = smc.originalPositionFor(generatedLoc);
74+
const expectedOriginalLoc = getLocation(originalCode, originalCode.indexOf('return'));
75+
assert.equal(originalLoc.line, expectedOriginalLoc.line);
76+
assert.equal(originalLoc.column, expectedOriginalLoc.column);
77+
}
78+
}
79+
]
80+
},
81+
async test() {}
82+
});
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
const { encode } = require('@jridgewell/sourcemap-codec');
2+
const terser = require('terser');
3+
const path = require('node:path');
4+
const ID_MAIN = path.join(__dirname, 'main.js');
5+
6+
const originalCode = `
7+
console.log(
8+
this + 2
9+
)
10+
`;
11+
12+
module.exports = defineTest({
13+
description: 'get correct mapping location with coarse sourcemap',
14+
formats: ['es'],
15+
options: {
16+
plugins: [
17+
{
18+
resolveId() {
19+
return ID_MAIN;
20+
},
21+
load() {
22+
return {
23+
code: originalCode
24+
};
25+
}
26+
},
27+
{
28+
transform(code) {
29+
return {
30+
code,
31+
map: {
32+
mappings: encode([
33+
[],
34+
[
35+
[0, 0, 1, 0],
36+
[8, 0, 1, 8]
37+
],
38+
[
39+
// coarse segment
40+
[0, 0, 2, 2],
41+
[9, 0, 2, 9]
42+
],
43+
[[0, 0, 3, 0]],
44+
[]
45+
]),
46+
sourcesContent: [code]
47+
}
48+
};
49+
}
50+
},
51+
{
52+
transform(code) {
53+
return terser.minify(code, {
54+
sourceMap: true
55+
});
56+
}
57+
}
58+
]
59+
},
60+
warnings: [
61+
{
62+
code: 'THIS_IS_UNDEFINED',
63+
message:
64+
"main.js (3:2): The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten",
65+
url: 'https://rollupjs.org/troubleshooting/#error-this-is-undefined',
66+
id: ID_MAIN,
67+
pos: 12,
68+
loc: {
69+
column: 2,
70+
file: ID_MAIN,
71+
line: 3
72+
},
73+
frame: ' 1:\n' + '2: console.log(\n' + '3: this + 2\n' + ' ^\n' + '4: )'
74+
}
75+
],
76+
async test() {}
77+
});

0 commit comments

Comments
 (0)