Skip to content

Commit dd2ab71

Browse files
authored
refactor: hookCollector add hookCalls collect, remove cost field (#216)
1 parent 3947585 commit dd2ab71

File tree

9 files changed

+102
-31
lines changed

9 files changed

+102
-31
lines changed

CHANGELOG.md

+26
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
1+
## v0.9.6 (Draft)
2+
3+
### Release Notes
4+
5+
#### Add rule `react-hooks/prefer-use-state-lazy-initialization`
6+
7+
---
8+
9+
#### 🏠 Internal
10+
11+
- `@eslint-react/eslint-plugin-react-hooks`
12+
- Add rule `react-hooks/prefer-use-state-lazy-initialization`.
13+
14+
- `@eslint-react/eslint-plugin-debug`
15+
- Report `hookCalls.length` instead of `cost`.
16+
17+
- `@eslint-react/core`
18+
- Replace `cost` field in `ERHook` with `hookCalls` field.
19+
- `hookCollector` add `hookCalls` collect.
20+
21+
#### Authors: 1
22+
23+
- Eva1ent ([@Rel1cx](https://github.com/Rel1cx))
24+
25+
---
26+
127
## v0.9.5 (Mon Dec 11 2023)
228

329
### Release Notes

packages/core/docs/README.md

+34-10
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
- [isMemberExpressionOfReactMember](README.md#ismemberexpressionofreactmember)
4949
- [isMemoOrForwardRefCall](README.md#ismemoorforwardrefcall)
5050
- [isPureComponent](README.md#ispurecomponent)
51+
- [isReactHookCall](README.md#isreacthookcall)
5152
- [isReactHookCallWithName](README.md#isreacthookcallwithname)
5253
- [isUseCallbackCall](README.md#isusecallbackcall)
5354
- [isUseContextCall](README.md#isusecontextcall)
@@ -152,13 +153,13 @@
152153

153154
#### Type declaration
154155

155-
| Name | Type |
156-
| :----- | :-------------------- |
157-
| `_` | `string` |
158-
| `cost` | `number` |
159-
| `id` | `TSESTree.Identifier` |
160-
| `name` | `string` |
161-
| `node` | `TSESTreeFunction` |
156+
| Name | Type |
157+
| :---------- | :-------------------------- |
158+
| `_` | `string` |
159+
| `hookCalls` | `TSESTree.CallExpression`[] |
160+
| `id` | `TSESTree.Identifier` |
161+
| `name` | `string` |
162+
| `node` | `TSESTreeFunction` |
162163

163164
## Variables
164165

@@ -678,6 +679,25 @@ Check if a node is a React PureComponent
678679

679680
---
680681

682+
### isReactHookCall
683+
684+
**isReactHookCall**(`node`): `void`
685+
686+
TODO: Implement this function.
687+
Check if the given node is a React Hook call by its name and its hierarchy.
688+
689+
#### Parameters
690+
691+
| Name | Type | Description |
692+
| :----- | :--------------- | :----------------- |
693+
| `node` | `CallExpression` | The node to check. |
694+
695+
#### Returns
696+
697+
`void`
698+
699+
---
700+
681701
### isReactHookCallWithName
682702

683703
**isReactHookCallWithName**(`name`): (`node`: `CallExpression`, `context`: `Readonly`\<`RuleContext`\<`string`, readonly `unknown`[]\>\>, `pragma`: `string`) => `boolean`
@@ -993,16 +1013,20 @@ _ = <Component rows={[{ render: () => <div /> }]} />;
9931013

9941014
**unsafeIsReactHookCall**(`node`): `boolean`
9951015

1016+
Check if the given node is a React Hook call by its name.
1017+
9961018
#### Parameters
9971019

998-
| Name | Type |
999-
| :----- | :--------------- |
1000-
| `node` | `CallExpression` |
1020+
| Name | Type | Description |
1021+
| :----- | :--------------- | :----------------- |
1022+
| `node` | `CallExpression` | The node to check. |
10011023

10021024
#### Returns
10031025

10041026
`boolean`
10051027

1028+
`true` if the node is a React hook call, `false` otherwise.
1029+
10061030
---
10071031

10081032
### unsafeIsRenderFunction

packages/core/src/hook/hook-call.ts

+16
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ export const isUseImperativeHandleCall = isReactHookCallWithName("useImperativeH
3737

3838
export const isUseDebugValueCall = isReactHookCallWithName("useDebugValue");
3939

40+
/**
41+
* Check if the given node is a React Hook call by its name.
42+
* @param node The node to check.
43+
* @returns `true` if the node is a React hook call, `false` otherwise.
44+
*/
4045
export function unsafeIsReactHookCall(node: TSESTree.CallExpression) {
4146
if (node.callee.type === NodeType.Identifier) {
4247
return isValidReactHookName(node.callee.name);
@@ -52,6 +57,17 @@ export function unsafeIsReactHookCall(node: TSESTree.CallExpression) {
5257
return false;
5358
}
5459

60+
/**
61+
* TODO: Implement this function.
62+
* Check if the given node is a React Hook call by its name and its hierarchy.
63+
* @param node The node to check.
64+
*/
65+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
66+
export function isReactHookCall(node: TSESTree.CallExpression) {
67+
// eslint-disable-next-line functional/no-throw-statements
68+
throw new Error("Not implemented");
69+
}
70+
5571
export function isMemoOrForwardRefCall(node: TSESTree.Node, context: RuleContext) {
5672
return isCallFromPragma("memo")(node, context)
5773
|| isCallFromPragma("forwardRef")(node, context);

packages/core/src/hook/hook-collector.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { uid } from "@eslint-react/shared";
33
import { O } from "@eslint-react/tools";
44
import type { ESLintUtils, TSESTree } from "@typescript-eslint/utils";
55

6+
import type { ERHook } from "./hook";
67
import { unsafeIsReactHookCall } from "./hook-call";
7-
import type { ERHook } from "./hook-kind";
88
import { isValidReactHookName } from "./hook-name";
99

1010
export function hookCollector(): {
@@ -34,7 +34,7 @@ export function hookCollector(): {
3434
_: key,
3535
id,
3636
name,
37-
cost: 1,
37+
hookCalls: [],
3838
node: currentFn,
3939
});
4040
}
@@ -76,7 +76,10 @@ export function hookCollector(): {
7676
}
7777
hooks.set(hook._, {
7878
...hook,
79-
cost: hook.cost + 1,
79+
hookCalls: [
80+
...hook.hookCalls,
81+
node,
82+
],
8083
});
8184
}
8285
},

packages/core/src/hook/hook-kind.ts renamed to packages/core/src/hook/hook.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,7 @@ export type ERHook = {
1717
// The number of hooks defined in the hook, reserved for future use
1818
// size: number;
1919
// The number of slots the hook takes, (1 + the number of other hooks it calls)
20-
cost: number;
20+
// cost: number;
21+
// The other hooks called by the hook
22+
hookCalls: TSESTree.CallExpression[];
2123
};

packages/core/src/hook/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export * from "./hierarchy";
2+
export * from "./hook";
23
export * from "./hook-call";
34
export * from "./hook-collector";
4-
export * from "./hook-kind";
55
export * from "./hook-name";

packages/eslint-plugin-debug/src/rules/react-hooks.spec.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ ruleTester.run(RULE_NAME, rule, {
2525
messageId: "REACT_HOOKS",
2626
data: {
2727
name: "useToggle",
28-
cost: 2,
28+
hookCalls: 1,
2929
},
3030
},
3131
],
@@ -42,7 +42,7 @@ ruleTester.run(RULE_NAME, rule, {
4242
messageId: "REACT_HOOKS",
4343
data: {
4444
name: "useSorted",
45-
cost: 1,
45+
hookCalls: 0,
4646
},
4747
},
4848
],
@@ -64,14 +64,14 @@ ruleTester.run(RULE_NAME, rule, {
6464
messageId: "REACT_HOOKS",
6565
data: {
6666
name: "useToggle",
67-
cost: 2,
67+
hookCalls: 1,
6868
},
6969
},
7070
{
7171
messageId: "REACT_HOOKS",
7272
data: {
7373
name: "useSorted",
74-
cost: 1,
74+
hookCalls: 0,
7575
},
7676
},
7777
],
@@ -95,7 +95,7 @@ ruleTester.run(RULE_NAME, rule, {
9595
messageId: "REACT_HOOKS",
9696
data: {
9797
name: "useClassnames",
98-
cost: 1,
98+
hookCalls: 0,
9999
},
100100
},
101101
],
@@ -119,7 +119,7 @@ ruleTester.run(RULE_NAME, rule, {
119119
messageId: "REACT_HOOKS",
120120
data: {
121121
name: "useClassnames",
122-
cost: 1,
122+
hookCalls: 0,
123123
},
124124
},
125125
],
@@ -140,14 +140,14 @@ ruleTester.run(RULE_NAME, rule, {
140140
messageId: "REACT_HOOKS",
141141
data: {
142142
name: "useNestedHook",
143-
cost: 2,
143+
hookCalls: 1,
144144
},
145145
},
146146
{
147147
messageId: "REACT_HOOKS",
148148
data: {
149149
name: "useInnerHook",
150-
cost: 1,
150+
hookCalls: 0,
151151
},
152152
},
153153
],
@@ -168,14 +168,14 @@ ruleTester.run(RULE_NAME, rule, {
168168
messageId: "REACT_HOOKS",
169169
data: {
170170
name: "useNestedHook",
171-
cost: 1,
171+
hookCalls: 0,
172172
},
173173
},
174174
{
175175
messageId: "REACT_HOOKS",
176176
data: {
177177
name: "useInnerHook",
178-
cost: 2,
178+
hookCalls: 1,
179179
},
180180
},
181181
],
@@ -196,7 +196,7 @@ ruleTester.run(RULE_NAME, rule, {
196196
messageId: "REACT_HOOKS",
197197
data: {
198198
name: "useNestedHook",
199-
cost: 1,
199+
hookCalls: 0,
200200
},
201201
},
202202
],

packages/eslint-plugin-debug/src/rules/react-hooks.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export default createRule<[], MessageID>({
1818
},
1919
schema: [],
2020
messages: {
21-
REACT_HOOKS: "[react hooks] name: {{name}}, cost: {{cost}}",
21+
REACT_HOOKS: "[react hooks] name: {{name}}, hookCalls: {{hookCalls}}",
2222
},
2323
},
2424
defaultOptions: [],
@@ -30,11 +30,11 @@ export default createRule<[], MessageID>({
3030
"Program:exit"(node) {
3131
const allHooks = ctx.getAllHooks(node);
3232

33-
for (const { name, cost, node } of allHooks.values()) {
33+
for (const { name, hookCalls, node } of allHooks.values()) {
3434
context.report({
3535
data: {
3636
name,
37-
cost,
37+
hookCalls: hookCalls.length,
3838
},
3939
messageId: "REACT_HOOKS",
4040
node,

packages/eslint-plugin-react-hooks/src/rules/ensure-custom-hooks-using-other-hooks.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ export default createRule<[], MessageID>({
2929
...listeners,
3030
"Program:exit"(node) {
3131
const allHooks = ctx.getAllHooks(node);
32-
for (const { name, cost, node } of allHooks.values()) {
33-
if (cost > 1) {
32+
for (const { name, hookCalls, node } of allHooks.values()) {
33+
if (hookCalls.length > 0) {
3434
continue;
3535
}
3636

0 commit comments

Comments
 (0)