eslint-plugin-react-hooks-extra 2.0.0-next.28 → 2.0.0-next.29
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 -451
- package/package.json +7 -7
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.29";
|
|
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.toStringFormat(node.callee, getText);
|
|
167
|
-
}
|
|
168
|
-
return AST.toStringFormat(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,37 +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
|
-
noDirectSetStateInUseEffect: "Do not call the 'set' function '{{name}}' of 'useState' directly in 'useEffect'."
|
|
316
|
-
},
|
|
317
|
-
schema: []
|
|
318
|
-
},
|
|
319
|
-
name: RULE_NAME,
|
|
320
|
-
create,
|
|
321
|
-
defaultOptions: []
|
|
322
|
-
});
|
|
323
|
-
function create(context) {
|
|
324
|
-
if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
|
|
325
|
-
return useNoDirectSetStateInUseEffect(context, {
|
|
326
|
-
onViolation(ctx, node, data) {
|
|
327
|
-
ctx.report({ messageId: "noDirectSetStateInUseEffect", node, data });
|
|
328
|
-
},
|
|
329
|
-
useEffectKind: "useEffect"
|
|
330
|
-
});
|
|
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);
|
|
331
304
|
}
|
|
332
305
|
|
|
333
306
|
// src/rules/no-direct-set-state-in-use-layout-effect.ts
|
|
@@ -338,7 +311,6 @@ var RULE_FEATURES2 = [
|
|
|
338
311
|
var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
339
312
|
meta: {
|
|
340
313
|
type: "problem",
|
|
341
|
-
deprecated: true,
|
|
342
314
|
docs: {
|
|
343
315
|
description: "Disallow direct calls to the `set` function of `useState` in `useLayoutEffect`.",
|
|
344
316
|
[Symbol.for("rule_features")]: RULE_FEATURES2
|
|
@@ -361,312 +333,6 @@ function create2(context) {
|
|
|
361
333
|
useEffectKind: "useLayoutEffect"
|
|
362
334
|
});
|
|
363
335
|
}
|
|
364
|
-
var RULE_NAME3 = "no-unnecessary-use-callback";
|
|
365
|
-
var RULE_FEATURES3 = [
|
|
366
|
-
"EXP"
|
|
367
|
-
];
|
|
368
|
-
var no_unnecessary_use_callback_default = createRule({
|
|
369
|
-
meta: {
|
|
370
|
-
type: "problem",
|
|
371
|
-
deprecated: true,
|
|
372
|
-
docs: {
|
|
373
|
-
description: "Disallow unnecessary usage of `useCallback`.",
|
|
374
|
-
[Symbol.for("rule_features")]: RULE_FEATURES3
|
|
375
|
-
},
|
|
376
|
-
messages: {
|
|
377
|
-
noUnnecessaryUseCallback: "An 'useCallback' with empty deps and no references to the component scope may be unnecessary."
|
|
378
|
-
},
|
|
379
|
-
schema: []
|
|
380
|
-
},
|
|
381
|
-
name: RULE_NAME3,
|
|
382
|
-
create: create3,
|
|
383
|
-
defaultOptions: []
|
|
384
|
-
});
|
|
385
|
-
function create3(context) {
|
|
386
|
-
if (!context.sourceCode.text.includes("use")) return {};
|
|
387
|
-
const alias = getSettingsFromContext(context).additionalHooks.useCallback ?? [];
|
|
388
|
-
const isUseCallbackCall = ER7.isReactHookCallWithNameAlias(context, "useCallback", alias);
|
|
389
|
-
return {
|
|
390
|
-
CallExpression(node) {
|
|
391
|
-
if (!ER7.isReactHookCall(node)) {
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
const initialScope = context.sourceCode.getScope(node);
|
|
395
|
-
if (!isUseCallbackCall(node)) {
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
const scope = context.sourceCode.getScope(node);
|
|
399
|
-
const component = scope.block;
|
|
400
|
-
if (!AST.isFunction(component)) {
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
const [arg0, arg1] = node.arguments;
|
|
404
|
-
if (arg0 == null || arg1 == null) {
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
const hasEmptyDeps = match(arg1).with({ type: AST_NODE_TYPES.ArrayExpression }, (n) => n.elements.length === 0).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
|
|
408
|
-
const variable = VAR4.findVariable(n.name, initialScope);
|
|
409
|
-
const variableNode = VAR4.getVariableInitNode(variable, 0);
|
|
410
|
-
if (variableNode?.type !== AST_NODE_TYPES.ArrayExpression) {
|
|
411
|
-
return false;
|
|
412
|
-
}
|
|
413
|
-
return variableNode.elements.length === 0;
|
|
414
|
-
}).otherwise(() => false);
|
|
415
|
-
if (!hasEmptyDeps) {
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
const arg0Node = match(arg0).with({ type: AST_NODE_TYPES.ArrowFunctionExpression }, (n) => {
|
|
419
|
-
if (n.body.type === AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
420
|
-
return n.body;
|
|
421
|
-
}
|
|
422
|
-
return n;
|
|
423
|
-
}).with({ type: AST_NODE_TYPES.FunctionExpression }, identity).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
|
|
424
|
-
const variable = VAR4.findVariable(n.name, initialScope);
|
|
425
|
-
const variableNode = VAR4.getVariableInitNode(variable, 0);
|
|
426
|
-
if (variableNode?.type !== AST_NODE_TYPES.ArrowFunctionExpression && variableNode?.type !== AST_NODE_TYPES.FunctionExpression) {
|
|
427
|
-
return null;
|
|
428
|
-
}
|
|
429
|
-
return variableNode;
|
|
430
|
-
}).otherwise(() => null);
|
|
431
|
-
if (arg0Node == null) return;
|
|
432
|
-
const arg0NodeScope = context.sourceCode.getScope(arg0Node);
|
|
433
|
-
const arg0NodeReferences = VAR4.getChidScopes(arg0NodeScope).flatMap((x) => x.references);
|
|
434
|
-
const isReferencedToComponentScope = arg0NodeReferences.some((x) => x.resolved?.scope.block === component);
|
|
435
|
-
if (!isReferencedToComponentScope) {
|
|
436
|
-
context.report({
|
|
437
|
-
messageId: "noUnnecessaryUseCallback",
|
|
438
|
-
node
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
var RULE_NAME4 = "no-unnecessary-use-memo";
|
|
445
|
-
var RULE_FEATURES4 = [
|
|
446
|
-
"EXP"
|
|
447
|
-
];
|
|
448
|
-
var no_unnecessary_use_memo_default = createRule({
|
|
449
|
-
meta: {
|
|
450
|
-
type: "problem",
|
|
451
|
-
deprecated: true,
|
|
452
|
-
docs: {
|
|
453
|
-
description: "Disallow unnecessary usage of `useMemo`.",
|
|
454
|
-
[Symbol.for("rule_features")]: RULE_FEATURES4
|
|
455
|
-
},
|
|
456
|
-
messages: {
|
|
457
|
-
noUnnecessaryUseMemo: "An 'useMemo' with empty deps and no references to the component scope may be unnecessary."
|
|
458
|
-
},
|
|
459
|
-
schema: []
|
|
460
|
-
},
|
|
461
|
-
name: RULE_NAME4,
|
|
462
|
-
create: create4,
|
|
463
|
-
defaultOptions: []
|
|
464
|
-
});
|
|
465
|
-
function create4(context) {
|
|
466
|
-
if (!context.sourceCode.text.includes("use")) return {};
|
|
467
|
-
const alias = getSettingsFromContext(context).additionalHooks.useMemo ?? [];
|
|
468
|
-
const isUseMemoCall = ER7.isReactHookCallWithNameAlias(context, "useMemo", alias);
|
|
469
|
-
return {
|
|
470
|
-
CallExpression(node) {
|
|
471
|
-
if (!ER7.isReactHookCall(node)) {
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
const initialScope = context.sourceCode.getScope(node);
|
|
475
|
-
if (!isUseMemoCall(node)) {
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
const scope = context.sourceCode.getScope(node);
|
|
479
|
-
const component = scope.block;
|
|
480
|
-
if (!AST.isFunction(component)) {
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
const [arg0, arg1] = node.arguments;
|
|
484
|
-
if (arg0 == null || arg1 == null) {
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
const hasCallInArg0 = AST.isFunction(arg0) && [...AST.getNestedCallExpressions(arg0.body), ...AST.getNestedNewExpressions(arg0.body)].length > 0;
|
|
488
|
-
if (hasCallInArg0) {
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
const hasEmptyDeps = match(arg1).with({ type: AST_NODE_TYPES.ArrayExpression }, (n) => n.elements.length === 0).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
|
|
492
|
-
const variable = VAR4.findVariable(n.name, initialScope);
|
|
493
|
-
const variableNode = VAR4.getVariableInitNode(variable, 0);
|
|
494
|
-
if (variableNode?.type !== AST_NODE_TYPES.ArrayExpression) {
|
|
495
|
-
return false;
|
|
496
|
-
}
|
|
497
|
-
return variableNode.elements.length === 0;
|
|
498
|
-
}).otherwise(() => false);
|
|
499
|
-
if (!hasEmptyDeps) {
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
const arg0Node = match(arg0).with({ type: AST_NODE_TYPES.ArrowFunctionExpression }, (n) => {
|
|
503
|
-
if (n.body.type === AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
504
|
-
return n.body;
|
|
505
|
-
}
|
|
506
|
-
return n;
|
|
507
|
-
}).with({ type: AST_NODE_TYPES.FunctionExpression }, identity).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
|
|
508
|
-
const variable = VAR4.findVariable(n.name, initialScope);
|
|
509
|
-
const variableNode = VAR4.getVariableInitNode(variable, 0);
|
|
510
|
-
if (variableNode?.type !== AST_NODE_TYPES.ArrowFunctionExpression && variableNode?.type !== AST_NODE_TYPES.FunctionExpression) {
|
|
511
|
-
return null;
|
|
512
|
-
}
|
|
513
|
-
return variableNode;
|
|
514
|
-
}).otherwise(() => null);
|
|
515
|
-
if (arg0Node == null) return;
|
|
516
|
-
const arg0NodeScope = context.sourceCode.getScope(arg0Node);
|
|
517
|
-
const arg0NodeReferences = VAR4.getChidScopes(arg0NodeScope).flatMap((x) => x.references);
|
|
518
|
-
const isReferencedToComponentScope = arg0NodeReferences.some((x) => x.resolved?.scope.block === component);
|
|
519
|
-
if (!isReferencedToComponentScope) {
|
|
520
|
-
context.report({
|
|
521
|
-
messageId: "noUnnecessaryUseMemo",
|
|
522
|
-
node
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
var RULE_NAME5 = "no-unnecessary-use-prefix";
|
|
529
|
-
var RULE_FEATURES5 = [];
|
|
530
|
-
var WELL_KNOWN_HOOKS = [
|
|
531
|
-
"useMDXComponents"
|
|
532
|
-
];
|
|
533
|
-
function containsUseComments(context, node) {
|
|
534
|
-
return context.sourceCode.getCommentsInside(node).some(({ value }) => /use\([\s\S]*?\)/u.test(value) || /use[A-Z0-9]\w*\([\s\S]*?\)/u.test(value));
|
|
535
|
-
}
|
|
536
|
-
var no_unnecessary_use_prefix_default = createRule({
|
|
537
|
-
meta: {
|
|
538
|
-
type: "problem",
|
|
539
|
-
deprecated: true,
|
|
540
|
-
docs: {
|
|
541
|
-
description: "Enforces that a function with the `use` prefix should use at least one Hook inside of it.",
|
|
542
|
-
[Symbol.for("rule_features")]: RULE_FEATURES5
|
|
543
|
-
},
|
|
544
|
-
messages: {
|
|
545
|
-
noUnnecessaryUsePrefix: "If your function doesn't call any Hooks, avoid the 'use' prefix. Instead, write it as a regular function without the 'use' prefix."
|
|
546
|
-
},
|
|
547
|
-
schema: []
|
|
548
|
-
},
|
|
549
|
-
name: RULE_NAME5,
|
|
550
|
-
create: create5,
|
|
551
|
-
defaultOptions: []
|
|
552
|
-
});
|
|
553
|
-
function create5(context) {
|
|
554
|
-
const { ctx, listeners } = ER7.useHookCollector();
|
|
555
|
-
return {
|
|
556
|
-
...listeners,
|
|
557
|
-
"Program:exit"(program) {
|
|
558
|
-
const allHooks = ctx.getAllHooks(program);
|
|
559
|
-
for (const { id, name: name3, node, hookCalls } of allHooks.values()) {
|
|
560
|
-
if (WELL_KNOWN_HOOKS.includes(name3)) {
|
|
561
|
-
continue;
|
|
562
|
-
}
|
|
563
|
-
if (AST.isEmptyFunction(node)) {
|
|
564
|
-
continue;
|
|
565
|
-
}
|
|
566
|
-
if (hookCalls.length > 0) {
|
|
567
|
-
continue;
|
|
568
|
-
}
|
|
569
|
-
if (containsUseComments(context, node)) {
|
|
570
|
-
continue;
|
|
571
|
-
}
|
|
572
|
-
if (id != null) {
|
|
573
|
-
context.report({
|
|
574
|
-
messageId: "noUnnecessaryUsePrefix",
|
|
575
|
-
data: {
|
|
576
|
-
name: name3
|
|
577
|
-
},
|
|
578
|
-
loc: getPreferredLoc(context, id)
|
|
579
|
-
});
|
|
580
|
-
continue;
|
|
581
|
-
}
|
|
582
|
-
context.report({
|
|
583
|
-
messageId: "noUnnecessaryUsePrefix",
|
|
584
|
-
node,
|
|
585
|
-
data: {
|
|
586
|
-
name: name3
|
|
587
|
-
}
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
};
|
|
592
|
-
}
|
|
593
|
-
function getPreferredLoc(context, id) {
|
|
594
|
-
if (AST.isMultiLine(id)) return id.loc;
|
|
595
|
-
if (!context.sourceCode.getText(id).startsWith("use")) return id.loc;
|
|
596
|
-
return {
|
|
597
|
-
end: {
|
|
598
|
-
column: id.loc.start.column + 3,
|
|
599
|
-
line: id.loc.start.line
|
|
600
|
-
},
|
|
601
|
-
start: {
|
|
602
|
-
column: id.loc.start.column,
|
|
603
|
-
line: id.loc.start.line
|
|
604
|
-
}
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
var RULE_NAME6 = "prefer-use-state-lazy-initialization";
|
|
608
|
-
var RULE_FEATURES6 = [
|
|
609
|
-
"EXP"
|
|
610
|
-
];
|
|
611
|
-
var ALLOW_LIST = [
|
|
612
|
-
"Boolean",
|
|
613
|
-
"String",
|
|
614
|
-
"Number"
|
|
615
|
-
];
|
|
616
|
-
var prefer_use_state_lazy_initialization_default = createRule({
|
|
617
|
-
meta: {
|
|
618
|
-
type: "problem",
|
|
619
|
-
deprecated: true,
|
|
620
|
-
docs: {
|
|
621
|
-
description: "Enforces function calls made inside `useState` to be wrapped in an `initializer function`.",
|
|
622
|
-
[Symbol.for("rule_features")]: RULE_FEATURES6
|
|
623
|
-
},
|
|
624
|
-
messages: {
|
|
625
|
-
preferUseStateLazyInitialization: "To prevent re-computation, consider using lazy initial state for useState calls that involve function calls. Ex: 'useState(() => getValue())'."
|
|
626
|
-
},
|
|
627
|
-
schema: []
|
|
628
|
-
},
|
|
629
|
-
name: RULE_NAME6,
|
|
630
|
-
create: create6,
|
|
631
|
-
defaultOptions: []
|
|
632
|
-
});
|
|
633
|
-
function create6(context) {
|
|
634
|
-
const alias = getSettingsFromContext(context).additionalHooks.useState ?? [];
|
|
635
|
-
const isUseStateCall = ER7.isReactHookCallWithNameAlias(context, "useState", alias);
|
|
636
|
-
return {
|
|
637
|
-
CallExpression(node) {
|
|
638
|
-
if (!ER7.isReactHookCall(node)) {
|
|
639
|
-
return;
|
|
640
|
-
}
|
|
641
|
-
if (!isUseStateCall(node)) {
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
644
|
-
const [useStateInput] = node.arguments;
|
|
645
|
-
if (useStateInput == null) {
|
|
646
|
-
return;
|
|
647
|
-
}
|
|
648
|
-
for (const expr of AST.getNestedNewExpressions(useStateInput)) {
|
|
649
|
-
if (!("name" in expr.callee)) continue;
|
|
650
|
-
if (ALLOW_LIST.includes(expr.callee.name)) continue;
|
|
651
|
-
if (AST.findParentNode(expr, (n) => ER7.isUseCall(context, n)) != null) continue;
|
|
652
|
-
context.report({
|
|
653
|
-
messageId: "preferUseStateLazyInitialization",
|
|
654
|
-
node: expr
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
for (const expr of AST.getNestedCallExpressions(useStateInput)) {
|
|
658
|
-
if (!("name" in expr.callee)) continue;
|
|
659
|
-
if (ER7.isReactHookName(expr.callee.name)) continue;
|
|
660
|
-
if (ALLOW_LIST.includes(expr.callee.name)) continue;
|
|
661
|
-
if (AST.findParentNode(expr, (n) => ER7.isUseCall(context, n)) != null) continue;
|
|
662
|
-
context.report({
|
|
663
|
-
messageId: "preferUseStateLazyInitialization",
|
|
664
|
-
node: expr
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
336
|
|
|
671
337
|
// src/plugin.ts
|
|
672
338
|
var plugin = {
|
|
@@ -675,18 +341,8 @@ var plugin = {
|
|
|
675
341
|
version
|
|
676
342
|
},
|
|
677
343
|
rules: {
|
|
678
|
-
/** @deprecated Use `react-x/no-direct-set-state-in-use-effect` instead */
|
|
679
344
|
"no-direct-set-state-in-use-effect": no_direct_set_state_in_use_effect_default,
|
|
680
|
-
|
|
681
|
-
"no-direct-set-state-in-use-layout-effect": no_direct_set_state_in_use_layout_effect_default,
|
|
682
|
-
/** @deprecated Use `react-x/no-unnecessary-use-callback` instead */
|
|
683
|
-
"no-unnecessary-use-callback": no_unnecessary_use_callback_default,
|
|
684
|
-
/** @deprecated Use `react-x/no-unnecessary-use-memo` instead */
|
|
685
|
-
"no-unnecessary-use-memo": no_unnecessary_use_memo_default,
|
|
686
|
-
/** @deprecated Use `react-x/no-unnecessary-use-prefix` instead */
|
|
687
|
-
"no-unnecessary-use-prefix": no_unnecessary_use_prefix_default,
|
|
688
|
-
/** @deprecated Use `react-x/prefer-use-state-lazy-initialization` instead */
|
|
689
|
-
"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
|
|
690
346
|
}
|
|
691
347
|
};
|
|
692
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.29",
|
|
4
4
|
"description": "ESLint React's ESLint plugin for React Hooks related rules.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
"@typescript-eslint/utils": "^8.33.0",
|
|
43
43
|
"string-ts": "^2.2.1",
|
|
44
44
|
"ts-pattern": "^5.7.1",
|
|
45
|
-
"@eslint-react/ast": "2.0.0-next.
|
|
46
|
-
"@eslint-react/
|
|
47
|
-
"@eslint-react/
|
|
48
|
-
"@eslint-react/
|
|
49
|
-
"@eslint-react/
|
|
50
|
-
"@eslint-react/
|
|
45
|
+
"@eslint-react/ast": "2.0.0-next.29",
|
|
46
|
+
"@eslint-react/kit": "2.0.0-next.29",
|
|
47
|
+
"@eslint-react/eff": "2.0.0-next.29",
|
|
48
|
+
"@eslint-react/shared": "2.0.0-next.29",
|
|
49
|
+
"@eslint-react/var": "2.0.0-next.29",
|
|
50
|
+
"@eslint-react/core": "2.0.0-next.29"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/react": "^19.1.6",
|