Since 2.3.3 (fix: preserve parantheses of jsdoc type casts, 3cbb677), the cast-parenthesization can fire on the left side of an AssignmentPattern in a function parameter list, where the parentheses are not valid syntax:
const row_template = ($$anchor, /** @type {any} */ (row) = $.noop) => {
(row) = $.noop is not a valid parameter binding, so the printed module fails to parse. 2.3.2 prints the same input as /** @type {any} */ row = $.noop, which is valid.
Related: #164 reported what looks like the same root regression for variable declarators (let /** @type {…} */ (computed);), fixed via #165/#167 — but the function-parameter case here still reproduces on 2.3.6.
Real-world impact
Svelte 5 resolves esrap ^2.x, so any Svelte 5 app with JSDoc-typed snippet parameters ({#snippet row(/** @type {any} */ row)}) now compiles to syntactically invalid JS with svelte ≥ 5.56.9 + esrap ≥ 2.3.3. vite build fails with parse errors that get attributed to whichever plugin re-parses the output first (in our case rolldown's builtin:vite-dynamic-import-vars, with the misleading message "Expected a semicolon or an implicit semicolon after a statement"). Dev mode and unit tests pass; only production builds break, so it surfaced as a Vercel deploy failure on a green CI.
Reproduction (via svelte 5, which drives esrap with loc-anchored comments)
package.json
{
"type": "module",
"dependencies": { "svelte": "5.56.9" },
"overrides": { "esrap": "2.3.6" }
}
Repro.svelte (minimized from a real component; every remaining line is load-bearing — removing any of them makes the output valid again)
<script>
const handle_delete = async (item) => {
try {
} catch (e) {
}
}
</script>
<div>
<div>
</div>
{#if ok}
<div>
<Form enhance={enhance_create}>
</Form>
</div>
{/if}
</div>
{#snippet row_template(/** @type {any} */ row, )}
{#if x}
<td >
<Btn onclick={() => handle_delete(row)}></Btn>
</td>
{/if}
{/snippet}
repro.mjs
import { compile } from 'svelte/compiler'
import vm from 'node:vm'
import fs from 'node:fs'
const out = compile(fs.readFileSync('Repro.svelte', 'utf8'), {
generate: 'client',
filename: 'Repro.svelte',
experimental: { async: true }
})
console.log(out.js.code.split('\n').slice(9, 12).join('\n'))
try {
new vm.SourceTextModule(out.js.code)
console.log('>>> output is valid JS')
} catch (e) {
console.log('>>> output is INVALID JS:', e.message)
}
Run with node --experimental-vm-modules repro.mjs:
export default function Repro($$anchor) {
const row_template = ($$anchor, /** @type {any} */ (row) = $.noop) => {
var fragment = $.comment();
>>> output is INVALID JS: Invalid destructuring assignment target
Flipping only the esrap override:
| esrap |
result |
| 2.3.2 |
valid |
| 2.3.3 |
invalid |
| 2.3.6 (latest) |
invalid |
Notes
- The comments in question are the user's
/** @type {any} */ annotations on snippet parameters in the .svelte source; svelte re-emits them through esrap's comments option, and the cast-paren preservation from 2.3.3 wraps the re-anchored target even when it is a binding position.
- A secondary observation while minimizing: re-anchored comments can also land on unrelated nodes as nested casts, e.g. a
{@render} call printed as /** @type {any} */ (/** @type {number} */ (row))($$anchor, …) — valid JS, but the comments have migrated from the snippet's parameter list to a call site.
- Happy to test a fix against the original (unminimized) component.
Since 2.3.3 (
fix: preserve parantheses of jsdoc type casts, 3cbb677), the cast-parenthesization can fire on the left side of anAssignmentPatternin a function parameter list, where the parentheses are not valid syntax:(row) = $.noopis not a valid parameter binding, so the printed module fails to parse. 2.3.2 prints the same input as/** @type {any} */ row = $.noop, which is valid.Related: #164 reported what looks like the same root regression for variable declarators (
let /** @type {…} */ (computed);), fixed via #165/#167 — but the function-parameter case here still reproduces on 2.3.6.Real-world impact
Svelte 5 resolves
esrap ^2.x, so any Svelte 5 app with JSDoc-typed snippet parameters ({#snippet row(/** @type {any} */ row)}) now compiles to syntactically invalid JS with svelte ≥ 5.56.9 + esrap ≥ 2.3.3.vite buildfails with parse errors that get attributed to whichever plugin re-parses the output first (in our case rolldown'sbuiltin:vite-dynamic-import-vars, with the misleading message "Expected a semicolon or an implicit semicolon after a statement"). Dev mode and unit tests pass; only production builds break, so it surfaced as a Vercel deploy failure on a green CI.Reproduction (via svelte 5, which drives esrap with loc-anchored comments)
package.json{ "type": "module", "dependencies": { "svelte": "5.56.9" }, "overrides": { "esrap": "2.3.6" } }Repro.svelte(minimized from a real component; every remaining line is load-bearing — removing any of them makes the output valid again)repro.mjsRun with
node --experimental-vm-modules repro.mjs:Flipping only the
esrapoverride:Notes
/** @type {any} */annotations on snippet parameters in the.sveltesource; svelte re-emits them through esrap'scommentsoption, and the cast-paren preservation from 2.3.3 wraps the re-anchored target even when it is a binding position.{@render}call printed as/** @type {any} */ (/** @type {number} */ (row))($$anchor, …)— valid JS, but the comments have migrated from the snippet's parameter list to a call site.