Repository navigation
feat(core): CORRECT004 — unmutated $state → const - #78
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… too Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…call-arg, dynamic-component prop (review)
|
Warning Review limit reached
Next review available in: 37 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds a new ChangesCORRECT004 rule implementation
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant CLI as parseComponentFacts
participant Walker as AST Walkers
participant Facts as ComponentFacts
participant Rule as correct004UnmutatedState
CLI->>Walker: collect $state declarations (stateDecls)
CLI->>Walker: walk instance script + template for writes/escapes
Walker-->>CLI: writtenOrEscaped set
CLI->>CLI: filter stateDecls not in writtenOrEscaped
CLI->>Facts: attach constableStates
Facts->>Rule: provide constableStates to RuleContext
Rule->>Rule: applies() checks constableStates.length > 0
Rule-->>Facts: emit finding per constable state
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Adds a new correctness rule CORRECT004 to identify $state declarations that are never mutated or escaped, so the tool can recommend const (or $state.raw for wholesale reassignment) and avoid unnecessary reactivity overhead. This fits the project’s “high-precision, deterministic Svelte code-health scanner” direction from #69 by expanding the Correctness/reactivity rule set.
Changes:
- Introduces
ComponentFacts.constableStatesand populates it in the CLI parser by analyzing both instance script and template ASTs. - Adds the
correct004UnmutatedStaterule to@svelte-vitals/core, registers/exports it, and adds rule-level tests. - Adds CORRECT004 docs (en/ja), design/spec docs, and a changeset for minor releases.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/core/test/security-rules.test.ts | Updates test helper ComponentFacts shape with constableStates. |
| packages/core/test/correctness-rules.test.ts | Adds CORRECT004 tests and updates helper ComponentFacts shape. |
| packages/core/test/bundle-rules.test.ts | Updates test helper ComponentFacts shape with constableStates. |
| packages/core/test/architecture-rules.test.ts | Updates test helper ComponentFacts shape with constableStates. |
| packages/core/src/rules/index.ts | Registers and re-exports correct004UnmutatedState in allRules. |
| packages/core/src/rules/correctness/correct004-unmutated-state.ts | Implements CORRECT004 as a componentRule based on constableStates. |
| packages/core/src/index.ts | Re-exports correct004UnmutatedState from the core package entrypoint. |
| packages/core/src/component.ts | Extends ComponentFacts with constableStates. |
| packages/cli/test/parse-component-facts.test.ts | Adds parser coverage for constable $state detection and suppression cases. |
| packages/cli/src/providers/source/parse.ts | Implements collection of $state writes/escapes across script + template to compute constableStates. |
| packages/cli/src/providers/source/components.ts | Ensures fallback ComponentFacts includes constableStates. |
| docs/superpowers/specs/2026-07-02-correct004-unmutated-state-design.md | Documents CORRECT004 design and precision rules. |
| docs/superpowers/plans/2026-07-02-correct004-unmutated-state.md | Documents implementation plan and validation steps for CORRECT004. |
| docs/src/content/docs/rules/correct004.md | Adds English user-facing rule reference for CORRECT004. |
| docs/src/content/docs/ja/rules/correct004.md | Adds Japanese user-facing rule reference for CORRECT004. |
| .changeset/correct004-unmutated-state.md | Adds changeset for minor bumps across core/app/mcp. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…-args (PR #78 review) UpdateExpression now resolves its argument via rootObjectName so `s.n++` / `s[i]++` mark the state written (not just bare `s++`); spread call arguments `f(...s)` / `f(...s.items)` are unwrapped before rootObjectName. Both were missed write/escape paths → false-positive CORRECT004 flags. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Another Correctness/reactivity rule from #69. Adds CORRECT004, flagging a
let x = $state(...)that is never written or escaped anywhere in the component — its reactivity is unused, soconst(or$state.rawif only reassigned wholesale) is clearer and cheaper.infoseverity.Distinct from CORRECT002/003 (which are about
$effectmisuse); this is about$statethat never changes.What's flagged
let title = $state('Dashboard'); // ✗ flag ({title} read only) → const let cfg = $state({ a: 1 }); // ✗ flag ({cfg.a} read only) let n = $state(0); n++; // ok (written) <button onclick={() => n++}> // ok (handler mutation) <input bind:value={name}> // ok (bind) <Child d={data} /> // ok (component prop escape)How it works (precision-first — the cardinal sin is a false positive)
A new
ComponentFacts.constableStatesfield, computed by the CLI parser. A$stateis suppressed (not flagged) if anywhere it is:++/--, member/element assigned (x.a=), destructuring-assigned (({x} = o)),delete x.p;x.push()), or passed to any call (f(x),f(x.a));bind:value={x}) or passed as a component prop (<Child d={x}>,<svelte:component d={x}>).Writes are detected over both the instance script AND the template AST — inline handlers like
onclick={() => x++}live in the template, and missing them would be a false positive. Reads (interpolation,x.areads, DOM attributes, component slot children) do not suppress.The
componentRule-built rule flags each constable state; it no-ops in rendered mode.stateNames/assignsOnlyState/reactiveNames(CORRECT002/003) are untouched.Docs & release
@svelte-vitals/core+svelte-vitals+@svelte-vitals/mcpminor (CLI/static rule; not@svelte-vitals/vite).Validation
pnpm -r test(565: core 253 / vite 76 / cli 227 / mcp 9),pnpm -r typecheck,pnpm lint,pnpm --filter docs build(113 pages incl. the 2 new) — all green.Process
spec → plan → subagent-driven (3 impl tasks, each spec + quality reviewed) + a whole-branch review on Opus. During planning I caught that inline-handler mutations live in the template AST (fixed the spec to walk both). The Opus review then found four more false-positive vectors (destructuring-assign — which produced build-breaking
constadvice —delete, member call-args, and dynamic-component props); all were fixed with regression tests before this PR. Every write/escape addition is strictly additive, so it can only ever produce a benign false negative, never a false positive.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
$statevalues that are never mutated or passed around, helping suggest simplerconstusage.Bug Fixes
Documentation