eslint-plugin-react-hooks-extra 2.0.0-beta.172 → 2.0.0-beta.173

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.
Files changed (2) hide show
  1. package/dist/index.js +70 -444
  2. package/package.json +7 -7
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { getConfigAdapters, getDocsUrl, getSettingsFromContext } from "@eslint-react/shared";
1
+ import { getConfigAdapters, getDocsUrl } from "@eslint-react/shared";
2
2
  import * as AST from "@eslint-react/ast";
3
3
  import * as ER from "@eslint-react/core";
4
- import { constVoid, getOrElseUpdate, identity, not } from "@eslint-react/eff";
4
+ import { constVoid, getOrElseUpdate, not } from "@eslint-react/eff";
5
5
  import * as VAR from "@eslint-react/var";
6
6
  import { AST_NODE_TYPES } from "@typescript-eslint/types";
7
7
  import { getStaticValue } from "@typescript-eslint/utils/ast-utils";
@@ -30,19 +30,48 @@ const rules = { "react-hooks-extra/no-direct-set-state-in-use-effect": "warn" };
30
30
  //#endregion
31
31
  //#region package.json
32
32
  var name = "eslint-plugin-react-hooks-extra";
33
- var version = "2.0.0-beta.172";
33
+ var version = "2.0.0-beta.173";
34
34
 
35
35
  //#endregion
36
- //#region src/rules-hooks/use-no-direct-set-state-in-use-effect.ts
37
- function useNoDirectSetStateInUseEffect(context, options) {
38
- const { onViolation, useEffectKind } = options;
39
- const settings = getSettingsFromContext(context);
40
- const hooks = settings.additionalHooks;
41
- const getText = (n) => context.sourceCode.getText(n);
42
- const isUseEffectLikeCall = ER.isReactHookCallWithNameAlias(context, useEffectKind, hooks[useEffectKind]);
43
- const isUseStateCall = ER.isReactHookCallWithNameAlias(context, "useState", hooks.useState);
44
- const isUseMemoCall = ER.isReactHookCallWithNameAlias(context, "useMemo", hooks.useMemo);
45
- const isUseCallbackCall = ER.isReactHookCallWithNameAlias(context, "useCallback", hooks.useCallback);
36
+ //#region src/utils/create-rule.ts
37
+ const createRule = ESLintUtils.RuleCreator(getDocsUrl("hooks-extra"));
38
+
39
+ //#endregion
40
+ //#region src/utils/is-variable-declarator-from-hook-call.ts
41
+ function isInitFromHookCall(init) {
42
+ if (init?.type !== AST_NODE_TYPES.CallExpression) return false;
43
+ switch (init.callee.type) {
44
+ case AST_NODE_TYPES.Identifier: return ER.isReactHookName(init.callee.name);
45
+ case AST_NODE_TYPES.MemberExpression: return init.callee.property.type === AST_NODE_TYPES.Identifier && ER.isReactHookName(init.callee.property.name);
46
+ default: return false;
47
+ }
48
+ }
49
+ function isVariableDeclaratorFromHookCall(node) {
50
+ if (node.type !== AST_NODE_TYPES.VariableDeclarator) return false;
51
+ if (node.id.type !== AST_NODE_TYPES.Identifier) return false;
52
+ return isInitFromHookCall(node.init);
53
+ }
54
+
55
+ //#endregion
56
+ //#region src/rules/no-direct-set-state-in-use-effect.ts
57
+ const RULE_NAME = "no-direct-set-state-in-use-effect";
58
+ const RULE_FEATURES = ["EXP"];
59
+ var no_direct_set_state_in_use_effect_default = createRule({
60
+ meta: {
61
+ type: "problem",
62
+ docs: {
63
+ description: "Disallow direct calls to the `set` function of `useState` in `useEffect`.",
64
+ [Symbol.for("rule_features")]: RULE_FEATURES
65
+ },
66
+ messages: { noDirectSetStateInUseEffect: "Do not call the 'set' function '{{name}}' of 'useState' directly in 'useEffect'." },
67
+ schema: []
68
+ },
69
+ name: RULE_NAME,
70
+ create,
71
+ defaultOptions: []
72
+ });
73
+ function create(context) {
74
+ if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
46
75
  const functionEntries = [];
47
76
  const setupFnRef = { current: null };
48
77
  const setupFnIds = [];
@@ -51,6 +80,7 @@ function useNoDirectSetStateInUseEffect(context, options) {
51
80
  const setStateInEffectArg = /* @__PURE__ */ new WeakMap();
52
81
  const setStateInEffectSetup = /* @__PURE__ */ new Map();
53
82
  const setStateInHookCallbacks = /* @__PURE__ */ new WeakMap();
83
+ const getText = (n) => context.sourceCode.getText(n);
54
84
  const onSetupFunctionEnter = (node) => {
55
85
  setupFnRef.current = node;
56
86
  };
@@ -58,14 +88,14 @@ function useNoDirectSetStateInUseEffect(context, options) {
58
88
  if (setupFnRef.current === node) setupFnRef.current = null;
59
89
  };
60
90
  function isFunctionOfUseEffectSetup(node) {
61
- return node.parent?.type === AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseEffectLikeCall(node.parent);
91
+ return node.parent?.type === AST_NODE_TYPES.CallExpression && node.parent.callee !== node && ER.isUseEffectCall(node.parent);
62
92
  }
63
93
  function getCallName(node) {
64
94
  if (node.type === AST_NODE_TYPES.CallExpression) return AST.toStringFormat(node.callee, getText);
65
95
  return AST.toStringFormat(node, getText);
66
96
  }
67
97
  function getCallKind(node) {
68
- return match(node).when(isUseStateCall, () => "useState").when(isUseEffectLikeCall, () => useEffectKind).when(isSetStateCall, () => "setState").when(AST.isThenCall, () => "then").otherwise(() => "other");
98
+ return match(node).when(ER.isUseStateCall, () => "useState").when(ER.isUseEffectLikeCall, () => "useEffect").when(isSetStateCall, () => "setState").when(AST.isThenCall, () => "then").otherwise(() => "other");
69
99
  }
70
100
  function getFunctionKind(node) {
71
101
  const parent = AST.findParentNode(node, not(AST.isTypeExpression)) ?? node.parent;
@@ -82,7 +112,7 @@ function useNoDirectSetStateInUseEffect(context, options) {
82
112
  const variableNode = VAR.getVariableDefinitionNode(variable, 0);
83
113
  if (variableNode == null) return false;
84
114
  if (variableNode.type !== AST_NODE_TYPES.CallExpression) return false;
85
- if (!ER.isReactHookCallWithNameAlias(context, "useState", hooks.useState)(variableNode)) return false;
115
+ if (!ER.isUseStateCall(variableNode)) return false;
86
116
  const variableNodeParent = variableNode.parent;
87
117
  if (!("id" in variableNodeParent) || variableNodeParent.id?.type !== AST_NODE_TYPES.ArrayPattern) return true;
88
118
  return variableNodeParent.id.elements.findIndex((e) => e?.type === AST_NODE_TYPES.Identifier && e.name === topLevelId.name) === at;
@@ -135,7 +165,11 @@ function useNoDirectSetStateInUseEffect(context, options) {
135
165
  case pEntry.node.async: break;
136
166
  case pEntry.node === setupFunction:
137
167
  case pEntry.kind === "immediate" && AST.findParentNode(pEntry.node, AST.isFunction) === setupFunction:
138
- onViolation(context, node, { name: context.sourceCode.getText(node.callee) });
168
+ context.report({
169
+ messageId: "noDirectSetStateInUseEffect",
170
+ node,
171
+ data: { name: context.sourceCode.getText(node.callee) }
172
+ });
139
173
  return;
140
174
  default: {
141
175
  const vd = AST.findParentNode(node, isVariableDeclaratorFromHookCall);
@@ -143,7 +177,7 @@ function useNoDirectSetStateInUseEffect(context, options) {
143
177
  else getOrElseUpdate(setStateInHookCallbacks, vd.init, () => []).push(node);
144
178
  }
145
179
  }
146
- }).with(useEffectKind, () => {
180
+ }).with("useEffect", () => {
147
181
  if (AST.isFunction(node.arguments.at(0))) return;
148
182
  setupFnIds.push(...AST.getNestedIdentifiers(node));
149
183
  }).with("other", () => {
@@ -158,19 +192,19 @@ function useNoDirectSetStateInUseEffect(context, options) {
158
192
  case AST_NODE_TYPES.ArrowFunctionExpression: {
159
193
  const parent = node.parent.parent;
160
194
  if (parent.type !== AST_NODE_TYPES.CallExpression) break;
161
- if (!isUseMemoCall(parent)) break;
195
+ if (!ER.isUseMemoCall(parent)) break;
162
196
  const vd = AST.findParentNode(parent, isVariableDeclaratorFromHookCall);
163
197
  if (vd != null) getOrElseUpdate(setStateInEffectArg, vd.init, () => []).push(node);
164
198
  break;
165
199
  }
166
200
  case AST_NODE_TYPES.CallExpression:
167
201
  if (node !== node.parent.arguments.at(0)) break;
168
- if (isUseCallbackCall(node.parent)) {
202
+ if (ER.isUseCallbackCall(node.parent)) {
169
203
  const vd = AST.findParentNode(node.parent, isVariableDeclaratorFromHookCall);
170
204
  if (vd != null) getOrElseUpdate(setStateInEffectArg, vd.init, () => []).push(node);
171
205
  break;
172
206
  }
173
- if (isUseEffectLikeCall(node.parent)) getOrElseUpdate(setStateInEffectSetup, node.parent, () => []).push(node);
207
+ if (ER.isUseEffectCall(node.parent)) getOrElseUpdate(setStateInEffectSetup, node.parent, () => []).push(node);
174
208
  }
175
209
  },
176
210
  "Program:exit"() {
@@ -184,433 +218,32 @@ function useNoDirectSetStateInUseEffect(context, options) {
184
218
  }
185
219
  return [];
186
220
  };
187
- for (const [, calls] of setStateInEffectSetup) for (const call of calls) onViolation(context, call, { name: call.name });
221
+ for (const [, calls] of setStateInEffectSetup) for (const call of calls) context.report({
222
+ messageId: "noDirectSetStateInUseEffect",
223
+ node: call,
224
+ data: { name: call.name }
225
+ });
188
226
  for (const { callee } of trackedFnCalls) {
189
227
  if (!("name" in callee)) continue;
190
228
  const { name: name$2 } = callee;
191
229
  const setStateCalls = getSetStateCalls(name$2, context.sourceCode.getScope(callee));
192
- for (const setStateCall of setStateCalls) onViolation(context, setStateCall, { name: getCallName(setStateCall) });
230
+ for (const setStateCall of setStateCalls) context.report({
231
+ messageId: "noDirectSetStateInUseEffect",
232
+ node: setStateCall,
233
+ data: { name: getCallName(setStateCall) }
234
+ });
193
235
  }
194
236
  for (const id of setupFnIds) {
195
237
  const setStateCalls = getSetStateCalls(id.name, context.sourceCode.getScope(id));
196
- for (const setStateCall of setStateCalls) onViolation(context, setStateCall, { name: getCallName(setStateCall) });
197
- }
198
- }
199
- };
200
- }
201
- function isInitFromHookCall(init) {
202
- if (init?.type !== AST_NODE_TYPES.CallExpression) return false;
203
- switch (init.callee.type) {
204
- case AST_NODE_TYPES.Identifier: return ER.isReactHookName(init.callee.name);
205
- case AST_NODE_TYPES.MemberExpression: return init.callee.property.type === AST_NODE_TYPES.Identifier && ER.isReactHookName(init.callee.property.name);
206
- default: return false;
207
- }
208
- }
209
- function isVariableDeclaratorFromHookCall(node) {
210
- if (node.type !== AST_NODE_TYPES.VariableDeclarator) return false;
211
- if (node.id.type !== AST_NODE_TYPES.Identifier) return false;
212
- return isInitFromHookCall(node.init);
213
- }
214
-
215
- //#endregion
216
- //#region src/utils/create-rule.ts
217
- const createRule = ESLintUtils.RuleCreator(getDocsUrl("hooks-extra"));
218
-
219
- //#endregion
220
- //#region src/rules/no-direct-set-state-in-use-effect.ts
221
- const RULE_NAME$5 = "no-direct-set-state-in-use-effect";
222
- const RULE_FEATURES$5 = ["EXP"];
223
- var no_direct_set_state_in_use_effect_default = createRule({
224
- meta: {
225
- type: "problem",
226
- docs: {
227
- description: "Disallow direct calls to the `set` function of `useState` in `useEffect`.",
228
- [Symbol.for("rule_features")]: RULE_FEATURES$5
229
- },
230
- messages: { noDirectSetStateInUseEffect: "Do not call the 'set' function '{{name}}' of 'useState' directly in 'useEffect'." },
231
- schema: []
232
- },
233
- name: RULE_NAME$5,
234
- create: create$5,
235
- defaultOptions: []
236
- });
237
- function create$5(context) {
238
- if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
239
- return useNoDirectSetStateInUseEffect(context, {
240
- onViolation(ctx, node, data) {
241
- ctx.report({
242
- messageId: "noDirectSetStateInUseEffect",
243
- node,
244
- data
245
- });
246
- },
247
- useEffectKind: "useEffect"
248
- });
249
- }
250
-
251
- //#endregion
252
- //#region src/rules/no-direct-set-state-in-use-layout-effect.ts
253
- const RULE_NAME$4 = "no-direct-set-state-in-use-layout-effect";
254
- const RULE_FEATURES$4 = ["EXP"];
255
- var no_direct_set_state_in_use_layout_effect_default = createRule({
256
- meta: {
257
- type: "problem",
258
- docs: {
259
- description: "Disallow direct calls to the `set` function of `useState` in `useLayoutEffect`.",
260
- [Symbol.for("rule_features")]: RULE_FEATURES$4
261
- },
262
- messages: { noDirectSetStateInUseLayoutEffect: "Do not call the 'set' function '{{name}}' of 'useState' directly in 'useLayoutEffect'." },
263
- schema: []
264
- },
265
- name: RULE_NAME$4,
266
- create: create$4,
267
- defaultOptions: []
268
- });
269
- function create$4(context) {
270
- if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
271
- return useNoDirectSetStateInUseEffect(context, {
272
- onViolation(ctx, node, data) {
273
- ctx.report({
274
- messageId: "noDirectSetStateInUseLayoutEffect",
275
- node,
276
- data
277
- });
278
- },
279
- useEffectKind: "useLayoutEffect"
280
- });
281
- }
282
-
283
- //#endregion
284
- //#region src/rules-removed/no-unnecessary-use-callback.ts
285
- const RULE_NAME$3 = "no-unnecessary-use-callback";
286
- const RULE_FEATURES$3 = ["EXP"];
287
- var no_unnecessary_use_callback_default = createRule({
288
- meta: {
289
- type: "problem",
290
- deprecated: {
291
- deprecatedSince: "2.0.0",
292
- replacedBy: [{
293
- message: "Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead.",
294
- plugin: {
295
- name: "eslint-plugin-react-x",
296
- url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x"
297
- },
298
- rule: {
299
- name: "no-unnecessary-use-callback",
300
- url: "https://eslint-react.xyz/docs/rules/no-unnecessary-use-callback"
301
- }
302
- }, {
303
- message: "Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead.",
304
- plugin: {
305
- name: "@eslint-react/eslint-plugin",
306
- url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin"
307
- },
308
- rule: {
309
- name: "no-unnecessary-use-callback",
310
- url: "https://eslint-react.xyz/docs/rules/no-unnecessary-use-callback"
311
- }
312
- }]
313
- },
314
- docs: {
315
- description: "Disallow unnecessary usage of `useCallback`.",
316
- [Symbol.for("rule_features")]: RULE_FEATURES$3
317
- },
318
- messages: { noUnnecessaryUseCallback: "An 'useCallback' with empty deps and no references to the component scope may be unnecessary." },
319
- schema: []
320
- },
321
- name: RULE_NAME$3,
322
- create: create$3,
323
- defaultOptions: []
324
- });
325
- function create$3(context) {
326
- if (!context.sourceCode.text.includes("use")) return {};
327
- const alias = getSettingsFromContext(context).additionalHooks.useCallback ?? [];
328
- const isUseCallbackCall = ER.isReactHookCallWithNameAlias(context, "useCallback", alias);
329
- return { CallExpression(node) {
330
- if (!ER.isReactHookCall(node)) return;
331
- const initialScope = context.sourceCode.getScope(node);
332
- if (!isUseCallbackCall(node)) return;
333
- const scope = context.sourceCode.getScope(node);
334
- const component = scope.block;
335
- if (!AST.isFunction(component)) return;
336
- const [arg0, arg1] = node.arguments;
337
- if (arg0 == null || arg1 == null) return;
338
- const hasEmptyDeps = match(arg1).with({ type: AST_NODE_TYPES.ArrayExpression }, (n) => n.elements.length === 0).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
339
- const variable = VAR.findVariable(n.name, initialScope);
340
- const variableNode = VAR.getVariableDefinitionNode(variable, 0);
341
- if (variableNode?.type !== AST_NODE_TYPES.ArrayExpression) return false;
342
- return variableNode.elements.length === 0;
343
- }).otherwise(() => false);
344
- if (!hasEmptyDeps) return;
345
- const arg0Node = match(arg0).with({ type: AST_NODE_TYPES.ArrowFunctionExpression }, (n) => {
346
- if (n.body.type === AST_NODE_TYPES.ArrowFunctionExpression) return n.body;
347
- return n;
348
- }).with({ type: AST_NODE_TYPES.FunctionExpression }, identity).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
349
- const variable = VAR.findVariable(n.name, initialScope);
350
- const variableNode = VAR.getVariableDefinitionNode(variable, 0);
351
- if (variableNode?.type !== AST_NODE_TYPES.ArrowFunctionExpression && variableNode?.type !== AST_NODE_TYPES.FunctionExpression) return null;
352
- return variableNode;
353
- }).otherwise(() => null);
354
- if (arg0Node == null) return;
355
- const arg0NodeScope = context.sourceCode.getScope(arg0Node);
356
- const arg0NodeReferences = VAR.getChildScopes(arg0NodeScope).flatMap((x) => x.references);
357
- const isReferencedToComponentScope = arg0NodeReferences.some((x) => x.resolved?.scope.block === component);
358
- if (!isReferencedToComponentScope) context.report({
359
- messageId: "noUnnecessaryUseCallback",
360
- node
361
- });
362
- } };
363
- }
364
-
365
- //#endregion
366
- //#region src/rules-removed/no-unnecessary-use-memo.ts
367
- const RULE_NAME$2 = "no-unnecessary-use-memo";
368
- const RULE_FEATURES$2 = ["EXP"];
369
- var no_unnecessary_use_memo_default = createRule({
370
- meta: {
371
- type: "problem",
372
- deprecated: {
373
- deprecatedSince: "2.0.0",
374
- replacedBy: [{
375
- message: "Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead.",
376
- plugin: {
377
- name: "eslint-plugin-react-x",
378
- url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x"
379
- },
380
- rule: {
381
- name: "no-unnecessary-use-memo",
382
- url: "https://eslint-react.xyz/docs/rules/no-unnecessary-use-memo"
383
- }
384
- }, {
385
- message: "Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead.",
386
- plugin: {
387
- name: "@eslint-react/eslint-plugin",
388
- url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin"
389
- },
390
- rule: {
391
- name: "no-unnecessary-use-memo",
392
- url: "https://eslint-react.xyz/docs/rules/no-unnecessary-use-memo"
393
- }
394
- }]
395
- },
396
- docs: {
397
- description: "Disallow unnecessary usage of `useMemo`.",
398
- [Symbol.for("rule_features")]: RULE_FEATURES$2
399
- },
400
- messages: { noUnnecessaryUseMemo: "An 'useMemo' with empty deps and no references to the component scope may be unnecessary." },
401
- schema: []
402
- },
403
- name: RULE_NAME$2,
404
- create: create$2,
405
- defaultOptions: []
406
- });
407
- function create$2(context) {
408
- if (!context.sourceCode.text.includes("use")) return {};
409
- const alias = getSettingsFromContext(context).additionalHooks.useMemo ?? [];
410
- const isUseMemoCall = ER.isReactHookCallWithNameAlias(context, "useMemo", alias);
411
- return { CallExpression(node) {
412
- if (!ER.isReactHookCall(node)) return;
413
- const initialScope = context.sourceCode.getScope(node);
414
- if (!isUseMemoCall(node)) return;
415
- const scope = context.sourceCode.getScope(node);
416
- const component = scope.block;
417
- if (!AST.isFunction(component)) return;
418
- const [arg0, arg1] = node.arguments;
419
- if (arg0 == null || arg1 == null) return;
420
- const hasCallInArg0 = AST.isFunction(arg0) && [...AST.getNestedCallExpressions(arg0.body), ...AST.getNestedNewExpressions(arg0.body)].length > 0;
421
- if (hasCallInArg0) return;
422
- const hasEmptyDeps = match(arg1).with({ type: AST_NODE_TYPES.ArrayExpression }, (n) => n.elements.length === 0).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
423
- const variable = VAR.findVariable(n.name, initialScope);
424
- const variableNode = VAR.getVariableDefinitionNode(variable, 0);
425
- if (variableNode?.type !== AST_NODE_TYPES.ArrayExpression) return false;
426
- return variableNode.elements.length === 0;
427
- }).otherwise(() => false);
428
- if (!hasEmptyDeps) return;
429
- const arg0Node = match(arg0).with({ type: AST_NODE_TYPES.ArrowFunctionExpression }, (n) => {
430
- if (n.body.type === AST_NODE_TYPES.ArrowFunctionExpression) return n.body;
431
- return n;
432
- }).with({ type: AST_NODE_TYPES.FunctionExpression }, identity).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
433
- const variable = VAR.findVariable(n.name, initialScope);
434
- const variableNode = VAR.getVariableDefinitionNode(variable, 0);
435
- if (variableNode?.type !== AST_NODE_TYPES.ArrowFunctionExpression && variableNode?.type !== AST_NODE_TYPES.FunctionExpression) return null;
436
- return variableNode;
437
- }).otherwise(() => null);
438
- if (arg0Node == null) return;
439
- const arg0NodeScope = context.sourceCode.getScope(arg0Node);
440
- const arg0NodeReferences = VAR.getChildScopes(arg0NodeScope).flatMap((x) => x.references);
441
- const isReferencedToComponentScope = arg0NodeReferences.some((x) => x.resolved?.scope.block === component);
442
- if (!isReferencedToComponentScope) context.report({
443
- messageId: "noUnnecessaryUseMemo",
444
- node
445
- });
446
- } };
447
- }
448
-
449
- //#endregion
450
- //#region src/rules-removed/no-unnecessary-use-prefix.ts
451
- const RULE_NAME$1 = "no-unnecessary-use-prefix";
452
- const RULE_FEATURES$1 = [];
453
- const WELL_KNOWN_HOOKS = ["useMDXComponents"];
454
- function containsUseComments(context, node) {
455
- return context.sourceCode.getCommentsInside(node).some(({ value }) => /use\([\s\S]*?\)/u.test(value) || /use[A-Z0-9]\w*\([\s\S]*?\)/u.test(value));
456
- }
457
- var no_unnecessary_use_prefix_default = createRule({
458
- meta: {
459
- type: "problem",
460
- deprecated: {
461
- deprecatedSince: "2.0.0",
462
- replacedBy: [{
463
- message: "Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead.",
464
- plugin: {
465
- name: "eslint-plugin-react-x",
466
- url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x"
467
- },
468
- rule: {
469
- name: "no-unnecessary-use-prefix",
470
- url: "https://eslint-react.xyz/docs/rules/no-unnecessary-use-prefix"
471
- }
472
- }, {
473
- message: "Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead.",
474
- plugin: {
475
- name: "@eslint-react/eslint-plugin",
476
- url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin"
477
- },
478
- rule: {
479
- name: "no-unnecessary-use-prefix",
480
- url: "https://eslint-react.xyz/docs/rules/no-unnecessary-use-prefix"
481
- }
482
- }]
483
- },
484
- docs: {
485
- description: "Enforces that a function with the `use` prefix should use at least one Hook inside of it.",
486
- [Symbol.for("rule_features")]: RULE_FEATURES$1
487
- },
488
- messages: { noUnnecessaryUsePrefix: "If your function doesn't call any Hooks, avoid the 'use' prefix. Instead, write it as a regular function without the 'use' prefix." },
489
- schema: []
490
- },
491
- name: RULE_NAME$1,
492
- create: create$1,
493
- defaultOptions: []
494
- });
495
- function create$1(context) {
496
- const { ctx, listeners } = ER.useHookCollector();
497
- return {
498
- ...listeners,
499
- "Program:exit"(program) {
500
- const allHooks = ctx.getAllHooks(program);
501
- for (const { id, name: name$2, node, hookCalls } of allHooks.values()) {
502
- if (WELL_KNOWN_HOOKS.includes(name$2)) continue;
503
- if (AST.isFunctionEmpty(node)) continue;
504
- if (hookCalls.length > 0) continue;
505
- if (containsUseComments(context, node)) continue;
506
- if (id != null) {
507
- context.report({
508
- messageId: "noUnnecessaryUsePrefix",
509
- data: { name: name$2 },
510
- loc: getPreferredLoc(context, id)
511
- });
512
- continue;
513
- }
514
- context.report({
515
- messageId: "noUnnecessaryUsePrefix",
516
- node,
517
- data: { name: name$2 }
238
+ for (const setStateCall of setStateCalls) context.report({
239
+ messageId: "noDirectSetStateInUseEffect",
240
+ node: setStateCall,
241
+ data: { name: getCallName(setStateCall) }
518
242
  });
519
243
  }
520
244
  }
521
245
  };
522
246
  }
523
- function getPreferredLoc(context, id) {
524
- if (AST.isMultiLine(id)) return id.loc;
525
- if (!context.sourceCode.getText(id).startsWith("use")) return id.loc;
526
- return {
527
- end: {
528
- column: id.loc.start.column + 3,
529
- line: id.loc.start.line
530
- },
531
- start: {
532
- column: id.loc.start.column,
533
- line: id.loc.start.line
534
- }
535
- };
536
- }
537
-
538
- //#endregion
539
- //#region src/rules-removed/prefer-use-state-lazy-initialization.ts
540
- const RULE_NAME = "prefer-use-state-lazy-initialization";
541
- const RULE_FEATURES = ["EXP"];
542
- const ALLOW_LIST = [
543
- "Boolean",
544
- "String",
545
- "Number"
546
- ];
547
- var prefer_use_state_lazy_initialization_default = createRule({
548
- meta: {
549
- type: "problem",
550
- deprecated: {
551
- deprecatedSince: "2.0.0",
552
- replacedBy: [{
553
- message: "Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead.",
554
- plugin: {
555
- name: "eslint-plugin-react-x",
556
- url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x"
557
- },
558
- rule: {
559
- name: "prefer-use-state-lazy-initialization",
560
- url: "https://eslint-react.xyz/docs/rules/prefer-use-state-lazy-initialization"
561
- }
562
- }, {
563
- message: "Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead.",
564
- plugin: {
565
- name: "@eslint-react/eslint-plugin",
566
- url: "https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin"
567
- },
568
- rule: {
569
- name: "prefer-use-state-lazy-initialization",
570
- url: "https://eslint-react.xyz/docs/rules/prefer-use-state-lazy-initialization"
571
- }
572
- }]
573
- },
574
- docs: {
575
- description: "Enforces function calls made inside `useState` to be wrapped in an `initializer function`.",
576
- [Symbol.for("rule_features")]: RULE_FEATURES
577
- },
578
- messages: { preferUseStateLazyInitialization: "To prevent re-computation, consider using lazy initial state for useState calls that involve function calls. Ex: 'useState(() => getValue())'." },
579
- schema: []
580
- },
581
- name: RULE_NAME,
582
- create,
583
- defaultOptions: []
584
- });
585
- function create(context) {
586
- const alias = getSettingsFromContext(context).additionalHooks.useState ?? [];
587
- const isUseStateCall = ER.isReactHookCallWithNameAlias(context, "useState", alias);
588
- return { CallExpression(node) {
589
- if (!ER.isReactHookCall(node)) return;
590
- if (!isUseStateCall(node)) return;
591
- const [useStateInput] = node.arguments;
592
- if (useStateInput == null) return;
593
- for (const expr of AST.getNestedNewExpressions(useStateInput)) {
594
- if (!("name" in expr.callee)) continue;
595
- if (ALLOW_LIST.includes(expr.callee.name)) continue;
596
- if (AST.findParentNode(expr, (n) => ER.isUseCall(context, n)) != null) continue;
597
- context.report({
598
- messageId: "preferUseStateLazyInitialization",
599
- node: expr
600
- });
601
- }
602
- for (const expr of AST.getNestedCallExpressions(useStateInput)) {
603
- if (!("name" in expr.callee)) continue;
604
- if (ER.isReactHookName(expr.callee.name)) continue;
605
- if (ALLOW_LIST.includes(expr.callee.name)) continue;
606
- if (AST.findParentNode(expr, (n) => ER.isUseCall(context, n)) != null) continue;
607
- context.report({
608
- messageId: "preferUseStateLazyInitialization",
609
- node: expr
610
- });
611
- }
612
- } };
613
- }
614
247
 
615
248
  //#endregion
616
249
  //#region src/plugin.ts
@@ -619,14 +252,7 @@ const plugin = {
619
252
  name,
620
253
  version
621
254
  },
622
- rules: {
623
- "no-direct-set-state-in-use-effect": no_direct_set_state_in_use_effect_default,
624
- "no-direct-set-state-in-use-layout-effect": no_direct_set_state_in_use_layout_effect_default,
625
- "no-unnecessary-use-callback": no_unnecessary_use_callback_default,
626
- "no-unnecessary-use-memo": no_unnecessary_use_memo_default,
627
- "no-unnecessary-use-prefix": no_unnecessary_use_prefix_default,
628
- "prefer-use-state-lazy-initialization": prefer_use_state_lazy_initialization_default
629
- }
255
+ rules: { "no-direct-set-state-in-use-effect": no_direct_set_state_in_use_effect_default }
630
256
  };
631
257
 
632
258
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-hooks-extra",
3
- "version": "2.0.0-beta.172",
3
+ "version": "2.0.0-beta.173",
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.42.0",
43
43
  "string-ts": "^2.2.1",
44
44
  "ts-pattern": "^5.8.0",
45
- "@eslint-react/core": "2.0.0-beta.172",
46
- "@eslint-react/eff": "2.0.0-beta.172",
47
- "@eslint-react/kit": "2.0.0-beta.172",
48
- "@eslint-react/shared": "2.0.0-beta.172",
49
- "@eslint-react/ast": "2.0.0-beta.172",
50
- "@eslint-react/var": "2.0.0-beta.172"
45
+ "@eslint-react/ast": "2.0.0-beta.173",
46
+ "@eslint-react/core": "2.0.0-beta.173",
47
+ "@eslint-react/kit": "2.0.0-beta.173",
48
+ "@eslint-react/shared": "2.0.0-beta.173",
49
+ "@eslint-react/var": "2.0.0-beta.173",
50
+ "@eslint-react/eff": "2.0.0-beta.173"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/react": "^19.1.12",