feat: support modern Svelte 5 syntax #51

Merged
corneliusio merged 1 commit from feat/modern-svelte-syntax into master 2026-08-24 19:40:18 +00:00
corneliusio commented 2026-08-24 02:08:46 +00:00 (Migrated from github.com)

Closes the remaining Svelte 5 gaps in the grammar. Each item mirrors the compiler's parser rather than the docs prose where the two differ.

  • Comments between attributes. // and /* */ in attribute position (elements and components) are scoped comment.line.double-slash.svelte / comment.block.svelte, exactly where Svelte's read_attribute reads them. Unquoted values (href=//host) and /> are untouched; a stray //> deliberately does not start a comment so it can't swallow the tag end.
  • Member component tags of any depth. <Foo.Bar.Baz>, <foo.bar.baz>, <Foo.$bar.Baz> are components; each ./: is punctuation.accessor.svelte. Name classes follow regex_valid_component_name ($ allowed, member segments may start with digits).
  • Event attributes. Any attribute whose name starts with on and whose value is a single expression (={…}, ="{…}", ='{…}') is entity.other.attribute-name.event.svelte, styled like the on:click directive and wrapped in meta.attribute-with-value.event.html so selectors for HTML events still match. This is the compiler's is_event_attribute contract: names are case sensitive, on={x} / on-click={x} / once={x} count, and string values (onclick="alert(1)", onlabel="{a} b") stay ordinary attributes.
  • Runes. $state, $state.raw|snapshot|eager, $derived(.by), $effect(.pre|tracking|root|pending), $props(.id), $bindable, $inspect(.trace), $host are support.function.rune.svelte in template expressions and in <script> / <script lang="ts">. Script contents now embed two package-owned hidden syntaxes (source.js.script.svelte, source.ts.script.svelte) that extend the stock JS/TS syntaxes; the embed scope stays source.js|ts.embedded.html, so third-party scope selectors, comment toggling and symbol indexing are unaffected. The TS side also gains the CDATA branch the JS side already had.
  • Itemless each blocks. {#each expr} and {#each expr, index}; the source expression stops at a top-level comma.
  • Typed block bindings. {#each rows as row: Row}, as { id }: Row, {:then value: Value}, {:catch { message }: Err} reuse the TS ts-type-annotation context. {:then} / {:catch} now go through the binding context like the inline then / catch forms.
  • Each-block as detection is rewritten: the block's own as is the last top-level as (one not followed by another as outside brackets). That is what the compiler does — acorn-typescript parses greedily and Svelte hands the trailing TSAsExpression back — and it fixes {#each items as item (item.id as string)} while keeping {#each items as Item[] as item} and as { a: { b: string } } as item. Limits (two bracket levels, single line) are documented in the variable's comment.

Tests add ~450 assertions: the above plus class arrays/objects, <svelte:boundary> with failed / pending snippets and onerror, markup await, bind: on elements and components, <script module>, CDATA in JS and TS scripts, and negative cases for the comment and event rules. Suite passes locally on builds 4143 and latest via the syntax-test binary; reviewed by Codex (two rounds) and an independent Claude pass, with every finding either fixed or documented as an engine limit.

Closes the remaining Svelte 5 gaps in the grammar. Each item mirrors the compiler's parser rather than the docs prose where the two differ. - **Comments between attributes.** `//` and `/* */` in attribute position (elements and components) are scoped `comment.line.double-slash.svelte` / `comment.block.svelte`, exactly where Svelte's `read_attribute` reads them. Unquoted values (`href=//host`) and `/>` are untouched; a stray `//>` deliberately does not start a comment so it can't swallow the tag end. - **Member component tags of any depth.** `<Foo.Bar.Baz>`, `<foo.bar.baz>`, `<Foo.$bar.Baz>` are components; each `.`/`:` is `punctuation.accessor.svelte`. Name classes follow `regex_valid_component_name` (`$` allowed, member segments may start with digits). - **Event attributes.** Any attribute whose name starts with `on` and whose value is a single expression (`={…}`, `="{…}"`, `='{…}'`) is `entity.other.attribute-name.event.svelte`, styled like the `on:click` directive and wrapped in `meta.attribute-with-value.event.html` so selectors for HTML events still match. This is the compiler's `is_event_attribute` contract: names are case sensitive, `on={x}` / `on-click={x}` / `once={x}` count, and string values (`onclick="alert(1)"`, `onlabel="{a} b"`) stay ordinary attributes. - **Runes.** `$state`, `$state.raw|snapshot|eager`, `$derived(.by)`, `$effect(.pre|tracking|root|pending)`, `$props(.id)`, `$bindable`, `$inspect(.trace)`, `$host` are `support.function.rune.svelte` in template expressions and in `<script>` / `<script lang="ts">`. Script contents now embed two package-owned hidden syntaxes (`source.js.script.svelte`, `source.ts.script.svelte`) that extend the stock JS/TS syntaxes; the embed scope stays `source.js|ts.embedded.html`, so third-party scope selectors, comment toggling and symbol indexing are unaffected. The TS side also gains the CDATA branch the JS side already had. - **Itemless each blocks.** `{#each expr}` and `{#each expr, index}`; the source expression stops at a top-level comma. - **Typed block bindings.** `{#each rows as row: Row}`, `as { id }: Row`, `{:then value: Value}`, `{:catch { message }: Err}` reuse the TS `ts-type-annotation` context. `{:then}` / `{:catch}` now go through the binding context like the inline `then` / `catch` forms. - **Each-block `as` detection** is rewritten: the block's own `as` is the last top-level `as` (one not followed by another `as` outside brackets). That is what the compiler does — acorn-typescript parses greedily and Svelte hands the trailing `TSAsExpression` back — and it fixes `{#each items as item (item.id as string)}` while keeping `{#each items as Item[] as item}` and `as { a: { b: string } } as item`. Limits (two bracket levels, single line) are documented in the variable's comment. Tests add ~450 assertions: the above plus `class` arrays/objects, `<svelte:boundary>` with `failed` / `pending` snippets and `onerror`, markup `await`, `bind:` on elements and components, `<script module>`, CDATA in JS and TS scripts, and negative cases for the comment and event rules. Suite passes locally on builds 4143 and latest via the syntax-test binary; reviewed by Codex (two rounds) and an independent Claude pass, with every finding either fixed or documented as an engine limit.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
repos/svelte-sublime!51
No description provided.