Skip to content

Commit 80e7b49

Browse files
committed
No breaking changes
1 parent 8be13de commit 80e7b49

File tree

6 files changed

+295
-177
lines changed

6 files changed

+295
-177
lines changed

packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js

+115-36
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99

1010
'use strict';
1111

12-
const ESLintTester = require('eslint').RuleTester;
12+
const ESLintTesterV7 = require('eslint-v7').RuleTester;
13+
const ESLintTesterV9 = require('eslint-v9').RuleTester;
1314
const ReactHooksESLintPlugin = require('eslint-plugin-react-hooks');
1415
const ReactHooksESLintRule = ReactHooksESLintPlugin.rules['exhaustive-deps'];
1516

@@ -8223,8 +8224,15 @@ if (!process.env.CI) {
82238224
testsTypescript.invalid = testsTypescript.invalid.filter(predicate);
82248225
}
82258226

8226-
describe('react-hooks', () => {
8227-
const languageOptions = {
8227+
describe('rules-of-hooks/exhaustive-deps', () => {
8228+
const parserOptionsV7 = {
8229+
ecmaFeatures: {
8230+
jsx: true,
8231+
},
8232+
ecmaVersion: 6,
8233+
sourceType: 'module',
8234+
};
8235+
const languageOptionsV9 = {
82288236
ecmaVersion: 6,
82298237
sourceType: 'module',
82308238
parserOptions: {
@@ -8239,13 +8247,22 @@ describe('react-hooks', () => {
82398247
invalid: [...testsFlow.invalid, ...tests.invalid],
82408248
};
82418249

8242-
new ESLintTester({
8250+
new ESLintTesterV7({
8251+
parser: require.resolve('babel-eslint'),
8252+
parserOptions: parserOptionsV7,
8253+
}).run(
8254+
'eslint: v7, parser: babel-eslint',
8255+
ReactHooksESLintRule,
8256+
testsBabelEslint
8257+
);
8258+
8259+
new ESLintTesterV9({
82438260
languageOptions: {
8244-
...languageOptions,
8261+
...languageOptionsV9,
82458262
parser: require('@babel/eslint-parser'),
82468263
},
82478264
}).run(
8248-
'parser: @babel/eslint-parser',
8265+
'eslint: v9, parser: @babel/eslint-parser',
82498266
ReactHooksESLintRule,
82508267
testsBabelEslint
82518268
);
@@ -8255,57 +8272,119 @@ describe('react-hooks', () => {
82558272
invalid: [...testsTypescript.invalid, ...tests.invalid],
82568273
};
82578274

8258-
new ESLintTester({
8275+
new ESLintTesterV7({
8276+
parser: require.resolve('@typescript-eslint/parser-v2'),
8277+
parserOptions: parserOptionsV7,
8278+
}).run(
8279+
'eslint: v9, parser: @typescript-eslint/[email protected]',
8280+
ReactHooksESLintRule,
8281+
testsTypescriptEslintParser
8282+
);
8283+
8284+
new ESLintTesterV9({
82598285
languageOptions: {
8260-
...languageOptions,
8286+
...languageOptionsV9,
82618287
parser: require('@typescript-eslint/parser-v2'),
82628288
},
82638289
}).run(
8264-
'parser: @typescript-eslint/[email protected]',
8290+
'eslint: v9, parser: @typescript-eslint/[email protected]',
82658291
ReactHooksESLintRule,
82668292
testsTypescriptEslintParser
82678293
);
82688294

8269-
new ESLintTester({
8295+
new ESLintTesterV7({
8296+
parser: require.resolve('@typescript-eslint/parser-v3'),
8297+
parserOptions: parserOptionsV7,
8298+
}).run(
8299+
'eslint: v7, parser: @typescript-eslint/[email protected]',
8300+
ReactHooksESLintRule,
8301+
testsTypescriptEslintParser
8302+
);
8303+
8304+
new ESLintTesterV9({
82708305
languageOptions: {
8271-
...languageOptions,
8306+
...languageOptionsV9,
82728307
parser: require('@typescript-eslint/parser-v3'),
82738308
},
82748309
}).run(
8275-
'parser: @typescript-eslint/[email protected]',
8310+
'eslint: v9, parser: @typescript-eslint/[email protected]',
82768311
ReactHooksESLintRule,
82778312
testsTypescriptEslintParser
82788313
);
82798314

8280-
new ESLintTester({
8315+
new ESLintTesterV7({
8316+
parser: require.resolve('@typescript-eslint/parser-v4'),
8317+
parserOptions: parserOptionsV7,
8318+
}).run(
8319+
'eslint: v7, parser: @typescript-eslint/[email protected]',
8320+
ReactHooksESLintRule,
8321+
{
8322+
valid: [
8323+
...testsTypescriptEslintParserV4.valid,
8324+
...testsTypescriptEslintParser.valid,
8325+
],
8326+
invalid: [
8327+
...testsTypescriptEslintParserV4.invalid,
8328+
...testsTypescriptEslintParser.invalid,
8329+
],
8330+
}
8331+
);
8332+
8333+
new ESLintTesterV9({
82818334
languageOptions: {
8282-
...languageOptions,
8335+
...languageOptionsV9,
82838336
parser: require('@typescript-eslint/parser-v4'),
82848337
},
8285-
}).run('parser: @typescript-eslint/[email protected]', ReactHooksESLintRule, {
8286-
valid: [
8287-
...testsTypescriptEslintParserV4.valid,
8288-
...testsTypescriptEslintParser.valid,
8289-
],
8290-
invalid: [
8291-
...testsTypescriptEslintParserV4.invalid,
8292-
...testsTypescriptEslintParser.invalid,
8293-
],
8294-
});
8338+
}).run(
8339+
'eslint: v9, parser: @typescript-eslint/[email protected]',
8340+
ReactHooksESLintRule,
8341+
{
8342+
valid: [
8343+
...testsTypescriptEslintParserV4.valid,
8344+
...testsTypescriptEslintParser.valid,
8345+
],
8346+
invalid: [
8347+
...testsTypescriptEslintParserV4.invalid,
8348+
...testsTypescriptEslintParser.invalid,
8349+
],
8350+
}
8351+
);
82958352

8296-
new ESLintTester({
8353+
new ESLintTesterV7({
8354+
parser: require.resolve('@typescript-eslint/parser-v5'),
8355+
parserOptions: parserOptionsV7,
8356+
}).run(
8357+
'eslint: v7, parser: @typescript-eslint/parser@^5.0.0-0',
8358+
ReactHooksESLintRule,
8359+
{
8360+
valid: [
8361+
...testsTypescriptEslintParserV4.valid,
8362+
...testsTypescriptEslintParser.valid,
8363+
],
8364+
invalid: [
8365+
...testsTypescriptEslintParserV4.invalid,
8366+
...testsTypescriptEslintParser.invalid,
8367+
],
8368+
}
8369+
);
8370+
8371+
new ESLintTesterV9({
82978372
languageOptions: {
8298-
...languageOptions,
8373+
...languageOptionsV9,
82998374
parser: require('@typescript-eslint/parser-v5'),
83008375
},
8301-
}).run('parser: @typescript-eslint/parser@^5.0.0-0', ReactHooksESLintRule, {
8302-
valid: [
8303-
...testsTypescriptEslintParserV4.valid,
8304-
...testsTypescriptEslintParser.valid,
8305-
],
8306-
invalid: [
8307-
...testsTypescriptEslintParserV4.invalid,
8308-
...testsTypescriptEslintParser.invalid,
8309-
],
8310-
});
8376+
}).run(
8377+
'eslint: v9, parser: @typescript-eslint/parser@^5.0.0-0',
8378+
ReactHooksESLintRule,
8379+
{
8380+
valid: [
8381+
...testsTypescriptEslintParserV4.valid,
8382+
...testsTypescriptEslintParser.valid,
8383+
],
8384+
invalid: [
8385+
...testsTypescriptEslintParserV4.invalid,
8386+
...testsTypescriptEslintParser.invalid,
8387+
],
8388+
}
8389+
);
83118390
});

packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js

+19-11
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,12 @@
99

1010
'use strict';
1111

12-
const ESLintTester = require('eslint').RuleTester;
12+
const ESLintTesterV7 = require('eslint-v7').RuleTester;
13+
const ESLintTesterV9 = require('eslint-v9').RuleTester;
1314
const ReactHooksESLintPlugin = require('eslint-plugin-react-hooks');
1415
const BabelEslintParser = require('@babel/eslint-parser');
1516
const ReactHooksESLintRule = ReactHooksESLintPlugin.rules['rules-of-hooks'];
1617

17-
ESLintTester.setDefaultConfig({
18-
languageOptions: {
19-
parser: BabelEslintParser,
20-
ecmaVersion: 6,
21-
sourceType: 'module',
22-
},
23-
});
24-
2518
/**
2619
* A string template tag that removes padding from the left side of multi-line strings
2720
* @param {Array} strings array of code strings (only one expected)
@@ -1464,5 +1457,20 @@ if (!process.env.CI) {
14641457
tests.invalid = tests.invalid.filter(predicate);
14651458
}
14661459

1467-
const eslintTester = new ESLintTester();
1468-
eslintTester.run('react-hooks', ReactHooksESLintRule, tests);
1460+
describe('rules-of-hooks/rules-of-hooks', () => {
1461+
new ESLintTesterV7({
1462+
parser: require.resolve('babel-eslint'),
1463+
parserOptions: {
1464+
ecmaVersion: 6,
1465+
sourceType: 'module',
1466+
},
1467+
}).run('eslint: v7', ReactHooksESLintRule, tests);
1468+
1469+
new ESLintTesterV9({
1470+
languageOptions: {
1471+
parser: BabelEslintParser,
1472+
ecmaVersion: 6,
1473+
sourceType: 'module',
1474+
},
1475+
}).run('eslint: v9', ReactHooksESLintRule, tests);
1476+
});

packages/eslint-plugin-react-hooks/package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,16 @@
2828
},
2929
"homepage": "https://react.dev/",
3030
"peerDependencies": {
31-
"eslint": "^9.0.0"
31+
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
3232
},
3333
"devDependencies": {
3434
"@babel/eslint-parser": "^7.11.4",
3535
"@typescript-eslint/parser-v2": "npm:@typescript-eslint/parser@^2.26.0",
3636
"@typescript-eslint/parser-v3": "npm:@typescript-eslint/parser@^3.10.0",
3737
"@typescript-eslint/parser-v4": "npm:@typescript-eslint/parser@^4.1.0",
3838
"@typescript-eslint/parser-v5": "npm:@typescript-eslint/parser@^5.0.0-0",
39-
"eslint": "^9.0.0"
39+
"babel-eslint": "^10.0.3",
40+
"eslint-v7": "npm:eslint@^7.7.0",
41+
"eslint-v9": "npm:eslint@^9.0.0"
4042
}
4143
}

packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js

+32-21
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,27 @@ export default {
6767
context.report(problem);
6868
}
6969

70+
/**
71+
* SourceCode#getText that also works down to ESLint 3.0.0
72+
*/
73+
function getSource(node) {
74+
if (typeof context.getSource === 'function') {
75+
return context.getSource(node);
76+
} else {
77+
return context.sourceCode.getText(node);
78+
}
79+
}
80+
/**
81+
* SourceCode#getScope that also works down to ESLint 3.0.0
82+
*/
83+
function getScope(node) {
84+
if (typeof context.getScope === 'function') {
85+
return context.getScope();
86+
} else {
87+
return context.sourceCode.getScope(node);
88+
}
89+
}
90+
7091
const scopeManager = context.getSourceCode().scopeManager;
7192

7293
// Should be shared between visitors.
@@ -526,13 +547,11 @@ export default {
526547
node: writeExpr,
527548
message:
528549
`Assignments to the '${key}' variable from inside React Hook ` +
529-
`${context.sourceCode.getText(
530-
reactiveHook,
531-
)} will be lost after each ` +
550+
`${getSource(reactiveHook)} will be lost after each ` +
532551
`render. To preserve the value over time, store it in a useRef ` +
533552
`Hook and keep the mutable value in the '.current' property. ` +
534553
`Otherwise, you can move this variable directly inside ` +
535-
`${context.sourceCode.getText(reactiveHook)}.`,
554+
`${getSource(reactiveHook)}.`,
536555
});
537556
}
538557

@@ -632,9 +651,7 @@ export default {
632651
reportProblem({
633652
node: declaredDependenciesNode,
634653
message:
635-
`React Hook ${context.sourceCode.getText(
636-
reactiveHook,
637-
)} was passed a ` +
654+
`React Hook ${getSource(reactiveHook)} was passed a ` +
638655
'dependency list that is not an array literal. This means we ' +
639656
"can't statically verify whether you've passed the correct " +
640657
'dependencies.',
@@ -654,9 +671,7 @@ export default {
654671
reportProblem({
655672
node: declaredDependencyNode,
656673
message:
657-
`React Hook ${context.sourceCode.getText(
658-
reactiveHook,
659-
)} has a spread ` +
674+
`React Hook ${getSource(reactiveHook)} has a spread ` +
660675
"element in its dependency array. This means we can't " +
661676
"statically verify whether you've passed the " +
662677
'correct dependencies.',
@@ -668,12 +683,12 @@ export default {
668683
node: declaredDependencyNode,
669684
message:
670685
'Functions returned from `useEffectEvent` must not be included in the dependency array. ' +
671-
`Remove \`${context.sourceCode.getText(
686+
`Remove \`${getSource(
672687
declaredDependencyNode,
673688
)}\` from the list.`,
674689
suggest: [
675690
{
676-
desc: `Remove the dependency \`${context.sourceCode.getText(
691+
desc: `Remove the dependency \`${getSource(
677692
declaredDependencyNode,
678693
)}\``,
679694
fix(fixer) {
@@ -714,9 +729,7 @@ export default {
714729
reportProblem({
715730
node: declaredDependencyNode,
716731
message:
717-
`React Hook ${context.sourceCode.getText(
718-
reactiveHook,
719-
)} has a ` +
732+
`React Hook ${getSource(reactiveHook)} has a ` +
720733
`complex expression in the dependency array. ` +
721734
'Extract it to a separate variable so it can be statically checked.',
722735
});
@@ -986,7 +999,7 @@ export default {
986999
` However, 'props' will change when *any* prop changes, so the ` +
9871000
`preferred fix is to destructure the 'props' object outside of ` +
9881001
`the ${reactiveHookName} call and refer to those specific props ` +
989-
`inside ${context.sourceCode.getText(reactiveHook)}.`;
1002+
`inside ${getSource(reactiveHook)}.`;
9901003
}
9911004
}
9921005

@@ -1136,7 +1149,7 @@ export default {
11361149
reportProblem({
11371150
node: declaredDependenciesNode,
11381151
message:
1139-
`React Hook ${context.sourceCode.getText(reactiveHook)} has ` +
1152+
`React Hook ${getSource(reactiveHook)} has ` +
11401153
// To avoid a long message, show the next actionable item.
11411154
(getWarningMessage(missingDependencies, 'a', 'missing', 'include') ||
11421155
getWarningMessage(
@@ -1258,9 +1271,7 @@ export default {
12581271
return; // Handled
12591272
}
12601273
// We'll do our best effort to find it, complain otherwise.
1261-
const variable = context.sourceCode
1262-
.getScope(callback)
1263-
.set.get(callback.name);
1274+
const variable = getScope(callback).set.get(callback.name);
12641275
if (variable == null || variable.defs == null) {
12651276
// If it's not in scope, we don't care.
12661277
return; // Handled
@@ -1804,7 +1815,7 @@ function getReactiveHookCallbackIndex(calleeNode, options) {
18041815
}
18051816

18061817
/**
1807-
* ESLint won't assign node.parent to references from context.sourceCode.getScope()
1818+
* ESLint won't assign node.parent to references from context.getScope()
18081819
*
18091820
* So instead we search for the node from an ancestor assigning node.parent
18101821
* as we go. This mutates the AST.

0 commit comments

Comments
 (0)