[lint] Use settings for additional hooks in exhaustive deps (#34637)

Like in the diff below, we can read from the shared configuration to
check exhaustive deps.

I allow the classic additionalHooks configuration to override it so that
this change
is backwards compatible.


--

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34637).
* __->__ #34637
* #34497
This commit is contained in:
Jordan Brown
2025-09-30 16:44:43 -04:00
committed by GitHub
parent 92cfdc3a4e
commit 2a04bae651
2 changed files with 108 additions and 4 deletions

View File

@@ -1485,6 +1485,70 @@ const tests = {
}
`,
},
{
// Test settings-based additionalHooks - should work with settings
code: normalizeIndent`
function MyComponent(props) {
useCustomEffect(() => {
console.log(props.foo);
});
}
`,
settings: {
'react-hooks': {
additionalEffectHooks: 'useCustomEffect',
},
},
},
{
// Test settings-based additionalHooks - should work with dependencies
code: normalizeIndent`
function MyComponent(props) {
useCustomEffect(() => {
console.log(props.foo);
}, [props.foo]);
}
`,
settings: {
'react-hooks': {
additionalEffectHooks: 'useCustomEffect',
},
},
},
{
// Test that rule-level additionalHooks takes precedence over settings
code: normalizeIndent`
function MyComponent(props) {
useCustomEffect(() => {
console.log(props.foo);
}, []);
}
`,
options: [{additionalHooks: 'useAnotherEffect'}],
settings: {
'react-hooks': {
additionalEffectHooks: 'useCustomEffect',
},
},
},
{
// Test settings with multiple hooks pattern
code: normalizeIndent`
function MyComponent(props) {
useCustomEffect(() => {
console.log(props.foo);
}, [props.foo]);
useAnotherEffect(() => {
console.log(props.bar);
}, [props.bar]);
}
`,
settings: {
'react-hooks': {
additionalEffectHooks: '(useCustomEffect|useAnotherEffect)',
},
},
},
],
invalid: [
{
@@ -3714,6 +3778,40 @@ const tests = {
},
],
},
{
// Test settings-based additionalHooks - should detect missing dependency
code: normalizeIndent`
function MyComponent(props) {
useCustomEffect(() => {
console.log(props.foo);
}, []);
}
`,
settings: {
'react-hooks': {
additionalEffectHooks: 'useCustomEffect',
},
},
errors: [
{
message:
"React Hook useCustomEffect has a missing dependency: 'props.foo'. " +
'Either include it or remove the dependency array.',
suggestions: [
{
desc: 'Update the dependencies array to be: [props.foo]',
output: normalizeIndent`
function MyComponent(props) {
useCustomEffect(() => {
console.log(props.foo);
}, [props.foo]);
}
`,
},
],
},
],
},
{
code: normalizeIndent`
function MyComponent() {

View File

@@ -21,6 +21,8 @@ import type {
VariableDeclarator,
} from 'estree';
import { getAdditionalEffectHooksFromSettings } from '../shared/Utils';
type DeclaredDependency = {
key: string;
node: Node;
@@ -69,19 +71,22 @@ const rule = {
},
requireExplicitEffectDeps: {
type: 'boolean',
}
},
},
},
],
},
create(context: Rule.RuleContext) {
const rawOptions = context.options && context.options[0];
const settings = context.settings || {};
// Parse the `additionalHooks` regex.
// Use rule-level additionalHooks if provided, otherwise fall back to settings
const additionalHooks =
rawOptions && rawOptions.additionalHooks
? new RegExp(rawOptions.additionalHooks)
: undefined;
: getAdditionalEffectHooksFromSettings(settings);
const enableDangerousAutofixThisMayCauseInfiniteLoops: boolean =
(rawOptions &&
@@ -93,7 +98,8 @@ const rule = {
? rawOptions.experimental_autoDependenciesHooks
: [];
const requireExplicitEffectDeps: boolean = rawOptions && rawOptions.requireExplicitEffectDeps || false;
const requireExplicitEffectDeps: boolean =
(rawOptions && rawOptions.requireExplicitEffectDeps) || false;
const options = {
additionalHooks,
@@ -1351,7 +1357,7 @@ const rule = {
node: reactiveHook,
message:
`React Hook ${reactiveHookName} always requires dependencies. ` +
`Please add a dependency array or an explicit \`undefined\``
`Please add a dependency array or an explicit \`undefined\``,
});
}