Describe the bug
When a $bindable() prop is destructured with a renamed key (source: renamed), svelte2tsx emits a "mark prop as used" reference using the source key rather than the renamed identifier in scope. The destructure binds to the renamed name, so the emitted reference points at an identifier that doesn't exist.
This is the workaround introduced for #2268 — emit ;propName; so the prop counts as used. The issue is that the workaround uses the wrong name when there's a rename.
Severity scales with the source key:
| Key |
Emitted reference |
tsc/tsgo |
Non-reserved (foo, x) |
;foo; |
TS2304 "Cannot find name" — normal semantic error, isolated |
Reserved word (class, default, for) |
;class; |
TS1005 syntax error — tsc -p/tsgo -p then silently drops semantic diagnostics across the whole program |
svelte-check --tsgo and svelte-check --incremental route through the overlay tsconfig + tsc -p, so a single component using class: name = $bindable() can hide every TS2339-class error in the project. Default svelte-check uses a per-file diagnostic path and isn't affected.
Reproduction
import { svelte2tsx } from 'svelte2tsx';
import * as compiler from 'svelte/compiler';
const cases = [
['rename + $bindable() (reserved key)', '<script lang="ts">let { class: className = $bindable() }: { class?: string } = $props();</script>'],
['rename + $bindable() (non-reserved key)', '<script lang="ts">let { x: y = $bindable() }: { x?: string } = $props();</script>'],
['rename only (no $bindable)', '<script lang="ts">let { class: className }: { class?: string } = $props();</script>'],
['$bindable() only (no rename)', '<script lang="ts">let { foo = $bindable() }: { foo?: string } = $props();</script>'],
];
for (const [name, src] of cases) {
const out = svelte2tsx(src, { parse: compiler.parse, version: compiler.VERSION, filename: 'x.svelte', isTsFile: true, mode: 'ts' });
console.log('---', name, '---\n', out.code);
}
The reserved-key case emits ;class; and the non-reserved case emits ;x;. Running tsc --noEmit confirms TS1005 and TS2304 respectively. Removing $bindable() removes the use-emit entirely; removing the rename makes the use-emit reference the same name as the binding (correct).
Expected behaviour
The use-reference should match the in-scope identifier — i.e. the renamed name when present, otherwise the key:
// for `class: className = $bindable()`
let { class: className = $bindable() }: $$ComponentProps = $props();
className; // correct, in scope, no syntax issue
System Info
- OS: macOS 14 (arm64)
- Editor: Neovim (irrelevant — the bug is in svelte2tsx codegen, surfaces in any tsserver/tsc/tsgo)
- `svelte`: 5.55.2
- `svelte2tsx`: 0.7.53
- `svelte-check`: 4.4.8
- `typescript`: 6.0.2
- `@typescript/native-preview`: 7.0.0-dev (latest)
Which package is the issue about?
svelte2tsx (root cause); svelte-check (amplifies via diagnostic suppression in --tsgo/--incremental)
Additional Information
Probably a one-line fix in the $bindable()-rewriting path — wherever the trailing ;${key}; is appended, use the renamed identifier when the destructure has key: name form, falling back to key when there's no rename.
Related: #2268 (introduced the ;propName; workaround).
Describe the bug
When a
$bindable()prop is destructured with a renamed key (source: renamed), svelte2tsx emits a "mark prop as used" reference using the source key rather than the renamed identifier in scope. The destructure binds to the renamed name, so the emitted reference points at an identifier that doesn't exist.This is the workaround introduced for #2268 — emit
;propName;so the prop counts as used. The issue is that the workaround uses the wrong name when there's a rename.Severity scales with the source key:
foo,x);foo;class,default,for);class;tsc -p/tsgo -pthen silently drops semantic diagnostics across the whole programsvelte-check --tsgoandsvelte-check --incrementalroute through the overlay tsconfig +tsc -p, so a single component usingclass: name = $bindable()can hide every TS2339-class error in the project. Defaultsvelte-checkuses a per-file diagnostic path and isn't affected.Reproduction
The reserved-key case emits
;class;and the non-reserved case emits;x;. Runningtsc --noEmitconfirms TS1005 and TS2304 respectively. Removing$bindable()removes the use-emit entirely; removing the rename makes the use-emit reference the same name as the binding (correct).Expected behaviour
The use-reference should match the in-scope identifier — i.e. the renamed name when present, otherwise the key:
System Info
Which package is the issue about?
svelte2tsx (root cause); svelte-check (amplifies via diagnostic suppression in
--tsgo/--incremental)Additional Information
Probably a one-line fix in the
$bindable()-rewriting path — wherever the trailing;${key};is appended, use the renamed identifier when the destructure haskey: nameform, falling back tokeywhen there's no rename.Related: #2268 (introduced the
;propName;workaround).