feat(eslint-rules): add base-hook-signature rule#36252
Open
Hotell wants to merge 6 commits into
Open
Conversation
Registers @nx/workspace-base-hook-signature. Rule is not yet enabled in any project config and is fully inert until wired up in a follow-up PR.
📊 Bundle size report✅ No changes found |
|
Pull request demo site: URL |
…nting Add docs-only Sibling/useSibling.ts and Orphan/useOrphan.ts stubs so the fixture folder layout mirrors the situations the tests assert against, and document in the spec why useSiblingBase.ts (must exist) and the absent useOrphanContextValuesBase.ts(x) (must NOT exist) are the only files that actually drive rule behavior.
…ops` param Untyped `props` would be inferred as `any` and fail under `noImplicitAny`. The rule now reports `missingPropsType` when a base/paired hook's first parameter has no type annotation. The shape of the type is intentionally not validated \u2014 just its presence.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds a new internal Nx workspace ESLint rule, @nx/workspace-base-hook-signature, to enforce the parameter contract for v9 base hooks (use<Name>Base_unstable) and (when paired) their wrapping state hooks (use<Name>_unstable). It’s implemented in tools/eslint-rules/ and is registered but not enabled in any lint config yet.
Changes:
- Added the
base-hook-signaturerule implementation, including same-file + sibling-file pair detection for enforcing state-hook signatures only when a base hook exists. - Added a comprehensive
RuleTestertest suite plus a fixture directory tree to validate sibling-file detection behavior. - Registered the rule in the workspace rules index and excluded
__fixtures__from the lint tsconfig compilation.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/eslint-rules/tsconfig.lint.json | Excludes __fixtures__ from lint TS compilation. |
| tools/eslint-rules/rules/base-hook-signature.ts | New ESLint rule enforcing (props, ref?) signature and React.Ref<...> typing with pairing detection. |
| tools/eslint-rules/rules/base-hook-signature.spec.ts | Unit tests covering valid/invalid signatures and sibling-file pairing behavior. |
| tools/eslint-rules/rules/fixtures/base-hook-signature/src/components/Sibling/useSiblingBase.ts | Fixture file used for sibling-file existence detection. |
| tools/eslint-rules/rules/fixtures/base-hook-signature/src/components/Sibling/useSibling.ts | Docs-only stub fixture mirroring component layout. |
| tools/eslint-rules/rules/fixtures/base-hook-signature/src/components/Orphan/useOrphan.ts | Docs-only stub fixture to validate “no pair ⇒ no enforcement”. |
| tools/eslint-rules/index.ts | Registers the new rule for Nx workspace ESLint rule exposure. |
- Fix short-circuiting: use for loop instead of forEach to allow early returns after missingPropsType check, preventing simultaneous reporting of missingPropsType and invalidRefType
- Add invalidBaseHookInit check: report errors for non-function base hook initializers (literals like 42, {}, []), while allowing valid re-exports (identifiers)
- Distinguish between param-name errors (all reported) and type-annotation errors (short-circuit for fundamental issues)
- Add describeInitializer() helper for user-facing error messages
- Add test cases for invalid initializers (4 new invalid cases) and valid re-exports (2 new valid cases)
- All 65 tests passing
Parameters with wrong names now stop validation immediately (don't check subsequent params or types). Update tests to expect only first param-name error.
…hecks Since the max param count is 2, loops add no value. Destructure [propsParam, refParam] directly and check each in sequence for clearer linear flow.
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
Introduces a new workspace ESLint rule
@nx/workspace-base-hook-signaturethat enforces the consumed signature foruseFooBase/useFooBase_unstablehook implementations:props, optionalref).propsmust be a plainIdentifiernamedpropswith an explicit type annotation — untypedpropswould be inferred asanyand fail undernoImplicitAny. The shape of the type is intentionally not validated; only its presence.refmust be anIdentifiernamedreftyped asReact.Ref<...>(resolved through scope so a locally-shadowedRef/Reactdoes not pass).FunctionDeclarationorconst useFooBase = (...) => {}— or a re-export identifier (const useFooBase_unstable = useFooBase;). Literals, object expressions, and arrays are rejected withinvalidBaseHookInit.useFoo_unstablewhen their siblinguseFooBase_unstableexists (same file oruseFooBase.ts(x)next touseFoo.ts(x)).Validation order (stops at the first failure):
invalidParamCount)propsname must be correct; thenrefname if present (invalidParamName, stops at first wrong name)propsmust have a type annotation (missingPropsType, short-circuits before the ref check)reftype must beReact.Ref<...>(invalidRefType)Diagnostic message IDs:
invalidParamCount,invalidParamName,missingPropsType,invalidRefType,invalidBaseHookInit.The rule is fully inert in this PR — it is registered with
@nx/workspacebut not enabled in any project config. Wiring happens in a follow-up PR.Plan reference
Part of the split of #36251 — see
prd/eslint-rules-base-hook-pr-split.spec.md(PR1).Fixtures
Self-contained fixture tree under
tools/eslint-rules/rules/__fixtures__/base-hook-signature/:src/components/Sibling/useSiblingBase.ts— MUST exist; presence triggers pair detection for the sibling-file test.src/components/Sibling/useSibling.tsandsrc/components/Orphan/useOrphan.ts— docs-only stubs (export {};+ header comment). The rule never reads them; they exist purely so the fixture tree mirrors a real component folder layout. TheOrphan/folder intentionally has nouseOrphanContextValuesBase.ts(x)— the absence drives the "non-paired hook is exempt" test.The spec file contains a
NOTE on fixture filenamesblock explainingRuleTestersemantics and which fixture files actually drive behavior.Verification
→ 4 suites / 65 tests pass.
ESLint CLI perf (TIMING=200)
Measured on a combined branch (PR1 + PR2 + PR3 wiring) so the rule actually runs.
@nx/workspace-base-hook-signaturereact-button(no*Basehook)react-combobox(1*Basehook)react-tags(1*Basehook)Negligible — well below
@typescript-eslint/no-deprecated(≈1000 ms) andreact-hooks/static-components(≈400 ms) in the same runs.Out of scope
@fluentui/eslint-plugin(separate PR).