Skip to content

Commit ef1b98f

Browse files
authored
fix: account for sourcemap in meta info (#8778)
We need to use a different method for getting the meta info because `locate` is used to help construct the source map that references the preprocessed Svelte file. If we would now add source maps to that `locate` function it would go the the original source directly which means skipping potentially intermediate source maps which we would need in other situations. Sadly we can't map the character offset because for that we would need to the original source contents which we don't have in this context. fixes #8360 closes #8362
1 parent 5702142 commit ef1b98f

File tree

9 files changed

+96
-5
lines changed

9 files changed

+96
-5
lines changed

.changeset/silly-ladybugs-marry.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: account for preprocessor source maps when calculating meta info

.prettierrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
}
1313
},
1414
{
15-
"files": ["README.md", "packages/*/README.md"],
15+
"files": ["README.md", "packages/*/README.md", "**/package.json"],
1616
"options": {
1717
"useTabs": false,
1818
"tabWidth": 2

packages/svelte/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
"dependencies": {
104104
"@ampproject/remapping": "^2.2.1",
105105
"@jridgewell/sourcemap-codec": "^1.4.15",
106+
"@jridgewell/trace-mapping": "^0.3.18",
106107
"acorn": "^8.8.2",
107108
"aria-query": "^5.2.1",
108109
"axobject-query": "^3.2.1",
@@ -135,4 +136,4 @@
135136
"typescript": "^5.0.4",
136137
"vitest": "^0.31.1"
137138
}
138-
}
139+
}

packages/svelte/src/compiler/compile/Component.js

+31-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { TraceMap, originalPositionFor } from '@jridgewell/trace-mapping';
12
import { walk } from 'estree-walker';
23
import { getLocator } from 'locate-character';
34
import { reserved, is_valid } from '../utils/names.js';
@@ -142,9 +143,20 @@ export default class Component {
142143
/** @type {string} */
143144
file;
144145

145-
/** @type {(c: number) => { line: number; column: number }} */
146+
/**
147+
* Use this for stack traces. It is 1-based and acts on pre-processed sources.
148+
* Use `meta_locate` for metadata on DOM elements.
149+
* @type {(c: number) => { line: number; column: number }}
150+
*/
146151
locate;
147152

153+
/**
154+
* Use this for metadata on DOM elements. It is 1-based and acts on sources that have not been pre-processed.
155+
* Use `locate` for source mappings.
156+
* @type {(c: number) => { line: number; column: number }}
157+
*/
158+
meta_locate;
159+
148160
/** @type {import('./nodes/Element.js').default[]} */
149161
elements = [];
150162

@@ -199,7 +211,25 @@ export default class Component {
199211
.replace(process.cwd(), '')
200212
.replace(regex_leading_directory_separator, '')
201213
: compile_options.filename);
214+
215+
// line numbers in stack trace frames are 1-based. source maps are 0-based
202216
this.locate = getLocator(this.source, { offsetLine: 1 });
217+
/** @type {TraceMap | null | undefined} initialise lazy because only used in dev mode */
218+
let tracer;
219+
this.meta_locate = (c) => {
220+
/** @type {{ line: number, column: number }} */
221+
let location = this.locate(c);
222+
if (tracer === undefined) {
223+
// @ts-expect-error - fix the type of CompileOptions.sourcemap
224+
tracer = compile_options.sourcemap ? new TraceMap(compile_options.sourcemap) : null;
225+
}
226+
if (tracer) {
227+
// originalPositionFor returns 1-based lines like locator
228+
location = originalPositionFor(tracer, location);
229+
}
230+
return location;
231+
};
232+
203233
// styles
204234
this.stylesheet = new Stylesheet({
205235
source,

packages/svelte/src/compiler/compile/render_dom/Renderer.js

+13-1
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,20 @@ export default class Renderer {
6262
/** @type {import('estree').Identifier} */
6363
file_var;
6464

65-
/** @type {(c: number) => { line: number; column: number }} */
65+
/**
66+
* Use this for stack traces. It is 1-based and acts on pre-processed sources.
67+
* Use `meta_locate` for metadata on DOM elements.
68+
* @type {(c: number) => { line: number; column: number }}
69+
*/
6670
locate;
6771

72+
/**
73+
* Use this for metadata on DOM elements. It is 1-based and acts on sources that have not been pre-processed.
74+
* Use `locate` for source mappings.
75+
* @type {(c: number) => { line: number; column: number }}
76+
*/
77+
meta_locate;
78+
6879
/**
6980
* @param {import('../Component.js').default} component
7081
* @param {import('../../interfaces.js').CompileOptions} options
@@ -73,6 +84,7 @@ export default class Renderer {
7384
this.component = component;
7485
this.options = options;
7586
this.locate = component.locate; // TODO messy
87+
this.meta_locate = component.meta_locate; // TODO messy
7688
this.file_var = options.dev && this.component.get_unique_name('file');
7789
component.vars
7890
.filter((v) => !v.hoistable || (v.export_name && !v.module))

packages/svelte/src/compiler/compile/render_dom/wrappers/Element/index.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -585,9 +585,11 @@ export default class ElementWrapper extends Wrapper {
585585
);
586586
}
587587
if (renderer.options.dev) {
588-
const loc = renderer.locate(this.node.start);
588+
const loc = renderer.meta_locate(this.node.start);
589589
block.chunks.hydrate.push(
590590
b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line - 1}, ${loc.column}, ${
591+
// TODO this.node.start isn't correct if there's a source map. But since we don't know how the
592+
// original source file looked, there's not much we can do.
591593
this.node.start
592594
});`
593595
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import MagicString from 'magic-string';
2+
import * as path from 'node:path';
3+
4+
// fake preprocessor by doing transforms on the source
5+
const str = new MagicString(
6+
`<script>
7+
type Foo = 'foo';
8+
let foo = 'foo';
9+
</script>
10+
11+
<h1>{foo}</h1>
12+
`.replace(/\r\n/g, '\n')
13+
);
14+
str.remove(8, 26); // remove line type Foo = ...
15+
str.remove(55, 56); // remove whitespace before <h1>
16+
17+
export default {
18+
compileOptions: {
19+
dev: true,
20+
sourcemap: str.generateMap({ hires: true })
21+
},
22+
23+
test({ assert, target }) {
24+
const h1 = target.querySelector('h1');
25+
26+
assert.deepEqual(h1.__svelte_meta.loc, {
27+
file: path.relative(process.cwd(), path.resolve(__dirname, 'main.svelte')),
28+
line: 5, // line 4 in main.svelte, but that's the preprocessed code, the original code is above in the fake preprocessor
29+
column: 1, // line 0 in main.svelte, but that's the preprocessed code, the original code is above in the fake preprocessor
30+
char: 38 // TODO this is wrong but we can't backtrace it due to limitations, see add_location function usage comment for more info
31+
});
32+
}
33+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
let foo = 'foo';
3+
</script>
4+
5+
<h1>{foo}</h1>

pnpm-lock.yaml

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)