Skip to content

Commit a095950

Browse files
committed
fix(no-derived-state): account for non-calls when flagging single setter calls
1 parent da144f5 commit a095950

File tree

3 files changed

+20
-41
lines changed

3 files changed

+20
-41
lines changed

src/no-derived-state.js

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
isHOCProp,
1111
getUpstreamReactVariables,
1212
isState,
13+
countCalls,
1314
} from "./util/react.js";
1415
import { getCallExpr, getDownstreamRefs } from "./util/ast.js";
1516
import { arraysEqual } from "./util/javascript.js";
@@ -48,36 +49,26 @@ export const rule = {
4849
const stateName = (
4950
useStateNode.id.elements[0] ?? useStateNode.id.elements[1]
5051
)?.name;
51-
const argsRefs = callExpr.arguments.flatMap((arg) =>
52-
getDownstreamRefs(context, arg),
53-
);
54-
const argsUpstreamVariables = argsRefs.flatMap((ref) =>
52+
53+
const argsUpstreamVars = callExpr.arguments
54+
.flatMap((arg) => getDownstreamRefs(context, arg))
55+
.flatMap((ref) => getUpstreamReactVariables(context, ref.resolved));
56+
const depsUpstreamVars = depsRefs.flatMap((ref) =>
5557
getUpstreamReactVariables(context, ref.resolved),
5658
);
57-
const isAllArgsInternal = argsUpstreamVariables.notEmptyEvery(
59+
const isAllArgsInternal = argsUpstreamVars.notEmptyEvery(
5860
(variable) =>
5961
isState(variable) || (isProp(variable) && !isHOCProp(variable)),
6062
);
61-
const isAllArgsInDeps = argsRefs
62-
.filter((ref) =>
63-
ref.resolved.defs.every((def) => def.type !== "Parameter"),
64-
)
65-
.notEmptyEvery((argRef) =>
66-
depsRefs.some((depRef) =>
67-
arraysEqual(
68-
getUpstreamReactVariables(context, argRef.resolved),
69-
getUpstreamReactVariables(context, depRef.resolved),
70-
),
71-
),
72-
);
73-
// In this case the derived state will always be in sync,
74-
// thus it could be computed directly during render
75-
const isStateSetterCalledOnce =
76-
ref.resolved.references.length - 1 === 1;
63+
const isAllArgsInDeps = argsUpstreamVars.notEmptyEvery((argVar) =>
64+
depsUpstreamVars.some((depVar) => argVar.name === depVar.name),
65+
);
7766

7867
if (
7968
isAllArgsInternal ||
80-
(isAllArgsInDeps && isStateSetterCalledOnce)
69+
// In this case the derived state will always be in sync,
70+
// thus it could be computed directly during render
71+
(isAllArgsInDeps && countCalls(ref) === 1)
8172
) {
8273
context.report({
8374
node: callExpr,

src/util/react.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,10 @@ const countUseStates = (context, componentNode) => {
242242
return count;
243243
};
244244

245-
export const countStateSetterCalls = (stateSetterRef) =>
246-
stateSetterRef.resolved.references.length - 1; // -1 for the initial declaration
245+
export const countCalls = (ref) =>
246+
ref.resolved.references.filter(
247+
(ref) => ref.identifier.parent.type === "CallExpression",
248+
).length;
247249

248250
// Returns the component or custom hook that contains the `useEffect` node
249251
const findContainingNode = (node) => {

test/no-derived-state.test.js

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -147,20 +147,6 @@ new MyRuleTester().run(name, rule, {
147147
}
148148
`,
149149
},
150-
{
151-
name: "From external state via member function",
152-
code: js`
153-
function Counter() {
154-
const countGetter = useSomeAPI();
155-
const [count, setCount] = useState(0);
156-
157-
useEffect(() => {
158-
const newCount = countGetter.getCount();
159-
setCount(newCount);
160-
}, [countGetter, setCount]);
161-
}
162-
`,
163-
},
164150
{
165151
name: "Via pure local function",
166152
code: js`
@@ -445,7 +431,7 @@ new MyRuleTester().run(name, rule, {
445431
446432
useEffect(() => {
447433
setSelectedPost(posts[0]);
448-
}, [posts]);
434+
}, [posts, setSelectedPost]);
449435
}
450436
`,
451437
errors: [
@@ -465,7 +451,7 @@ new MyRuleTester().run(name, rule, {
465451
useEffect(() => {
466452
const prefixedName = 'Dr. ' + name;
467453
setFullName(prefixedName)
468-
}, [name]);
454+
}, [name, setFullName]);
469455
}
470456
`,
471457
errors: [
@@ -486,7 +472,7 @@ new MyRuleTester().run(name, rule, {
486472
487473
useEffect(() => {
488474
setLocation(history.location);
489-
}, [history.location]);
475+
}, [history.location, setLocation]);
490476
});
491477
`,
492478
errors: [

0 commit comments

Comments
 (0)