Skip to content

Commit 8253255

Browse files
committed
Detect props in custom hooks
1 parent 6a42f6b commit 8253255

File tree

2 files changed

+43
-22
lines changed

2 files changed

+43
-22
lines changed

src/util/react.js

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ export const isReactFunctionalHOC = (node) =>
2828
node.id.type === "Identifier" &&
2929
node.id.name[0].toUpperCase() === node.id.name[0];
3030

31+
export const isCustomHook = (node) =>
32+
(node.type === "FunctionDeclaration" ||
33+
(node.type === "VariableDeclarator" &&
34+
node.init &&
35+
(node.init.type === "ArrowFunctionExpression" ||
36+
node.init.type === "FunctionExpression"))) &&
37+
node.id.type === "Identifier" &&
38+
node.id.name.startsWith("use") &&
39+
node.id.name[3] === node.id.name[3].toUpperCase();
40+
3141
export const isUseState = (node) =>
3242
node.type === "VariableDeclarator" &&
3343
node.init &&
@@ -114,29 +124,22 @@ export const isProp = (variable) =>
114124
variable.defs.some(
115125
(def) =>
116126
def.type === "Parameter" &&
117-
isReactFunctionalComponent(
118-
// TODO: Simplify
119-
def.node.type === "ArrowFunctionExpression"
120-
? def.node.parent.type === "CallExpression"
121-
? def.node.parent.parent
122-
: def.node.parent
123-
: def.node,
124-
),
127+
(isReactFunctionalComponent(getDeclNode(def.node)) ||
128+
isCustomHook(getDeclNode(def.node))),
125129
);
126130
export const isHOCProp = (variable) =>
127131
variable.defs.some(
128132
(def) =>
129-
def.type === "Parameter" &&
130-
isReactFunctionalHOC(
131-
// TODO: Simplify
132-
def.node.type === "ArrowFunctionExpression"
133-
? def.node.parent.type === "CallExpression"
134-
? def.node.parent.parent
135-
: def.node.parent
136-
: def.node,
137-
),
133+
def.type === "Parameter" && isReactFunctionalHOC(getDeclNode(def.node)),
138134
);
139135

136+
const getDeclNode = (node) =>
137+
node.type === "ArrowFunctionExpression"
138+
? node.parent.type === "CallExpression"
139+
? node.parent.parent
140+
: node.parent
141+
: node;
142+
140143
export const getUseStateNode = (context, ref) => {
141144
return getUpstreamReactVariables(context, ref.identifier)
142145
.find((variable) => isState(variable))

test/syntax.test.js

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -430,10 +430,9 @@ new MyRuleTester().run("/syntax", {
430430
],
431431
},
432432
{
433-
name: "Custom hook with props",
434-
todo: true, // TODO: `isProp` checks that the def is inside a React component, so ignores custom hooks
433+
name: "FunctionDeclaration custom hook with props",
435434
code: js`
436-
function useCustomHook({ prop }) {
435+
function useCustomHook(prop) {
437436
const [state, setState] = useState(0);
438437
439438
useEffect(() => {
@@ -442,9 +441,28 @@ new MyRuleTester().run("/syntax", {
442441
443442
return state;
444443
}
444+
`,
445+
errors: [
446+
{
447+
messageId: messageIds.avoidInternalEffect,
448+
},
449+
{
450+
messageId: messageIds.avoidDerivedState,
451+
data: { state: "state" },
452+
},
453+
],
454+
},
455+
{
456+
name: "VariableDeclarator custom hook with object props",
457+
code: js`
458+
const useCustomHook = ({ prop }) => {
459+
const [state, setState] = useState(0);
445460
446-
function Component() {
447-
const customState = useCustomHook({ prop: 42 });
461+
useEffect(() => {
462+
setState(prop);
463+
}, [prop]);
464+
465+
return state;
448466
}
449467
`,
450468
errors: [

0 commit comments

Comments
 (0)