feat(react/js): add noComponentHookFactories#9916
Conversation
🦋 Changeset detectedLatest commit: 7055b16 The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 packages
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 |
Merging this PR will not alter performance
Comparing Footnotes
|
WalkthroughAdds a new nursery lint rule Suggested reviewers
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/biome_js_analyze/src/lint/nursery/no_component_hook_factories.rs (1)
230-272: Consider extending test mock detection for additional frameworks.The current implementation supports
vi.mockandjest.mock. You might want to consider whether other common patterns likesinon.stub,td.replace(testdouble), or Bun'smock.modulewould benefit from similar treatment. Not blocking, as the current coverage handles the most common cases.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/biome_js_analyze/src/lint/nursery/no_component_hook_factories.rs` around lines 230 - 272, is_inside_test_mock_callback currently only recognizes calls where the member name token equals "mock" and the object identifier equals "vi" or "jest"; extend its detection to include other common test utilities by matching additional object+method combinations (e.g., object "sinon" with method "stub", object "td" with method "replace", and Bun's "mock" module such as object "mock" with method "module" or similar). Update the checks around method_token.text_trimmed() and object_token.text_trimmed() inside is_inside_test_mock_callback to accept those extra pairs (or a small whitelist map of allowed object→method names) so the function returns true for those frameworks as well while preserving existing early-returns and structure.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@crates/biome_js_analyze/src/lint/nursery/no_component_hook_factories.rs`:
- Around line 230-272: is_inside_test_mock_callback currently only recognizes
calls where the member name token equals "mock" and the object identifier equals
"vi" or "jest"; extend its detection to include other common test utilities by
matching additional object+method combinations (e.g., object "sinon" with method
"stub", object "td" with method "replace", and Bun's "mock" module such as
object "mock" with method "module" or similar). Update the checks around
method_token.text_trimmed() and object_token.text_trimmed() inside
is_inside_test_mock_callback to accept those extra pairs (or a small whitelist
map of allowed object→method names) so the function returns true for those
frameworks as well while preserving existing early-returns and structure.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 7b433763-9e95-4480-b24e-e8c5285c4609
⛔ Files ignored due to path filters (9)
crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rsis excluded by!**/migrate/eslint_any_rule_to_biome.rsand included by**crates/biome_configuration/src/analyzer/linter/rules.rsis excluded by!**/rules.rsand included by**crates/biome_configuration/src/generated/domain_selector.rsis excluded by!**/generated/**,!**/generated/**and included by**crates/biome_configuration/src/generated/linter_options_check.rsis excluded by!**/generated/**,!**/generated/**and included by**crates/biome_diagnostics_categories/src/categories.rsis excluded by!**/categories.rsand included by**crates/biome_js_analyze/tests/specs/nursery/noComponentHookFactories/invalid.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/noComponentHookFactories/valid.jsx.snapis excluded by!**/*.snapand included by**packages/@biomejs/backend-jsonrpc/src/workspace.tsis excluded by!**/backend-jsonrpc/src/workspace.tsand included by**packages/@biomejs/biome/configuration_schema.jsonis excluded by!**/configuration_schema.jsonand included by**
📒 Files selected for processing (6)
.changeset/dry-dryers-flash.mdcrates/biome_js_analyze/src/lint/nursery/no_component_hook_factories.rscrates/biome_js_analyze/tests/specs/nursery/noComponentHookFactories/invalid.jsxcrates/biome_js_analyze/tests/specs/nursery/noComponentHookFactories/valid.jsxcrates/biome_rule_options/src/lib.rscrates/biome_rule_options/src/no_component_hook_factories.rs
dyc3
left a comment
There was a problem hiding this comment.
Looks good. Just a couple notes.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
crates/biome_js_analyze/src/lint/nursery/no_component_hook_factories.rs (2)
239-259: Minor semantic inconsistency in return type usage.
is_test_mock_callreturnsNonewhen the method isn't"mock"(line 248) but returnsSome(false)when the object isn'tvi/jest(line 258). Both are treated identically by the caller viaunwrap_or(false), so no bug here—just a minor readability nit.♻️ Optional: consistent return semantics
fn is_test_mock_call(ancestor: &JsSyntaxNode) -> Option<bool> { let call_expr = JsCallExpression::cast_ref(ancestor)?; let member = call_expr .callee() .ok()? .as_js_static_member_expression()? .clone(); let method_token = member.member().ok()?.as_js_name()?.value_token().ok()?; if method_token.text_trimmed() != "mock" { - return None; + return Some(false); } let object_token = member .object() .ok()? .as_js_identifier_expression()? .name() .ok()? .value_token() .ok()?; Some(matches!(object_token.text_trimmed(), "vi" | "jest")) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/biome_js_analyze/src/lint/nursery/no_component_hook_factories.rs` around lines 239 - 259, The helper is_test_mock_call has inconsistent semantics: it returns None when the callee method isn't "mock" but returns Some(false) when the object isn't "vi"/"jest"; change the early exit in is_test_mock_call (the branch that currently returns None when method_token.text_trimmed() != "mock") to return Some(false) so the function consistently returns Some(bool) and callers can rely on the boolean result without treating None specially.
293-299: Heuristic is reasonable but worth documenting.Checking only the first character catches the common HOC parameter patterns (
WrappedComponent,Component), but would also match less conventional names likeURLorID. This is probably fine in practice; a brief comment noting the trade-off could help future readers.📝 Optional: add clarifying comment
Some( + // PascalCase heuristic: first char uppercase. May match acronyms (URL, ID), + // but these are rare as component parameter names. name_token .text_trimmed() .chars() .next() .is_some_and(char::is_uppercase), )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/biome_js_analyze/src/lint/nursery/no_component_hook_factories.rs` around lines 293 - 299, The heuristic that checks only the first character of name_token (the Some(name_token.text_trimmed().chars().next().is_some_and(char::is_uppercase) expression) can misclassify all-uppercase identifiers like URL or ID; add a concise inline comment above this expression explaining the trade-off (we intentionally only inspect the first character to catch common HOC param names like WrappedComponent/Component while accepting rare false positives such as URL/ID) so future readers understand the reasoning and limitations of the heuristic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@crates/biome_js_analyze/src/lint/nursery/no_component_hook_factories.rs`:
- Around line 239-259: The helper is_test_mock_call has inconsistent semantics:
it returns None when the callee method isn't "mock" but returns Some(false) when
the object isn't "vi"/"jest"; change the early exit in is_test_mock_call (the
branch that currently returns None when method_token.text_trimmed() != "mock")
to return Some(false) so the function consistently returns Some(bool) and
callers can rely on the boolean result without treating None specially.
- Around line 293-299: The heuristic that checks only the first character of
name_token (the
Some(name_token.text_trimmed().chars().next().is_some_and(char::is_uppercase)
expression) can misclassify all-uppercase identifiers like URL or ID; add a
concise inline comment above this expression explaining the trade-off (we
intentionally only inspect the first character to catch common HOC param names
like WrappedComponent/Component while accepting rare false positives such as
URL/ID) so future readers understand the reasoning and limitations of the
heuristic.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: f488339f-e30d-494b-aaae-bfbbe24ef45d
📒 Files selected for processing (1)
crates/biome_js_analyze/src/lint/nursery/no_component_hook_factories.rs
8dafcb7 to
7055b16
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/biome_js_analyze/src/lint/nursery/no_component_hook_factories.rs (1)
280-300: Minor: Unnecessary.clone()on line 285.The
binding()method takes&self, so you can call it directly on the reference fromas_js_formal_parameter()without cloning.♻️ Suggested simplification
fn is_pascal_case_param(param: SyntaxResult<AnyJsParameter>) -> Option<bool> { - let formal = param + let name_token = param .ok()? .as_any_js_formal_parameter()? - .as_js_formal_parameter()? - .clone(); - let name_token = formal + .as_js_formal_parameter()? .binding() .ok()? .as_any_js_binding()? .as_js_identifier_binding()? .name_token() .ok()?; Some( name_token .text_trimmed() .chars() .next() .is_some_and(char::is_uppercase), ) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/biome_js_analyze/src/lint/nursery/no_component_hook_factories.rs` around lines 280 - 300, The helper function is_pascal_case_param unnecessarily calls .clone() on the result of as_js_formal_parameter(); remove the .clone() and call binding() directly on the borrowed formal parameter (i.e., use the reference returned by as_js_formal_parameter() rather than cloning it). Update is_pascal_case_param so the chain uses the borrowed value from as_js_formal_parameter() before calling binding(), preserving the rest of the unwrap/ok logic (keep names: is_pascal_case_param, as_js_formal_parameter, binding, name_token).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@crates/biome_js_analyze/src/lint/nursery/no_component_hook_factories.rs`:
- Around line 280-300: The helper function is_pascal_case_param unnecessarily
calls .clone() on the result of as_js_formal_parameter(); remove the .clone()
and call binding() directly on the borrowed formal parameter (i.e., use the
reference returned by as_js_formal_parameter() rather than cloning it). Update
is_pascal_case_param so the chain uses the borrowed value from
as_js_formal_parameter() before calling binding(), preserving the rest of the
unwrap/ok logic (keep names: is_pascal_case_param, as_js_formal_parameter,
binding, name_token).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 736fe590-e938-48b8-a607-11319ed4e1af
⛔ Files ignored due to path filters (9)
crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rsis excluded by!**/migrate/eslint_any_rule_to_biome.rsand included by**crates/biome_configuration/src/analyzer/linter/rules.rsis excluded by!**/rules.rsand included by**crates/biome_configuration/src/generated/domain_selector.rsis excluded by!**/generated/**,!**/generated/**and included by**crates/biome_configuration/src/generated/linter_options_check.rsis excluded by!**/generated/**,!**/generated/**and included by**crates/biome_diagnostics_categories/src/categories.rsis excluded by!**/categories.rsand included by**crates/biome_js_analyze/tests/specs/nursery/noComponentHookFactories/invalid.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/noComponentHookFactories/valid.jsx.snapis excluded by!**/*.snapand included by**packages/@biomejs/backend-jsonrpc/src/workspace.tsis excluded by!**/backend-jsonrpc/src/workspace.tsand included by**packages/@biomejs/biome/configuration_schema.jsonis excluded by!**/configuration_schema.jsonand included by**
📒 Files selected for processing (6)
.changeset/dry-dryers-flash.mdcrates/biome_js_analyze/src/lint/nursery/no_component_hook_factories.rscrates/biome_js_analyze/tests/specs/nursery/noComponentHookFactories/invalid.jsxcrates/biome_js_analyze/tests/specs/nursery/noComponentHookFactories/valid.jsxcrates/biome_rule_options/src/lib.rscrates/biome_rule_options/src/no_component_hook_factories.rs
✅ Files skipped from review due to trivial changes (5)
- crates/biome_rule_options/src/lib.rs
- .changeset/dry-dryers-flash.md
- crates/biome_rule_options/src/no_component_hook_factories.rs
- crates/biome_js_analyze/tests/specs/nursery/noComponentHookFactories/invalid.jsx
- crates/biome_js_analyze/tests/specs/nursery/noComponentHookFactories/valid.jsx
Summary
Implement this react rule
Test Plan
New tests
Docs