Skip to content

feat(rules): add prefer-standard-hook architecture rule#573

Open
devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
devin/1780028067-prefer-standard-hook
Open

feat(rules): add prefer-standard-hook architecture rule#573
devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
devin/1780028067-prefer-standard-hook

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot commented May 29, 2026

Summary

Adds a new architecture rule, prefer-standard-hook, that flags hand-rolled React hooks whose name matches a hook already shipped by the standard hook libraries (react-standard-hooks, which is the union of react-use + usehooks-ts) and recommends importing the battle-tested library version instead. Agents (and humans) routinely re-implement basic hooks like useDebounce, useLocalStorage, usePrevious, useMediaQuery, useOnClickOutside, … with subtle bugs (stale closures, missing cleanup, SSR mismatches). A vetted library hook avoids that whole class of defects.

How it works (detector design)

The rule is scope-aware and only reports when all of these hold, so a function that merely shares a name is never flagged:

  1. The binding is a function (function useX(), const useX = () => {}, or a function expression) whose name is a valid React hook name.
  2. The name is in the matched standard-library set (the full 127-hook union, minus the exclusions below).
  3. The body actually calls a React built-in hook (useState/useEffect/useRef/useReducer/…, including via a namespace import like React.useState) — this is the proof that it's a real hook reimplementation rather than an unrelated helper.
  4. The body does not delegate to a hook imported from one of the standard libraries (so thin re-export wrappers are left alone).

The rule is tagged test-noise, so it stays quiet in test files (where mock hooks are common).

Hook catalog + precision exclusions

  • constants/standard-library-hooks.ts enumerates all 127 unique hook names across react-use (107) and usehooks-ts (33), so name matching is exact rather than guessed.
  • constants/react.ts adds REACT_BUILTIN_HOOK_NAMES (every React primitive hook) used to prove reimplementation.
  • An evidence-based exclusion set removes generic English-word names (useScroll, useList, useLocation, useSize, useScreen, useVideo, useSpeech, usePermission, useSearchParam, useStep, useCss, …) that applications routinely repurpose for an unrelated, app-specific hook. These were the only sources of false positives during eval validation.

Eval validation (false-positive judgement)

I validated the rule by running the built CLI against a sample of the curated repos in react-doctor-evals/repos.json (~109 distinct OSS repos scanned across two batches) and manually inspecting every hit.

  • Before exclusions: generic single-word hook names produced semantic-collision false positives — e.g. useScroll as a scrolled-past-threshold boolean (not react-use's {x,y} tracker), useLocation as a geo lat/lng editor, useSize/useScreen as viewport-size hooks, useVideo as a context accessor, usePermission as app authorization, useStep as a wizard step-context accessor.
  • After adding those names to the exclusion set: every remaining hit I inspected was a genuine reimplementation (true positive) — useDebounce, usePrevious, useLocalStorage, useMediaQuery, useCopyToClipboard, useOnClickOutside, useIntersectionObserver, useResizeObserver, useInterval, useEventListener, useClickAway, useLongPress, useRafState, useMount/useUnmount, etc. Precision on the inspected sample is ≈100%.

The exclusion list is an explicit, justified skiplist (with the eval reasoning in a comment) so future eval findings can be turned into one-line additions.

Review & Testing Checklist for Human

  • Confirm the default-enabled decision. The rule ships enabled by default (no defaultEnabled: false). If you'd prefer a soft launch for a brand-new rule, flip it to defaultEnabled: false — happy to do that in this PR.
  • Sanity-check the exclusion list in constants/standard-library-hooks.ts. The line between "distinctive name" (matched) and "generic word" (excluded) is a judgement call; verify you agree with where it's drawn (e.g. useMount/useClickAway matched vs useScroll/useStep excluded).
  • Spot-check the message/recommendation wording for tone consistency with other architecture rules.
  • Run the rule on an internal repo you know well and confirm the hits are reimplementations you'd actually want migrated to the library.

Notes

  • Files: new rule rules/architecture/prefer-standard-hook.ts + 18-test spec, new constants/standard-library-hooks.ts, REACT_BUILTIN_HOOK_NAMES added to constants/react.ts, and the regenerated rule-registry.ts (307 rules).
  • lint, typecheck, format, and the rule's test file all pass locally. The eval harness in react-doctor-evals does not build on main here (missing ./Github.ts, server.api.ts type errors — unrelated to this change), so eval validation was done by driving the built react-doctor CLI directly over the curated repo set rather than via node dist/cli.js run.

Link to Devin session: https://app.devin.ai/sessions/bbecd54887c34dbfb9897383405139ba
Requested by: @NisargIO


Note

Low Risk
Adds an opt-in-style architecture lint with name exclusions and delegation checks; no runtime or security behavior changes, though name-collision edge cases remain possible for unlisted generic hook names.

Overview
Adds a new Architecture lint rule prefer-standard-hook that warns when a locally defined use* function reimplements a hook already available from react-standard-hooks / react-use / usehooks-ts.

Detection only runs when the name matches the curated library catalog (minus an eval-driven exclusion list for generic names like useScroll / useLocation), the body calls a React built-in hook (new REACT_BUILTIN_HOOK_NAMES in react.ts), and it does not delegate to an import from those libraries. Thin wrappers and non-hook helpers are skipped; the rule is tagged test-noise and ships with a broad unit test suite plus registry wiring.

Reviewed by Cursor Bugbot for commit 741d1b9. Bugbot is set up for automated code reviews on this repo. Configure here.

devin-ai-integration Bot and others added 2 commits May 29, 2026 04:17
Co-Authored-By: nisarg@million.dev <awesomenisarg@gmail.com>
…oisy in evals

Co-Authored-By: nisarg@million.dev <awesomenisarg@gmail.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

Co-Authored-By: nisarg@million.dev <awesomenisarg@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant