diff --git a/.changeset/silly-ladybugs-marry.md b/.changeset/silly-ladybugs-marry.md new file mode 100644 index 000000000000..10cf4b0888f0 --- /dev/null +++ b/.changeset/silly-ladybugs-marry.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: account for preprocessor source maps when calculating meta info diff --git a/.prettierrc b/.prettierrc index c187505941b7..0ea7a0b1e9fe 100644 --- a/.prettierrc +++ b/.prettierrc @@ -12,7 +12,7 @@ } }, { - "files": ["README.md", "packages/*/README.md"], + "files": ["README.md", "packages/*/README.md", "**/package.json"], "options": { "useTabs": false, "tabWidth": 2 diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 10df08e9f1af..50af14cbcd65 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -100,6 +100,7 @@ "dependencies": { "@ampproject/remapping": "^2.2.1", "@jridgewell/sourcemap-codec": "^1.4.15", + "@jridgewell/trace-mapping": "^0.3.18", "acorn": "^8.8.2", "aria-query": "^5.2.1", "axobject-query": "^3.2.1", @@ -132,4 +133,4 @@ "typescript": "^5.0.4", "vitest": "^0.31.1" } -} \ No newline at end of file +} diff --git a/packages/svelte/src/compiler/compile/Component.js b/packages/svelte/src/compiler/compile/Component.js index 92c7fcaf46de..03d2c2fad323 100644 --- a/packages/svelte/src/compiler/compile/Component.js +++ b/packages/svelte/src/compiler/compile/Component.js @@ -1,3 +1,4 @@ +import { TraceMap, originalPositionFor } from '@jridgewell/trace-mapping'; import { walk } from 'estree-walker'; import { getLocator } from 'locate-character'; import { reserved, is_valid } from '../utils/names.js'; @@ -142,9 +143,20 @@ export default class Component { /** @type {string} */ file; - /** @type {(c: number) => { line: number; column: number }} */ + /** + * Use this for stack traces. It is 1-based and acts on pre-processed sources. + * Use `meta_locate` for metadata on DOM elements. + * @type {(c: number) => { line: number; column: number }} + */ locate; + /** + * Use this for metadata on DOM elements. It is 1-based and acts on sources that have not been pre-processed. + * Use `locate` for source mappings. + * @type {(c: number) => { line: number; column: number }} + */ + meta_locate; + /** @type {import('./nodes/Element.js').default[]} */ elements = []; @@ -199,7 +211,25 @@ export default class Component { .replace(process.cwd(), '') .replace(regex_leading_directory_separator, '') : compile_options.filename); + + // line numbers in stack trace frames are 1-based. source maps are 0-based this.locate = getLocator(this.source, { offsetLine: 1 }); + /** @type {TraceMap | null | undefined} initialise lazy because only used in dev mode */ + let tracer; + this.meta_locate = (c) => { + /** @type {{ line: number, column: number }} */ + let location = this.locate(c); + if (tracer === undefined) { + // @ts-expect-error - fix the type of CompileOptions.sourcemap + tracer = compile_options.sourcemap ? new TraceMap(compile_options.sourcemap) : null; + } + if (tracer) { + // originalPositionFor returns 1-based lines like locator + location = originalPositionFor(tracer, location); + } + return location; + }; + // styles this.stylesheet = new Stylesheet({ source, diff --git a/packages/svelte/src/compiler/compile/render_dom/Renderer.js b/packages/svelte/src/compiler/compile/render_dom/Renderer.js index 23f9ab5fba0c..0a7cd11ea6a7 100644 --- a/packages/svelte/src/compiler/compile/render_dom/Renderer.js +++ b/packages/svelte/src/compiler/compile/render_dom/Renderer.js @@ -62,9 +62,20 @@ export default class Renderer { /** @type {import('estree').Identifier} */ file_var; - /** @type {(c: number) => { line: number; column: number }} */ + /** + * Use this for stack traces. It is 1-based and acts on pre-processed sources. + * Use `meta_locate` for metadata on DOM elements. + * @type {(c: number) => { line: number; column: number }} + */ locate; + /** + * Use this for metadata on DOM elements. It is 1-based and acts on sources that have not been pre-processed. + * Use `locate` for source mappings. + * @type {(c: number) => { line: number; column: number }} + */ + meta_locate; + /** * @param {import('../Component.js').default} component * @param {import('../../interfaces.js').CompileOptions} options @@ -73,6 +84,7 @@ export default class Renderer { this.component = component; this.options = options; this.locate = component.locate; // TODO messy + this.meta_locate = component.meta_locate; // TODO messy this.file_var = options.dev && this.component.get_unique_name('file'); component.vars .filter((v) => !v.hoistable || (v.export_name && !v.module)) diff --git a/packages/svelte/src/compiler/compile/render_dom/wrappers/Element/index.js b/packages/svelte/src/compiler/compile/render_dom/wrappers/Element/index.js index eca5f09fc5fe..5f8def5f2910 100644 --- a/packages/svelte/src/compiler/compile/render_dom/wrappers/Element/index.js +++ b/packages/svelte/src/compiler/compile/render_dom/wrappers/Element/index.js @@ -585,9 +585,11 @@ export default class ElementWrapper extends Wrapper { ); } if (renderer.options.dev) { - const loc = renderer.locate(this.node.start); + const loc = renderer.meta_locate(this.node.start); block.chunks.hydrate.push( b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line - 1}, ${loc.column}, ${ + // TODO this.node.start isn't correct if there's a source map. But since we don't know how the + // original source file looked, there's not much we can do. this.node.start });` ); diff --git a/packages/svelte/test/runtime/samples/element-source-location-preprocessed/_config.js b/packages/svelte/test/runtime/samples/element-source-location-preprocessed/_config.js new file mode 100644 index 000000000000..d132db01417c --- /dev/null +++ b/packages/svelte/test/runtime/samples/element-source-location-preprocessed/_config.js @@ -0,0 +1,33 @@ +import MagicString from 'magic-string'; +import * as path from 'node:path'; + +// fake preprocessor by doing transforms on the source +const str = new MagicString( + ` + +