eslint-plugin-react-hooks-extra 2.0.0-next.45 → 2.0.0-next.48
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.js +556 -558
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import * as
|
|
1
|
+
import * as AST from '@eslint-react/ast';
|
|
2
|
+
import * as ER from '@eslint-react/core';
|
|
3
3
|
import { identity, getOrElseUpdate, constVoid, not } 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';
|
|
@@ -27,50 +27,279 @@ var rules = {
|
|
|
27
27
|
|
|
28
28
|
// package.json
|
|
29
29
|
var name2 = "eslint-plugin-react-hooks-extra";
|
|
30
|
-
var version = "2.0.0-next.
|
|
30
|
+
var version = "2.0.0-next.48";
|
|
31
|
+
function useNoDirectSetStateInUseEffect(context, options) {
|
|
32
|
+
const { onViolation, useEffectKind } = options;
|
|
33
|
+
const settings = getSettingsFromContext(context);
|
|
34
|
+
const hooks = settings.additionalHooks;
|
|
35
|
+
const getText = (n) => context.sourceCode.getText(n);
|
|
36
|
+
const isUseEffectLikeCall = ER.isReactHookCallWithNameAlias(context, useEffectKind, hooks[useEffectKind]);
|
|
37
|
+
const isUseStateCall = ER.isReactHookCallWithNameAlias(context, "useState", hooks.useState);
|
|
38
|
+
const isUseMemoCall = ER.isReactHookCallWithNameAlias(context, "useMemo", hooks.useMemo);
|
|
39
|
+
const isUseCallbackCall = ER.isReactHookCallWithNameAlias(context, "useCallback", hooks.useCallback);
|
|
40
|
+
const functionEntries = [];
|
|
41
|
+
const setupFunctionRef = { current: null };
|
|
42
|
+
const setupFunctionIdentifiers = [];
|
|
43
|
+
const indFunctionCalls = [];
|
|
44
|
+
const indSetStateCalls = /* @__PURE__ */ new WeakMap();
|
|
45
|
+
const indSetStateCallsInUseEffectArg0 = /* @__PURE__ */ new WeakMap();
|
|
46
|
+
const indSetStateCallsInUseEffectSetup = /* @__PURE__ */ new Map();
|
|
47
|
+
const indSetStateCallsInUseMemoOrCallback = /* @__PURE__ */ new WeakMap();
|
|
48
|
+
const onSetupFunctionEnter = (node) => {
|
|
49
|
+
setupFunctionRef.current = node;
|
|
50
|
+
};
|
|
51
|
+
const onSetupFunctionExit = (node) => {
|
|
52
|
+
if (setupFunctionRef.current === node) {
|
|
53
|
+
setupFunctionRef.current = null;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
function isFunctionOfUseEffectSetup(node) {
|
|
57
|
+
return node.parent?.type === AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseEffectLikeCall(node.parent);
|
|
58
|
+
}
|
|
59
|
+
function getCallName(node) {
|
|
60
|
+
if (node.type === AST_NODE_TYPES.CallExpression) {
|
|
61
|
+
return AST.toStringFormat(node.callee, getText);
|
|
62
|
+
}
|
|
63
|
+
return AST.toStringFormat(node, getText);
|
|
64
|
+
}
|
|
65
|
+
function getCallKind(node) {
|
|
66
|
+
return match(node).when(isUseStateCall, () => "useState").when(isUseEffectLikeCall, () => useEffectKind).when(isSetStateCall, () => "setState").when(AST.isThenCall, () => "then").otherwise(() => "other");
|
|
67
|
+
}
|
|
68
|
+
function getFunctionKind(node) {
|
|
69
|
+
const parent = AST.findParentNode(node, not(AST.isTypeExpression)) ?? node.parent;
|
|
70
|
+
switch (true) {
|
|
71
|
+
case node.async:
|
|
72
|
+
case (parent.type === AST_NODE_TYPES.CallExpression && AST.isThenCall(parent)):
|
|
73
|
+
return "deferred";
|
|
74
|
+
case (node.type !== AST_NODE_TYPES.FunctionDeclaration && parent.type === AST_NODE_TYPES.CallExpression && parent.callee === node):
|
|
75
|
+
return "immediate";
|
|
76
|
+
case isFunctionOfUseEffectSetup(node):
|
|
77
|
+
return "setup";
|
|
78
|
+
default:
|
|
79
|
+
return "other";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function isIdFromUseStateCall(topLevelId, at) {
|
|
83
|
+
const variable = VAR.findVariable(topLevelId, context.sourceCode.getScope(topLevelId));
|
|
84
|
+
const variableNode = VAR.getVariableInitNode(variable, 0);
|
|
85
|
+
if (variableNode == null) return false;
|
|
86
|
+
if (variableNode.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
87
|
+
if (!ER.isReactHookCallWithNameAlias(context, "useState", hooks.useState)(variableNode)) return false;
|
|
88
|
+
const variableNodeParent = variableNode.parent;
|
|
89
|
+
if (!("id" in variableNodeParent) || variableNodeParent.id?.type !== AST_NODE_TYPES.ArrayPattern) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return variableNodeParent.id.elements.findIndex((e) => e?.type === AST_NODE_TYPES.Identifier && e.name === topLevelId.name) === at;
|
|
93
|
+
}
|
|
94
|
+
function isSetStateCall(node) {
|
|
95
|
+
switch (node.callee.type) {
|
|
96
|
+
// const data = useState();
|
|
97
|
+
// data.at(1)();
|
|
98
|
+
case AST_NODE_TYPES.CallExpression: {
|
|
99
|
+
const { callee } = node.callee;
|
|
100
|
+
if (callee.type !== AST_NODE_TYPES.MemberExpression) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
if (!("name" in callee.object)) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
const isAt = callee.property.type === AST_NODE_TYPES.Identifier && callee.property.name === "at";
|
|
107
|
+
const [index] = node.callee.arguments;
|
|
108
|
+
if (!isAt || index == null) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
const indexScope = context.sourceCode.getScope(node);
|
|
112
|
+
const indexValue = VAR.toStaticValue({
|
|
113
|
+
kind: "lazy",
|
|
114
|
+
node: index,
|
|
115
|
+
initialScope: indexScope
|
|
116
|
+
}).value;
|
|
117
|
+
return indexValue === 1 && isIdFromUseStateCall(callee.object);
|
|
118
|
+
}
|
|
119
|
+
// const [data, setData] = useState();
|
|
120
|
+
// setData();
|
|
121
|
+
case AST_NODE_TYPES.Identifier: {
|
|
122
|
+
return isIdFromUseStateCall(node.callee, 1);
|
|
123
|
+
}
|
|
124
|
+
// const data = useState();
|
|
125
|
+
// data[1]();
|
|
126
|
+
case AST_NODE_TYPES.MemberExpression: {
|
|
127
|
+
if (!("name" in node.callee.object)) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
const property = node.callee.property;
|
|
131
|
+
const propertyScope = context.sourceCode.getScope(node);
|
|
132
|
+
const propertyValue = VAR.toStaticValue({
|
|
133
|
+
kind: "lazy",
|
|
134
|
+
node: property,
|
|
135
|
+
initialScope: propertyScope
|
|
136
|
+
}).value;
|
|
137
|
+
return propertyValue === 1 && isIdFromUseStateCall(node.callee.object, 1);
|
|
138
|
+
}
|
|
139
|
+
default: {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
":function"(node) {
|
|
146
|
+
const kind = getFunctionKind(node);
|
|
147
|
+
functionEntries.push({ kind, node });
|
|
148
|
+
if (kind === "setup") {
|
|
149
|
+
onSetupFunctionEnter(node);
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
":function:exit"(node) {
|
|
153
|
+
const { kind } = functionEntries.at(-1) ?? {};
|
|
154
|
+
if (kind === "setup") {
|
|
155
|
+
onSetupFunctionExit(node);
|
|
156
|
+
}
|
|
157
|
+
functionEntries.pop();
|
|
158
|
+
},
|
|
159
|
+
CallExpression(node) {
|
|
160
|
+
const setupFunction = setupFunctionRef.current;
|
|
161
|
+
const pEntry = functionEntries.at(-1);
|
|
162
|
+
if (pEntry == null || pEntry.node.async) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
match(getCallKind(node)).with("setState", () => {
|
|
166
|
+
switch (true) {
|
|
167
|
+
case pEntry.kind === "deferred":
|
|
168
|
+
case pEntry.node.async:
|
|
169
|
+
break;
|
|
170
|
+
case pEntry.node === setupFunction:
|
|
171
|
+
case (pEntry.kind === "immediate" && AST.findParentNode(pEntry.node, AST.isFunction) === setupFunction): {
|
|
172
|
+
onViolation(context, node, {
|
|
173
|
+
name: context.sourceCode.getText(node.callee)
|
|
174
|
+
});
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
default: {
|
|
178
|
+
const vd = AST.findParentNode(node, isVariableDeclaratorFromHookCall);
|
|
179
|
+
if (vd == null) getOrElseUpdate(indSetStateCalls, pEntry.node, () => []).push(node);
|
|
180
|
+
else getOrElseUpdate(indSetStateCallsInUseMemoOrCallback, vd.init, () => []).push(node);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}).with(useEffectKind, () => {
|
|
184
|
+
if (AST.isFunction(node.arguments.at(0))) return;
|
|
185
|
+
setupFunctionIdentifiers.push(...AST.getNestedIdentifiers(node));
|
|
186
|
+
}).with("other", () => {
|
|
187
|
+
if (pEntry.node !== setupFunction) return;
|
|
188
|
+
indFunctionCalls.push(node);
|
|
189
|
+
}).otherwise(constVoid);
|
|
190
|
+
},
|
|
191
|
+
Identifier(node) {
|
|
192
|
+
if (node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee === node) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (!isIdFromUseStateCall(node, 1)) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
switch (node.parent.type) {
|
|
199
|
+
case AST_NODE_TYPES.ArrowFunctionExpression: {
|
|
200
|
+
const parent = node.parent.parent;
|
|
201
|
+
if (parent.type !== AST_NODE_TYPES.CallExpression) {
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
if (!isUseMemoCall(parent)) {
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
const vd = AST.findParentNode(parent, isVariableDeclaratorFromHookCall);
|
|
208
|
+
if (vd != null) {
|
|
209
|
+
getOrElseUpdate(indSetStateCallsInUseEffectArg0, vd.init, () => []).push(node);
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case AST_NODE_TYPES.CallExpression: {
|
|
214
|
+
if (node !== node.parent.arguments.at(0)) {
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
if (isUseCallbackCall(node.parent)) {
|
|
218
|
+
const vd = AST.findParentNode(node.parent, isVariableDeclaratorFromHookCall);
|
|
219
|
+
if (vd != null) {
|
|
220
|
+
getOrElseUpdate(indSetStateCallsInUseEffectArg0, vd.init, () => []).push(node);
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
if (isUseEffectLikeCall(node.parent)) {
|
|
225
|
+
getOrElseUpdate(indSetStateCallsInUseEffectSetup, node.parent, () => []).push(node);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
"Program:exit"() {
|
|
231
|
+
const getSetStateCalls = (id, initialScope) => {
|
|
232
|
+
const node = VAR.getVariableInitNode(VAR.findVariable(id, initialScope), 0);
|
|
233
|
+
switch (node?.type) {
|
|
234
|
+
case AST_NODE_TYPES.ArrowFunctionExpression:
|
|
235
|
+
case AST_NODE_TYPES.FunctionDeclaration:
|
|
236
|
+
case AST_NODE_TYPES.FunctionExpression:
|
|
237
|
+
return indSetStateCalls.get(node) ?? [];
|
|
238
|
+
case AST_NODE_TYPES.CallExpression:
|
|
239
|
+
return indSetStateCallsInUseMemoOrCallback.get(node) ?? indSetStateCallsInUseEffectArg0.get(node) ?? [];
|
|
240
|
+
}
|
|
241
|
+
return [];
|
|
242
|
+
};
|
|
243
|
+
for (const [, calls] of indSetStateCallsInUseEffectSetup) {
|
|
244
|
+
for (const call of calls) {
|
|
245
|
+
onViolation(context, call, { name: call.name });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
for (const { callee } of indFunctionCalls) {
|
|
249
|
+
if (!("name" in callee)) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const { name: name3 } = callee;
|
|
253
|
+
const setStateCalls = getSetStateCalls(name3, context.sourceCode.getScope(callee));
|
|
254
|
+
for (const setStateCall of setStateCalls) {
|
|
255
|
+
onViolation(context, setStateCall, {
|
|
256
|
+
name: getCallName(setStateCall)
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
for (const id of setupFunctionIdentifiers) {
|
|
261
|
+
const setStateCalls = getSetStateCalls(id.name, context.sourceCode.getScope(id));
|
|
262
|
+
for (const setStateCall of setStateCalls) {
|
|
263
|
+
onViolation(context, setStateCall, {
|
|
264
|
+
name: getCallName(setStateCall)
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function isInitFromHookCall(init) {
|
|
272
|
+
if (init?.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
273
|
+
switch (init.callee.type) {
|
|
274
|
+
case AST_NODE_TYPES.Identifier:
|
|
275
|
+
return ER.isReactHookName(init.callee.name);
|
|
276
|
+
case AST_NODE_TYPES.MemberExpression:
|
|
277
|
+
return init.callee.property.type === AST_NODE_TYPES.Identifier && ER.isReactHookName(init.callee.property.name);
|
|
278
|
+
default:
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function isVariableDeclaratorFromHookCall(node) {
|
|
283
|
+
if (node.type !== AST_NODE_TYPES.VariableDeclarator) return false;
|
|
284
|
+
if (node.id.type !== AST_NODE_TYPES.Identifier) return false;
|
|
285
|
+
return isInitFromHookCall(node.init);
|
|
286
|
+
}
|
|
31
287
|
var createRule = ESLintUtils.RuleCreator(getDocsUrl("hooks-extra"));
|
|
32
288
|
|
|
33
|
-
// src/rules
|
|
34
|
-
var RULE_NAME = "no-
|
|
289
|
+
// src/rules/no-direct-set-state-in-use-effect.ts
|
|
290
|
+
var RULE_NAME = "no-direct-set-state-in-use-effect";
|
|
35
291
|
var RULE_FEATURES = [
|
|
36
292
|
"EXP"
|
|
37
293
|
];
|
|
38
|
-
var
|
|
294
|
+
var no_direct_set_state_in_use_effect_default = createRule({
|
|
39
295
|
meta: {
|
|
40
296
|
type: "problem",
|
|
41
|
-
deprecated: {
|
|
42
|
-
deprecatedSince: "2.0.0",
|
|
43
|
-
replacedBy: [
|
|
44
|
-
{
|
|
45
|
-
message: "Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead.",
|
|
46
|
-
plugin: {
|
|
47
|
-
name: "eslint-plugin-react-x",
|
|
48
|
-
url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x"
|
|
49
|
-
},
|
|
50
|
-
rule: {
|
|
51
|
-
name: "no-unnecessary-use-callback",
|
|
52
|
-
url: "https://next.eslint-react.xyz/docs/rules/no-unnecessary-use-callback"
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
message: "Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead.",
|
|
57
|
-
plugin: {
|
|
58
|
-
name: "@eslint-react/eslint-plugin",
|
|
59
|
-
url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin"
|
|
60
|
-
},
|
|
61
|
-
rule: {
|
|
62
|
-
name: "no-unnecessary-use-callback",
|
|
63
|
-
url: "https://next.eslint-react.xyz/docs/rules/no-unnecessary-use-callback"
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
]
|
|
67
|
-
},
|
|
68
297
|
docs: {
|
|
69
|
-
description: "Disallow
|
|
298
|
+
description: "Disallow direct calls to the `set` function of `useState` in `useEffect`.",
|
|
70
299
|
[Symbol.for("rule_features")]: RULE_FEATURES
|
|
71
300
|
},
|
|
72
301
|
messages: {
|
|
73
|
-
|
|
302
|
+
noDirectSetStateInUseEffect: "Do not call the 'set' function '{{name}}' of 'useState' directly in 'useEffect'."
|
|
74
303
|
},
|
|
75
304
|
schema: []
|
|
76
305
|
},
|
|
@@ -79,69 +308,50 @@ var no_unnecessary_use_callback_default = createRule({
|
|
|
79
308
|
defaultOptions: []
|
|
80
309
|
});
|
|
81
310
|
function create(context) {
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
const initialScope = context.sourceCode.getScope(node);
|
|
91
|
-
if (!isUseCallbackCall(node)) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const scope = context.sourceCode.getScope(node);
|
|
95
|
-
const component = scope.block;
|
|
96
|
-
if (!AST5.isFunction(component)) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
const [arg0, arg1] = node.arguments;
|
|
100
|
-
if (arg0 == null || arg1 == null) {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
const hasEmptyDeps = match(arg1).with({ type: AST_NODE_TYPES.ArrayExpression }, (n) => n.elements.length === 0).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
|
|
104
|
-
const variable = VAR3.findVariable(n.name, initialScope);
|
|
105
|
-
const variableNode = VAR3.getVariableInitNode(variable, 0);
|
|
106
|
-
if (variableNode?.type !== AST_NODE_TYPES.ArrayExpression) {
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
return variableNode.elements.length === 0;
|
|
110
|
-
}).otherwise(() => false);
|
|
111
|
-
if (!hasEmptyDeps) {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
const arg0Node = match(arg0).with({ type: AST_NODE_TYPES.ArrowFunctionExpression }, (n) => {
|
|
115
|
-
if (n.body.type === AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
116
|
-
return n.body;
|
|
117
|
-
}
|
|
118
|
-
return n;
|
|
119
|
-
}).with({ type: AST_NODE_TYPES.FunctionExpression }, identity).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
|
|
120
|
-
const variable = VAR3.findVariable(n.name, initialScope);
|
|
121
|
-
const variableNode = VAR3.getVariableInitNode(variable, 0);
|
|
122
|
-
if (variableNode?.type !== AST_NODE_TYPES.ArrowFunctionExpression && variableNode?.type !== AST_NODE_TYPES.FunctionExpression) {
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
return variableNode;
|
|
126
|
-
}).otherwise(() => null);
|
|
127
|
-
if (arg0Node == null) return;
|
|
128
|
-
const arg0NodeScope = context.sourceCode.getScope(arg0Node);
|
|
129
|
-
const arg0NodeReferences = VAR3.getChidScopes(arg0NodeScope).flatMap((x) => x.references);
|
|
130
|
-
const isReferencedToComponentScope = arg0NodeReferences.some((x) => x.resolved?.scope.block === component);
|
|
131
|
-
if (!isReferencedToComponentScope) {
|
|
132
|
-
context.report({
|
|
133
|
-
messageId: "noUnnecessaryUseCallback",
|
|
134
|
-
node
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
};
|
|
311
|
+
if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
|
|
312
|
+
return useNoDirectSetStateInUseEffect(context, {
|
|
313
|
+
onViolation(ctx, node, data) {
|
|
314
|
+
ctx.report({ messageId: "noDirectSetStateInUseEffect", node, data });
|
|
315
|
+
},
|
|
316
|
+
useEffectKind: "useEffect"
|
|
317
|
+
});
|
|
139
318
|
}
|
|
140
|
-
|
|
319
|
+
|
|
320
|
+
// src/rules/no-direct-set-state-in-use-layout-effect.ts
|
|
321
|
+
var RULE_NAME2 = "no-direct-set-state-in-use-layout-effect";
|
|
141
322
|
var RULE_FEATURES2 = [
|
|
142
323
|
"EXP"
|
|
143
324
|
];
|
|
144
|
-
var
|
|
325
|
+
var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
326
|
+
meta: {
|
|
327
|
+
type: "problem",
|
|
328
|
+
docs: {
|
|
329
|
+
description: "Disallow direct calls to the `set` function of `useState` in `useLayoutEffect`.",
|
|
330
|
+
[Symbol.for("rule_features")]: RULE_FEATURES2
|
|
331
|
+
},
|
|
332
|
+
messages: {
|
|
333
|
+
noDirectSetStateInUseLayoutEffect: "Do not call the 'set' function '{{name}}' of 'useState' directly in 'useLayoutEffect'."
|
|
334
|
+
},
|
|
335
|
+
schema: []
|
|
336
|
+
},
|
|
337
|
+
name: RULE_NAME2,
|
|
338
|
+
create: create2,
|
|
339
|
+
defaultOptions: []
|
|
340
|
+
});
|
|
341
|
+
function create2(context) {
|
|
342
|
+
if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
|
|
343
|
+
return useNoDirectSetStateInUseEffect(context, {
|
|
344
|
+
onViolation(ctx, node, data) {
|
|
345
|
+
ctx.report({ messageId: "noDirectSetStateInUseLayoutEffect", node, data });
|
|
346
|
+
},
|
|
347
|
+
useEffectKind: "useLayoutEffect"
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
var RULE_NAME3 = "no-unnecessary-use-callback";
|
|
351
|
+
var RULE_FEATURES3 = [
|
|
352
|
+
"EXP"
|
|
353
|
+
];
|
|
354
|
+
var no_unnecessary_use_callback_default = createRule({
|
|
145
355
|
meta: {
|
|
146
356
|
type: "problem",
|
|
147
357
|
deprecated: {
|
|
@@ -154,8 +364,8 @@ var no_unnecessary_use_memo_default = createRule({
|
|
|
154
364
|
url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x"
|
|
155
365
|
},
|
|
156
366
|
rule: {
|
|
157
|
-
name: "no-unnecessary-use-
|
|
158
|
-
url: "https://next.eslint-react.xyz/docs/rules/no-unnecessary-use-
|
|
367
|
+
name: "no-unnecessary-use-callback",
|
|
368
|
+
url: "https://next.eslint-react.xyz/docs/rules/no-unnecessary-use-callback"
|
|
159
369
|
}
|
|
160
370
|
},
|
|
161
371
|
{
|
|
@@ -165,54 +375,50 @@ var no_unnecessary_use_memo_default = createRule({
|
|
|
165
375
|
url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin"
|
|
166
376
|
},
|
|
167
377
|
rule: {
|
|
168
|
-
name: "no-unnecessary-use-
|
|
169
|
-
url: "https://next.eslint-react.xyz/docs/rules/no-unnecessary-use-
|
|
378
|
+
name: "no-unnecessary-use-callback",
|
|
379
|
+
url: "https://next.eslint-react.xyz/docs/rules/no-unnecessary-use-callback"
|
|
170
380
|
}
|
|
171
381
|
}
|
|
172
382
|
]
|
|
173
383
|
},
|
|
174
384
|
docs: {
|
|
175
|
-
description: "Disallow unnecessary usage of `
|
|
176
|
-
[Symbol.for("rule_features")]:
|
|
385
|
+
description: "Disallow unnecessary usage of `useCallback`.",
|
|
386
|
+
[Symbol.for("rule_features")]: RULE_FEATURES3
|
|
177
387
|
},
|
|
178
388
|
messages: {
|
|
179
|
-
|
|
389
|
+
noUnnecessaryUseCallback: "An 'useCallback' with empty deps and no references to the component scope may be unnecessary."
|
|
180
390
|
},
|
|
181
391
|
schema: []
|
|
182
392
|
},
|
|
183
|
-
name:
|
|
184
|
-
create:
|
|
393
|
+
name: RULE_NAME3,
|
|
394
|
+
create: create3,
|
|
185
395
|
defaultOptions: []
|
|
186
396
|
});
|
|
187
|
-
function
|
|
397
|
+
function create3(context) {
|
|
188
398
|
if (!context.sourceCode.text.includes("use")) return {};
|
|
189
|
-
const alias = getSettingsFromContext(context).additionalHooks.
|
|
190
|
-
const
|
|
399
|
+
const alias = getSettingsFromContext(context).additionalHooks.useCallback ?? [];
|
|
400
|
+
const isUseCallbackCall = ER.isReactHookCallWithNameAlias(context, "useCallback", alias);
|
|
191
401
|
return {
|
|
192
402
|
CallExpression(node) {
|
|
193
|
-
if (!
|
|
403
|
+
if (!ER.isReactHookCall(node)) {
|
|
194
404
|
return;
|
|
195
405
|
}
|
|
196
406
|
const initialScope = context.sourceCode.getScope(node);
|
|
197
|
-
if (!
|
|
407
|
+
if (!isUseCallbackCall(node)) {
|
|
198
408
|
return;
|
|
199
409
|
}
|
|
200
410
|
const scope = context.sourceCode.getScope(node);
|
|
201
411
|
const component = scope.block;
|
|
202
|
-
if (!
|
|
412
|
+
if (!AST.isFunction(component)) {
|
|
203
413
|
return;
|
|
204
414
|
}
|
|
205
415
|
const [arg0, arg1] = node.arguments;
|
|
206
416
|
if (arg0 == null || arg1 == null) {
|
|
207
417
|
return;
|
|
208
418
|
}
|
|
209
|
-
const hasCallInArg0 = AST5.isFunction(arg0) && [...AST5.getNestedCallExpressions(arg0.body), ...AST5.getNestedNewExpressions(arg0.body)].length > 0;
|
|
210
|
-
if (hasCallInArg0) {
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
419
|
const hasEmptyDeps = match(arg1).with({ type: AST_NODE_TYPES.ArrayExpression }, (n) => n.elements.length === 0).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
|
|
214
|
-
const variable =
|
|
215
|
-
const variableNode =
|
|
420
|
+
const variable = VAR.findVariable(n.name, initialScope);
|
|
421
|
+
const variableNode = VAR.getVariableInitNode(variable, 0);
|
|
216
422
|
if (variableNode?.type !== AST_NODE_TYPES.ArrayExpression) {
|
|
217
423
|
return false;
|
|
218
424
|
}
|
|
@@ -223,145 +429,35 @@ function create2(context) {
|
|
|
223
429
|
}
|
|
224
430
|
const arg0Node = match(arg0).with({ type: AST_NODE_TYPES.ArrowFunctionExpression }, (n) => {
|
|
225
431
|
if (n.body.type === AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
226
|
-
return n.body;
|
|
227
|
-
}
|
|
228
|
-
return n;
|
|
229
|
-
}).with({ type: AST_NODE_TYPES.FunctionExpression }, identity).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
|
|
230
|
-
const variable =
|
|
231
|
-
const variableNode =
|
|
232
|
-
if (variableNode?.type !== AST_NODE_TYPES.ArrowFunctionExpression && variableNode?.type !== AST_NODE_TYPES.FunctionExpression) {
|
|
233
|
-
return null;
|
|
234
|
-
}
|
|
235
|
-
return variableNode;
|
|
236
|
-
}).otherwise(() => null);
|
|
237
|
-
if (arg0Node == null) return;
|
|
238
|
-
const arg0NodeScope = context.sourceCode.getScope(arg0Node);
|
|
239
|
-
const arg0NodeReferences = VAR3.getChidScopes(arg0NodeScope).flatMap((x) => x.references);
|
|
240
|
-
const isReferencedToComponentScope = arg0NodeReferences.some((x) => x.resolved?.scope.block === component);
|
|
241
|
-
if (!isReferencedToComponentScope) {
|
|
242
|
-
context.report({
|
|
243
|
-
messageId: "noUnnecessaryUseMemo",
|
|
244
|
-
node
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
var RULE_NAME3 = "no-unnecessary-use-prefix";
|
|
251
|
-
var RULE_FEATURES3 = [];
|
|
252
|
-
var WELL_KNOWN_HOOKS = [
|
|
253
|
-
"useMDXComponents"
|
|
254
|
-
];
|
|
255
|
-
function containsUseComments(context, node) {
|
|
256
|
-
return context.sourceCode.getCommentsInside(node).some(({ value }) => /use\([\s\S]*?\)/u.test(value) || /use[A-Z0-9]\w*\([\s\S]*?\)/u.test(value));
|
|
257
|
-
}
|
|
258
|
-
var no_unnecessary_use_prefix_default = createRule({
|
|
259
|
-
meta: {
|
|
260
|
-
type: "problem",
|
|
261
|
-
deprecated: {
|
|
262
|
-
deprecatedSince: "2.0.0",
|
|
263
|
-
replacedBy: [
|
|
264
|
-
{
|
|
265
|
-
message: "Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead.",
|
|
266
|
-
plugin: {
|
|
267
|
-
name: "eslint-plugin-react-x",
|
|
268
|
-
url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x"
|
|
269
|
-
},
|
|
270
|
-
rule: {
|
|
271
|
-
name: "no-unnecessary-use-prefix",
|
|
272
|
-
url: "https://next.eslint-react.xyz/docs/rules/no-unnecessary-use-prefix"
|
|
273
|
-
}
|
|
274
|
-
},
|
|
275
|
-
{
|
|
276
|
-
message: "Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead.",
|
|
277
|
-
plugin: {
|
|
278
|
-
name: "@eslint-react/eslint-plugin",
|
|
279
|
-
url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin"
|
|
280
|
-
},
|
|
281
|
-
rule: {
|
|
282
|
-
name: "no-unnecessary-use-prefix",
|
|
283
|
-
url: "https://next.eslint-react.xyz/docs/rules/no-unnecessary-use-prefix"
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
]
|
|
287
|
-
},
|
|
288
|
-
docs: {
|
|
289
|
-
description: "Enforces that a function with the `use` prefix should use at least one Hook inside of it.",
|
|
290
|
-
[Symbol.for("rule_features")]: RULE_FEATURES3
|
|
291
|
-
},
|
|
292
|
-
messages: {
|
|
293
|
-
noUnnecessaryUsePrefix: "If your function doesn't call any Hooks, avoid the 'use' prefix. Instead, write it as a regular function without the 'use' prefix."
|
|
294
|
-
},
|
|
295
|
-
schema: []
|
|
296
|
-
},
|
|
297
|
-
name: RULE_NAME3,
|
|
298
|
-
create: create3,
|
|
299
|
-
defaultOptions: []
|
|
300
|
-
});
|
|
301
|
-
function create3(context) {
|
|
302
|
-
const { ctx, listeners } = ER5.useHookCollector();
|
|
303
|
-
return {
|
|
304
|
-
...listeners,
|
|
305
|
-
"Program:exit"(program) {
|
|
306
|
-
const allHooks = ctx.getAllHooks(program);
|
|
307
|
-
for (const { id, name: name3, node, hookCalls } of allHooks.values()) {
|
|
308
|
-
if (WELL_KNOWN_HOOKS.includes(name3)) {
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
if (AST5.isEmptyFunction(node)) {
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
if (hookCalls.length > 0) {
|
|
315
|
-
continue;
|
|
316
|
-
}
|
|
317
|
-
if (containsUseComments(context, node)) {
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
if (id != null) {
|
|
321
|
-
context.report({
|
|
322
|
-
messageId: "noUnnecessaryUsePrefix",
|
|
323
|
-
data: {
|
|
324
|
-
name: name3
|
|
325
|
-
},
|
|
326
|
-
loc: getPreferredLoc(context, id)
|
|
327
|
-
});
|
|
328
|
-
continue;
|
|
432
|
+
return n.body;
|
|
433
|
+
}
|
|
434
|
+
return n;
|
|
435
|
+
}).with({ type: AST_NODE_TYPES.FunctionExpression }, identity).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
|
|
436
|
+
const variable = VAR.findVariable(n.name, initialScope);
|
|
437
|
+
const variableNode = VAR.getVariableInitNode(variable, 0);
|
|
438
|
+
if (variableNode?.type !== AST_NODE_TYPES.ArrowFunctionExpression && variableNode?.type !== AST_NODE_TYPES.FunctionExpression) {
|
|
439
|
+
return null;
|
|
329
440
|
}
|
|
441
|
+
return variableNode;
|
|
442
|
+
}).otherwise(() => null);
|
|
443
|
+
if (arg0Node == null) return;
|
|
444
|
+
const arg0NodeScope = context.sourceCode.getScope(arg0Node);
|
|
445
|
+
const arg0NodeReferences = VAR.getChidScopes(arg0NodeScope).flatMap((x) => x.references);
|
|
446
|
+
const isReferencedToComponentScope = arg0NodeReferences.some((x) => x.resolved?.scope.block === component);
|
|
447
|
+
if (!isReferencedToComponentScope) {
|
|
330
448
|
context.report({
|
|
331
|
-
messageId: "
|
|
332
|
-
node
|
|
333
|
-
data: {
|
|
334
|
-
name: name3
|
|
335
|
-
}
|
|
449
|
+
messageId: "noUnnecessaryUseCallback",
|
|
450
|
+
node
|
|
336
451
|
});
|
|
337
452
|
}
|
|
338
453
|
}
|
|
339
454
|
};
|
|
340
455
|
}
|
|
341
|
-
|
|
342
|
-
if (AST5.isMultiLine(id)) return id.loc;
|
|
343
|
-
if (!context.sourceCode.getText(id).startsWith("use")) return id.loc;
|
|
344
|
-
return {
|
|
345
|
-
end: {
|
|
346
|
-
column: id.loc.start.column + 3,
|
|
347
|
-
line: id.loc.start.line
|
|
348
|
-
},
|
|
349
|
-
start: {
|
|
350
|
-
column: id.loc.start.column,
|
|
351
|
-
line: id.loc.start.line
|
|
352
|
-
}
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
var RULE_NAME4 = "prefer-use-state-lazy-initialization";
|
|
456
|
+
var RULE_NAME4 = "no-unnecessary-use-memo";
|
|
356
457
|
var RULE_FEATURES4 = [
|
|
357
458
|
"EXP"
|
|
358
459
|
];
|
|
359
|
-
var
|
|
360
|
-
"Boolean",
|
|
361
|
-
"String",
|
|
362
|
-
"Number"
|
|
363
|
-
];
|
|
364
|
-
var prefer_use_state_lazy_initialization_default = createRule({
|
|
460
|
+
var no_unnecessary_use_memo_default = createRule({
|
|
365
461
|
meta: {
|
|
366
462
|
type: "problem",
|
|
367
463
|
deprecated: {
|
|
@@ -374,8 +470,8 @@ var prefer_use_state_lazy_initialization_default = createRule({
|
|
|
374
470
|
url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x"
|
|
375
471
|
},
|
|
376
472
|
rule: {
|
|
377
|
-
name: "
|
|
378
|
-
url: "https://next.eslint-react.xyz/docs/rules/
|
|
473
|
+
name: "no-unnecessary-use-memo",
|
|
474
|
+
url: "https://next.eslint-react.xyz/docs/rules/no-unnecessary-use-memo"
|
|
379
475
|
}
|
|
380
476
|
},
|
|
381
477
|
{
|
|
@@ -385,18 +481,18 @@ var prefer_use_state_lazy_initialization_default = createRule({
|
|
|
385
481
|
url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin"
|
|
386
482
|
},
|
|
387
483
|
rule: {
|
|
388
|
-
name: "
|
|
389
|
-
url: "https://next.eslint-react.xyz/docs/rules/
|
|
484
|
+
name: "no-unnecessary-use-memo",
|
|
485
|
+
url: "https://next.eslint-react.xyz/docs/rules/no-unnecessary-use-memo"
|
|
390
486
|
}
|
|
391
487
|
}
|
|
392
488
|
]
|
|
393
489
|
},
|
|
394
490
|
docs: {
|
|
395
|
-
description: "
|
|
491
|
+
description: "Disallow unnecessary usage of `useMemo`.",
|
|
396
492
|
[Symbol.for("rule_features")]: RULE_FEATURES4
|
|
397
493
|
},
|
|
398
494
|
messages: {
|
|
399
|
-
|
|
495
|
+
noUnnecessaryUseMemo: "An 'useMemo' with empty deps and no references to the component scope may be unnecessary."
|
|
400
496
|
},
|
|
401
497
|
schema: []
|
|
402
498
|
},
|
|
@@ -405,313 +501,112 @@ var prefer_use_state_lazy_initialization_default = createRule({
|
|
|
405
501
|
defaultOptions: []
|
|
406
502
|
});
|
|
407
503
|
function create4(context) {
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
CallExpression(node) {
|
|
412
|
-
if (!ER5.isReactHookCall(node)) {
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
if (!isUseStateCall(node)) {
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
const [useStateInput] = node.arguments;
|
|
419
|
-
if (useStateInput == null) {
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
for (const expr of AST5.getNestedNewExpressions(useStateInput)) {
|
|
423
|
-
if (!("name" in expr.callee)) continue;
|
|
424
|
-
if (ALLOW_LIST.includes(expr.callee.name)) continue;
|
|
425
|
-
if (AST5.findParentNode(expr, (n) => ER5.isUseCall(context, n)) != null) continue;
|
|
426
|
-
context.report({
|
|
427
|
-
messageId: "preferUseStateLazyInitialization",
|
|
428
|
-
node: expr
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
for (const expr of AST5.getNestedCallExpressions(useStateInput)) {
|
|
432
|
-
if (!("name" in expr.callee)) continue;
|
|
433
|
-
if (ER5.isReactHookName(expr.callee.name)) continue;
|
|
434
|
-
if (ALLOW_LIST.includes(expr.callee.name)) continue;
|
|
435
|
-
if (AST5.findParentNode(expr, (n) => ER5.isUseCall(context, n)) != null) continue;
|
|
436
|
-
context.report({
|
|
437
|
-
messageId: "preferUseStateLazyInitialization",
|
|
438
|
-
node: expr
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
function useNoDirectSetStateInUseEffect(context, options) {
|
|
445
|
-
const { onViolation, useEffectKind } = options;
|
|
446
|
-
const settings = getSettingsFromContext(context);
|
|
447
|
-
const hooks = settings.additionalHooks;
|
|
448
|
-
const getText = (n) => context.sourceCode.getText(n);
|
|
449
|
-
const isUseEffectLikeCall = ER5.isReactHookCallWithNameAlias(context, useEffectKind, hooks[useEffectKind]);
|
|
450
|
-
const isUseStateCall = ER5.isReactHookCallWithNameAlias(context, "useState", hooks.useState);
|
|
451
|
-
const isUseMemoCall = ER5.isReactHookCallWithNameAlias(context, "useMemo", hooks.useMemo);
|
|
452
|
-
const isUseCallbackCall = ER5.isReactHookCallWithNameAlias(context, "useCallback", hooks.useCallback);
|
|
453
|
-
const functionEntries = [];
|
|
454
|
-
const setupFunctionRef = { current: null };
|
|
455
|
-
const setupFunctionIdentifiers = [];
|
|
456
|
-
const indFunctionCalls = [];
|
|
457
|
-
const indSetStateCalls = /* @__PURE__ */ new WeakMap();
|
|
458
|
-
const indSetStateCallsInUseEffectArg0 = /* @__PURE__ */ new WeakMap();
|
|
459
|
-
const indSetStateCallsInUseEffectSetup = /* @__PURE__ */ new Map();
|
|
460
|
-
const indSetStateCallsInUseMemoOrCallback = /* @__PURE__ */ new WeakMap();
|
|
461
|
-
const onSetupFunctionEnter = (node) => {
|
|
462
|
-
setupFunctionRef.current = node;
|
|
463
|
-
};
|
|
464
|
-
const onSetupFunctionExit = (node) => {
|
|
465
|
-
if (setupFunctionRef.current === node) {
|
|
466
|
-
setupFunctionRef.current = null;
|
|
467
|
-
}
|
|
468
|
-
};
|
|
469
|
-
function isFunctionOfUseEffectSetup(node) {
|
|
470
|
-
return node.parent?.type === AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseEffectLikeCall(node.parent);
|
|
471
|
-
}
|
|
472
|
-
function getCallName(node) {
|
|
473
|
-
if (node.type === AST_NODE_TYPES.CallExpression) {
|
|
474
|
-
return AST5.toStringFormat(node.callee, getText);
|
|
475
|
-
}
|
|
476
|
-
return AST5.toStringFormat(node, getText);
|
|
477
|
-
}
|
|
478
|
-
function getCallKind(node) {
|
|
479
|
-
return match(node).when(isUseStateCall, () => "useState").when(isUseEffectLikeCall, () => useEffectKind).when(isSetStateCall, () => "setState").when(AST5.isThenCall, () => "then").otherwise(() => "other");
|
|
480
|
-
}
|
|
481
|
-
function getFunctionKind(node) {
|
|
482
|
-
const parent = AST5.findParentNode(node, not(AST5.isTypeExpression)) ?? node.parent;
|
|
483
|
-
switch (true) {
|
|
484
|
-
case node.async:
|
|
485
|
-
case (parent.type === AST_NODE_TYPES.CallExpression && AST5.isThenCall(parent)):
|
|
486
|
-
return "deferred";
|
|
487
|
-
case (node.type !== AST_NODE_TYPES.FunctionDeclaration && parent.type === AST_NODE_TYPES.CallExpression && parent.callee === node):
|
|
488
|
-
return "immediate";
|
|
489
|
-
case isFunctionOfUseEffectSetup(node):
|
|
490
|
-
return "setup";
|
|
491
|
-
default:
|
|
492
|
-
return "other";
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
function isIdFromUseStateCall(topLevelId, at) {
|
|
496
|
-
const variable = VAR3.findVariable(topLevelId, context.sourceCode.getScope(topLevelId));
|
|
497
|
-
const variableNode = VAR3.getVariableInitNode(variable, 0);
|
|
498
|
-
if (variableNode == null) return false;
|
|
499
|
-
if (variableNode.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
500
|
-
if (!ER5.isReactHookCallWithNameAlias(context, "useState", hooks.useState)(variableNode)) return false;
|
|
501
|
-
const variableNodeParent = variableNode.parent;
|
|
502
|
-
if (!("id" in variableNodeParent) || variableNodeParent.id?.type !== AST_NODE_TYPES.ArrayPattern) {
|
|
503
|
-
return true;
|
|
504
|
-
}
|
|
505
|
-
return variableNodeParent.id.elements.findIndex((e) => e?.type === AST_NODE_TYPES.Identifier && e.name === topLevelId.name) === at;
|
|
506
|
-
}
|
|
507
|
-
function isSetStateCall(node) {
|
|
508
|
-
switch (node.callee.type) {
|
|
509
|
-
// const data = useState();
|
|
510
|
-
// data.at(1)();
|
|
511
|
-
case AST_NODE_TYPES.CallExpression: {
|
|
512
|
-
const { callee } = node.callee;
|
|
513
|
-
if (callee.type !== AST_NODE_TYPES.MemberExpression) {
|
|
514
|
-
return false;
|
|
515
|
-
}
|
|
516
|
-
if (!("name" in callee.object)) {
|
|
517
|
-
return false;
|
|
518
|
-
}
|
|
519
|
-
const isAt = callee.property.type === AST_NODE_TYPES.Identifier && callee.property.name === "at";
|
|
520
|
-
const [index] = node.callee.arguments;
|
|
521
|
-
if (!isAt || index == null) {
|
|
522
|
-
return false;
|
|
523
|
-
}
|
|
524
|
-
const indexScope = context.sourceCode.getScope(node);
|
|
525
|
-
const indexValue = VAR3.toStaticValue({
|
|
526
|
-
kind: "lazy",
|
|
527
|
-
node: index,
|
|
528
|
-
initialScope: indexScope
|
|
529
|
-
}).value;
|
|
530
|
-
return indexValue === 1 && isIdFromUseStateCall(callee.object);
|
|
531
|
-
}
|
|
532
|
-
// const [data, setData] = useState();
|
|
533
|
-
// setData();
|
|
534
|
-
case AST_NODE_TYPES.Identifier: {
|
|
535
|
-
return isIdFromUseStateCall(node.callee, 1);
|
|
536
|
-
}
|
|
537
|
-
// const data = useState();
|
|
538
|
-
// data[1]();
|
|
539
|
-
case AST_NODE_TYPES.MemberExpression: {
|
|
540
|
-
if (!("name" in node.callee.object)) {
|
|
541
|
-
return false;
|
|
542
|
-
}
|
|
543
|
-
const property = node.callee.property;
|
|
544
|
-
const propertyScope = context.sourceCode.getScope(node);
|
|
545
|
-
const propertyValue = VAR3.toStaticValue({
|
|
546
|
-
kind: "lazy",
|
|
547
|
-
node: property,
|
|
548
|
-
initialScope: propertyScope
|
|
549
|
-
}).value;
|
|
550
|
-
return propertyValue === 1 && isIdFromUseStateCall(node.callee.object, 1);
|
|
551
|
-
}
|
|
552
|
-
default: {
|
|
553
|
-
return false;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
504
|
+
if (!context.sourceCode.text.includes("use")) return {};
|
|
505
|
+
const alias = getSettingsFromContext(context).additionalHooks.useMemo ?? [];
|
|
506
|
+
const isUseMemoCall = ER.isReactHookCallWithNameAlias(context, "useMemo", alias);
|
|
557
507
|
return {
|
|
558
|
-
":function"(node) {
|
|
559
|
-
const kind = getFunctionKind(node);
|
|
560
|
-
functionEntries.push({ kind, node });
|
|
561
|
-
if (kind === "setup") {
|
|
562
|
-
onSetupFunctionEnter(node);
|
|
563
|
-
}
|
|
564
|
-
},
|
|
565
|
-
":function:exit"(node) {
|
|
566
|
-
const { kind } = functionEntries.at(-1) ?? {};
|
|
567
|
-
if (kind === "setup") {
|
|
568
|
-
onSetupFunctionExit(node);
|
|
569
|
-
}
|
|
570
|
-
functionEntries.pop();
|
|
571
|
-
},
|
|
572
508
|
CallExpression(node) {
|
|
573
|
-
|
|
574
|
-
const pEntry = functionEntries.at(-1);
|
|
575
|
-
if (pEntry == null || pEntry.node.async) {
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
match(getCallKind(node)).with("setState", () => {
|
|
579
|
-
switch (true) {
|
|
580
|
-
case pEntry.kind === "deferred":
|
|
581
|
-
case pEntry.node.async:
|
|
582
|
-
break;
|
|
583
|
-
case pEntry.node === setupFunction:
|
|
584
|
-
case (pEntry.kind === "immediate" && AST5.findParentNode(pEntry.node, AST5.isFunction) === setupFunction): {
|
|
585
|
-
onViolation(context, node, {
|
|
586
|
-
name: context.sourceCode.getText(node.callee)
|
|
587
|
-
});
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
default: {
|
|
591
|
-
const vd = AST5.findParentNode(node, isVariableDeclaratorFromHookCall);
|
|
592
|
-
if (vd == null) getOrElseUpdate(indSetStateCalls, pEntry.node, () => []).push(node);
|
|
593
|
-
else getOrElseUpdate(indSetStateCallsInUseMemoOrCallback, vd.init, () => []).push(node);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
}).with(useEffectKind, () => {
|
|
597
|
-
if (AST5.isFunction(node.arguments.at(0))) return;
|
|
598
|
-
setupFunctionIdentifiers.push(...AST5.getNestedIdentifiers(node));
|
|
599
|
-
}).with("other", () => {
|
|
600
|
-
if (pEntry.node !== setupFunction) return;
|
|
601
|
-
indFunctionCalls.push(node);
|
|
602
|
-
}).otherwise(constVoid);
|
|
603
|
-
},
|
|
604
|
-
Identifier(node) {
|
|
605
|
-
if (node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee === node) {
|
|
509
|
+
if (!ER.isReactHookCall(node)) {
|
|
606
510
|
return;
|
|
607
511
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
switch (node.parent.type) {
|
|
612
|
-
case AST_NODE_TYPES.ArrowFunctionExpression: {
|
|
613
|
-
const parent = node.parent.parent;
|
|
614
|
-
if (parent.type !== AST_NODE_TYPES.CallExpression) {
|
|
615
|
-
break;
|
|
616
|
-
}
|
|
617
|
-
if (!isUseMemoCall(parent)) {
|
|
618
|
-
break;
|
|
619
|
-
}
|
|
620
|
-
const vd = AST5.findParentNode(parent, isVariableDeclaratorFromHookCall);
|
|
621
|
-
if (vd != null) {
|
|
622
|
-
getOrElseUpdate(indSetStateCallsInUseEffectArg0, vd.init, () => []).push(node);
|
|
623
|
-
}
|
|
624
|
-
break;
|
|
625
|
-
}
|
|
626
|
-
case AST_NODE_TYPES.CallExpression: {
|
|
627
|
-
if (node !== node.parent.arguments.at(0)) {
|
|
628
|
-
break;
|
|
629
|
-
}
|
|
630
|
-
if (isUseCallbackCall(node.parent)) {
|
|
631
|
-
const vd = AST5.findParentNode(node.parent, isVariableDeclaratorFromHookCall);
|
|
632
|
-
if (vd != null) {
|
|
633
|
-
getOrElseUpdate(indSetStateCallsInUseEffectArg0, vd.init, () => []).push(node);
|
|
634
|
-
}
|
|
635
|
-
break;
|
|
636
|
-
}
|
|
637
|
-
if (isUseEffectLikeCall(node.parent)) {
|
|
638
|
-
getOrElseUpdate(indSetStateCallsInUseEffectSetup, node.parent, () => []).push(node);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
},
|
|
643
|
-
"Program:exit"() {
|
|
644
|
-
const getSetStateCalls = (id, initialScope) => {
|
|
645
|
-
const node = VAR3.getVariableInitNode(VAR3.findVariable(id, initialScope), 0);
|
|
646
|
-
switch (node?.type) {
|
|
647
|
-
case AST_NODE_TYPES.ArrowFunctionExpression:
|
|
648
|
-
case AST_NODE_TYPES.FunctionDeclaration:
|
|
649
|
-
case AST_NODE_TYPES.FunctionExpression:
|
|
650
|
-
return indSetStateCalls.get(node) ?? [];
|
|
651
|
-
case AST_NODE_TYPES.CallExpression:
|
|
652
|
-
return indSetStateCallsInUseMemoOrCallback.get(node) ?? indSetStateCallsInUseEffectArg0.get(node) ?? [];
|
|
653
|
-
}
|
|
654
|
-
return [];
|
|
655
|
-
};
|
|
656
|
-
for (const [, calls] of indSetStateCallsInUseEffectSetup) {
|
|
657
|
-
for (const call of calls) {
|
|
658
|
-
onViolation(context, call, { name: call.name });
|
|
659
|
-
}
|
|
512
|
+
const initialScope = context.sourceCode.getScope(node);
|
|
513
|
+
if (!isUseMemoCall(node)) {
|
|
514
|
+
return;
|
|
660
515
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
516
|
+
const scope = context.sourceCode.getScope(node);
|
|
517
|
+
const component = scope.block;
|
|
518
|
+
if (!AST.isFunction(component)) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const [arg0, arg1] = node.arguments;
|
|
522
|
+
if (arg0 == null || arg1 == null) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const hasCallInArg0 = AST.isFunction(arg0) && [...AST.getNestedCallExpressions(arg0.body), ...AST.getNestedNewExpressions(arg0.body)].length > 0;
|
|
526
|
+
if (hasCallInArg0) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
const hasEmptyDeps = match(arg1).with({ type: AST_NODE_TYPES.ArrayExpression }, (n) => n.elements.length === 0).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
|
|
530
|
+
const variable = VAR.findVariable(n.name, initialScope);
|
|
531
|
+
const variableNode = VAR.getVariableInitNode(variable, 0);
|
|
532
|
+
if (variableNode?.type !== AST_NODE_TYPES.ArrayExpression) {
|
|
533
|
+
return false;
|
|
671
534
|
}
|
|
535
|
+
return variableNode.elements.length === 0;
|
|
536
|
+
}).otherwise(() => false);
|
|
537
|
+
if (!hasEmptyDeps) {
|
|
538
|
+
return;
|
|
672
539
|
}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
540
|
+
const arg0Node = match(arg0).with({ type: AST_NODE_TYPES.ArrowFunctionExpression }, (n) => {
|
|
541
|
+
if (n.body.type === AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
542
|
+
return n.body;
|
|
543
|
+
}
|
|
544
|
+
return n;
|
|
545
|
+
}).with({ type: AST_NODE_TYPES.FunctionExpression }, identity).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
|
|
546
|
+
const variable = VAR.findVariable(n.name, initialScope);
|
|
547
|
+
const variableNode = VAR.getVariableInitNode(variable, 0);
|
|
548
|
+
if (variableNode?.type !== AST_NODE_TYPES.ArrowFunctionExpression && variableNode?.type !== AST_NODE_TYPES.FunctionExpression) {
|
|
549
|
+
return null;
|
|
679
550
|
}
|
|
551
|
+
return variableNode;
|
|
552
|
+
}).otherwise(() => null);
|
|
553
|
+
if (arg0Node == null) return;
|
|
554
|
+
const arg0NodeScope = context.sourceCode.getScope(arg0Node);
|
|
555
|
+
const arg0NodeReferences = VAR.getChidScopes(arg0NodeScope).flatMap((x) => x.references);
|
|
556
|
+
const isReferencedToComponentScope = arg0NodeReferences.some((x) => x.resolved?.scope.block === component);
|
|
557
|
+
if (!isReferencedToComponentScope) {
|
|
558
|
+
context.report({
|
|
559
|
+
messageId: "noUnnecessaryUseMemo",
|
|
560
|
+
node
|
|
561
|
+
});
|
|
680
562
|
}
|
|
681
563
|
}
|
|
682
564
|
};
|
|
683
565
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
return ER5.isReactHookName(init.callee.name);
|
|
689
|
-
case AST_NODE_TYPES.MemberExpression:
|
|
690
|
-
return init.callee.property.type === AST_NODE_TYPES.Identifier && ER5.isReactHookName(init.callee.property.name);
|
|
691
|
-
default:
|
|
692
|
-
return false;
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
function isVariableDeclaratorFromHookCall(node) {
|
|
696
|
-
if (node.type !== AST_NODE_TYPES.VariableDeclarator) return false;
|
|
697
|
-
if (node.id.type !== AST_NODE_TYPES.Identifier) return false;
|
|
698
|
-
return isInitFromHookCall(node.init);
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
// src/rules/no-direct-set-state-in-use-effect.ts
|
|
702
|
-
var RULE_NAME5 = "no-direct-set-state-in-use-effect";
|
|
703
|
-
var RULE_FEATURES5 = [
|
|
704
|
-
"EXP"
|
|
566
|
+
var RULE_NAME5 = "no-unnecessary-use-prefix";
|
|
567
|
+
var RULE_FEATURES5 = [];
|
|
568
|
+
var WELL_KNOWN_HOOKS = [
|
|
569
|
+
"useMDXComponents"
|
|
705
570
|
];
|
|
706
|
-
|
|
571
|
+
function containsUseComments(context, node) {
|
|
572
|
+
return context.sourceCode.getCommentsInside(node).some(({ value }) => /use\([\s\S]*?\)/u.test(value) || /use[A-Z0-9]\w*\([\s\S]*?\)/u.test(value));
|
|
573
|
+
}
|
|
574
|
+
var no_unnecessary_use_prefix_default = createRule({
|
|
707
575
|
meta: {
|
|
708
576
|
type: "problem",
|
|
577
|
+
deprecated: {
|
|
578
|
+
deprecatedSince: "2.0.0",
|
|
579
|
+
replacedBy: [
|
|
580
|
+
{
|
|
581
|
+
message: "Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead.",
|
|
582
|
+
plugin: {
|
|
583
|
+
name: "eslint-plugin-react-x",
|
|
584
|
+
url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x"
|
|
585
|
+
},
|
|
586
|
+
rule: {
|
|
587
|
+
name: "no-unnecessary-use-prefix",
|
|
588
|
+
url: "https://next.eslint-react.xyz/docs/rules/no-unnecessary-use-prefix"
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
message: "Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead.",
|
|
593
|
+
plugin: {
|
|
594
|
+
name: "@eslint-react/eslint-plugin",
|
|
595
|
+
url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin"
|
|
596
|
+
},
|
|
597
|
+
rule: {
|
|
598
|
+
name: "no-unnecessary-use-prefix",
|
|
599
|
+
url: "https://next.eslint-react.xyz/docs/rules/no-unnecessary-use-prefix"
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
]
|
|
603
|
+
},
|
|
709
604
|
docs: {
|
|
710
|
-
description: "
|
|
605
|
+
description: "Enforces that a function with the `use` prefix should use at least one Hook inside of it.",
|
|
711
606
|
[Symbol.for("rule_features")]: RULE_FEATURES5
|
|
712
607
|
},
|
|
713
608
|
messages: {
|
|
714
|
-
|
|
609
|
+
noUnnecessaryUsePrefix: "If your function doesn't call any Hooks, avoid the 'use' prefix. Instead, write it as a regular function without the 'use' prefix."
|
|
715
610
|
},
|
|
716
611
|
schema: []
|
|
717
612
|
},
|
|
@@ -720,29 +615,104 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
720
615
|
defaultOptions: []
|
|
721
616
|
});
|
|
722
617
|
function create5(context) {
|
|
723
|
-
|
|
724
|
-
return
|
|
725
|
-
|
|
726
|
-
|
|
618
|
+
const { ctx, listeners } = ER.useHookCollector();
|
|
619
|
+
return {
|
|
620
|
+
...listeners,
|
|
621
|
+
"Program:exit"(program) {
|
|
622
|
+
const allHooks = ctx.getAllHooks(program);
|
|
623
|
+
for (const { id, name: name3, node, hookCalls } of allHooks.values()) {
|
|
624
|
+
if (WELL_KNOWN_HOOKS.includes(name3)) {
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
if (AST.isEmptyFunction(node)) {
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
if (hookCalls.length > 0) {
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (containsUseComments(context, node)) {
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (id != null) {
|
|
637
|
+
context.report({
|
|
638
|
+
messageId: "noUnnecessaryUsePrefix",
|
|
639
|
+
data: {
|
|
640
|
+
name: name3
|
|
641
|
+
},
|
|
642
|
+
loc: getPreferredLoc(context, id)
|
|
643
|
+
});
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
context.report({
|
|
647
|
+
messageId: "noUnnecessaryUsePrefix",
|
|
648
|
+
node,
|
|
649
|
+
data: {
|
|
650
|
+
name: name3
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
function getPreferredLoc(context, id) {
|
|
658
|
+
if (AST.isMultiLine(id)) return id.loc;
|
|
659
|
+
if (!context.sourceCode.getText(id).startsWith("use")) return id.loc;
|
|
660
|
+
return {
|
|
661
|
+
end: {
|
|
662
|
+
column: id.loc.start.column + 3,
|
|
663
|
+
line: id.loc.start.line
|
|
727
664
|
},
|
|
728
|
-
|
|
729
|
-
|
|
665
|
+
start: {
|
|
666
|
+
column: id.loc.start.column,
|
|
667
|
+
line: id.loc.start.line
|
|
668
|
+
}
|
|
669
|
+
};
|
|
730
670
|
}
|
|
731
|
-
|
|
732
|
-
// src/rules/no-direct-set-state-in-use-layout-effect.ts
|
|
733
|
-
var RULE_NAME6 = "no-direct-set-state-in-use-layout-effect";
|
|
671
|
+
var RULE_NAME6 = "prefer-use-state-lazy-initialization";
|
|
734
672
|
var RULE_FEATURES6 = [
|
|
735
673
|
"EXP"
|
|
736
674
|
];
|
|
737
|
-
var
|
|
675
|
+
var ALLOW_LIST = [
|
|
676
|
+
"Boolean",
|
|
677
|
+
"String",
|
|
678
|
+
"Number"
|
|
679
|
+
];
|
|
680
|
+
var prefer_use_state_lazy_initialization_default = createRule({
|
|
738
681
|
meta: {
|
|
739
682
|
type: "problem",
|
|
683
|
+
deprecated: {
|
|
684
|
+
deprecatedSince: "2.0.0",
|
|
685
|
+
replacedBy: [
|
|
686
|
+
{
|
|
687
|
+
message: "Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead.",
|
|
688
|
+
plugin: {
|
|
689
|
+
name: "eslint-plugin-react-x",
|
|
690
|
+
url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x"
|
|
691
|
+
},
|
|
692
|
+
rule: {
|
|
693
|
+
name: "prefer-use-state-lazy-initialization",
|
|
694
|
+
url: "https://next.eslint-react.xyz/docs/rules/prefer-use-state-lazy-initialization"
|
|
695
|
+
}
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
message: "Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead.",
|
|
699
|
+
plugin: {
|
|
700
|
+
name: "@eslint-react/eslint-plugin",
|
|
701
|
+
url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin"
|
|
702
|
+
},
|
|
703
|
+
rule: {
|
|
704
|
+
name: "prefer-use-state-lazy-initialization",
|
|
705
|
+
url: "https://next.eslint-react.xyz/docs/rules/prefer-use-state-lazy-initialization"
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
]
|
|
709
|
+
},
|
|
740
710
|
docs: {
|
|
741
|
-
description: "
|
|
711
|
+
description: "Enforces function calls made inside `useState` to be wrapped in an `initializer function`.",
|
|
742
712
|
[Symbol.for("rule_features")]: RULE_FEATURES6
|
|
743
713
|
},
|
|
744
714
|
messages: {
|
|
745
|
-
|
|
715
|
+
preferUseStateLazyInitialization: "To prevent re-computation, consider using lazy initial state for useState calls that involve function calls. Ex: 'useState(() => getValue())'."
|
|
746
716
|
},
|
|
747
717
|
schema: []
|
|
748
718
|
},
|
|
@@ -751,13 +721,41 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
751
721
|
defaultOptions: []
|
|
752
722
|
});
|
|
753
723
|
function create6(context) {
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
724
|
+
const alias = getSettingsFromContext(context).additionalHooks.useState ?? [];
|
|
725
|
+
const isUseStateCall = ER.isReactHookCallWithNameAlias(context, "useState", alias);
|
|
726
|
+
return {
|
|
727
|
+
CallExpression(node) {
|
|
728
|
+
if (!ER.isReactHookCall(node)) {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
if (!isUseStateCall(node)) {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
const [useStateInput] = node.arguments;
|
|
735
|
+
if (useStateInput == null) {
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
for (const expr of AST.getNestedNewExpressions(useStateInput)) {
|
|
739
|
+
if (!("name" in expr.callee)) continue;
|
|
740
|
+
if (ALLOW_LIST.includes(expr.callee.name)) continue;
|
|
741
|
+
if (AST.findParentNode(expr, (n) => ER.isUseCall(context, n)) != null) continue;
|
|
742
|
+
context.report({
|
|
743
|
+
messageId: "preferUseStateLazyInitialization",
|
|
744
|
+
node: expr
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
for (const expr of AST.getNestedCallExpressions(useStateInput)) {
|
|
748
|
+
if (!("name" in expr.callee)) continue;
|
|
749
|
+
if (ER.isReactHookName(expr.callee.name)) continue;
|
|
750
|
+
if (ALLOW_LIST.includes(expr.callee.name)) continue;
|
|
751
|
+
if (AST.findParentNode(expr, (n) => ER.isUseCall(context, n)) != null) continue;
|
|
752
|
+
context.report({
|
|
753
|
+
messageId: "preferUseStateLazyInitialization",
|
|
754
|
+
node: expr
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
};
|
|
761
759
|
}
|
|
762
760
|
|
|
763
761
|
// src/plugin.ts
|
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.48",
|
|
4
4
|
"description": "ESLint React's ESLint plugin for React Hooks related rules.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -36,18 +36,18 @@
|
|
|
36
36
|
"./package.json"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@typescript-eslint/scope-manager": "^8.34.
|
|
40
|
-
"@typescript-eslint/type-utils": "^8.34.
|
|
41
|
-
"@typescript-eslint/types": "^8.34.
|
|
42
|
-
"@typescript-eslint/utils": "^8.34.
|
|
39
|
+
"@typescript-eslint/scope-manager": "^8.34.1",
|
|
40
|
+
"@typescript-eslint/type-utils": "^8.34.1",
|
|
41
|
+
"@typescript-eslint/types": "^8.34.1",
|
|
42
|
+
"@typescript-eslint/utils": "^8.34.1",
|
|
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.48",
|
|
46
|
+
"@eslint-react/eff": "2.0.0-next.48",
|
|
47
|
+
"@eslint-react/core": "2.0.0-next.48",
|
|
48
|
+
"@eslint-react/kit": "2.0.0-next.48",
|
|
49
|
+
"@eslint-react/shared": "2.0.0-next.48",
|
|
50
|
+
"@eslint-react/var": "2.0.0-next.48"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/react": "^19.1.8",
|