From 699c44ef6cb304a8ba8734a8a459746e68579e26 Mon Sep 17 00:00:00 2001 From: Tu Shaokun <2801884530@qq.com> Date: Sun, 4 Jan 2026 16:04:04 +0800 Subject: [PATCH] fix(lint): fix useComponentExportOnlyModules false positive with TanStack Router 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's createFileRoute where components are passed via configuration objects. --- .../fix-component-export-tanstack-router.md | 13 +++++ .../use_component_export_only_modules.rs | 58 ++++++++++++++++++- .../valid_component_referenced_in_export.jsx | 12 ++++ ...id_component_referenced_in_export.jsx.snap | 20 +++++++ .../valid_component_shorthand_in_export.jsx | 10 ++++ ...lid_component_shorthand_in_export.jsx.snap | 18 ++++++ 6 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 .changeset/fix-component-export-tanstack-router.md create mode 100644 crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_referenced_in_export.jsx create mode 100644 crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_referenced_in_export.jsx.snap create mode 100644 crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_shorthand_in_export.jsx create mode 100644 crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_shorthand_in_export.jsx.snap diff --git a/.changeset/fix-component-export-tanstack-router.md b/.changeset/fix-component-export-tanstack-router.md new file mode 100644 index 000000000000..e9ae0c58074e --- /dev/null +++ b/.changeset/fix-component-export-tanstack-router.md @@ -0,0 +1,13 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#8628](https://github.com/biomejs/biome/issues/8628): [`useComponentExportOnlyModules`](https://biomejs.dev/linter/rules/use-component-export-only-modules/) now allows components referenced as object property values in exported expressions. This fixes false positives for TanStack Router patterns. + +```jsx +export const Route = createFileRoute('/')({ + component: HomeComponent, +}) + +function HomeComponent() { ... } // no longer reported as "should be exported" +``` diff --git a/crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs b/crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs index 0093ce1dec4f..cff55e5a8ac1 100644 --- a/crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs +++ b/crates/biome_js_analyze/src/lint/style/use_component_export_only_modules.rs @@ -4,10 +4,13 @@ use biome_analyze::{ }; use biome_console::markup; use biome_diagnostics::Severity; -use biome_js_syntax::{AnyJsModuleItem, AnyJsStatement, JsModule, export_ext::AnyJsExported}; -use biome_rowan::{AstNode, TextRange}; +use biome_js_syntax::{ + AnyJsModuleItem, AnyJsStatement, JsIdentifierExpression, JsModule, JsPropertyObjectMember, + JsShorthandPropertyObjectMember, export_ext::AnyJsExported, +}; +use biome_rowan::{AstNode, SyntaxNodeCast, TextRange}; use biome_rule_options::use_component_export_only_modules::UseComponentExportOnlyModulesOptions; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; declare_lint_rule! { /// Enforce declaring components only within modules that export React Components exclusively. @@ -215,6 +218,55 @@ impl Rule for UseComponentExportOnlyModules { } } + // Collect identifiers referenced as object property values in exported expressions. + // If a local component is referenced as an object property value, + // it should not be reported as unexported. + // This handles patterns like TanStack Router: + // export const Route = createFileRoute('/')({ component: HomeComponent }) + // function HomeComponent() { ... } + // + // We only exempt components referenced in object literals (like { component: X }) + // and not direct function call arguments (like hoge(X)), because the latter + // might be non-standard HOCs that could break Fast Refresh. + let referenced_ids: FxHashSet> = exported_non_component_ids + .iter() + .filter_map(|item| item.exported.as_ref()) + .flat_map(|exported| { + exported + .syntax() + .descendants() + .filter_map(JsIdentifierExpression::cast) + .filter(|id| { + // Only include identifiers that are object property values + id.syntax() + .parent() + .is_some_and(|parent| parent.cast::().is_some()) + }) + .filter_map(|id| id.name().ok()) + .filter_map(|name| name.value_token().ok()) + .map(|token| token.text_trimmed().into()) + }) + .collect(); + + // Also collect shorthand property references like { HomeComponent } + let shorthand_ids: FxHashSet> = 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(); + + // Remove components that are referenced as object property values + local_components + .retain(|name, _| !referenced_ids.contains(name) && !shorthand_ids.contains(name)); + if !exported_component_ids.is_empty() { return exported_non_component_ids .iter() diff --git a/crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_referenced_in_export.jsx b/crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_referenced_in_export.jsx new file mode 100644 index 000000000000..50547176866f --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_referenced_in_export.jsx @@ -0,0 +1,12 @@ +/* should not generate diagnostics */ + +// TanStack Router pattern - component referenced in exported object +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + component: HomeComponent, +}) + +function HomeComponent() { + return
Home
+} diff --git a/crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_referenced_in_export.jsx.snap b/crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_referenced_in_export.jsx.snap new file mode 100644 index 000000000000..384cf6d49ec5 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_referenced_in_export.jsx.snap @@ -0,0 +1,20 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid_component_referenced_in_export.jsx +--- +# Input +```jsx +/* should not generate diagnostics */ + +// TanStack Router pattern - component referenced in exported object +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + component: HomeComponent, +}) + +function HomeComponent() { + return
Home
+} + +``` diff --git a/crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_shorthand_in_export.jsx b/crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_shorthand_in_export.jsx new file mode 100644 index 000000000000..e9ee6824eb82 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_shorthand_in_export.jsx @@ -0,0 +1,10 @@ +/* should not generate diagnostics */ + +// Shorthand property pattern - component referenced via shorthand syntax +export const config = { + HomeComponent, +} + +function HomeComponent() { + return
Home
+} diff --git a/crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_shorthand_in_export.jsx.snap b/crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_shorthand_in_export.jsx.snap new file mode 100644 index 000000000000..31951b6ec3a1 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/style/useComponentExportOnlyModules/valid_component_shorthand_in_export.jsx.snap @@ -0,0 +1,18 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid_component_shorthand_in_export.jsx +--- +# Input +```jsx +/* should not generate diagnostics */ + +// Shorthand property pattern - component referenced via shorthand syntax +export const config = { + HomeComponent, +} + +function HomeComponent() { + return
Home
+} + +```