Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.7 KiB
Svelte / SvelteKit support
Svelte is compiler-based and .svelte files mix three languages — an HTML-like
template, one or more <script> / <script lang="ts"> blocks, and <style>. That
means generic structural tooling needs special handling. This skill supports Svelte in
two layers; use whichever the environment allows.
Detection strategy (in priority order)
1. Project's own Svelte tooling — PRIMARY, zero extra setup
Any SvelteKit project already ships the best Svelte analyzers. Use them as enrichment when present (run via the project's package manager; never install them yourself):
| Tool | Catches | Invoke |
|---|---|---|
svelte-check |
type errors, component validation, machine-readable diagnostics | npx svelte-check --output machine |
eslint-plugin-svelte |
100+ rules: store misuse, reactive-statement bugs, SvelteKit violations, a11y | project's eslint run on **/*.svelte |
svelte/compiler warnings |
reactivity (non_reactive_update, state_referenced_locally), a11y (a11y_*), deprecations, unused CSS, perf (perf_*) |
compile()/parse() (needs Node) |
Fold their output into the ledger as findings (detector: "svelte-check" etc.),
classified track: llm unless the tool offers a safe autofix (eslint --fix), in which
case track: ast-grep-equivalent (tool-applied) is fine.
These run only if already in the project (Node + the dev deps). Their absence never blocks a run — fall back to layer 2 / model-driven refactors.
Recipe — svelte-check → ledger (works today, no grammar):
# machine output is one diagnostic per line: TYPE filepath line col code message
npx --no-install svelte-check --output machine 2>/dev/null
For each ERROR/WARNING line, add a finding: detector: "svelte-check",
file/start_line from the diagnostic, apply_track: llm, difficulty: semantic
(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 (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:
- Build the grammar into
grammars/svelte.<ext>(~1s; needs only a C compiler — no tree-sitter CLI, because the grammar ships a prebuiltparser.c):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 - Scan Svelte files with the Svelte config (no edits to the default config needed):
ast-grep scan -c sgconfig.svelte.yml --json=compact <paths> - 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), andrules-svelte/adds template rules (on:event directives,<slot>).
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 with the guardrails below.
Svelte simplification catalog
Svelte 4 → 5 runes migration (mostly mechanical, but verify):
export let prop;→let { prop } = $props();let x = v;that is reactive →let x = $state(v);$: doubled = x * 2;(pure computed) →let doubled = $derived(x * 2);$: { sideEffect(); }(side effect) →$effect(() => { sideEffect(); });createEventDispatcher()+dispatch('e')→ callback props<slot />→{@render children?.()}on:click={h}→onclick={h}(template; seerules-svelte/template.yml)
Reactivity correctness (semantic — judgment required):
$effectused to compute a value → should be$derived(perf + correctness).- Object/array mutated without reassignment in legacy mode → won't react.
- Redundant writable store used purely inside one component → local
$state. derived(store, ...)replaceable by$derived(...).
Mechanical vs semantic:
- Mechanical:
on:→ property,export let→$props(),<slot/>→{@render}. - Semantic:
$derivedvs$effectchoice, store→state, hoisting load functions.
SvelteKit guardrails — DO NOT BREAK
Refactor the insides freely; never violate these conventions:
+page.svelte/+layout.svelte: UI only.load/actions must NOT live here. Keep thechildrensnippet/render in layouts.+page.ts/+page.server.ts/+layout(.server).ts:loadlives here; formactionsonly in+page.server.ts. Don't moveloadinto a component. Don't change universal-vs-server (.server) semantics. A pageload's data is page-scoped; only hoist to a layoutloadif siblings actually need it.+server.ts: HTTP handlers (GET/POST/…) — preserve method semantics; no GET side effects.+error.sveltemust keep acceptingerror;+layout.sveltemust keep acceptingdatafrom itsload.- Named vs default form actions can't coexist on one page; named actions need
action="?/name". Don't rename one without the other. - Never touch generated/
$app/*/$env/*wiring or.svelte-kit/.
After any Svelte change, the verify gate should run the project's svelte-check and
build/test from the ledger config, not just a generic build.