eslint-plugin-react-hooks-extra 2.0.0-next.3 → 2.0.0-next.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +0 -8
- package/dist/index.js +107 -439
- package/package.json +18 -18
package/dist/index.d.ts
CHANGED
|
@@ -13,10 +13,6 @@ declare const _default: {
|
|
|
13
13
|
readonly rules: {
|
|
14
14
|
readonly "no-direct-set-state-in-use-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"noDirectSetStateInUseEffect", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
15
15
|
readonly "no-direct-set-state-in-use-layout-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"noDirectSetStateInUseLayoutEffect", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
16
|
-
readonly "no-unnecessary-use-callback": _typescript_eslint_utils_ts_eslint.RuleModule<"noUnnecessaryUseCallback", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
17
|
-
readonly "no-unnecessary-use-memo": _typescript_eslint_utils_ts_eslint.RuleModule<"noUnnecessaryUseMemo", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
18
|
-
readonly "no-unnecessary-use-prefix": _typescript_eslint_utils_ts_eslint.RuleModule<"noUnnecessaryUsePrefix", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
19
|
-
readonly "prefer-use-state-lazy-initialization": _typescript_eslint_utils_ts_eslint.RuleModule<"preferUseStateLazyInitialization", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
20
16
|
};
|
|
21
17
|
};
|
|
22
18
|
};
|
|
@@ -35,10 +31,6 @@ declare const _default: {
|
|
|
35
31
|
rules: {
|
|
36
32
|
readonly "no-direct-set-state-in-use-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"noDirectSetStateInUseEffect", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
37
33
|
readonly "no-direct-set-state-in-use-layout-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"noDirectSetStateInUseLayoutEffect", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
38
|
-
readonly "no-unnecessary-use-callback": _typescript_eslint_utils_ts_eslint.RuleModule<"noUnnecessaryUseCallback", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
39
|
-
readonly "no-unnecessary-use-memo": _typescript_eslint_utils_ts_eslint.RuleModule<"noUnnecessaryUseMemo", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
40
|
-
readonly "no-unnecessary-use-prefix": _typescript_eslint_utils_ts_eslint.RuleModule<"noUnnecessaryUsePrefix", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
41
|
-
readonly "prefer-use-state-lazy-initialization": _typescript_eslint_utils_ts_eslint.RuleModule<"preferUseStateLazyInitialization", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
42
34
|
};
|
|
43
35
|
};
|
|
44
36
|
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as AST from '@eslint-react/ast';
|
|
2
|
-
import * as
|
|
3
|
-
import {
|
|
2
|
+
import * as ER from '@eslint-react/core';
|
|
3
|
+
import { getOrElseUpdate, constVoid } from '@eslint-react/eff';
|
|
4
4
|
import { getDocsUrl, getSettingsFromContext } from '@eslint-react/shared';
|
|
5
|
-
import * as
|
|
5
|
+
import * as VAR from '@eslint-react/var';
|
|
6
6
|
import { AST_NODE_TYPES } from '@typescript-eslint/types';
|
|
7
7
|
import { match } from 'ts-pattern';
|
|
8
8
|
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
@@ -22,41 +22,97 @@ __export(recommended_exports, {
|
|
|
22
22
|
var name = "react-hooks-extra/recommended";
|
|
23
23
|
var rules = {
|
|
24
24
|
"react-hooks-extra/no-direct-set-state-in-use-effect": "warn",
|
|
25
|
-
"react-hooks-extra/no-unnecessary-use-prefix": "warn"
|
|
26
|
-
"react-hooks-extra/prefer-use-state-lazy-initialization": "warn"
|
|
25
|
+
"react-hooks-extra/no-unnecessary-use-prefix": "warn"
|
|
27
26
|
};
|
|
28
27
|
|
|
29
28
|
// package.json
|
|
30
29
|
var name2 = "eslint-plugin-react-hooks-extra";
|
|
31
|
-
var version = "2.0.0-next.
|
|
30
|
+
var version = "2.0.0-next.31";
|
|
32
31
|
var createRule = ESLintUtils.RuleCreator(getDocsUrl("hooks-extra"));
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
|
|
33
|
+
// src/rules/no-direct-set-state-in-use-effect.ts
|
|
34
|
+
var RULE_NAME = "no-direct-set-state-in-use-effect";
|
|
35
|
+
var RULE_FEATURES = [
|
|
36
|
+
"EXP"
|
|
37
|
+
];
|
|
38
|
+
var no_direct_set_state_in_use_effect_default = createRule({
|
|
39
|
+
meta: {
|
|
40
|
+
type: "problem",
|
|
41
|
+
docs: {
|
|
42
|
+
description: "Disallow direct calls to the `set` function of `useState` in `useEffect`.",
|
|
43
|
+
[Symbol.for("rule_features")]: RULE_FEATURES
|
|
44
|
+
},
|
|
45
|
+
messages: {
|
|
46
|
+
noDirectSetStateInUseEffect: "Do not call the 'set' function '{{name}}' of 'useState' directly in 'useEffect'."
|
|
47
|
+
},
|
|
48
|
+
schema: []
|
|
49
|
+
},
|
|
50
|
+
name: RULE_NAME,
|
|
51
|
+
create,
|
|
52
|
+
defaultOptions: []
|
|
53
|
+
});
|
|
54
|
+
function create(context) {
|
|
55
|
+
if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
|
|
56
|
+
return useNoDirectSetStateInUseEffect(context, {
|
|
57
|
+
onViolation(ctx, node, data) {
|
|
58
|
+
ctx.report({ messageId: "noDirectSetStateInUseEffect", node, data });
|
|
59
|
+
},
|
|
60
|
+
useEffectKind: "useEffect"
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function useNoDirectSetStateInUseEffect(context, options) {
|
|
64
|
+
const { onViolation, useEffectKind } = options;
|
|
65
|
+
const settings = getSettingsFromContext(context);
|
|
66
|
+
const hooks = settings.additionalHooks;
|
|
67
|
+
const getText = (n) => context.sourceCode.getText(n);
|
|
68
|
+
const isUseEffectLikeCall = ER.isReactHookCallWithNameAlias(context, useEffectKind, hooks[useEffectKind]);
|
|
69
|
+
const isUseStateCall = ER.isReactHookCallWithNameAlias(context, "useState", hooks.useState);
|
|
70
|
+
const isUseMemoCall = ER.isReactHookCallWithNameAlias(context, "useMemo", hooks.useMemo);
|
|
71
|
+
const isUseCallbackCall = ER.isReactHookCallWithNameAlias(context, "useCallback", hooks.useCallback);
|
|
72
|
+
const functionEntries = [];
|
|
73
|
+
const setupFunctionRef = { current: null };
|
|
74
|
+
const setupFunctionIdentifiers = [];
|
|
75
|
+
const indFunctionCalls = [];
|
|
76
|
+
const indSetStateCalls = /* @__PURE__ */ new WeakMap();
|
|
77
|
+
const indSetStateCallsInUseEffectArg0 = /* @__PURE__ */ new WeakMap();
|
|
78
|
+
const indSetStateCallsInUseEffectSetup = /* @__PURE__ */ new Map();
|
|
79
|
+
const indSetStateCallsInUseMemoOrCallback = /* @__PURE__ */ new WeakMap();
|
|
80
|
+
const onSetupFunctionEnter = (node) => {
|
|
81
|
+
setupFunctionRef.current = node;
|
|
82
|
+
};
|
|
83
|
+
const onSetupFunctionExit = (node) => {
|
|
84
|
+
if (setupFunctionRef.current === node) {
|
|
85
|
+
setupFunctionRef.current = null;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
function isFunctionOfUseEffectSetup(node) {
|
|
89
|
+
return node.parent?.type === AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseEffectLikeCall(node.parent);
|
|
90
|
+
}
|
|
91
|
+
function getCallName(node) {
|
|
92
|
+
if (node.type === AST_NODE_TYPES.CallExpression) {
|
|
93
|
+
return AST.toStringFormat(node.callee, getText);
|
|
94
|
+
}
|
|
95
|
+
return AST.toStringFormat(node, getText);
|
|
96
|
+
}
|
|
97
|
+
function getCallKind(node) {
|
|
98
|
+
return match(node).when(isUseStateCall, () => "useState").when(isUseEffectLikeCall, () => useEffectKind).when(isSetStateCall, () => "setState").when(AST.isThenCall, () => "then").otherwise(() => "other");
|
|
99
|
+
}
|
|
100
|
+
function getFunctionKind(node) {
|
|
101
|
+
return match(node).when(isFunctionOfUseEffectSetup, () => "setup").when(AST.isImmediatelyInvokedFunction, () => "immediate").otherwise(() => "other");
|
|
102
|
+
}
|
|
103
|
+
function isIdFromUseStateCall(topLevelId, at) {
|
|
104
|
+
const variable = VAR.findVariable(topLevelId, context.sourceCode.getScope(topLevelId));
|
|
105
|
+
const variableNode = VAR.getVariableInitNode(variable, 0);
|
|
38
106
|
if (variableNode == null) return false;
|
|
39
107
|
if (variableNode.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
40
|
-
if (!
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
function isFromUseStateCall(context, settings) {
|
|
45
|
-
const predicate = (topLevelId, call) => {
|
|
46
|
-
const { parent } = call;
|
|
47
|
-
if (!("id" in parent) || parent.id?.type !== AST_NODE_TYPES.ArrayPattern) {
|
|
108
|
+
if (!ER.isReactHookCallWithNameAlias(context, "useState", hooks.useState)(variableNode)) return false;
|
|
109
|
+
const variableNodeParent = variableNode.parent;
|
|
110
|
+
if (!("id" in variableNodeParent) || variableNodeParent.id?.type !== AST_NODE_TYPES.ArrayPattern) {
|
|
48
111
|
return true;
|
|
49
112
|
}
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
function isFunctionOfImmediatelyInvoked(node) {
|
|
55
|
-
return node.type !== AST_NODE_TYPES.FunctionDeclaration && node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee === node;
|
|
56
|
-
}
|
|
57
|
-
function isSetFunctionCall(context, settings) {
|
|
58
|
-
const isIdFromUseStateCall = isFromUseStateCall(context, settings);
|
|
59
|
-
return (node) => {
|
|
113
|
+
return variableNodeParent.id.elements.findIndex((e) => e?.type === AST_NODE_TYPES.Identifier && e.name === topLevelId.name) === at;
|
|
114
|
+
}
|
|
115
|
+
function isSetStateCall(node) {
|
|
60
116
|
switch (node.callee.type) {
|
|
61
117
|
// const data = useState();
|
|
62
118
|
// data.at(1)();
|
|
@@ -74,7 +130,7 @@ function isSetFunctionCall(context, settings) {
|
|
|
74
130
|
return false;
|
|
75
131
|
}
|
|
76
132
|
const indexScope = context.sourceCode.getScope(node);
|
|
77
|
-
const indexValue =
|
|
133
|
+
const indexValue = VAR.toStaticValue({
|
|
78
134
|
kind: "lazy",
|
|
79
135
|
node: index,
|
|
80
136
|
initialScope: indexScope
|
|
@@ -84,7 +140,7 @@ function isSetFunctionCall(context, settings) {
|
|
|
84
140
|
// const [data, setData] = useState();
|
|
85
141
|
// setData();
|
|
86
142
|
case AST_NODE_TYPES.Identifier: {
|
|
87
|
-
return isIdFromUseStateCall(node.callee);
|
|
143
|
+
return isIdFromUseStateCall(node.callee, 1);
|
|
88
144
|
}
|
|
89
145
|
// const data = useState();
|
|
90
146
|
// data[1]();
|
|
@@ -94,84 +150,17 @@ function isSetFunctionCall(context, settings) {
|
|
|
94
150
|
}
|
|
95
151
|
const property = node.callee.property;
|
|
96
152
|
const propertyScope = context.sourceCode.getScope(node);
|
|
97
|
-
const propertyValue =
|
|
153
|
+
const propertyValue = VAR.toStaticValue({
|
|
98
154
|
kind: "lazy",
|
|
99
155
|
node: property,
|
|
100
156
|
initialScope: propertyScope
|
|
101
157
|
}).value;
|
|
102
|
-
return propertyValue === 1 && isIdFromUseStateCall(node.callee.object);
|
|
158
|
+
return propertyValue === 1 && isIdFromUseStateCall(node.callee.object, 1);
|
|
103
159
|
}
|
|
104
160
|
default: {
|
|
105
161
|
return false;
|
|
106
162
|
}
|
|
107
163
|
}
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
function isThenCall(node) {
|
|
111
|
-
return node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "then";
|
|
112
|
-
}
|
|
113
|
-
function isVariableDeclaratorFromHookCall(node) {
|
|
114
|
-
if (node.type !== AST_NODE_TYPES.VariableDeclarator) {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
if (node.id.type !== AST_NODE_TYPES.Identifier) {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
if (node.init?.type !== AST_NODE_TYPES.CallExpression) {
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
switch (node.init.callee.type) {
|
|
124
|
-
case AST_NODE_TYPES.Identifier:
|
|
125
|
-
return ER7.isReactHookName(node.init.callee.name);
|
|
126
|
-
case AST_NODE_TYPES.MemberExpression:
|
|
127
|
-
return node.init.callee.property.type === AST_NODE_TYPES.Identifier && ER7.isReactHookName(node.init.callee.property.name);
|
|
128
|
-
default:
|
|
129
|
-
return false;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// src/hooks/use-no-direct-set-state-in-use-effect.ts
|
|
134
|
-
function useNoDirectSetStateInUseEffect(context, options) {
|
|
135
|
-
const { onViolation, useEffectKind } = options;
|
|
136
|
-
const settings = getSettingsFromContext(context);
|
|
137
|
-
const hooks = settings.additionalHooks;
|
|
138
|
-
const getText = (n) => context.sourceCode.getText(n);
|
|
139
|
-
const isUseEffectLikeCall = ER7.isReactHookCallWithNameAlias(context, useEffectKind, hooks[useEffectKind]);
|
|
140
|
-
const isUseStateCall = ER7.isReactHookCallWithNameAlias(context, "useState", hooks.useState);
|
|
141
|
-
const isUseMemoCall = ER7.isReactHookCallWithNameAlias(context, "useMemo", hooks.useMemo);
|
|
142
|
-
const isUseCallbackCall = ER7.isReactHookCallWithNameAlias(context, "useCallback", hooks.useCallback);
|
|
143
|
-
const isSetStateCall = isSetFunctionCall(context, settings);
|
|
144
|
-
const isIdFromUseStateCall = isFromUseStateCall(context, settings);
|
|
145
|
-
const functionEntries = [];
|
|
146
|
-
const setupFunctionRef = { current: null };
|
|
147
|
-
const setupFunctionIdentifiers = [];
|
|
148
|
-
const indFunctionCalls = [];
|
|
149
|
-
const indSetStateCalls = /* @__PURE__ */ new WeakMap();
|
|
150
|
-
const indSetStateCallsInUseEffectArg0 = /* @__PURE__ */ new WeakMap();
|
|
151
|
-
const indSetStateCallsInUseEffectSetup = /* @__PURE__ */ new Map();
|
|
152
|
-
const indSetStateCallsInUseMemoOrCallback = /* @__PURE__ */ new WeakMap();
|
|
153
|
-
const onSetupFunctionEnter = (node) => {
|
|
154
|
-
setupFunctionRef.current = node;
|
|
155
|
-
};
|
|
156
|
-
const onSetupFunctionExit = (node) => {
|
|
157
|
-
if (setupFunctionRef.current === node) {
|
|
158
|
-
setupFunctionRef.current = null;
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
function isFunctionOfUseEffectSetup(node) {
|
|
162
|
-
return node.parent?.type === AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseEffectLikeCall(node.parent);
|
|
163
|
-
}
|
|
164
|
-
function getCallName(node) {
|
|
165
|
-
if (node.type === AST_NODE_TYPES.CallExpression) {
|
|
166
|
-
return AST.toString(node.callee, getText);
|
|
167
|
-
}
|
|
168
|
-
return AST.toString(node, getText);
|
|
169
|
-
}
|
|
170
|
-
function getCallKind(node) {
|
|
171
|
-
return match(node).when(isUseStateCall, () => "useState").when(isUseEffectLikeCall, () => useEffectKind).when(isSetStateCall, () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
|
|
172
|
-
}
|
|
173
|
-
function getFunctionKind(node) {
|
|
174
|
-
return match(node).when(isFunctionOfUseEffectSetup, () => "setup").when(isFunctionOfImmediatelyInvoked, () => "immediate").otherwise(() => "other");
|
|
175
164
|
}
|
|
176
165
|
return {
|
|
177
166
|
":function"(node) {
|
|
@@ -221,7 +210,7 @@ function useNoDirectSetStateInUseEffect(context, options) {
|
|
|
221
210
|
if (node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee === node) {
|
|
222
211
|
return;
|
|
223
212
|
}
|
|
224
|
-
if (!isIdFromUseStateCall(node)) {
|
|
213
|
+
if (!isIdFromUseStateCall(node, 1)) {
|
|
225
214
|
return;
|
|
226
215
|
}
|
|
227
216
|
switch (node.parent.type) {
|
|
@@ -258,7 +247,7 @@ function useNoDirectSetStateInUseEffect(context, options) {
|
|
|
258
247
|
},
|
|
259
248
|
"Program:exit"() {
|
|
260
249
|
const getSetStateCalls = (id, initialScope) => {
|
|
261
|
-
const node =
|
|
250
|
+
const node = VAR.getVariableInitNode(VAR.findVariable(id, initialScope), 0);
|
|
262
251
|
switch (node?.type) {
|
|
263
252
|
case AST_NODE_TYPES.ArrowFunctionExpression:
|
|
264
253
|
case AST_NODE_TYPES.FunctionDeclaration:
|
|
@@ -297,36 +286,21 @@ function useNoDirectSetStateInUseEffect(context, options) {
|
|
|
297
286
|
}
|
|
298
287
|
};
|
|
299
288
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
},
|
|
316
|
-
schema: []
|
|
317
|
-
},
|
|
318
|
-
name: RULE_NAME,
|
|
319
|
-
create,
|
|
320
|
-
defaultOptions: []
|
|
321
|
-
});
|
|
322
|
-
function create(context) {
|
|
323
|
-
if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
|
|
324
|
-
return useNoDirectSetStateInUseEffect(context, {
|
|
325
|
-
onViolation(ctx, node, data) {
|
|
326
|
-
ctx.report({ messageId: "noDirectSetStateInUseEffect", node, data });
|
|
327
|
-
},
|
|
328
|
-
useEffectKind: "useEffect"
|
|
329
|
-
});
|
|
289
|
+
function isInitFromHookCall(init) {
|
|
290
|
+
if (init?.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
291
|
+
switch (init.callee.type) {
|
|
292
|
+
case AST_NODE_TYPES.Identifier:
|
|
293
|
+
return ER.isReactHookName(init.callee.name);
|
|
294
|
+
case AST_NODE_TYPES.MemberExpression:
|
|
295
|
+
return init.callee.property.type === AST_NODE_TYPES.Identifier && ER.isReactHookName(init.callee.property.name);
|
|
296
|
+
default:
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function isVariableDeclaratorFromHookCall(node) {
|
|
301
|
+
if (node.type !== AST_NODE_TYPES.VariableDeclarator) return false;
|
|
302
|
+
if (node.id.type !== AST_NODE_TYPES.Identifier) return false;
|
|
303
|
+
return isInitFromHookCall(node.init);
|
|
330
304
|
}
|
|
331
305
|
|
|
332
306
|
// src/rules/no-direct-set-state-in-use-layout-effect.ts
|
|
@@ -359,308 +333,6 @@ function create2(context) {
|
|
|
359
333
|
useEffectKind: "useLayoutEffect"
|
|
360
334
|
});
|
|
361
335
|
}
|
|
362
|
-
var RULE_NAME3 = "no-unnecessary-use-callback";
|
|
363
|
-
var RULE_FEATURES3 = [
|
|
364
|
-
"EXP"
|
|
365
|
-
];
|
|
366
|
-
var no_unnecessary_use_callback_default = createRule({
|
|
367
|
-
meta: {
|
|
368
|
-
type: "problem",
|
|
369
|
-
docs: {
|
|
370
|
-
description: "Disallow unnecessary usage of `useCallback`.",
|
|
371
|
-
[Symbol.for("rule_features")]: RULE_FEATURES3
|
|
372
|
-
},
|
|
373
|
-
messages: {
|
|
374
|
-
noUnnecessaryUseCallback: "An 'useCallback' with empty deps and no references to the component scope may be unnecessary."
|
|
375
|
-
},
|
|
376
|
-
schema: []
|
|
377
|
-
},
|
|
378
|
-
name: RULE_NAME3,
|
|
379
|
-
create: create3,
|
|
380
|
-
defaultOptions: []
|
|
381
|
-
});
|
|
382
|
-
function create3(context) {
|
|
383
|
-
if (!context.sourceCode.text.includes("use")) return {};
|
|
384
|
-
const alias = getSettingsFromContext(context).additionalHooks.useCallback ?? [];
|
|
385
|
-
const isUseCallbackCall = ER7.isReactHookCallWithNameAlias(context, "useCallback", alias);
|
|
386
|
-
return {
|
|
387
|
-
CallExpression(node) {
|
|
388
|
-
if (!ER7.isReactHookCall(node)) {
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
const initialScope = context.sourceCode.getScope(node);
|
|
392
|
-
if (!isUseCallbackCall(node)) {
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
const scope = context.sourceCode.getScope(node);
|
|
396
|
-
const component = scope.block;
|
|
397
|
-
if (!AST.isFunction(component)) {
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
const [arg0, arg1] = node.arguments;
|
|
401
|
-
if (arg0 == null || arg1 == null) {
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
const hasEmptyDeps = match(arg1).with({ type: AST_NODE_TYPES.ArrayExpression }, (n) => n.elements.length === 0).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
|
|
405
|
-
const variable = VAR4.findVariable(n.name, initialScope);
|
|
406
|
-
const variableNode = VAR4.getVariableInitNode(variable, 0);
|
|
407
|
-
if (variableNode?.type !== AST_NODE_TYPES.ArrayExpression) {
|
|
408
|
-
return false;
|
|
409
|
-
}
|
|
410
|
-
return variableNode.elements.length === 0;
|
|
411
|
-
}).otherwise(() => false);
|
|
412
|
-
if (!hasEmptyDeps) {
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
const arg0Node = match(arg0).with({ type: AST_NODE_TYPES.ArrowFunctionExpression }, (n) => {
|
|
416
|
-
if (n.body.type === AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
417
|
-
return n.body;
|
|
418
|
-
}
|
|
419
|
-
return n;
|
|
420
|
-
}).with({ type: AST_NODE_TYPES.FunctionExpression }, identity).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
|
|
421
|
-
const variable = VAR4.findVariable(n.name, initialScope);
|
|
422
|
-
const variableNode = VAR4.getVariableInitNode(variable, 0);
|
|
423
|
-
if (variableNode?.type !== AST_NODE_TYPES.ArrowFunctionExpression && variableNode?.type !== AST_NODE_TYPES.FunctionExpression) {
|
|
424
|
-
return null;
|
|
425
|
-
}
|
|
426
|
-
return variableNode;
|
|
427
|
-
}).otherwise(() => null);
|
|
428
|
-
if (arg0Node == null) return;
|
|
429
|
-
const arg0NodeScope = context.sourceCode.getScope(arg0Node);
|
|
430
|
-
const arg0NodeReferences = VAR4.getChidScopes(arg0NodeScope).flatMap((x) => x.references);
|
|
431
|
-
const isReferencedToComponentScope = arg0NodeReferences.some((x) => x.resolved?.scope.block === component);
|
|
432
|
-
if (!isReferencedToComponentScope) {
|
|
433
|
-
context.report({
|
|
434
|
-
messageId: "noUnnecessaryUseCallback",
|
|
435
|
-
node
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
var RULE_NAME4 = "no-unnecessary-use-memo";
|
|
442
|
-
var RULE_FEATURES4 = [
|
|
443
|
-
"EXP"
|
|
444
|
-
];
|
|
445
|
-
var no_unnecessary_use_memo_default = createRule({
|
|
446
|
-
meta: {
|
|
447
|
-
type: "problem",
|
|
448
|
-
docs: {
|
|
449
|
-
description: "Disallow unnecessary usage of `useMemo`.",
|
|
450
|
-
[Symbol.for("rule_features")]: RULE_FEATURES4
|
|
451
|
-
},
|
|
452
|
-
messages: {
|
|
453
|
-
noUnnecessaryUseMemo: "An 'useMemo' with empty deps and no references to the component scope may be unnecessary."
|
|
454
|
-
},
|
|
455
|
-
schema: []
|
|
456
|
-
},
|
|
457
|
-
name: RULE_NAME4,
|
|
458
|
-
create: create4,
|
|
459
|
-
defaultOptions: []
|
|
460
|
-
});
|
|
461
|
-
function create4(context) {
|
|
462
|
-
if (!context.sourceCode.text.includes("use")) return {};
|
|
463
|
-
const alias = getSettingsFromContext(context).additionalHooks.useMemo ?? [];
|
|
464
|
-
const isUseMemoCall = ER7.isReactHookCallWithNameAlias(context, "useMemo", alias);
|
|
465
|
-
return {
|
|
466
|
-
CallExpression(node) {
|
|
467
|
-
if (!ER7.isReactHookCall(node)) {
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
const initialScope = context.sourceCode.getScope(node);
|
|
471
|
-
if (!isUseMemoCall(node)) {
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
const scope = context.sourceCode.getScope(node);
|
|
475
|
-
const component = scope.block;
|
|
476
|
-
if (!AST.isFunction(component)) {
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
const [arg0, arg1] = node.arguments;
|
|
480
|
-
if (arg0 == null || arg1 == null) {
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
const hasCallInArg0 = AST.isFunction(arg0) && [...AST.getNestedCallExpressions(arg0.body), ...AST.getNestedNewExpressions(arg0.body)].length > 0;
|
|
484
|
-
if (hasCallInArg0) {
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
const hasEmptyDeps = match(arg1).with({ type: AST_NODE_TYPES.ArrayExpression }, (n) => n.elements.length === 0).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
|
|
488
|
-
const variable = VAR4.findVariable(n.name, initialScope);
|
|
489
|
-
const variableNode = VAR4.getVariableInitNode(variable, 0);
|
|
490
|
-
if (variableNode?.type !== AST_NODE_TYPES.ArrayExpression) {
|
|
491
|
-
return false;
|
|
492
|
-
}
|
|
493
|
-
return variableNode.elements.length === 0;
|
|
494
|
-
}).otherwise(() => false);
|
|
495
|
-
if (!hasEmptyDeps) {
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
const arg0Node = match(arg0).with({ type: AST_NODE_TYPES.ArrowFunctionExpression }, (n) => {
|
|
499
|
-
if (n.body.type === AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
500
|
-
return n.body;
|
|
501
|
-
}
|
|
502
|
-
return n;
|
|
503
|
-
}).with({ type: AST_NODE_TYPES.FunctionExpression }, identity).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
|
|
504
|
-
const variable = VAR4.findVariable(n.name, initialScope);
|
|
505
|
-
const variableNode = VAR4.getVariableInitNode(variable, 0);
|
|
506
|
-
if (variableNode?.type !== AST_NODE_TYPES.ArrowFunctionExpression && variableNode?.type !== AST_NODE_TYPES.FunctionExpression) {
|
|
507
|
-
return null;
|
|
508
|
-
}
|
|
509
|
-
return variableNode;
|
|
510
|
-
}).otherwise(() => null);
|
|
511
|
-
if (arg0Node == null) return;
|
|
512
|
-
const arg0NodeScope = context.sourceCode.getScope(arg0Node);
|
|
513
|
-
const arg0NodeReferences = VAR4.getChidScopes(arg0NodeScope).flatMap((x) => x.references);
|
|
514
|
-
const isReferencedToComponentScope = arg0NodeReferences.some((x) => x.resolved?.scope.block === component);
|
|
515
|
-
if (!isReferencedToComponentScope) {
|
|
516
|
-
context.report({
|
|
517
|
-
messageId: "noUnnecessaryUseMemo",
|
|
518
|
-
node
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
var RULE_NAME5 = "no-unnecessary-use-prefix";
|
|
525
|
-
var RULE_FEATURES5 = [];
|
|
526
|
-
var WELL_KNOWN_HOOKS = [
|
|
527
|
-
"useMDXComponents"
|
|
528
|
-
];
|
|
529
|
-
function containsUseComments(context, node) {
|
|
530
|
-
return context.sourceCode.getCommentsInside(node).some(({ value }) => /use\([\s\S]*?\)/u.test(value) || /use[A-Z0-9]\w*\([\s\S]*?\)/u.test(value));
|
|
531
|
-
}
|
|
532
|
-
var no_unnecessary_use_prefix_default = createRule({
|
|
533
|
-
meta: {
|
|
534
|
-
type: "problem",
|
|
535
|
-
docs: {
|
|
536
|
-
description: "Enforces that a function with the `use` prefix should use at least one Hook inside of it.",
|
|
537
|
-
[Symbol.for("rule_features")]: RULE_FEATURES5
|
|
538
|
-
},
|
|
539
|
-
messages: {
|
|
540
|
-
noUnnecessaryUsePrefix: "If your function doesn't call any Hooks, avoid the 'use' prefix. Instead, write it as a regular function without the 'use' prefix."
|
|
541
|
-
},
|
|
542
|
-
schema: []
|
|
543
|
-
},
|
|
544
|
-
name: RULE_NAME5,
|
|
545
|
-
create: create5,
|
|
546
|
-
defaultOptions: []
|
|
547
|
-
});
|
|
548
|
-
function create5(context) {
|
|
549
|
-
const { ctx, listeners } = ER7.useHookCollector();
|
|
550
|
-
return {
|
|
551
|
-
...listeners,
|
|
552
|
-
"Program:exit"(program) {
|
|
553
|
-
const allHooks = ctx.getAllHooks(program);
|
|
554
|
-
for (const { id, name: name3, node, hookCalls } of allHooks.values()) {
|
|
555
|
-
if (WELL_KNOWN_HOOKS.includes(name3)) {
|
|
556
|
-
continue;
|
|
557
|
-
}
|
|
558
|
-
if (AST.isEmptyFunction(node)) {
|
|
559
|
-
continue;
|
|
560
|
-
}
|
|
561
|
-
if (hookCalls.length > 0) {
|
|
562
|
-
continue;
|
|
563
|
-
}
|
|
564
|
-
if (containsUseComments(context, node)) {
|
|
565
|
-
continue;
|
|
566
|
-
}
|
|
567
|
-
if (id != null) {
|
|
568
|
-
context.report({
|
|
569
|
-
messageId: "noUnnecessaryUsePrefix",
|
|
570
|
-
data: {
|
|
571
|
-
name: name3
|
|
572
|
-
},
|
|
573
|
-
loc: getPreferredLoc(context, id)
|
|
574
|
-
});
|
|
575
|
-
continue;
|
|
576
|
-
}
|
|
577
|
-
context.report({
|
|
578
|
-
messageId: "noUnnecessaryUsePrefix",
|
|
579
|
-
node,
|
|
580
|
-
data: {
|
|
581
|
-
name: name3
|
|
582
|
-
}
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
};
|
|
587
|
-
}
|
|
588
|
-
function getPreferredLoc(context, id) {
|
|
589
|
-
if (AST.isMultiLine(id)) return id.loc;
|
|
590
|
-
if (!context.sourceCode.getText(id).startsWith("use")) return id.loc;
|
|
591
|
-
return {
|
|
592
|
-
end: {
|
|
593
|
-
column: id.loc.start.column + 3,
|
|
594
|
-
line: id.loc.start.line
|
|
595
|
-
},
|
|
596
|
-
start: {
|
|
597
|
-
column: id.loc.start.column,
|
|
598
|
-
line: id.loc.start.line
|
|
599
|
-
}
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
var RULE_NAME6 = "prefer-use-state-lazy-initialization";
|
|
603
|
-
var RULE_FEATURES6 = [
|
|
604
|
-
"EXP"
|
|
605
|
-
];
|
|
606
|
-
var ALLOW_LIST = [
|
|
607
|
-
"Boolean",
|
|
608
|
-
"String",
|
|
609
|
-
"Number"
|
|
610
|
-
];
|
|
611
|
-
var prefer_use_state_lazy_initialization_default = createRule({
|
|
612
|
-
meta: {
|
|
613
|
-
type: "problem",
|
|
614
|
-
docs: {
|
|
615
|
-
description: "Enforces function calls made inside `useState` to be wrapped in an `initializer function`.",
|
|
616
|
-
[Symbol.for("rule_features")]: RULE_FEATURES6
|
|
617
|
-
},
|
|
618
|
-
messages: {
|
|
619
|
-
preferUseStateLazyInitialization: "To prevent re-computation, consider using lazy initial state for useState calls that involve function calls. Ex: 'useState(() => getValue())'."
|
|
620
|
-
},
|
|
621
|
-
schema: []
|
|
622
|
-
},
|
|
623
|
-
name: RULE_NAME6,
|
|
624
|
-
create: create6,
|
|
625
|
-
defaultOptions: []
|
|
626
|
-
});
|
|
627
|
-
function create6(context) {
|
|
628
|
-
const alias = getSettingsFromContext(context).additionalHooks.useState ?? [];
|
|
629
|
-
const isUseStateCall = ER7.isReactHookCallWithNameAlias(context, "useState", alias);
|
|
630
|
-
return {
|
|
631
|
-
CallExpression(node) {
|
|
632
|
-
if (!ER7.isReactHookCall(node)) {
|
|
633
|
-
return;
|
|
634
|
-
}
|
|
635
|
-
if (!isUseStateCall(node)) {
|
|
636
|
-
return;
|
|
637
|
-
}
|
|
638
|
-
const [useStateInput] = node.arguments;
|
|
639
|
-
if (useStateInput == null) {
|
|
640
|
-
return;
|
|
641
|
-
}
|
|
642
|
-
for (const expr of AST.getNestedNewExpressions(useStateInput)) {
|
|
643
|
-
if (!("name" in expr.callee)) continue;
|
|
644
|
-
if (ALLOW_LIST.includes(expr.callee.name)) continue;
|
|
645
|
-
if (AST.findParentNode(expr, (n) => ER7.isUseCall(context, n)) != null) continue;
|
|
646
|
-
context.report({
|
|
647
|
-
messageId: "preferUseStateLazyInitialization",
|
|
648
|
-
node: expr
|
|
649
|
-
});
|
|
650
|
-
}
|
|
651
|
-
for (const expr of AST.getNestedCallExpressions(useStateInput)) {
|
|
652
|
-
if (!("name" in expr.callee)) continue;
|
|
653
|
-
if (ER7.isReactHookName(expr.callee.name)) continue;
|
|
654
|
-
if (ALLOW_LIST.includes(expr.callee.name)) continue;
|
|
655
|
-
if (AST.findParentNode(expr, (n) => ER7.isUseCall(context, n)) != null) continue;
|
|
656
|
-
context.report({
|
|
657
|
-
messageId: "preferUseStateLazyInitialization",
|
|
658
|
-
node: expr
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
};
|
|
663
|
-
}
|
|
664
336
|
|
|
665
337
|
// src/plugin.ts
|
|
666
338
|
var plugin = {
|
|
@@ -670,11 +342,7 @@ var plugin = {
|
|
|
670
342
|
},
|
|
671
343
|
rules: {
|
|
672
344
|
"no-direct-set-state-in-use-effect": no_direct_set_state_in_use_effect_default,
|
|
673
|
-
"no-direct-set-state-in-use-layout-effect": no_direct_set_state_in_use_layout_effect_default
|
|
674
|
-
"no-unnecessary-use-callback": no_unnecessary_use_callback_default,
|
|
675
|
-
"no-unnecessary-use-memo": no_unnecessary_use_memo_default,
|
|
676
|
-
"no-unnecessary-use-prefix": no_unnecessary_use_prefix_default,
|
|
677
|
-
"prefer-use-state-lazy-initialization": prefer_use_state_lazy_initialization_default
|
|
345
|
+
"no-direct-set-state-in-use-layout-effect": no_direct_set_state_in_use_layout_effect_default
|
|
678
346
|
}
|
|
679
347
|
};
|
|
680
348
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-react-hooks-extra",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.31",
|
|
4
4
|
"description": "ESLint React's ESLint plugin for React Hooks related rules.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -36,28 +36,28 @@
|
|
|
36
36
|
"./package.json"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@typescript-eslint/scope-manager": "^8.
|
|
40
|
-
"@typescript-eslint/type-utils": "^8.
|
|
41
|
-
"@typescript-eslint/types": "^8.
|
|
42
|
-
"@typescript-eslint/utils": "^8.
|
|
39
|
+
"@typescript-eslint/scope-manager": "^8.33.0",
|
|
40
|
+
"@typescript-eslint/type-utils": "^8.33.0",
|
|
41
|
+
"@typescript-eslint/types": "^8.33.0",
|
|
42
|
+
"@typescript-eslint/utils": "^8.33.0",
|
|
43
43
|
"string-ts": "^2.2.1",
|
|
44
|
-
"ts-pattern": "^5.7.
|
|
45
|
-
"@eslint-react/ast": "2.0.0-next.
|
|
46
|
-
"@eslint-react/
|
|
47
|
-
"@eslint-react/
|
|
48
|
-
"@eslint-react/
|
|
49
|
-
"@eslint-react/
|
|
50
|
-
"@eslint-react/
|
|
44
|
+
"ts-pattern": "^5.7.1",
|
|
45
|
+
"@eslint-react/ast": "2.0.0-next.31",
|
|
46
|
+
"@eslint-react/eff": "2.0.0-next.31",
|
|
47
|
+
"@eslint-react/core": "2.0.0-next.31",
|
|
48
|
+
"@eslint-react/shared": "2.0.0-next.31",
|
|
49
|
+
"@eslint-react/var": "2.0.0-next.31",
|
|
50
|
+
"@eslint-react/kit": "2.0.0-next.31"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@types/react": "^19.1.
|
|
54
|
-
"@types/react-dom": "^19.1.
|
|
55
|
-
"tsup": "^8.
|
|
53
|
+
"@types/react": "^19.1.6",
|
|
54
|
+
"@types/react-dom": "^19.1.5",
|
|
55
|
+
"tsup": "^8.5.0",
|
|
56
56
|
"@local/configs": "0.0.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"eslint": "^9.
|
|
60
|
-
"typescript": "^4.9.5 || ^5.
|
|
59
|
+
"eslint": "^8.57.0 || ^9.0.0",
|
|
60
|
+
"typescript": "^4.9.5 || ^5.3.3"
|
|
61
61
|
},
|
|
62
62
|
"peerDependenciesMeta": {
|
|
63
63
|
"eslint": {
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
},
|
|
70
70
|
"engines": {
|
|
71
71
|
"bun": ">=1.0.15",
|
|
72
|
-
"node": ">=
|
|
72
|
+
"node": ">=18.18.0"
|
|
73
73
|
},
|
|
74
74
|
"publishConfig": {
|
|
75
75
|
"access": "public"
|