Skip to content

fix(lint): fix useComponentExportOnlyModules false positive with TanStack Router#8669

Merged
ematipico merged 1 commit intobiomejs:nextfrom
tt-a1i:fix/component-export-tanstack-router
Jan 4, 2026
Merged

fix(lint): fix useComponentExportOnlyModules false positive with TanStack Router#8669
ematipico merged 1 commit intobiomejs:nextfrom
tt-a1i:fix/component-export-tanstack-router

Conversation

@tt-a1i
Copy link
Contributor

@tt-a1i tt-a1i commented Jan 4, 2026

Summary

Fixes #8628

Components referenced as object property values in exported expressions are now exempt from the "should be exported" diagnostic.

This handles patterns like TanStack Router where components are passed via configuration objects:

export const Route = createFileRoute('/')({
  component: HomeComponent,  // HomeComponent is referenced here
})

function HomeComponent() { ... }  // no longer reported as "should be exported"

The fix only exempts components referenced in object literals (like { component: X }), not direct function call arguments (like hoge(X)), because the latter might be non-standard HOCs that could break Fast Refresh.

Test plan

  • Added test case for TanStack Router pattern
  • Added test case for shorthand property syntax ({ HomeComponent })
  • Verified existing tests still pass

AI Assistance Disclosure

I used Codex to review the changes, sanity-check the implementation against existing patterns, and help spot potential edge cases.

@changeset-bot
Copy link

changeset-bot bot commented Jan 4, 2026

🦋 Changeset detected

Latest commit: 699c44e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@biomejs/biome Patch
@biomejs/cli-win32-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-darwin-arm64 Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/wasm-web Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/backend-jsonrpc Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions bot added A-Linter Area: linter L-JavaScript Language: JavaScript and super languages labels Jan 4, 2026
…tack Router

Fixes biomejs#8628

Components referenced as object property values in exported expressions
are now exempt from the 'should be exported' diagnostic. This handles
patterns like TanStack Router's createFileRoute where components are
passed via configuration objects.
@tt-a1i tt-a1i force-pushed the fix/component-export-tanstack-router branch from 43e7e81 to 699c44e Compare January 4, 2026 08:18
@codspeed-hq
Copy link

codspeed-hq bot commented Jan 4, 2026

CodSpeed Performance Report

Merging #8669 will not alter performance

Comparing tt-a1i:fix/component-export-tanstack-router (699c44e) with next (a9025d4)1

Summary

✅ 58 untouched
⏩ 95 skipped2

Footnotes

  1. No successful run was found on next (b36ff03) during the generation of this report, so a9025d4 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

  2. 95 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@tt-a1i tt-a1i marked this pull request as ready for review January 4, 2026 08:18
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 4, 2026

Walkthrough

This change resolves false positives in the useComponentExportOnlyModules linter rule when components are referenced as object property values within exported expressions. The rule now recognises both regular and shorthand property patterns (e.g., TanStack Router's createFileRoute pattern), preventing valid local components from being flagged as unexported. The implementation adds detection logic for these specific reference patterns whilst maintaining existing validation for genuinely unexported components.

Suggested reviewers

  • ematipico
  • dyc3

Pre-merge checks and finishing touches

✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: fixing a false positive in the useComponentExportOnlyModules linter rule for TanStack Router patterns.
Description check ✅ Passed The description is directly related to the changeset, providing context for issue #8628, explaining the problem, solution approach, test coverage, and even disclosing AI assistance.
Linked Issues check ✅ Passed The PR fully addresses issue #8628 objectives: it exempts components in object property values from the diagnostic, preserves correctness for other cases, adds test coverage for TanStack Router and shorthand syntax.
Out of Scope Changes check ✅ Passed All changes are narrowly scoped to fixing the useComponentExportOnlyModules rule for the specific TanStack Router pattern; no unrelated modifications detected.
✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs (1)

266-268: LGTM!

The retain logic is correctly placed before diagnostic generation and efficiently filters in-place.

Optional: Consider combining the two sets
-        let referenced_ids: FxHashSet<Box<str>> = exported_non_component_ids
+        let mut referenced_ids: FxHashSet<Box<str>> = exported_non_component_ids
             .iter()
             // ... existing collection logic
             .collect();

-        // Also collect shorthand property references like { HomeComponent }
-        let shorthand_ids: FxHashSet<Box<str>> = exported_non_component_ids
-            .iter()
-            .filter_map(|item| item.exported.as_ref())
-            .flat_map(|exported| {
-                exported
-                    .syntax()
-                    .descendants()
-                    .filter_map(JsShorthandPropertyObjectMember::cast)
-                    .filter_map(|prop| prop.name().ok())
-                    .filter_map(|name| name.value_token().ok())
-                    .map(|token| token.text_trimmed().into())
-            })
-            .collect();
+        // Also collect shorthand property references like { HomeComponent }
+        referenced_ids.extend(
+            exported_non_component_ids
+                .iter()
+                .filter_map(|item| item.exported.as_ref())
+                .flat_map(|exported| {
+                    exported
+                        .syntax()
+                        .descendants()
+                        .filter_map(JsShorthandPropertyObjectMember::cast)
+                        .filter_map(|prop| prop.name().ok())
+                        .filter_map(|name| name.value_token().ok())
+                        .map(|token| token.text_trimmed().into())
+                }),
+        );

         // Remove components that are referenced as object property values
-        local_components
-            .retain(|name, _| !referenced_ids.contains(name) && !shorthand_ids.contains(name));
+        local_components.retain(|name, _| !referenced_ids.contains(name));

This would slightly simplify the retain condition, though the current approach is perfectly fine and arguably more readable with the separate sets.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b36ff03 and 699c44e.

⛔ Files ignored due to path filters (2)
  • crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_referenced_in_export.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_shorthand_in_export.jsx.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (4)
  • .changeset/fix-component-export-tanstack-router.md
  • crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs
  • crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_referenced_in_export.jsx
  • crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_shorthand_in_export.jsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Use inline rustdoc documentation for rules, assists, and their options
Use the dbg!() macro for debugging output in Rust tests and code
Use doc tests (doctest) format with code blocks in rustdoc comments; ensure assertions pass in tests

Files:

  • crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs
🧠 Learnings (18)
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/js_module_info/collector.rs : Implement module-level (thin) inference to resolve `TypeReference::Qualifier` variants by looking up declarations in module scopes and handling import statements

Applied to files:

  • crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs
📚 Learning: 2026-01-02T14:58:16.536Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-01-02T14:58:16.536Z
Learning: Applies to crates/biome_analyze/**/*_analyze/**/src/lint/**/*.rs : Check if a variable is global using the semantic model before reporting diagnostics for rules that ban global functions or variables

Applied to files:

  • crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs
📚 Learning: 2026-01-02T14:58:16.536Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-01-02T14:58:16.536Z
Learning: Applies to crates/biome_analyze/**/*_analyze/**/src/lint/**/*.rs : Use `declare_node_union!` macro to query multiple node types together to avoid redundant traversal passes

Applied to files:

  • crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Use `TypeReference` instead of `Arc` for types that reference other types to avoid stale cache issues when modules are replaced

Applied to files:

  • crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/scoped_resolver.rs : Implement full inference to resolve `TypeReference::Import` variants across the entire module graph

Applied to files:

  • crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/local_inference.rs : Implement local inference in dedicated modules to derive type definitions from expressions without context of surrounding scopes

Applied to files:

  • crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs
📚 Learning: 2026-01-02T14:58:16.536Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-01-02T14:58:16.536Z
Learning: Applies to crates/biome_analyze/**/*_analyze/**/src/lint/**/*.rs : Use helper functions like `map`, `filter`, and `and_then` to avoid excessive nested `if let` statements

Applied to files:

  • crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs
📚 Learning: 2026-01-02T14:58:16.536Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-01-02T14:58:16.536Z
Learning: Applies to crates/biome_analyze/**/*_analyze/**/src/lint/**/*.rs : Use the `Semantic<T>` query type to access semantic information about bindings, references, and scope within a rule

Applied to files:

  • crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Distinguish between `TypeData::Unknown` and `TypeData::UnknownKeyword` to measure inference effectiveness versus explicit user-provided unknown types

Applied to files:

  • crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs
📚 Learning: 2026-01-02T14:58:16.536Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-01-02T14:58:16.536Z
Learning: Applies to crates/biome_analyze/**/*_analyze/**/src/lint/**/*.rs : Rule names should use the `use` prefix when the rule's sole intention is to mandate a single concept

Applied to files:

  • crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs
📚 Learning: 2026-01-02T14:58:16.536Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-01-02T14:58:16.536Z
Learning: Applies to crates/biome_analyze/**/*_analyze/**/src/lint/**/*.rs : Rule names should use the `no` prefix when the rule's sole intention is to forbid a single concept

Applied to files:

  • crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs
📚 Learning: 2026-01-02T14:58:16.536Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-01-02T14:58:16.536Z
Learning: Applies to crates/biome_analyze/**/*_analyze/**/src/lint/**/*.rs : Deprecated rules must include a `deprecated` field in the `declare_lint_rule!` macro with an explanation of what rule to use instead

Applied to files:

  • crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs
📚 Learning: 2026-01-02T14:58:16.536Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-01-02T14:58:16.536Z
Learning: Applies to crates/biome_analyze/biome_rule_options/lib/**/*.rs : Rule options must be defined in the `biome_rule_options` crate with a file named after the rule

Applied to files:

  • crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs
📚 Learning: 2026-01-02T14:58:16.536Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-01-02T14:58:16.536Z
Learning: Applies to crates/biome_analyze/**/*_analyze/**/src/lint/**/*.rs : Use `declare_lint_rule!` macro with a `version` field set to `next` for new rules

Applied to files:

  • crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs
📚 Learning: 2026-01-02T14:58:16.536Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-01-02T14:58:16.536Z
Learning: Applies to crates/biome_analyze/**/*_analyze/**/src/lint/**/*.rs : When porting rules from other linters, use `sources` metadata with `RuleSource::Eslint().same()` for identical behavior or `.inspired()` for different behavior

Applied to files:

  • crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs
📚 Learning: 2026-01-02T14:58:16.536Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-01-02T14:58:16.536Z
Learning: Applies to crates/biome_analyze/**/*_analyze/**/src/lint/**/*.rs : Invalid code snippets in rule documentation must emit exactly one diagnostic

Applied to files:

  • crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs
📚 Learning: 2025-12-22T09:26:56.943Z
Learnt from: ematipico
Repo: biomejs/biome PR: 8537
File: crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs:167-210
Timestamp: 2025-12-22T09:26:56.943Z
Learning: When defining lint rules (declare_lint_rule!), only specify fix_kind if the rule implements an action(...) function. Rules that only emit diagnostics without a code fix should omit fix_kind. This applies to all Rust lint rule definitions under crates/.../src/lint (e.g., crates/biome_js_analyze/src/lint/...).

Applied to files:

  • crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs
📚 Learning: 2026-01-02T14:58:16.536Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-01-02T14:58:16.536Z
Learning: Applies to crates/biome_analyze/**/tests/specs/**/* : Create test files with `invalid` and `valid` prefixes to represent code that should and should not trigger the rule

Applied to files:

  • crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_referenced_in_export.jsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: End-to-end tests
  • GitHub Check: Check Dependencies
  • GitHub Check: Documentation
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: autofix
🔇 Additional comments (6)
.changeset/fix-component-export-tanstack-router.md (1)

1-13: LGTM!

Clear and well-documented changeset. The example nicely illustrates the fixed false positive scenario.

crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_shorthand_in_export.jsx (1)

1-10: LGTM!

Good test coverage for the shorthand property syntax. Based on learnings, the valid_ prefix correctly indicates this should not trigger diagnostics.

crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_referenced_in_export.jsx (1)

1-12: LGTM!

Excellent test coverage for the TanStack Router pattern. This directly addresses the issue #8628 use case. Based on learnings, the valid_ prefix correctly indicates this should not trigger diagnostics.

crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs (3)

7-13: LGTM!

Imports are well-chosen for the new functionality. Using FxHashSet for the lookup sets is a sensible choice.


221-249: Well-documented logic.

The inline comments clearly explain the rationale for exempting object property values whilst excluding direct function call arguments. The parent check correctly identifies identifiers that are values within JsPropertyObjectMember.

One minor consideration: for deeply nested object literals like { outer: { component: X } }, X would still be exempted since its immediate parent is a JsPropertyObjectMember. This seems like reasonable behaviour, but worth confirming it aligns with your intent.


251-264: LGTM!

Clean handling of shorthand property syntax. The separation from regular property collection keeps the logic readable.

Copy link
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

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

Great fix, thank you

@ematipico ematipico merged commit 683f50d into biomejs:next Jan 4, 2026
17 checks passed
@github-actions github-actions bot mentioned this pull request Feb 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Linter Area: linter L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants