fix(linter): accept digits after 'use' in hook names#19254
Merged
camc314 merged 1 commit intooxc-project:mainfrom Feb 11, 2026
Merged
fix(linter): accept digits after 'use' in hook names#19254camc314 merged 1 commit intooxc-project:mainfrom
camc314 merged 1 commit intooxc-project:mainfrom
Conversation
ESLint's isHookName regex is /^use[A-Z0-9]/, accepting digits after 'use' (e.g. use2FAMutation). Oxlint only accepted uppercase letters via char::is_uppercase, causing false positives for valid custom hooks like use2FAMutation and use3DEngine when called inside components, and false negatives when called at the top level. Add || c.is_ascii_digit() to is_react_hook_name to match ESLint's behavior.
sreetamdas
added a commit
to sreetamdas/oxc
that referenced
this pull request
Feb 11, 2026
…ate PR Move the is_react_hook_name digit acceptance changes to oxc-project#19254, keeping only the exhaustive-deps module-scoped callback refs fix here.
Merging this PR will not alter performance
Comparing Footnotes
|
camc314
added a commit
that referenced
this pull request
Feb 11, 2026
…stive-deps (#19220) Fix an `exhaustive-deps` divergence from ESLint's `eslint-plugin-react-hooks`: module-scoped function references passed as callbacks (e.g. `useMemo(getColumns, [])`) are not recognized as stable. --- ### Details Consider this pattern: ```js function getColumns() { return []; } // module scope function MyComponent() { const columns = useMemo(getColumns, []); // oxlint false positive } ``` `getColumns` is defined at module scope — it never changes between renders. ESLint correctly recognizes this and doesn't report missing dependencies. Oxlint was reporting a false positive. <details> <summary>Why? (explained by Claude Opus 4.6)</summary> When the callback to `useMemo` is an identifier rather than an inline arrow, the rule enters [this branch](https://github.com/oxc-project/oxc/blob/1b2f354/crates/oxc_linter/src/rules/react/exhaustive_deps.rs#L330-L400): 1. It calls `get_declaration_of_variable(ident, ...)` to resolve where `getColumns` is defined — this finds the declaration **regardless of scope** 2. It sees `AstKind::Function` and wraps it as `CallbackNode::Function`, treating the body as if it were an inline callback 3. It walks the function body with `ExhaustiveDepsVisitor`, collecting all identifier references as "found dependencies" 4. It compares those against the declared `[]` empty array and reports mismatches The problem: between step 1 and 2, there was no check for whether the declaration is even inside the component. The rule already has this exact check for the inline-callback path — [`is_identifier_a_dependency`](https://github.com/oxc-project/oxc/blob/1b2f354/crates/oxc_linter/src/rules/react/exhaustive_deps.rs#L932-L954) returns `false` when `declaration.scope_id() != component_scope_id`. The function-reference-as-callback path just never had this gate. </details> The fix adds a scope check before analyzing the resolved function body: ```rust if let Some(decl) = get_declaration_of_variable(ident, ctx.semantic()) { // NEW: module-scoped declarations are stable if decl.scope_id() != component_scope_id { return; } // ... existing body analysis logic } ``` Also see: #19254 --------- Co-authored-by: Cameron Clark <[email protected]>
camc314
added a commit
that referenced
this pull request
Feb 12, 2026
# Oxlint ### 🚀 Features - ebb80b3 ast: Add `node_id` field to all AST struct nodes (#18138) (Boshen) - 2879fc5 linter: Implement fixer for unicorn/prefer-math-trunc (#19275) (camc314) - a204eda linter: Implement fixer for unicorn/no-typeof-undefined (#19274) (camc314) - ab46d9c linter: Implement typescript/class-literal-property-style (#19252) (Vincent R) - 1a61f58 linter: Implement typescript/no-invalid-void-type (#19242) (Vincent R) ### 🐛 Bug Fixes - 45adda2 oxlint/lsp: Use blocking stdio in Oxlint (#19292) (overlookmotel) - 05bc855 linter/import: Count unique module sources in max-dependencies (#19270) (camc314) - 8566b44 linter: Check for preceeding token in math trunc fixer (#19277) (camc314) - f16f2b6 linter/import-no-cycle: Avoid traversal-order false negatives with type-only edges (#19267) (camc314) - d4937e7 linter: Recognize module-scoped callback refs as stable in exhaustive-deps (#19220) (Sreetam Das) - 140c9bd linter: Detect fallthrough from `default` when it is not the last case (#19261) (Boshen) - 740a009 linter: Accept digits after 'use' in hook names (#19254) (Sreetam Das) - 31b562f linter: Update `import/no-named-as-default` to allow named import if equivalent to the default import (#19100) (connorshea) - 79c82cc linter: Avoid applying object-level docs to nested object methods in require-param (#19231) (camc314) ### ⚡ Performance - 5670291 linter/class-literal-property-style: Avoid unneeded string allocations (#19262) (camc314) # Oxfmt ### 🚀 Features - ebb80b3 ast: Add `node_id` field to all AST struct nodes (#18138) (Boshen) ### 🐛 Bug Fixes - 1957908 formatter: Avoid unnecessary parentheses for string literal in labeled statement (#19272) (Dunqing) Co-authored-by: camc314 <[email protected]>
OskarLebuda
pushed a commit
to OskarLebuda/oxc
that referenced
this pull request
Feb 17, 2026
Fix a `rules-of-hooks` divergence from ESLint's `eslint-plugin-react-hooks`: hook name validation rejects digits after `use` (e.g. `use2FAMutation`). --- ### Details ESLint's [`isHookName`](https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts#L31-L33) uses the regex `/^use[A-Z0-9]/`: ```ts function isHookName(s: string): boolean { return s === 'use' || /^use[A-Z0-9]/.test(s); } ``` Oxlint's [`is_react_hook_name`](https://github.com/oxc-project/oxc/blob/1b2f354/crates/oxc_linter/src/utils/react.rs#L489=L491) only checked `char::is_uppercase`, which doesn't include digits: ```rust name.starts_with("use") && name.chars().nth(3).is_none_or(char::is_uppercase) ``` This means `use2FAMutation`, `use3DEngine` etc. were not recognized as valid custom hooks — causing false positives when called inside components and false negatives when called at the top level. The fix adds `|| c.is_ascii_digit()` to match ESLint's behavior: ```rust name.starts_with("use") && name.chars().nth(3).is_none_or(|c| c.is_uppercase() || c.is_ascii_digit()) ``` The snapshot update confirms this is correct: `Hook.use42()` called at the top level now produces a diagnostic. The existing test comments already expected this (`topLevelError('Hook.use42')`). Also see: oxc-project#19220
OskarLebuda
pushed a commit
to OskarLebuda/oxc
that referenced
this pull request
Feb 17, 2026
…stive-deps (oxc-project#19220) Fix an `exhaustive-deps` divergence from ESLint's `eslint-plugin-react-hooks`: module-scoped function references passed as callbacks (e.g. `useMemo(getColumns, [])`) are not recognized as stable. --- ### Details Consider this pattern: ```js function getColumns() { return []; } // module scope function MyComponent() { const columns = useMemo(getColumns, []); // oxlint false positive } ``` `getColumns` is defined at module scope — it never changes between renders. ESLint correctly recognizes this and doesn't report missing dependencies. Oxlint was reporting a false positive. <details> <summary>Why? (explained by Claude Opus 4.6)</summary> When the callback to `useMemo` is an identifier rather than an inline arrow, the rule enters [this branch](https://github.com/oxc-project/oxc/blob/1b2f354/crates/oxc_linter/src/rules/react/exhaustive_deps.rs#L330-L400): 1. It calls `get_declaration_of_variable(ident, ...)` to resolve where `getColumns` is defined — this finds the declaration **regardless of scope** 2. It sees `AstKind::Function` and wraps it as `CallbackNode::Function`, treating the body as if it were an inline callback 3. It walks the function body with `ExhaustiveDepsVisitor`, collecting all identifier references as "found dependencies" 4. It compares those against the declared `[]` empty array and reports mismatches The problem: between step 1 and 2, there was no check for whether the declaration is even inside the component. The rule already has this exact check for the inline-callback path — [`is_identifier_a_dependency`](https://github.com/oxc-project/oxc/blob/1b2f354/crates/oxc_linter/src/rules/react/exhaustive_deps.rs#L932-L954) returns `false` when `declaration.scope_id() != component_scope_id`. The function-reference-as-callback path just never had this gate. </details> The fix adds a scope check before analyzing the resolved function body: ```rust if let Some(decl) = get_declaration_of_variable(ident, ctx.semantic()) { // NEW: module-scoped declarations are stable if decl.scope_id() != component_scope_id { return; } // ... existing body analysis logic } ``` Also see: oxc-project#19254 --------- Co-authored-by: Cameron Clark <[email protected]>
OskarLebuda
pushed a commit
to OskarLebuda/oxc
that referenced
this pull request
Feb 17, 2026
# Oxlint ### 🚀 Features - ebb80b3 ast: Add `node_id` field to all AST struct nodes (oxc-project#18138) (Boshen) - 2879fc5 linter: Implement fixer for unicorn/prefer-math-trunc (oxc-project#19275) (camc314) - a204eda linter: Implement fixer for unicorn/no-typeof-undefined (oxc-project#19274) (camc314) - ab46d9c linter: Implement typescript/class-literal-property-style (oxc-project#19252) (Vincent R) - 1a61f58 linter: Implement typescript/no-invalid-void-type (oxc-project#19242) (Vincent R) ### 🐛 Bug Fixes - 45adda2 oxlint/lsp: Use blocking stdio in Oxlint (oxc-project#19292) (overlookmotel) - 05bc855 linter/import: Count unique module sources in max-dependencies (oxc-project#19270) (camc314) - 8566b44 linter: Check for preceeding token in math trunc fixer (oxc-project#19277) (camc314) - f16f2b6 linter/import-no-cycle: Avoid traversal-order false negatives with type-only edges (oxc-project#19267) (camc314) - d4937e7 linter: Recognize module-scoped callback refs as stable in exhaustive-deps (oxc-project#19220) (Sreetam Das) - 140c9bd linter: Detect fallthrough from `default` when it is not the last case (oxc-project#19261) (Boshen) - 740a009 linter: Accept digits after 'use' in hook names (oxc-project#19254) (Sreetam Das) - 31b562f linter: Update `import/no-named-as-default` to allow named import if equivalent to the default import (oxc-project#19100) (connorshea) - 79c82cc linter: Avoid applying object-level docs to nested object methods in require-param (oxc-project#19231) (camc314) ### ⚡ Performance - 5670291 linter/class-literal-property-style: Avoid unneeded string allocations (oxc-project#19262) (camc314) # Oxfmt ### 🚀 Features - ebb80b3 ast: Add `node_id` field to all AST struct nodes (oxc-project#18138) (Boshen) ### 🐛 Bug Fixes - 1957908 formatter: Avoid unnecessary parentheses for string literal in labeled statement (oxc-project#19272) (Dunqing) Co-authored-by: camc314 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix a
rules-of-hooksdivergence from ESLint'seslint-plugin-react-hooks: hook name validation rejects digits afteruse(e.g.use2FAMutation).Details
ESLint's
isHookNameuses the regex/^use[A-Z0-9]/:Oxlint's
is_react_hook_nameonly checkedchar::is_uppercase, which doesn't include digits:This means
use2FAMutation,use3DEngineetc. were not recognized as valid custom hooks — causing false positives when called inside components and false negatives when called at the top level.The fix adds
|| c.is_ascii_digit()to match ESLint's behavior:The snapshot update confirms this is correct:
Hook.use42()called at the top level now produces a diagnostic. The existing test comments already expected this (topLevelError('Hook.use42')).Also see: #19220