Skip to content

feat(core): add CORRECT006 — flag orphan $effect that throws effect_orphan at runtime - #233

Merged
oekazuma merged 18 commits into
mainfrom
feat/correct006-orphan-effect
Jul 15, 2026
Merged

oekazuma merged 18 commits into
mainfrom
feat/correct006-orphan-effect

Conversation

@oekazuma

@oekazuma oekazuma commented Jul 15, 2026 •

Copy link
Copy Markdown
Owner

Summary

Adds CORRECT006, the first critical correctness rule: it flags $effect / $effect.pre calls that are guaranteed to run outside component initialisation and therefore throw Svelte's runtime effect_orphan error — a compile-clean failure that typically surfaces as a production 500. Verified against svelte 5.56.4: the compiler passes all detected patterns through to runtime, and eslint-plugin-svelte has no equivalent rule.

Detected (conservative, no false positives by construction — the walk never crosses a function boundary):

  • a top-level $effect in a .svelte.ts / .svelte.js runes module or a .svelte <script module> block
  • a module-scope new X() of a same-file class whose constructor creates a bare $effect (not wrapped in $effect.root) — the shared-state-manager trap

This also introduces the first analysis of .svelte.ts / .svelte.js files: they are folded into the existing ComponentFacts pipeline via a <script lang="ts"> wrap parse (zero new dependencies), so the CLI, vite plugin, and MCP all pick the rule up automatically. Module files populate only orphanEffects + suppressions (loc: 0), so ARCH001/PERF009/010 stay silent on them.

Design doc: docs/superpowers/specs/2026-07-15-correct006-orphan-effect-design.md
Plan: docs/superpowers/plans/2026-07-15-correct006-orphan-effect.md

Changes

  • packages/core: OrphanEffectFact + eval-scope walker (walkEvalScope / collectOrphanEffects) in component-parse.ts; .svelte.ts/.svelte.js parse branch (parseModuleFacts); collector globs extended; correct006-orphan-effect.ts rule registered in all four sites
  • Rule docs (en/ja) + CLI-guide suppression range updated to CORRECT001–006; known conservative misses documented (cross-file classes, factories, IIFEs, class field initializers / static blocks / anonymous class expressions)
  • Changeset: minor for @svelte-vitals/core, svelte-vitals, @svelte-vitals/vite, @svelte-vitals/mcp

Test plan

  • 27 new tests (parse facts for both file kinds, collection incl. the </script>-literal fail-safe, rule mapping incl. suppression e2e)
  • Root pnpm build / pnpm typecheck / pnpm test (1,279 tests) / pnpm lint all green
  • Final whole-branch review probed adversarial shapes (class expressions, ternary new, satisfies, getters) — no false positives found

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added CORRECT006, a critical check for orphaned Svelte $effect and $effect.pre calls that can cause runtime failures.
    • Analysis now includes .svelte.ts and .svelte.js runes modules, including relevant module-scope class instantiations.
    • Added support for suppressing CORRECT006 findings inline.
  • Documentation

    • Added English and Japanese guidance for CORRECT006, including detection details and remediation examples.
    • Updated CLI suppression examples to include CORRECT006.

@coderabbitai

coderabbitai Bot commented Jul 15, 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: 15 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: 44335ba1-740a-47d9-be98-eb7d50aaacd8

📥 Commits

Reviewing files that changed from the base of the PR and between e25b764 and 4c3ee00.

⛔ Files ignored due to path filters (1)
  • packages/action/dist/index.js is excluded by !**/dist/**
📒 Files selected for processing (10)
  • docs/src/content/docs/ja/rules/correct006.md
  • docs/src/content/docs/rules/correct006.md
  • docs/superpowers/specs/2026-07-15-correct006-orphan-effect-design.md
  • packages/cli/test/helpers/memory-runtime.ts
  • packages/core/src/component-collect.ts
  • packages/core/src/component-parse.ts
  • packages/core/src/rules/correctness/correct006-orphan-effect.ts
  • packages/core/test/component-collect.test.ts
  • packages/core/test/component-parse.test.ts
  • packages/core/test/correctness-rules.test.ts
📝 Walkthrough

Walkthrough

Adds CORRECT006 orphan $effect analysis for component module scripts and .svelte.ts/.svelte.js files, carries findings through collection and rule registration, and adds tests, documentation, suppression guidance, and package release metadata.

Changes

CORRECT006 orphan effect analysis

Layer / File(s) Summary
Facts and module parsing
packages/core/src/component.ts, packages/core/src/component-parse.ts, docs/superpowers/...
Adds OrphanEffectFact, detects module-evaluated effects and instantiated-class constructor effects, and preserves source-line suppression mapping.
Runes module collection
packages/core/src/component-collect.ts, packages/core/test/component-collect.test.ts, packages/cli/test/malformed-svelte.test.ts
Collects .svelte.ts and .svelte.js files, updates empty-facts shapes, and tests module discovery and parsing edge cases.
CORRECT006 rule integration
packages/core/src/rules/*, packages/core/src/index.ts, packages/core/test/*, packages/cli/test/suppression-e2e.test.ts
Adds the critical rule, registers and exports it, validates diagnostics and suppression, and updates ComponentFacts fixtures.
Documentation and release metadata
docs/src/content/docs/rules/*, docs/src/content/docs/ja/rules/*, docs/src/content/docs/guides/cli.md, .changeset/*
Documents detection, exclusions, remediation, and suppression for CORRECT006 and declares minor package bumps.

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

Sequence Diagram(s)

sequenceDiagram
  participant Collector
  participant parseComponentFacts
  participant ComponentFacts
  participant correct006OrphanEffect
  Collector->>parseComponentFacts: parse .svelte, .svelte.ts, or .svelte.js source
  parseComponentFacts->>ComponentFacts: populate orphanEffects and suppressions
  Collector-->>correct006OrphanEffect: provide ComponentFacts
  correct006OrphanEffect->>correct006OrphanEffect: emit critical findings for unsuppressed facts
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 names the main change and matches the new CORRECT006 orphan $effect rule.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% 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.

🧹 Nitpick comments (1)
packages/core/test/correctness-rules.test.ts (1)

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

Extract the duplicated comp helper to a shared test fixture.

The comp (or ComponentFacts factory) helper is duplicated across 6 different test files. As demonstrated in this PR, adding a single new fact type (orphanEffects) required updating 6 identical object literals.

To reduce maintenance overhead and adhere to the DRY principle, consider extracting this helper into a shared test utility (e.g., packages/core/test/fixtures/component.ts) and importing it where needed.

  • packages/core/test/correctness-rules.test.ts#L32-L32: Extract comp to a shared utility and use it here.
  • packages/core/test/architecture-rules.test.ts#L25-L25: Replace with the imported shared helper.
  • packages/core/test/bundle-rules.test.ts#L28-L28: Replace with the imported shared helper.
  • packages/core/test/component-rule.test.ts#L25-L25: Replace with the imported shared helper.
  • packages/core/test/security-rules.test.ts#L25-L25: Replace with the imported shared helper.
  • packages/cli/test/suppression-e2e.test.ts#L29-L29: Reuse the helper if feasible across package boundaries, or maintain a single CLI-specific copy.
🤖 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 `@packages/core/test/correctness-rules.test.ts` at line 32, Extract the
duplicated comp/ComponentFacts factory into a shared test fixture and replace
the local helpers with imports:
packages/core/test/correctness-rules.test.ts:32-32,
packages/core/test/architecture-rules.test.ts:25-25,
packages/core/test/bundle-rules.test.ts:28-28,
packages/core/test/component-rule.test.ts:25-25, and
packages/core/test/security-rules.test.ts:25-25. Reuse that fixture from
packages/cli/test/suppression-e2e.test.ts:29-29 if cross-package imports are
supported; otherwise retain one CLI-specific copy. Ensure the shared factory
includes orphanEffects and all existing ComponentFacts fields.
🤖 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.

Nitpick comments:
In `@packages/core/test/correctness-rules.test.ts`:
- Line 32: Extract the duplicated comp/ComponentFacts factory into a shared test
fixture and replace the local helpers with imports:
packages/core/test/correctness-rules.test.ts:32-32,
packages/core/test/architecture-rules.test.ts:25-25,
packages/core/test/bundle-rules.test.ts:28-28,
packages/core/test/component-rule.test.ts:25-25, and
packages/core/test/security-rules.test.ts:25-25. Reuse that fixture from
packages/cli/test/suppression-e2e.test.ts:29-29 if cross-package imports are
supported; otherwise retain one CLI-specific copy. Ensure the shared factory
includes orphanEffects and all existing ComponentFacts fields.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e36ea575-3bd5-41d8-a027-4945a1689394

📥 Commits

Reviewing files that changed from the base of the PR and between 90b7538 and e25b764.

⛔ Files ignored due to path filters (1)
  • packages/action/dist/index.js is excluded by !**/dist/**
📒 Files selected for processing (22)
  • .changeset/correct006-orphan-effect.md
  • docs/src/content/docs/guides/cli.md
  • docs/src/content/docs/ja/guides/cli.md
  • docs/src/content/docs/ja/rules/correct006.md
  • docs/src/content/docs/rules/correct006.md
  • docs/superpowers/plans/2026-07-15-correct006-orphan-effect.md
  • docs/superpowers/specs/2026-07-15-correct006-orphan-effect-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/correctness/correct006-orphan-effect.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-parse.test.ts
  • packages/core/test/component-rule.test.ts
  • packages/core/test/correctness-rules.test.ts
  • packages/core/test/security-rules.test.ts

oekazuma added 8 commits July 15, 2026 19:18
…ns and see through TS constructor overloads

Pattern 2 (module-scope new of a same-file effectful class) walked the
whole program looking for ClassDeclaration/ClassExpression nodes and
NewExpression callees anywhere, so a block-scoped class shadowing an
imported name of the same name, or a class expression's own (inner-only)
name, could produce a false-positive critical finding. Restrict both
halves to direct top-level statements (unwrapping export/export default
only) — matches the design spec's own wording for pattern 2.

Also: the constructor lookup picked the first MethodDefinition named
"constructor", which for a TS-overloaded constructor is a bodiless
signature — the class was never marked effectful. Require a body.
…opping analysis

parseModuleFacts wraps a .svelte.ts/.svelte.js source in a <script lang="ts">
tag so the Svelte script parser can produce the ESTree program. A source
containing the literal string "</script>" (e.g. in a string or comment)
terminated that wrapper tag early and threw, silently losing both orphan-
effect detection and inline suppressions for the whole file.

Neutralise any literal `</script` occurrence with a same-length placeholder
before wrapping — string contents don't affect fact extraction, and the
same-length swap preserves every offset/line number. Suppressions keep being
collected from the original (non-neutralised) source.
…RECT006

orphanEffects is typed as required on ComponentFacts, but a facts object
built by an older/external constructor could omit it — applies() would
throw and take the whole runRules Promise.all down with it. Default to
an empty array in both applies and bad.
collectComponentFacts issued three separate rt.glob calls (.svelte,
.svelte.ts, .svelte.js) plus a Set-based dedupe of the concatenated
results. Both production Runtime.glob implementations (cli node runtime,
vite provider) delegate to tinyglobby, which supports picomatch-style
brace patterns — a single 'src/**/*.svelte{,.ts,.js}' call replaces all
three globs (one directory traversal) and the dedupe is no longer needed.

Also teaches the cli test helper's mock glob-to-regex converter to expand
brace groups (including the empty alternative), matching real
tinyglobby/picomatch behaviour, so collect-component-facts.test.ts keeps
passing against the new pattern.
type/start/end/loc/range was hand-listed as an ignored-key check in four
places (walkEstree, walkScoped, bodyReadsReactive's IGNORED_KEYS,
walkEvalScope). Extract one WALK_IGNORED_KEYS constant and reuse it in
all four. Pure refactor, no behavior change.
…ent the conditional-effect caveat

The +layout.svelte snippet imported '$lib/store.svelte.ts', which fails
under a default SvelteKit tsconfig (TS5097) — use the Svelte-docs
convention '$lib/store.svelte.js' instead. Also document that a
conditionally-guarded constructor effect is still flagged, since the
guard can't be evaluated statically (same policy as top-level if
branches); suggest the inline suppression for an intentional guard.
Updated in both en/ja.
@oekazuma

Copy link
Copy Markdown
Owner Author

Post-review hardening (8 commits)

A high-effort adversarial review (multi-agent, every finding independently verified with reproduction) surfaced 8 issues; all are addressed:

Detection correctness

  • False positive fixed (7b6ebc7): pattern-2 (constructor-instantiated) matching now operates on top-level declarations/statements only. Previously a block-scoped class sharing a name with an imported class could flag a legal module-scope new of the import. Named class expressions no longer register under their inner-only name.
  • False negative fixed (7b6ebc7): TS constructor overload signatures (bodiless, listed first) no longer shadow the real constructor — the effectful-class check now requires a constructor body.
  • Silent skip fixed (23c9599): a valid runes module containing a literal "</script>" string used to defeat the wrap parse and silently drop detection and suppressions. The wrap now neutralises </script with a same-length placeholder (offsets/lines preserved), so such modules are analysed normally. The spec's known-limitations entry is updated accordingly.

Robustness / perf / cleanup

  • b58210c: CORRECT006 tolerates ComponentFacts built without orphanEffects (older external constructors) instead of taking down the whole runRules pass.
  • 950ac8c: component collection is back to a single directory traversal via one brace-pattern glob (src/**/*.svelte{,.ts,.js}).
  • e4211d2: the AST-walker ignored-key list is now a single shared constant (was duplicated 4×).

Docs

  • 7536cea: the "How to fix" example imports $lib/store.svelte.js (the .ts extension fails under a default SvelteKit tsconfig, TS5097), and both en/ja pages document that conditionally-guarded effects are still flagged (guards can't be evaluated statically — suppress with svelte-vitals-disable-next-line CORRECT006 when intentional), with a pin test.

New/updated tests: +8 (427 core tests total). Root pnpm build / typecheck / test / lint all green. The fix series itself was re-reviewed: approved with no Critical/Important findings.

🤖 Generated with Claude Code

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