feat(rules): add prefer-standard-hook architecture rule#573
Open
devin-ai-integration[bot] wants to merge 3 commits into
Open
feat(rules): add prefer-standard-hook architecture rule#573devin-ai-integration[bot] wants to merge 3 commits into
devin-ai-integration[bot] wants to merge 3 commits into
Conversation
Co-Authored-By: nisarg@million.dev <awesomenisarg@gmail.com>
…oisy in evals Co-Authored-By: nisarg@million.dev <awesomenisarg@gmail.com>
Contributor
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Co-Authored-By: nisarg@million.dev <awesomenisarg@gmail.com>
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.
Summary
Adds a new
architecturerule,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 ofreact-use+usehooks-ts) and recommends importing the battle-tested library version instead. Agents (and humans) routinely re-implement basic hooks likeuseDebounce,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:
function useX(),const useX = () => {}, or a function expression) whose name is a valid React hook name.useState/useEffect/useRef/useReducer/…, including via a namespace import likeReact.useState) — this is the proof that it's a real hook reimplementation rather than an unrelated helper.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.tsenumerates all 127 unique hook names acrossreact-use(107) andusehooks-ts(33), so name matching is exact rather than guessed.constants/react.tsaddsREACT_BUILTIN_HOOK_NAMES(every React primitive hook) used to prove reimplementation.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.useScrollas a scrolled-past-threshold boolean (not react-use's{x,y}tracker),useLocationas a geo lat/lng editor,useSize/useScreenas viewport-size hooks,useVideoas a context accessor,usePermissionas app authorization,useStepas a wizard step-context accessor.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
defaultEnabled: false). If you'd prefer a soft launch for a brand-new rule, flip it todefaultEnabled: false— happy to do that in this PR.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/useClickAwaymatched vsuseScroll/useStepexcluded).Notes
rules/architecture/prefer-standard-hook.ts+ 18-test spec, newconstants/standard-library-hooks.ts,REACT_BUILTIN_HOOK_NAMESadded toconstants/react.ts, and the regeneratedrule-registry.ts(307 rules).lint,typecheck,format, and the rule's test file all pass locally. The eval harness inreact-doctor-evalsdoes not build onmainhere (missing./Github.ts,server.api.tstype errors — unrelated to this change), so eval validation was done by driving the builtreact-doctorCLI directly over the curated repo set rather than vianode 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-hookthat warns when a locally defineduse*function reimplements a hook already available fromreact-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 (newREACT_BUILTIN_HOOK_NAMESinreact.ts), and it does not delegate to an import from those libraries. Thin wrappers and non-hook helpers are skipped; the rule is taggedtest-noiseand 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.