Skip to content
12 changes: 12 additions & 0 deletions .changeset/correct004-unmutated-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@svelte-vitals/core': minor
'svelte-vitals': minor
'@svelte-vitals/mcp': minor
---

Add **CORRECT004 (unmutated $state)** — a Correctness/reactivity rule from #69.
Flags a `let x = $state(...)` that is never written or escaped anywhere in the
component (no reassignment, member/method mutation, bind, call-arg, or
component-prop pass), so its reactivity is unused — use `const` (or `$state.raw`
if only reassigned wholesale). Reported under `correctness` (info). `ComponentFacts`
gains `constableStates`.
27 changes: 27 additions & 0 deletions docs/src/content/docs/ja/rules/correct004.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: CORRECT004 · 変更されない $state
description: 変更されない $state には const(または $state.raw)を使います。
---

**重大度:** info · **カテゴリ:** correctness

## チェック内容

コンポーネント内のどこでも書き込み・エスケープされない `let x = $state(...)` を検出します — 再代入なし、変更なし(`x.a = …`、`x.push()`)、バインドなし(`bind:value={x}`)、関数やコンポーネントへの受け渡しなしのものです。コンポーネントのスクリプトとテンプレートを静的(CLI)解析します。

## なぜ重要か

変更されない `$state` は、使わないリアクティビティ(deep proxy と依存追跡)のコストを払っています。`const` の方が明確で軽量です。値をまるごと差し替えるだけ(プロパティは変更しない)なら `$state.raw` が適します。

## 修正方法

```svelte
<script>
// let title = $state('Dashboard'); の代わりに
const title = 'Dashboard';

// まるごと差し替えるが deep mutate しないなら $state.raw:
let data = $state.raw(initial);
data = nextValue;
</script>
```
27 changes: 27 additions & 0 deletions docs/src/content/docs/rules/correct004.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: CORRECT004 · Unmutated $state
description: Use const (or $state.raw) for a $state that is never mutated.
---

**Severity:** info · **Category:** correctness

## What it checks

Flags a `let x = $state(...)` whose value is never written or escaped anywhere in the component — not reassigned, not mutated (`x.a = …`, `x.push()`), not bound (`bind:value={x}`), not passed to a function or component. Checked by static (CLI) analysis of the component script and template.

## Why it matters

A `$state` that is never mutated pays for reactivity — deep proxying and dependency tracking — that it never uses. `const` is clearer and cheaper; `$state.raw` fits when you only ever reassign the value wholesale (never mutate its properties).

## How to fix

```svelte
<script>
// Instead of: let title = $state('Dashboard');
const title = 'Dashboard';

// Reassigned wholesale but never deeply mutated? Use $state.raw:
let data = $state.raw(initial);
data = nextValue;
</script>
```
Loading