feat(svelte): validated opt-in grammar path + all-platform engine checksums

- Build tree-sitter-svelte in one cc command (prebuilt parser.c, no tree-sitter CLI).
- sgconfig.svelte.yml: registers .svelte + injects JS/TS into <script> blocks so the
  existing TS/JS rule packs apply there; rules-svelte/ adds template rules (on:, slot).
  Proven on fixtures/svelte/Messy.svelte; default sgconfig.yml unaffected.
- Record sha256 for all 5 platform ast-grep binaries so first-run fetch is verifiable
  everywhere (binaries stay gitignored — no repo bloat).
- Retire non-working svelte .example stubs; runes migration stays model-driven.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Giancarmine Salucci
2026-06-23 01:02:11 +02:00
co-authored by Claude Opus 4.8
parent 3f6f642f6b
commit b293afe7cf
11 changed files with 105 additions and 127 deletions
+3 -2
View File
@@ -53,7 +53,8 @@ to "simplify this codebase" (preview) or "simplify and apply".
JS/TS, Python, Go, Rust, Java today (ast-grep supports 20+; add a rule pack to extend).
**Svelte/SvelteKit** is supported specially (no built-in grammar): detection leans on the
project's own Svelte tooling + model-driven refactors with SvelteKit guardrails, with an
opt-in ast-grep grammar path — see `references/SVELTE.md`.
project's own Svelte tooling + model-driven refactors with SvelteKit guardrails, plus an
opt-in ast-grep grammar (one-command build → `sgconfig.svelte.yml`) that injects JS/TS
rules into `<script>` blocks and adds template rules — see `references/SVELTE.md`.
License: MIT.
+3 -2
View File
@@ -110,8 +110,9 @@ these specially (full guide: `references/SVELTE.md`):
- Detect primarily with the project's own Svelte tooling **if present** — run
`svelte-check` (machine output), the project's `eslint` (eslint-plugin-svelte) on
`**/*.svelte`, and/or `svelte/compiler` warnings; fold results into the ledger.
- Optionally, if a tree-sitter-svelte grammar has been built and enabled in
`sgconfig.yml`, the TS/JS packs apply to `<script>` blocks via injection.
- Optionally, if the tree-sitter-svelte grammar is built (one command, see SVELTE.md),
scan `.svelte` with `sgconfig.svelte.yml` TS/JS packs then apply to `<script>` blocks
via injection, plus `rules-svelte/` template rules.
- Otherwise do model-driven refactors (runes migration, store→`$state`, etc.) — and
**obey the SvelteKit guardrails in `references/SVELTE.md`** (never break
`+page`/`+layout`/`load`/form-action conventions). Verify with `svelte-check` too.
+4
View File
@@ -1 +1,5 @@
e08eea52023724e309c7104ca32c41fb9b205d976d3b3e6e681753b2394a3069 linux-x64/ast-grep
036104827852aa70feae65be7e7581b164becb64526c872a312677de5d82d4c9 linux-arm64/ast-grep
bdc4ad8bd72ae05a9c816296ccace501dafb3fb4a98df0a87fe49699b016057d darwin-x64/ast-grep
428f35359b297f7562cfc536bdf3104fdc6bc6c50ab38df70994ec15e992df0b darwin-arm64/ast-grep
584c59eaf3b50bf436ad43cf36193af1d2fe5e29ddb21b7485c39f131691390f win32-x64/ast-grep.exe
+19
View File
@@ -0,0 +1,19 @@
<script lang="ts">
// TS inside a Svelte script block — existing TS rules should fire here via injection.
export let tags: string[];
let active = false;
function hasTag(tag: string): boolean {
return tags.indexOf(tag) !== -1;
}
function ready(flag: boolean): boolean {
return flag ? true : false;
}
</script>
<button on:click={() => (active = !active)}>toggle</button>
{#if active}
<slot />
{/if}
+19 -16
View File
@@ -34,26 +34,29 @@ For each `ERROR`/`WARNING` line, add a finding: `detector: "svelte-check"`,
(or `mechanical` if the code is `eslint --fix`-able), `status: pending`. Same idea for
`eslint -f json **/*.svelte` (use `messages[].ruleId/line`, `fix` present ⇒ mechanical).
### 2. ast-grep structural rules — OPT-IN (requires building a grammar)
ast-grep has no built-in Svelte grammar. To run structural rules on `.svelte`:
### 2. ast-grep structural rules — OPT-IN (one-command grammar build)
ast-grep has no built-in Svelte grammar, so this is opt-in (a compiled grammar can't be
bundled cross-platform). Setup is one command + a separate config — **validated working**:
1. Build `tree-sitter-svelte` into a shared library and put it at
`grammars/svelte.so` (`.dylib` on macOS, `.dll` on Windows):
1. Build the grammar into `grammars/svelte.<ext>` (~1s; needs only a C compiler — no
tree-sitter CLI, because the grammar ships a prebuilt `parser.c`):
```bash
git clone --depth 1 https://github.com/tree-sitter-grammars/tree-sitter-svelte /tmp/tss
cc -shared -fPIC -O2 -I /tmp/tss/src /tmp/tss/src/parser.c /tmp/tss/src/scanner.c \
-o grammars/svelte.so # .dylib on macOS, .dll on Windows
```
# one-time, needs the tree-sitter CLI + a C compiler
git clone https://github.com/tree-sitter-grammars/tree-sitter-svelte
cd tree-sitter-svelte && tree-sitter build --output svelte.so
2. Scan Svelte files with the Svelte config (no edits to the default config needed):
```bash
ast-grep scan -c sgconfig.svelte.yml --json=compact <paths>
```
2. Uncomment the `customLanguages` + `languageInjections` blocks in `sgconfig.yml`.
3. Enable the template rules: `mv rules/svelte/template.yml.example rules/svelte/template.yml`
(shipped disabled because a `language: svelte` rule errors every scan until the grammar
is registered).
4. Result: the existing **typescript/javascript rule packs automatically apply to the
code inside `<script>` blocks** (via injection), and `rules/svelte/` adds
template-level rules (e.g. `on:click` → `onclick`).
3. Result (proven on `fixtures/svelte/Messy.svelte`): the **typescript/javascript rule
packs apply to the code inside `<script>` blocks** via injection (e.g. `indexOf`
`includes`, ternary→`Boolean`), and `rules-svelte/` adds template rules (`on:` event
directives, `<slot>`).
This is opt-in because shipping a compiled grammar per platform conflicts with the
skill's "bundled, no-compile" principle. Most users should rely on layer 1 + layer 3.
`grammars/` is gitignored (like the engine binary), so this stays a one-command local
setup. Most users can rely on layers 1 + 3; turn this on for structural script-block
cleanups at scale.
### 3. Model-driven semantic refactors — always available
For Svelte-specific refactors that no rule expresses, the model edits the relevant span
+22
View File
@@ -0,0 +1,22 @@
# Svelte template-level rules. Loaded ONLY via sgconfig.svelte.yml (needs the grammar).
# The JS/TS inside <script> blocks is handled by the typescript/javascript packs via the
# language injections in sgconfig.svelte.yml — no Svelte rule needed for that.
id: svelte-event-directive-to-property
language: svelte
severity: hint
message: "Svelte 5: prefer the event property (e.g. `onclick`) over the `on:` directive."
rule:
kind: attribute
has:
kind: attribute_name
regex: "^on:"
---
id: svelte-slot-to-render
language: svelte
severity: hint
message: "Svelte 5: replace <slot> with the {@render children()} snippet."
rule:
kind: tag_name
regex: "^slot$"
-3
View File
@@ -27,9 +27,6 @@
"py-magic-number": { "pattern": "magic-number", "track": "llm", "difficulty": "semantic", "weight": 3 },
"svelte-event-directive-to-property": { "pattern": "svelte-event-property", "track": "llm", "difficulty": "mechanical", "weight": 4, "requires": "svelte-grammar" },
"svelte-export-let-to-props": { "pattern": "svelte-props", "track": "llm", "difficulty": "semantic", "weight": 6, "requires": "svelte-grammar" },
"svelte-reactive-to-derived": { "pattern": "svelte-runes-reactivity", "track": "llm", "difficulty": "semantic", "weight": 8, "requires": "svelte-grammar" },
"svelte-create-event-dispatcher":{ "pattern": "svelte-callback-props", "track": "llm", "difficulty": "semantic", "weight": 6, "requires": "svelte-grammar" },
"svelte-slot-to-render": { "pattern": "svelte-snippets", "track": "llm", "difficulty": "semantic", "weight": 5, "requires": "svelte-grammar" },
"svelte-check": { "pattern": "svelte-diagnostic", "track": "llm", "difficulty": "semantic", "weight": 7 },
-53
View File
@@ -1,53 +0,0 @@
# Svelte 4 -> 5 runes migration rules.
#
# REQUIRES the opt-in Svelte grammar (see ../../sgconfig.yml + ../../references/SVELTE.md).
# Shipped as .example so it never breaks default scans. Enable with:
# mv runes.yml.example runes.yml
# These are conservative: prop/event rewrites are mechanical-ish, but reactive-statement
# migrations (`$:`) need judgment (derived vs effect), so they are detection-only (Track B).
id: svelte-export-let-to-props
language: svelte
severity: hint
message: "Svelte 5: replace `export let` with `$props()` destructuring."
metadata: { pattern: svelte-props, track: llm, difficulty: semantic }
rule:
any:
- pattern: "export let $PROP;"
- pattern: "export let $PROP = $DEFAULT;"
note: |
Merge ALL props of a component into a single `let { a, b = 1 } = $props();` — do not
emit one destructure per prop. That cross-statement merge is why this is Track B.
---
id: svelte-reactive-to-derived
language: svelte
severity: hint
message: "Svelte 5: `$:` computed value -> `$derived`; `$:` side effect -> `$effect`."
metadata: { pattern: svelte-runes-reactivity, track: llm, difficulty: semantic }
rule:
any:
- pattern: "$: $X = $EXPR;" # pure assignment -> $derived
- pattern: "$: { $$$BODY }" # block -> usually $effect
note: |
`$: x = expr;` with no side effects -> `let x = $derived(expr);`.
`$: { ... }` performing side effects -> `$effect(() => { ... });`.
Decide by inspecting the body; never blindly convert.
---
id: svelte-create-event-dispatcher
language: svelte
severity: hint
message: "Svelte 5: prefer callback props over createEventDispatcher()."
metadata: { pattern: svelte-callback-props, track: llm, difficulty: semantic }
rule:
pattern: "createEventDispatcher($$$)"
---
id: svelte-slot-to-render
language: svelte
severity: hint
message: "Svelte 5: replace <slot/> with the {@render children()} snippet."
metadata: { pattern: svelte-snippets, track: llm, difficulty: semantic }
rule:
pattern: "<slot />"
-21
View File
@@ -1,21 +0,0 @@
# Svelte — template-level mechanical simplifications.
#
# REQUIRES the opt-in Svelte grammar (see sgconfig.yml + references/SVELTE.md).
# Without it these rules are inert (ast-grep can't parse .svelte). The JS/TS code
# inside <script> blocks is handled by the existing typescript/javascript packs via
# language injection — no Svelte-specific rules needed for that part.
#
# NOTE: `language: svelte` only resolves once tree-sitter-svelte is registered as a
# customLanguage. These patterns target Svelte 5 syntax.
id: svelte-event-directive-to-property
language: svelte
severity: hint
message: "Svelte 5: prefer the `onclick` property over the `on:click` directive."
metadata: { pattern: svelte-event-property, track: llm, difficulty: mechanical }
rule:
any:
- pattern: 'on:click={$H}'
- pattern: 'on:input={$H}'
- pattern: 'on:change={$H}'
- pattern: 'on:submit={$H}'
+28
View File
@@ -0,0 +1,28 @@
# Svelte-enabled ast-grep config — OPT-IN.
#
# Use this instead of sgconfig.yml ONLY when the Svelte grammar is built at
# grammars/svelte.so (.dylib/.dll). Build (one-time, ~1s, needs a C compiler):
# git clone --depth 1 https://github.com/tree-sitter-grammars/tree-sitter-svelte /tmp/tss
# cc -shared -fPIC -O2 -I /tmp/tss/src /tmp/tss/src/parser.c /tmp/tss/src/scanner.c \
# -o grammars/svelte.so
# Then scan Svelte files with: ast-grep scan -c sgconfig.svelte.yml <paths>
#
# This loads the normal rule packs PLUS rules-svelte/, registers .svelte, and injects
# JS/TS into <script> blocks so the existing typescript/javascript rules apply there too.
ruleDirs:
- rules
- rules-svelte
customLanguages:
svelte:
libraryPath: ./grammars/svelte.so
extensions: [svelte]
expandoChar: _
languageInjections:
- hostLanguage: svelte
rule: { pattern: '<script lang="ts">$CONTENT</script>' }
injected: typescript
- hostLanguage: svelte
rule: { pattern: "<script>$CONTENT</script>" }
injected: javascript
+7 -30
View File
@@ -7,34 +7,11 @@
ruleDirs:
- rules
# ---------------------------------------------------------------------------
# OPT-IN: Svelte / SvelteKit structural rules on .svelte files.
# This config covers JS/TS, Python, Go, Rust, Java — the always-on languages. It does
# NOT register Svelte (ast-grep has no built-in Svelte grammar), so .svelte files are
# skipped cleanly here.
#
# ast-grep has no built-in Svelte grammar. To run structural rules on .svelte
# files you must register tree-sitter-svelte as a custom language AND inject the
# JS/TS grammar into <script> blocks. This needs a compiled grammar library
# (.so/.dylib/.dll), so it is NOT bundled — it is opt-in. See references/SVELTE.md
# for the one-time build steps. Once built, uncomment the blocks below AND enable the
# template rules (mv rules/svelte/template.yml.example rules/svelte/template.yml); then
# the existing TypeScript/JavaScript rule packs automatically apply to the code inside
# <script> blocks via injection, and rules/svelte/ adds template-level rules.
#
# customLanguages:
# svelte:
# libraryPath: ./grammars/svelte.so # build per references/SVELTE.md
# extensions: [svelte]
# expandoChar: _ # $ -> _ so $state/$props parse cleanly
#
# languageInjections:
# - hostLanguage: svelte
# rule: { pattern: '<script lang="ts">$CONTENT</script>' }
# injected: typescript
# - hostLanguage: svelte
# rule: { pattern: "<script>$CONTENT</script>" }
# injected: javascript
# ---------------------------------------------------------------------------
#
# Without the grammar, Svelte is still supported: detection falls back to the
# project's own Svelte tooling (svelte-check, eslint-plugin-svelte, svelte
# compiler warnings) as enrichment, and semantic refactors go to the model with
# the SvelteKit guardrails in references/SVELTE.md.
# For Svelte: scan with the separate `sgconfig.svelte.yml` after a one-command grammar
# build (see references/SVELTE.md). That config adds rules-svelte/ + injects JS/TS into
# <script> blocks. Without the grammar, Svelte is still supported via the project's own
# tooling (svelte-check, eslint-plugin-svelte) + model refactors with SvelteKit guardrails.