Skip to content

Commit 8c39ca3

Browse files
committed
Don't lint against Hooks after conditional throw
Seems like this should be OK. Fixes #14038. Now when tracking paths, we completely ignore segments that end in a throw. In https://eslint.org/docs/developer-guide/code-path-analysis I don't see a way to detect throws other than manually tracking them, so that's what I've done.
1 parent 169f935 commit 8c39ca3

File tree

2 files changed

+29
-1
lines changed

2 files changed

+29
-1
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,15 @@ eslintTester.run('react-hooks', ReactHooksESLintRule, {
261261
useState();
262262
}
263263
`,
264+
`
265+
// Valid because exceptions abort rendering
266+
function RegressionTest() {
267+
if (page == null) {
268+
throw new Error('oh no!');
269+
}
270+
useState();
271+
}
272+
`,
264273
],
265274
invalid: [
266275
{

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,20 +71,25 @@ export default {
7171
create(context) {
7272
const codePathReactHooksMapStack = [];
7373
const codePathSegmentStack = [];
74+
const codePathThrowingSegmentsStack = [];
7475
return {
7576
// Maintain code segment path stack as we traverse.
7677
onCodePathSegmentStart: segment => codePathSegmentStack.push(segment),
7778
onCodePathSegmentEnd: () => codePathSegmentStack.pop(),
7879

7980
// Maintain code path stack as we traverse.
80-
onCodePathStart: () => codePathReactHooksMapStack.push(new Map()),
81+
onCodePathStart: () => {
82+
codePathReactHooksMapStack.push(new Map());
83+
codePathThrowingSegmentsStack.push(new Set());
84+
},
8185

8286
// Process our code path.
8387
//
8488
// Everything is ok if all React Hooks are both reachable from the initial
8589
// segment and reachable from every final segment.
8690
onCodePathEnd(codePath, codePathNode) {
8791
const reactHooksMap = codePathReactHooksMapStack.pop();
92+
const throwingSegments = codePathThrowingSegmentsStack.pop();
8893
if (reactHooksMap.size === 0) {
8994
return;
9095
}
@@ -115,6 +120,10 @@ export default {
115120
*/
116121

117122
function countPathsFromStart(segment) {
123+
if (throwingSegments.has(segment)) {
124+
return 0;
125+
}
126+
118127
const {cache} = countPathsFromStart;
119128
let paths = cache.get(segment.id);
120129

@@ -175,6 +184,10 @@ export default {
175184
*/
176185

177186
function countPathsToEnd(segment) {
187+
if (throwingSegments.has(segment)) {
188+
return 0;
189+
}
190+
178191
const {cache} = countPathsToEnd;
179192
let paths = cache.get(segment.id);
180193

@@ -462,6 +475,12 @@ export default {
462475
reactHooks.push(node.callee);
463476
}
464477
},
478+
479+
ThrowStatement(node) {
480+
const throwingSegments = last(codePathThrowingSegmentsStack);
481+
const codePathSegment = last(codePathSegmentStack);
482+
throwingSegments.add(codePathSegment);
483+
},
465484
};
466485
},
467486
};

0 commit comments

Comments
 (0)