Skip to content

feat(core): add architecture/route-component-import - #335

Merged
oekazuma merged 8 commits into
mainfrom
feat/route-component-import
Jul 31, 2026
Merged

oekazuma merged 8 commits into
mainfrom
feat/route-component-import

Conversation

@oekazuma

@oekazuma oekazuma commented Jul 31, 2026 •

Copy link
Copy Markdown
Owner

Why

A SvelteKit route entry — +page.svelte, +layout.svelte, +error.svelte — is written on the assumption that Kit renders it. Kit hands a page its data and params; it hands an error page its page.error and page.status. Imported from somewhere else, the component receives none of that and renders against nothing, or against the importing page's data standing in for its own.

The mistake is easy to make and reads as reasonable: another page needs the same markup, the markup already exists in a +page.svelte, so it gets imported. Nothing in the toolchain objects. The component renders — emptily.

What

architecture/route-component-import, at info. It resolves each import specifier to a project-relative path, and reports one whose basename is a route-entry name and which sits under the routes directory. Those two conditions are separate on purpose: a file named +page.svelte outside src/routes is not a route entry, because Kit gives those names meaning only there.

This is the first Architecture rule that is on by default. The three that shipped before it assert nothing until configured, so a design error there costs a user nothing; here it reaches everyone who upgrades. Two consequences shaped the design.

The route-entry matcher reproduces Kit's own, not a tidier approximation. analyze() in @sveltejs/kit/src/core/sync/create_manifest_data/index.js strips only the component extension before testing /^\+(?:(page(?:@(.*))?)|(layout(?:@(.*))?)|(error))$/, so the @ breakout suffix is unbounded — a layout name may contain dots, and +page@foo.bar.svelte is a real route entry. A narrower [^./]* would silently skip it.

exemptImporters ships deliberately narrow. A string-list option adds to its default and can never shrink it, so the two failure directions are not symmetric:

If the default is The failure is Can a user fix it?
too narrow a false positive yes — append to exemptImporters
too broad a missed true positive no — a string-list cannot be shrunk

Only the narrow side leaves a lever, which decides it. Stories, tests and specs are exempt out of the box; configuring the option is an expected step for a project whose satellite convention is its own, and the rule page says so rather than treating it as an edge case.

An exempt importer earns a pass, not silence — its route-entry imports really are fine, which is a true statement worth recording. Putting the exemption into applies instead would call such a file signal-free, which it is not.

Supporting changes

  • ComponentFacts.importSpans gains type?: true — an import that contributes no runtime value binding. It covers import type … and a declaration whose every specifier is inline-typed; a specifier-less side-effect import stays unmarked, because the module really is loaded. Without it the rule would report import type P from './+page.svelte', which renders nothing.
  • componentRule hands applies/bad the RuleContext, which its sibling kitModuleRule already did. The rule needs ctx.project.kitAliases to resolve a specifier through a project's declared aliases (feat: resolve import specifiers through a project's declared SvelteKit aliases #330); without it the rule would be blind to exactly the imports it was measured to need. Purely additive — all 23 existing callers declare fewer parameters and are unaffected.

Measured reach, recorded rather than assumed

The design was measured against a real monorepo of several SvelteKit apps on a convention-compliant branch. No route entry was imported anywhere, by any file type — the search pattern was validated first against a synthetic file carrying a relative import, an alias import with an @ breakout, and a type-only import, all three of which matched. So the rule reports nothing and misses nothing on that tree. The built-in exempt list also covered only a minority of that tree's satellite files, which is why configuration is documented as expected rather than exceptional.

Not reported

Dynamic import() (not an import declaration); an import made from a plain .ts/.js file, or from a .svelte.ts/.svelte.js runes module (the parser leaves those files' import spans empty); a type-only import; and a project whose routes live outside src/routes. The first is a genuine gap; the second is load-bearing — it is why the exempt list can be three entries long instead of an open-ended guess at every project's test-file convention.

Verification

core 1119, cli 779, vite 205, mcp 25 tests pass; typecheck clean in all four packages; lint and format clean. Six independent mutations are each caught by a test — including the dotted @ suffix, the type-only skip, and moving the exemption into applies — and one test carries a route entry from real source text through the parser into the rule, so the seam between the fact and its consumer is covered rather than assumed.

Design: docs/superpowers/specs/2026-07-30-route-component-import-design.md.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added an architecture rule that flags Svelte components importing SvelteKit route entries.
    • Includes built-in exemptions for stories, tests, and specs, with configurable additional exemptions.
    • Type-only imports and non-route imports are ignored.
  • Documentation

    • Added English and Japanese documentation, configuration guidance, examples, and rule listings.
    • Added release notes for the new rule.
  • Tests

    • Added coverage for route detection, aliases, exemptions, and import classifications.

oekazuma added 6 commits July 31, 2026 09:59
… gaps

- Memoize `routeEntryImports` per component in
  `architecture/route-component-import` so `applies`/`bad` share one
  resolution pass instead of resolving every import specifier twice per
  analysis (measured ~27-30% reduction in an isolated per-rule
  microbenchmark; the whole-project bench is too noisy to show a few-ms
  change).
- Add `configuration.mdx` (en + ja) coverage for this rule: it belongs in
  the "rules that take options" list (`exemptImporters`) and the "Import
  aliases" list, both of which had omitted it.
- Point the rule's test at the package barrel (`../src/index.js`) like its
  siblings, so a missed registration in the fourth site fails a test.
- Add an end-to-end test that parses real Svelte source (a value import
  and a type-only import of a route entry) through `parseComponentFacts`
  into the rule, pinning the Task 1/Task 2 seam.
- Update two "why this is exported" comments (`routeGlobToRegExp`,
  `resolveRepoLocalPath`) to name this rule as a second consumer.
@coderabbitai

coderabbitai Bot commented Jul 31, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c8cd3ec5-2634-44aa-b464-f76a4265c738

📥 Commits

Reviewing files that changed from the base of the PR and between 5fd3c29 and f83ac88.

📒 Files selected for processing (1)
  • packages/core/src/rules/architecture/route-component-import.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/src/rules/architecture/route-component-import.ts

📝 Walkthrough

Walkthrough

The PR adds the default-on architecture/route-component-import rule. It detects runtime imports of SvelteKit route entries, supports aliases and importer exemptions, and adds parser metadata, tests, exports, documentation, and release metadata.

Changes

Route component import rule

Layer / File(s) Summary
Type-only import metadata
packages/core/src/component.ts, packages/core/src/component-parse.ts, packages/core/test/component-parse.test.ts
importSpans now marks fully type-only imports with type: true. Value, mixed, and side-effect imports remain runtime imports.
Route-entry detection and evaluation
packages/core/src/rules/architecture/route-component-import.ts, packages/core/src/rules/component-rule.ts, packages/core/src/config-apply.ts, packages/core/src/kit-module-parse.ts
The rule resolves imports, detects route entries under src/routes/, skips type-only imports, applies built-in and configured exemptions, and reports diagnostics. Component-rule callbacks receive RuleContext.
Rule registration and validation
packages/core/src/index.ts, packages/core/src/rules/index.ts, packages/core/test/route-component-import.test.ts
The rule is exported and registered. Tests cover route filenames, aliases, type-only imports, boundaries, exemptions, memoization, and PASS results.
Documentation and release metadata
docs/src/content/docs/..., docs/superpowers/..., packages/cli/scripts/rules-index.mjs, .changeset/route-component-import.md
English and Japanese documentation, indexes, design records, implementation plans, generated rule cards, and package release metadata describe the rule and exemptImporters option.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SvelteComponent
  participant componentParse
  participant componentRule
  participant routeComponentImport
  participant resolveRepoLocalPath
  SvelteComponent->>componentParse: expose importSpans
  componentRule->>routeComponentImport: pass ComponentFacts and RuleContext
  routeComponentImport->>resolveRepoLocalPath: resolve import source
  resolveRepoLocalPath-->>routeComponentImport: return repository-local path
  routeComponentImport-->>componentRule: report route-entry diagnostics
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 and concisely identifies the main change: adding the architecture/route-component-import rule.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% 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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

🤖 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 `@docs/src/content/docs/rules/index.mdx`:
- Line 27: Replace the hard-coded Architecture rule count in
docs/src/content/docs/rules/index.mdx lines 27-27 with durable non-counted
wording. Apply the equivalent durable Japanese wording in
docs/src/content/docs/ja/rules/index.mdx lines 29-29, removing the fixed count
in both indexes.

In `@docs/superpowers/plans/2026-07-31-route-component-import.md`:
- Around line 576-587: Fix the Markdown fence around the rule-page example in
the surrounding documentation: remove the premature closing fence before “## Not
reported” and place the closing fence after the entire “Not reported” section,
ensuring the example remains inside one properly matched fence.

In `@packages/core/src/rules/architecture/route-component-import.ts`:
- Around line 61-66: Scope cachedRouteEntryImports to the current evaluation
context instead of sharing results process-wide: remove routeEntryImportsCache
or key it with ctx.project.kitAliases (and the relevant context identity)
alongside ComponentFacts. Ensure each check() recomputes or retrieves only
imports resolved for the current aliases, preventing stale targets across
evaluations.
🪄 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 Plus

Run ID: 341bfa9e-70d3-40f0-959d-470f1535c5a0

📥 Commits

Reviewing files that changed from the base of the PR and between 3d4dbc3 and 8d823f6.

📒 Files selected for processing (21)
  • .changeset/route-component-import.md
  • docs/src/content/docs/guides/(setup)/configuration.mdx
  • docs/src/content/docs/ja/guides/(setup)/configuration.mdx
  • docs/src/content/docs/ja/rules/architecture/index.mdx
  • docs/src/content/docs/ja/rules/architecture/route-component-import.md
  • docs/src/content/docs/ja/rules/index.mdx
  • docs/src/content/docs/rules/architecture/index.mdx
  • docs/src/content/docs/rules/architecture/route-component-import.md
  • docs/src/content/docs/rules/index.mdx
  • docs/superpowers/plans/2026-07-31-route-component-import.md
  • docs/superpowers/specs/2026-07-30-route-component-import-design.md
  • packages/core/src/component-parse.ts
  • packages/core/src/component.ts
  • packages/core/src/config-apply.ts
  • packages/core/src/index.ts
  • packages/core/src/kit-module-parse.ts
  • packages/core/src/rules/architecture/route-component-import.ts
  • packages/core/src/rules/component-rule.ts
  • packages/core/src/rules/index.ts
  • packages/core/test/component-parse.test.ts
  • packages/core/test/route-component-import.test.ts

Comment thread docs/src/content/docs/rules/index.mdx Outdated
Comment thread docs/superpowers/plans/2026-07-31-route-component-import.md Outdated
Comment thread packages/core/src/rules/architecture/route-component-import.ts
The per-component cache in architecture/route-component-import was keyed only
on the ComponentFacts WeakMap identity, but the memoized computation also
depends on ctx.project.kitAliases. A caller reusing the same ComponentFacts
across two check() calls with different alias configuration got back the
first call's resolved targets. Store the aliases reference alongside the
cached result and recompute when it differs (reference equality is correct:
a fresh analysis always rebuilds the alias array). Added a regression test
that reuses one ComponentFacts object across two check() calls with
different kitAliases and confirmed it fails without the fix.

Also, while reviewing the same PR:

- Remove the generated (N rules) / (N 件のルール) suffixes from the rule
  index cards. They live inside gen-rules-index.mjs's generated block (not
  hand-written prose as initially assumed), so the fix is in the generator
  (packages/cli/scripts/rules-index.mjs) rather than the .mdx files
  directly; regenerated both locales' index pages and re-ran the formatter.
- Fix an unmatched Markdown fence in
  docs/superpowers/plans/2026-07-31-route-component-import.md: the outer
  fence around the Step 2 rule-page example closed before its "Not
  reported" section instead of after, and a stray unlabeled fence followed.
  Closed the outer fence in the right place and normalized one other
  asymmetric fence pair found while checking the rest of the file.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a new default-on Architecture rule to @svelte-vitals/core that detects Svelte components importing SvelteKit route entry components (+page.svelte, +layout.svelte, +error.svelte, including @ breakout forms) from within src/routes/, which can render without the data SvelteKit normally provides.

Changes:

  • Add architecture/route-component-import (severity: info), including alias-aware specifier resolution and configurable importer exemptions.
  • Extend component import facts to mark type-only imports (import type … and all-inline-typed specifiers) so the rule ignores imports with no runtime binding.
  • Add tests, documentation (en/ja), rules index regeneration changes, and a changeset for release.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/core/test/route-component-import.test.ts New unit tests covering route-entry detection, alias resolution, caching, type-only skips, and exemptions.
packages/core/test/component-parse.test.ts Adds coverage ensuring importSpans marks type-only imports.
packages/core/src/rules/index.ts Registers and re-exports the new Architecture rule in the core rule registry.
packages/core/src/rules/component-rule.ts Extends componentRule callbacks to receive RuleContext for alias-aware rule logic.
packages/core/src/rules/architecture/route-component-import.ts Implements the new architecture/route-component-import rule.
packages/core/src/kit-module-parse.ts Updates doc comment to reflect new internal consumer of resolveRepoLocalPath.
packages/core/src/index.ts Adds the new rule to the public re-export list.
packages/core/src/config-apply.ts Updates doc comment to reflect route-glob compilation reuse by the new rule.
packages/core/src/component.ts Extends ComponentFacts.importSpans to include an optional type?: true marker.
packages/core/src/component-parse.ts Implements detection of type-only import declarations when collecting importSpans.
packages/cli/scripts/rules-index.mjs Removes rule-count text from generated rules index pages (avoids hard-coded counts).
docs/superpowers/specs/2026-07-30-route-component-import-design.md Updates design doc status/blocker notes and documents additional “not reported” cases.
docs/superpowers/plans/2026-07-31-route-component-import.md Adds an implementation plan for the rule and supporting changes.
docs/src/content/docs/rules/index.mdx Updates generated rules listing to include the new rule and remove hard-coded rule counts.
docs/src/content/docs/rules/architecture/route-component-import.md Adds English documentation page for the new rule.
docs/src/content/docs/rules/architecture/index.mdx Updates Architecture rules index to include the new rule.
docs/src/content/docs/ja/rules/index.mdx Updates Japanese rules listing to include the new rule and remove hard-coded rule counts.
docs/src/content/docs/ja/rules/architecture/route-component-import.md Adds Japanese documentation page for the new rule.
docs/src/content/docs/ja/rules/architecture/index.mdx Updates Japanese Architecture rules index to include the new rule.
docs/src/content/docs/ja/guides/(setup)/configuration.mdx Documents the new rule option and adds it to the alias-following rules list (ja).
docs/src/content/docs/guides/(setup)/configuration.mdx Documents the new rule option and adds it to the alias-following rules list (en).
.changeset/route-component-import.md Declares a minor release for packages impacted by adding a default-on rule.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/core/src/rules/architecture/route-component-import.ts Outdated
@oekazuma
oekazuma merged commit 67f5035 into main Jul 31, 2026
7 checks passed
@oekazuma
oekazuma deleted the feat/route-component-import branch July 31, 2026 03:23
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.

2 participants