Skip to content

feat: add correctness/stale-prop-derivation — flag prop-derived values frozen at init - #267

Merged
oekazuma merged 13 commits into
mainfrom
feat/stale-prop-derivation
Jul 22, 2026
Merged

oekazuma merged 13 commits into
mainfrom
feat/stale-prop-derivation

Conversation

@oekazuma

@oekazuma oekazuma commented Jul 22, 2026 •

Copy link
Copy Markdown
Owner

Summary

Second of three rules from the Svelte best-practices survey. The official guidance ("Treat props as though they will change") shows this exact do/don't pair:

let { type } = $props();

// don't do this — `color` will not update if `type` changes
let color = type === 'danger' ? 'red' : 'green';

The plain form renders correctly on first mount and silently stops tracking the parent afterwards — a stale-UI bug nothing in the compiler or svelte-check catches. correctness/stale-prop-derivation (warning) flags it.

How it works — conservative by design

All four must hold: the initializer references a $props() prop in an eager position (references inside functions/arrows/getters stay reactive — verified against compiled runes output — and don't count); the initializer is call-free (no calls/new/await, which structurally exempts $state(initial) capture, $derived, and service construction); the binding is never written or escaped (reusing the unmutated-state machinery, incl. bind:); and it is rendered in the template (eager positions again — inline-handler bodies don't count, {#snippet} bodies do), shadow-aware.

Along the way, scopeIntroducedNames learned {#each} indexes, {#snippet} parameters, and {#await} bindings (additive — existing consumers only get more conservative, which fixed two latent shadowing misattributions in unmutated-state/prop-mutation probes), and correctness/unmutated-state's recommendation now points at $derived for prop-computed state instead of steering users into this rule's anti-pattern.

Review process

The design spec itself went through an adversarial review before implementation (it caught that closures/getters compile to reactive call-time reads, corrected two reuse claims against the real code, and added the $bindable coverage). Then 3 tasks via subagent-driven development with per-task reviews, and a final whole-branch review running 39 empirical probes against the built dist over realistic component shapes (uncontrolled inputs, +page.svelte data, context/dispatcher patterns, whole-object props, spread escapes, TS destructures, directive usage, suppression) — zero false positives, READY TO MERGE.

Verification

  • pnpm build / pnpm typecheck / pnpm lint pass (2 pre-existing warnings in meta-object.test.ts only)
  • pnpm test: core 621, cli 694, vite 162, action 15, mcp 21 — all green
  • Rule docs added (en/ja), docs-links gate green; changeset (minor × core/cli/vite/mcp); packages/action/dist rebuilt via full workspace build

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a new correctness/stale-prop-derivation rule to flag prop-derived top-level values that become stale.
    • Updated recommendations to steer prop/state-derived values toward $derived.
  • Documentation
    • Added English and Japanese documentation for correctness/stale-prop-derivation, including detection criteria, fixes, limitations, and configuration.
  • Bug Fixes
    • Improved rule output/remediation so findings can include targeted fix guidance.
  • Tests
    • Added rule parsing and end-to-end coverage for detection, exclusions, registration, and suppression behavior.
  • Chores
    • Minor dependency version bumps.

oekazuma added 9 commits July 22, 2026 12:23
Adds the stalePropDerivations fact to ComponentFacts, plumbed through
parseComponentFacts (and empty for parseModuleFacts/emptyComponentFacts):
a generalized collectPropNames(program, includeBindable) replaces
collectNonBindableProps, scopeIntroducedNames now also tracks each-index,
snippet, and await value/error bindings, and two new shadow-aware walkers
(refsNamesEagerly, collectFragmentRefs) find top-level const/let bindings
that eagerly capture a prop at init and are referenced in the template.

Required updating every other ComponentFacts fixture/pin (core rule tests
and two cli e2e tests) to include the new required field.
Add the rule via componentRule factory with optional fix support. Extends
component-rule.ts to handle fix option (matching kit-module-rule pattern).
Registers in four places: rules/index.ts (import, allRules, re-export) and
core/index.ts re-export. Updates unmutated-state recommendation to mention
$derived for prop/state computations.
@coderabbitai

coderabbitai Bot commented Jul 22, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@oekazuma, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 37 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1f1ae861-0c6f-41c9-8a98-ad0e70e94e68

📥 Commits

Reviewing files that changed from the base of the PR and between c11a8d6 and af03aab.

📒 Files selected for processing (1)
  • docs/src/content/docs/ja/rules/correctness/stale-prop-derivation.md
📝 Walkthrough

Walkthrough

Adds correctness/stale-prop-derivation, which detects rendered top-level values derived from $props() without $derived. It extends component facts and parsing, registers diagnostics with fixes, adds tests, documentation, and package changeset metadata.

Changes

Stale prop derivation

Layer / File(s) Summary
Detection contract and facts shape
docs/superpowers/specs/*, docs/superpowers/plans/*, packages/core/src/component.ts, packages/core/src/component-collect.ts
Defines stalePropDerivations facts and conservative detection criteria for frozen prop-derived bindings.
Parser analysis and boundary tests
packages/core/src/component-parse.ts, packages/core/test/stale-prop-derivation-parse.test.ts
Analyzes eager, call-free prop-derived initializers, handles Svelte scope shadowing and $bindable, filters writes and escapes, and verifies inclusion and exclusion cases.
Rule diagnostics and registry integration
packages/core/src/rules/*, packages/core/src/index.ts, packages/core/test/stale-prop-derivation.test.ts, packages/core/test/*
Adds the rule, fix support, registry exports, related recommendation guidance, and updated ComponentFacts fixtures.
Documentation, release metadata, and compatibility updates
docs/src/content/docs/rules/correctness/*, docs/src/content/docs/ja/rules/correctness/*, .changeset/*, packages/cli/test/*
Documents detection and remediation, records minor package bumps, and updates empty-facts and suppression fixtures.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Component
  participant Parser
  participant Rule
  participant Reporter
  Component->>Parser: Parse props, bindings, and template references
  Parser->>Rule: Provide stalePropDerivations facts
  Rule->>Reporter: Emit line diagnostic and $derived fix
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding the correctness/stale-prop-derivation rule for prop-derived values that freeze at init.
Docstring Coverage ✅ Passed Docstring coverage is 86.67% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
docs/superpowers/specs/2026-07-22-stale-prop-derivation-design.md (1)

68-68: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid hard-coded registration counts in guides. Export layout changes will make “four places” and “exactly 5” stale; describe the required registration categories instead.

  • docs/superpowers/specs/2026-07-22-stale-prop-derivation-design.md#L68-L68: replace the fixed location/hit counts with registration categories.
  • docs/superpowers/plans/2026-07-22-stale-prop-derivation.md#L21-L21: remove the exact grep-count requirement.

As per coding guidelines, “Do not hard-code rule counts or ID ranges in READMEs or guides; refer to rule categories instead.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-07-22-stale-prop-derivation-design.md` at line
68, Replace fixed registration-location and grep-hit counts in
docs/superpowers/specs/2026-07-22-stale-prop-derivation-design.md at lines 68-68
with the required registration categories. In
docs/superpowers/plans/2026-07-22-stale-prop-derivation.md at lines 21-21,
remove the exact grep-count requirement and describe the applicable categories
instead.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/component-parse.ts`:
- Around line 447-455: Update collectFragmentRefs in
packages/core/src/component-parse.ts (lines 447-455) so EachBlock and AwaitBlock
header expressions are traversed with the incoming scope, then apply their
context/index/value/error bindings only while traversing the corresponding
bodies; add the same-name each-header regression in
packages/core/test/stale-prop-derivation-parse.test.ts (lines 40-48), asserting
the outer candidate is reported.

In `@packages/core/src/component.ts`:
- Around line 97-101: Make ComponentFacts.stalePropDerivations optional to
preserve compatibility with downstream object literals, and update internal
collectors consuming this field to default missing values to an empty array
before use.

In `@packages/core/src/rules/correctness/stale-prop-derivation.ts`:
- Around line 17-20: Remove the hard-coded Fix.snippet from the fix metadata in
the stale-prop derivation rule, or generate it using the actual offending
binding and expression for each result. Update componentRule so every penalized
finding receives only a valid, context-specific fix rather than the shared
color/type transformation.

---

Nitpick comments:
In `@docs/superpowers/specs/2026-07-22-stale-prop-derivation-design.md`:
- Line 68: Replace fixed registration-location and grep-hit counts in
docs/superpowers/specs/2026-07-22-stale-prop-derivation-design.md at lines 68-68
with the required registration categories. In
docs/superpowers/plans/2026-07-22-stale-prop-derivation.md at lines 21-21,
remove the exact grep-count requirement and describe the applicable categories
instead.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0354b665-880a-4ac5-8839-7b2ac278d2f8

📥 Commits

Reviewing files that changed from the base of the PR and between 0a041b0 and 8d1663a.

⛔ Files ignored due to path filters (1)
  • packages/action/dist/index.js is excluded by !**/dist/**
📒 Files selected for processing (24)
  • .changeset/stale-prop-derivation.md
  • docs/src/content/docs/ja/rules/correctness/stale-prop-derivation.md
  • docs/src/content/docs/rules/correctness/stale-prop-derivation.md
  • docs/superpowers/plans/2026-07-22-stale-prop-derivation.md
  • docs/superpowers/specs/2026-07-22-stale-prop-derivation-design.md
  • packages/cli/test/malformed-svelte.test.ts
  • packages/cli/test/suppression-e2e.test.ts
  • packages/core/src/component-collect.ts
  • packages/core/src/component-parse.ts
  • packages/core/src/component.ts
  • packages/core/src/index.ts
  • packages/core/src/rules/component-rule.ts
  • packages/core/src/rules/correctness/stale-prop-derivation.ts
  • packages/core/src/rules/correctness/unmutated-state.ts
  • packages/core/src/rules/index.ts
  • packages/core/test/architecture-rules.test.ts
  • packages/core/test/bundle-rules.test.ts
  • packages/core/test/component-collect.test.ts
  • packages/core/test/component-rule.test.ts
  • packages/core/test/correctness-rules.test.ts
  • packages/core/test/security-kit-rules.test.ts
  • packages/core/test/security-rules.test.ts
  • packages/core/test/stale-prop-derivation-parse.test.ts
  • packages/core/test/stale-prop-derivation.test.ts

Comment thread packages/core/src/component-parse.ts
Comment thread packages/core/src/component.ts
Comment thread packages/core/src/rules/correctness/stale-prop-derivation.ts Outdated
oekazuma added 3 commits July 22, 2026 14:44
…tale-prop refs

{#each items as items} and {#await p then p} hid an outer prop/derived
candidate of the same name in the block's header expression, which
actually evaluates before the block's own bindings exist. Walk the
header expression under the incoming (unshadowed) scope, and only the
rest of the block under the block's introduced scope.
The fix snippet hard-coded a color/type example while findings name
arbitrary bindings, risking an agent applying the wrong transformation.
Drop snippet/lang and keep only the description.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant