From 0ea5dd350c3a5bcb2a6c07254a8002dcec93daf5 Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Sat, 25 Jul 2026 09:32:11 -0400 Subject: [PATCH] fix: reconstruct Svelte name_loc from the no-locations wire --- benches/js/CLAUDE.md | 5 +- benches/js/diagnostics/no_locations_parity.ts | 142 +++++++---- .../diagnostics/reconstruct_vs_materialize.ts | 5 +- crates/tsv_svelte/CLAUDE.md | 2 +- crates/tsv_wasm/CLAUDE.md | 9 +- crates/tsv_wasm/README_all.md | 2 +- crates/tsv_wasm/README_parse.md | 2 +- crates/tsv_wasm/npm/locations.d.ts | 10 +- crates/tsv_wasm/npm/locations.js | 228 ++++++++++++++++-- scripts/test_npm.ts | 14 +- 10 files changed, 338 insertions(+), 81 deletions(-) diff --git a/benches/js/CLAUDE.md b/benches/js/CLAUDE.md index 2719b2ff4..03fbb5940 100644 --- a/benches/js/CLAUDE.md +++ b/benches/js/CLAUDE.md @@ -1619,7 +1619,10 @@ CWD-relative). `loc` from `start`/`end` (UTF-16 offsets) + source via the ECMAScript (TS) / LF-only (Svelte) line rules, and assert equality. TS is 100% exact; the two Svelte non-derivable cases (the `'); Three formatters (`format_svelte`, `format_typescript`, `format_css`) take a source `string` and return the formatted `string`. Three parsers (`parse_svelte`, `parse_typescript`, `parse_css`) return a Svelte-compatible JSON AST; the `parse_*_json` variants return the AST as a compact JSON string instead (faster when writing to disk or the wire). For TypeScript and Svelte, `parse_{typescript,svelte}_no_locations` (and `_json_no_locations`) emit a **span-only** wire — `start`/`end` offsets, no per-node `loc` (Svelte also no `name_loc`) — ~46% smaller and faster to materialize, with line/column derivable from offsets + source. All throw on a parse error. -To turn a span-only wire back into a loc-bearing one, `reconstruct_locations(ast, source)` adds `loc` to every node (mutating in place; `structuredClone` first to keep the input) — **exact for TypeScript** (each node's `loc` value equals acorn's; the key is appended last, so an object consumer matches but a re-serialized tree won't byte-match the wire's key order), **approximate for Svelte** (no `name_loc`, and it skips Svelte's `\n\n
{x}
'; + // Covers each `name_loc` shape (tag name, attribute, shorthand padded and + // not, a directive head with modifiers) and each identifier Svelte gives the + // name-shaped `loc` (shorthand expansion, snippet name, block patterns). + const sv = + '\n\n
\n\tt\n\t{@const y = x}\n\t{#each [x] as item}{item}{/each}\n\t{#await x}p{:then value}{value}{:catch err}{err}{/await}\n
\n{#snippet row(a)}{a}{/snippet}'; const full = node_entry.parse_svelte(sv); const recon = node_entry.reconstruct_locations(node_entry.parse_svelte_no_locations(sv), sv); let checked = 0; + let name_locs_checked = 0; const walk = (r: any, f: any): void => { if (Array.isArray(f)) { f.forEach((x, i) => walk(r[i], x)); @@ -242,6 +247,12 @@ describe(`locations helper (index.js): ${pkg_dir}`, { skip: !has_parse }, () => assert.deepEqual(r.loc, f.loc, `loc mismatch at ${f.type}@${f.start}`); checked++; } + // `name_loc` (elements, attributes, directives) is exact — its span is a + // function of the node's own start/end + type. + if (f.name_loc) { + assert.deepEqual(r.name_loc, f.name_loc, `name_loc mismatch at ${f.type}@${f.start}`); + name_locs_checked++; + } for (const k of Object.keys(f)) { if (k === 'loc' || k === 'name_loc') continue; walk(r[k], f[k]); @@ -250,6 +261,7 @@ describe(`locations helper (index.js): ${pkg_dir}`, { skip: !has_parse }, () => }; walk(recon, full); assert.ok(checked > 0, 'expected at least one acorn node to compare'); + assert.ok(name_locs_checked > 0, 'expected at least one name_loc to compare'); // The walk is a superset: it adds `loc` to template nodes Svelte's wire omits. assert.ok(recon.loc, 'reconstruct added loc to the Root (template node)'); });