Skip to content

Commit 64a8978

Browse files
committed
Simplify IIFE handling. Handle internal callback inside IIFE.
Enahnces #16
1 parent ae504b3 commit 64a8978

File tree

4 files changed

+28
-23
lines changed

4 files changed

+28
-23
lines changed

src/rule.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,7 @@ export const rule = {
7373
// Don't analyze HOC prop callbacks -- we don't have control over them to lift state or logic
7474
!isHOCProp(ref.resolved)),
7575
)
76-
.filter(
77-
(ref) =>
78-
isDirectCall(ref.identifier) ||
79-
isDirectCall(findIIFE(ref.identifier)),
80-
)
76+
.filter((ref) => isDirectCall(ref.identifier))
8177
.forEach((ref) => {
8278
const callExpr = getCallExpr(ref);
8379

src/util/ast.js

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,7 @@ export const getCallExpr = (ref, current = ref.identifier.parent) => {
9595
return undefined;
9696
};
9797

98-
export const findIIFE = (node) => {
99-
if (!node) {
100-
return undefined;
101-
} else if (
102-
node.type === "CallExpression" &&
103-
(node.callee.type === "ArrowFunctionExpression" ||
104-
node.callee.type === "FunctionExpression")
105-
) {
106-
return node;
107-
} else {
108-
return findIIFE(node.parent);
109-
}
110-
};
98+
export const isIIFE = (node) =>
99+
node.type === "CallExpression" &&
100+
(node.callee.type === "ArrowFunctionExpression" ||
101+
node.callee.type === "FunctionExpression");

src/util/react.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
getUpstreamVariables,
44
getDownstreamRefs,
55
getCallExpr,
6+
isIIFE,
67
} from "./ast.js";
78

89
export const isReactFunctionalComponent = (node) =>
@@ -151,15 +152,14 @@ export const getUseStateNode = (context, ref) => {
151152
// When false, it's likely inside a callback, e.g. a listener, or Promise chain that retrieves external data.
152153
// Note we'll still analyze derived setters because isStateSetter considers that.
153154
// Heuristic inspired by https://eslint-react.xyz/docs/rules/hooks-extra-no-direct-set-state-in-use-effect
154-
// TODO: Also returns false for IIFEs, which could cause a false negative.
155-
// But IIFEs in effects are typically used to call async functions, implying it retrieves external state.
156-
// So, not a big deal.
157155
export const isDirectCall = (node) => {
158156
if (!node) {
159157
return false;
160158
} else if (
161-
node.type === "ArrowFunctionExpression" ||
162-
node.type === "FunctionExpression"
159+
(node.type === "ArrowFunctionExpression" ||
160+
node.type === "FunctionExpression") &&
161+
// Keep walking up IIFEs -- we consider them direct calls because they're invoked immediately, as opposed to being a callback
162+
!isIIFE(node.parent)
163163
) {
164164
return isUseEffect(node.parent);
165165
} else {

test/syntax.test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,24 @@ new MyRuleTester().run("/syntax", {
171171
};
172172
`,
173173
},
174+
{
175+
name: "Internal callback inside IIFE",
176+
code: js`
177+
import { useEffect, useState } from 'react';
178+
179+
export const MyComponent = () => {
180+
const [state, setState] = useState();
181+
182+
useEffect(() => {
183+
(async () => {
184+
window.addEventListener('load', () => {
185+
setState('Loaded');
186+
});
187+
})();
188+
}, []);
189+
};
190+
`,
191+
},
174192
],
175193
invalid: [
176194
{

0 commit comments

Comments
 (0)