Skip to content

Commit cdf4c53

Browse files
committed
fix(linter/only-export-components): support tanstack router (#21937)
fixes #21921 Treat nested configured HOC calls like `createFileRoute('/profile')({ component })` as React component exports. This prevents the local route component from being reported when TanStack Router creators are listed in `customHOCs`.
1 parent 93740ed commit cdf4c53

1 file changed

Lines changed: 24 additions & 20 deletions

File tree

crates/oxc_linter/src/rules/react/only_export_components.rs

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,15 @@ impl OnlyExportComponents {
512512
is_function: bool,
513513
init: Option<&Expression>,
514514
) -> ExportType {
515+
if let Some(init_expr) = init
516+
&& let Expression::CallExpression(call_expr) = Self::skip_ts_expression(init_expr)
517+
&& self.is_callee_hoc(&call_expr.callee)
518+
&& !call_expr.arguments.is_empty()
519+
&& is_react_component_name(name)
520+
{
521+
return ExportType::ReactComponent;
522+
}
523+
515524
if self.allow_export_names.contains(name) {
516525
return ExportType::Allowed;
517526
}
@@ -547,16 +556,6 @@ impl OnlyExportComponents {
547556
return ExportType::ReactContext(span);
548557
}
549558

550-
// For named exports the binding name already provides a
551-
// stable component identity, so only the callee needs to be
552-
// a recognized HOC. The argument shape (arrow function,
553-
// identifier, etc.) does not matter.
554-
if self.is_callee_hoc(&call_expr.callee)
555-
&& !call_expr.arguments.is_empty()
556-
&& is_react_component_name(name)
557-
{
558-
return ExportType::ReactComponent;
559-
}
560559
return ExportType::NonComponent(span);
561560
}
562561

@@ -597,18 +596,13 @@ impl OnlyExportComponents {
597596
fn is_callee_hoc(&self, callee: &Expression) -> bool {
598597
match callee {
599598
Expression::CallExpression(inner_call) => {
600-
if let Expression::Identifier(ident) = &inner_call.callee {
601-
ident.name == "connect"
602-
} else {
603-
false
604-
}
599+
matches!(&inner_call.callee, Expression::Identifier(ident) if ident.name == "connect")
600+
|| self.is_callee_hoc(&inner_call.callee)
605601
}
606602
Expression::StaticMemberExpression(member) => {
607-
if let Expression::Identifier(_) = &member.object {
608-
self.is_react_hoc(&member.property.name)
609-
} else {
610-
false
611-
}
603+
self.is_react_hoc(&member.property.name)
604+
|| matches!(&member.object, Expression::Identifier(ident) if self.is_react_hoc(&ident.name))
605+
|| matches!(&member.object, Expression::CallExpression(call_expr) if self.is_callee_hoc(&call_expr.callee))
612606
}
613607
Expression::Identifier(ident) => self.is_react_hoc(&ident.name),
614608
_ => false,
@@ -762,6 +756,16 @@ fn test() {
762756
"const MyComponent = () => {}; export default observer(MyComponent);",
763757
Some(serde_json::json!([{ "customHOCs": ["observer"] }])),
764758
),
759+
(
760+
"export const Route = createFileRoute('/profile')({ component: Component }); function Component() { return <div />; }",
761+
Some(serde_json::json!([{ "customHOCs": ["createFileRoute"] }])),
762+
),
763+
(
764+
"export const Route = createFileRoute('/profile')({ component: Component }); function Component() { return <div />; }",
765+
Some(
766+
serde_json::json!([{ "customHOCs": ["createFileRoute"], "allowExportNames": ["Route"] }]),
767+
),
768+
),
765769
("const SomeConstant = 42; export function someUtility() { return SomeConstant }", None),
766770
(
767771
"export const MyComponent = () => {}; export const MENU_WIDTH = 232 as const;",

0 commit comments

Comments
 (0)