eslint-plugin-react-hooks-extra 2.0.0-next.3 → 2.0.0-next.31

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