eslint-plugin-react-hooks-extra 1.6.1-next.1 → 1.6.1-next.3

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.js +108 -169
  2. package/dist/index.mjs +109 -170
  3. package/package.json +8 -8
package/dist/index.js CHANGED
@@ -9,44 +9,22 @@ var astUtils = require('@typescript-eslint/utils/ast-utils');
9
9
 
10
10
  // package.json
11
11
  var name = "eslint-plugin-react-hooks-extra";
12
- var version = "1.6.1-next.1";
12
+ var version = "1.6.1-next.3";
13
13
  var createRule = shared.createRuleForPlugin("hooks-extra");
14
-
15
- // src/rules/ensure-custom-hooks-using-other-hooks.ts
16
- var RULE_NAME = "ensure-custom-hooks-using-other-hooks";
17
- var ensure_custom_hooks_using_other_hooks_default = createRule({
18
- meta: {
19
- type: "problem",
20
- docs: {
21
- description: "enforce custom hooks using other hooks"
22
- },
23
- messages: {
24
- ENSURE_CUSTOM_HOOKS_USING_OTHER_HOOKS: "A custom hook '{{name}}' should use other hooks."
25
- },
26
- schema: []
27
- },
28
- name: RULE_NAME,
29
- create(context) {
30
- const { ctx, listeners } = core.useHookCollector();
31
- return {
32
- ...listeners,
33
- "Program:exit"(node) {
34
- const allHooks = ctx.getAllHooks(node);
35
- for (const { name: name2, hookCalls, node: node2 } of allHooks.values()) {
36
- if (hookCalls.length > 0) continue;
37
- context.report({
38
- data: {
39
- name: name2.value
40
- },
41
- messageId: "ENSURE_CUSTOM_HOOKS_USING_OTHER_HOOKS",
42
- node: node2
43
- });
44
- }
45
- }
46
- };
47
- },
48
- defaultOptions: []
49
- });
14
+ function isFromUseStateCall(context, useStateAlias) {
15
+ return (topLevelId) => {
16
+ const useStateCall = tools.F.pipe(
17
+ _var.findVariable(topLevelId, context.sourceCode.getScope(topLevelId)),
18
+ tools.O.flatMap(_var.getVariableNode(0)),
19
+ tools.O.filter(ast.is(ast.NodeType.CallExpression)),
20
+ tools.O.filter(core.isReactHookCallWithNameAlias("useState", context, useStateAlias))
21
+ );
22
+ if (tools.O.isNone(useStateCall)) return false;
23
+ const { parent } = useStateCall.value;
24
+ if (!("id" in parent && parent.id?.type === ast.NodeType.ArrayPattern)) return true;
25
+ return parent.id.elements.findIndex((e2) => e2?.type === ast.NodeType.Identifier && e2.name === topLevelId.name) === 1;
26
+ };
27
+ }
50
28
 
51
29
  // ../../../node_modules/.pnpm/ts-pattern@5.2.0/node_modules/ts-pattern/dist/index.js
52
30
  var t = Symbol.for("@ts-pattern/matcher");
@@ -231,7 +209,74 @@ var z = class _z {
231
209
  }
232
210
  };
233
211
 
234
- // src/rules/ensure-use-callback-has-non-empty-deps.ts
212
+ // src/utils/is-set-state-call.ts
213
+ function isSetStateCall(context, useStateAlias) {
214
+ return (node) => {
215
+ const topLevelId = $(node.callee).with({ type: ast.NodeType.Identifier }, tools.O.some).with({ type: ast.NodeType.MemberExpression }, (n2) => {
216
+ if (!("name" in n2.object)) return tools.O.none();
217
+ const initialScope = context.sourceCode.getScope(n2);
218
+ const property = astUtils.getStaticValue(n2.property, initialScope);
219
+ if (property?.value === 1) return tools.O.fromNullable(n2.object);
220
+ return tools.O.none();
221
+ }).with({ type: ast.NodeType.CallExpression }, (n2) => {
222
+ if (!ast.is(ast.NodeType.MemberExpression)(n2.callee)) return tools.O.none();
223
+ if (!("name" in n2.callee.object)) return tools.O.none();
224
+ const isAt = a({
225
+ type: ast.NodeType.MemberExpression,
226
+ property: {
227
+ type: ast.NodeType.Identifier,
228
+ name: "at"
229
+ }
230
+ }, n2.callee);
231
+ const [index] = n2.arguments;
232
+ if (!isAt || !index) return tools.O.none();
233
+ const initialScope = context.sourceCode.getScope(n2);
234
+ const value = astUtils.getStaticValue(index, initialScope);
235
+ if (value?.value === 1) return tools.O.fromNullable(n2.callee.object);
236
+ return tools.O.none();
237
+ }).otherwise(tools.O.none);
238
+ return tools.O.exists(topLevelId, isFromUseStateCall(context, useStateAlias));
239
+ };
240
+ }
241
+ function isThenCall(node) {
242
+ return node.callee.type === ast.NodeType.MemberExpression && node.callee.property.type === ast.NodeType.Identifier && node.callee.property.name === "then";
243
+ }
244
+
245
+ // src/rules/ensure-custom-hooks-using-other-hooks.ts
246
+ var RULE_NAME = "ensure-custom-hooks-using-other-hooks";
247
+ var ensure_custom_hooks_using_other_hooks_default = createRule({
248
+ meta: {
249
+ type: "problem",
250
+ docs: {
251
+ description: "enforce custom hooks using other hooks"
252
+ },
253
+ messages: {
254
+ ENSURE_CUSTOM_HOOKS_USING_OTHER_HOOKS: "A custom hook '{{name}}' should use other hooks."
255
+ },
256
+ schema: []
257
+ },
258
+ name: RULE_NAME,
259
+ create(context) {
260
+ const { ctx, listeners } = core.useHookCollector();
261
+ return {
262
+ ...listeners,
263
+ "Program:exit"(node) {
264
+ const allHooks = ctx.getAllHooks(node);
265
+ for (const { name: name2, hookCalls, node: node2 } of allHooks.values()) {
266
+ if (hookCalls.length > 0) continue;
267
+ context.report({
268
+ data: {
269
+ name: name2.value
270
+ },
271
+ messageId: "ENSURE_CUSTOM_HOOKS_USING_OTHER_HOOKS",
272
+ node: node2
273
+ });
274
+ }
275
+ }
276
+ };
277
+ },
278
+ defaultOptions: []
279
+ });
235
280
  var RULE_NAME2 = "ensure-use-callback-has-non-empty-deps";
236
281
  var ensure_use_callback_has_non_empty_deps_default = createRule({
237
282
  meta: {
@@ -251,7 +296,7 @@ var ensure_use_callback_has_non_empty_deps_default = createRule({
251
296
  CallExpression(node) {
252
297
  if (!core.isReactHookCall(node)) return;
253
298
  const initialScope = context.sourceCode.getScope(node);
254
- if (!core.isUseCallbackCall(node, context) && !alias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node))) {
299
+ if (!core.isUseCallbackCall(node, context) && !alias.some(core.isReactHookCallWithNameLoose(node))) {
255
300
  return;
256
301
  }
257
302
  const scope = context.sourceCode.getScope(node);
@@ -329,7 +374,7 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
329
374
  CallExpression(node) {
330
375
  if (!core.isReactHookCall(node)) return;
331
376
  const initialScope = context.sourceCode.getScope(node);
332
- if (!core.isUseMemoCall(node, context) && !alias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node))) {
377
+ if (!core.isUseMemoCall(node, context) && !alias.some(core.isReactHookCallWithNameLoose(node))) {
333
378
  return;
334
379
  }
335
380
  const scope = context.sourceCode.getScope(node);
@@ -389,6 +434,15 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
389
434
  defaultOptions: []
390
435
  });
391
436
  var RULE_NAME4 = "no-direct-set-state-in-use-effect";
437
+ function isEffectFunction(context, useSomeEffectAlias) {
438
+ return (node) => node.parent?.type === ast.NodeType.CallExpression && node.parent.callee !== node && core.isReactHookCallWithNameAlias("useEffect", context, useSomeEffectAlias)(node.parent);
439
+ }
440
+ function getCallKind(node, context, useStateAlias, useEffectAlias) {
441
+ return $(node).when(isThenCall, () => "then").when(isSetStateCall(context, useStateAlias), () => "setState").when(core.isReactHookCallWithNameAlias("useState", context, useStateAlias), () => "useState").when(core.isReactHookCallWithNameAlias("useEffect", context, useEffectAlias), () => "useEffect").otherwise(() => "other");
442
+ }
443
+ function getFunctionKind(node, context, useEffectAlias) {
444
+ return $(node).when(isEffectFunction(context, useEffectAlias), () => "effect").when(ast.isIIFE, () => "immediate").otherwise(() => "other");
445
+ }
392
446
  var no_direct_set_state_in_use_effect_default = createRule({
393
447
  meta: {
394
448
  type: "problem",
@@ -404,68 +458,6 @@ var no_direct_set_state_in_use_effect_default = createRule({
404
458
  create(context) {
405
459
  const settings = shared.decodeSettings(context.settings);
406
460
  const { useEffect: useEffectAlias = [], useState: useStateAlias = [] } = settings.additionalHooks ?? {};
407
- function isUseEffectCallWithAlias(node) {
408
- return core.isUseEffectCall(node, context) || useEffectAlias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node));
409
- }
410
- function isUseStateCallWithAlias(node) {
411
- return core.isUseStateCall(node, context) || useStateAlias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node));
412
- }
413
- function isEffectFunction(node) {
414
- return node.parent?.type === ast.NodeType.CallExpression && node.parent.callee !== node && isUseEffectCallWithAlias(node.parent);
415
- }
416
- function isCleanUpFunction(_2) {
417
- }
418
- function isFromUseStateCall(topLevelId) {
419
- const useStateCall = tools.F.pipe(
420
- _var.findVariable(topLevelId, context.sourceCode.getScope(topLevelId)),
421
- tools.O.flatMap(_var.getVariableNode(0)),
422
- tools.O.filter(ast.is(ast.NodeType.CallExpression)),
423
- tools.O.filter(isUseStateCallWithAlias)
424
- );
425
- if (tools.O.isNone(useStateCall)) return false;
426
- const { parent } = useStateCall.value;
427
- if (!a({ id: { type: ast.NodeType.ArrayPattern }, type: ast.NodeType.VariableDeclarator }, parent)) return true;
428
- return parent.id.elements.findIndex((e2) => e2?.type === ast.NodeType.Identifier && e2.name === topLevelId.name) === 1;
429
- }
430
- function isSetStateCall(node) {
431
- const topLevelId = $(node.callee).with({ type: ast.NodeType.Identifier }, tools.O.some).with({ type: ast.NodeType.MemberExpression }, (n2) => {
432
- if (!("name" in n2.object)) return tools.O.none();
433
- const initialScope = context.sourceCode.getScope(n2);
434
- const property = astUtils.getStaticValue(n2.property, initialScope);
435
- if (property?.value === 1) return tools.O.fromNullable(n2.object);
436
- return tools.O.none();
437
- }).with({ type: ast.NodeType.CallExpression }, (n2) => {
438
- if (!ast.is(ast.NodeType.MemberExpression)(n2.callee)) return tools.O.none();
439
- if (!("name" in n2.callee.object)) return tools.O.none();
440
- const isAt = a({
441
- type: ast.NodeType.MemberExpression,
442
- property: {
443
- type: ast.NodeType.Identifier,
444
- name: "at"
445
- }
446
- }, n2.callee);
447
- const [index] = n2.arguments;
448
- if (!isAt || !index) return tools.O.none();
449
- const initialScope = context.sourceCode.getScope(n2);
450
- const value = astUtils.getStaticValue(index, initialScope);
451
- if (value?.value === 1) return tools.O.fromNullable(n2.callee.object);
452
- return tools.O.none();
453
- }).otherwise(tools.O.none);
454
- return tools.O.exists(topLevelId, isFromUseStateCall);
455
- }
456
- function isThenCall(node) {
457
- if (node.callee.type !== ast.NodeType.MemberExpression) return false;
458
- return a({
459
- type: ast.NodeType.Identifier,
460
- name: "then"
461
- }, node.callee.property);
462
- }
463
- function getCallKind(node) {
464
- return $(node).when(isThenCall, () => "then").when(isSetStateCall, () => "setState").when(isUseStateCallWithAlias, () => "useState").when(isUseEffectCallWithAlias, () => "useEffect").otherwise(() => "other");
465
- }
466
- function getFunctionKind(node) {
467
- return $(node).when(isEffectFunction, () => "effect").when(isCleanUpFunction, () => "cleanup").when(ast.isIIFE, () => "immediate").otherwise(() => "other");
468
- }
469
461
  const functionStack = tools.MutList.make();
470
462
  const effectFunctionRef = tools.MutRef.make(null);
471
463
  const effectFunctionIdentifiers = [];
@@ -479,7 +471,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
479
471
  };
480
472
  return {
481
473
  ":function"(node) {
482
- const functionKind = getFunctionKind(node);
474
+ const functionKind = getFunctionKind(node, context, useEffectAlias);
483
475
  tools.MutList.append(functionStack, [node, functionKind]);
484
476
  $(functionKind).with("effect", () => {
485
477
  onEffectFunctionEnter(node);
@@ -493,7 +485,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
493
485
  const effectFn = tools.MutRef.get(effectFunctionRef);
494
486
  const [parentFn, parentFnKind] = tools.MutList.tail(functionStack) ?? [];
495
487
  if (parentFn?.async) return;
496
- const callKind = getCallKind(node);
488
+ const callKind = getCallKind(node, context, useStateAlias, useEffectAlias);
497
489
  $(callKind).with("setState", () => {
498
490
  if (!parentFn) return;
499
491
  if (parentFn !== effectFn && parentFnKind !== "immediate") {
@@ -553,6 +545,15 @@ var no_direct_set_state_in_use_effect_default = createRule({
553
545
  defaultOptions: []
554
546
  });
555
547
  var RULE_NAME5 = "no-direct-set-state-in-use-layout-effect";
548
+ function isLayoutEffectFunction(context, useSomeEffectAlias) {
549
+ return (node) => node.parent?.type === ast.NodeType.CallExpression && node.parent.callee !== node && core.isReactHookCallWithNameAlias("useLayoutEffect", context, useSomeEffectAlias)(node.parent);
550
+ }
551
+ function getCallKind2(node, context, useStateAlias, useLayoutEffectAlias) {
552
+ return $(node).when(isThenCall, () => "then").when(isSetStateCall(context, useStateAlias), () => "setState").when(core.isReactHookCallWithNameAlias("useState", context, useStateAlias), () => "useState").when(core.isReactHookCallWithNameAlias("useLayoutEffect", context, useLayoutEffectAlias), () => "useLayoutEffect").otherwise(() => "other");
553
+ }
554
+ function getFunctionKind2(node, context, useLayoutEffectAlias) {
555
+ return $(node).when(isLayoutEffectFunction(context, useLayoutEffectAlias), () => "effect").when(ast.isIIFE, () => "immediate").otherwise(() => "other");
556
+ }
556
557
  var no_direct_set_state_in_use_layout_effect_default = createRule({
557
558
  meta: {
558
559
  type: "problem",
@@ -568,68 +569,6 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
568
569
  create(context) {
569
570
  const settings = shared.decodeSettings(context.settings);
570
571
  const { useLayoutEffect: useLayoutEffectAlias = [], useState: useStateAlias = [] } = settings.additionalHooks ?? {};
571
- function isUseLayoutEffectCallWithAlias(node) {
572
- return core.isUseLayoutEffectCall(node, context) || useLayoutEffectAlias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node));
573
- }
574
- function isUseStateCallWithAlias(node) {
575
- return core.isUseStateCall(node, context) || useStateAlias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node));
576
- }
577
- function isEffectFunction(node) {
578
- return node.parent?.type === ast.NodeType.CallExpression && node.parent.callee !== node && isUseLayoutEffectCallWithAlias(node.parent);
579
- }
580
- function isCleanUpFunction(_2) {
581
- }
582
- function isFromUseStateCall(topLevelId) {
583
- const useStateCall = tools.F.pipe(
584
- _var.findVariable(topLevelId, context.sourceCode.getScope(topLevelId)),
585
- tools.O.flatMap(_var.getVariableNode(0)),
586
- tools.O.filter(ast.is(ast.NodeType.CallExpression)),
587
- tools.O.filter(isUseStateCallWithAlias)
588
- );
589
- if (tools.O.isNone(useStateCall)) return false;
590
- const { parent } = useStateCall.value;
591
- if (!a({ id: { type: ast.NodeType.ArrayPattern }, type: ast.NodeType.VariableDeclarator }, parent)) return true;
592
- return parent.id.elements.findIndex((e2) => e2?.type === ast.NodeType.Identifier && e2.name === topLevelId.name) === 1;
593
- }
594
- function isSetStateCall(node) {
595
- const topLevelId = $(node.callee).with({ type: ast.NodeType.Identifier }, tools.O.some).with({ type: ast.NodeType.MemberExpression }, (n2) => {
596
- if (!("name" in n2.object)) return tools.O.none();
597
- const initialScope = context.sourceCode.getScope(n2);
598
- const property = astUtils.getStaticValue(n2.property, initialScope);
599
- if (property?.value === 1) return tools.O.fromNullable(n2.object);
600
- return tools.O.none();
601
- }).with({ type: ast.NodeType.CallExpression }, (n2) => {
602
- if (!ast.is(ast.NodeType.MemberExpression)(n2.callee)) return tools.O.none();
603
- if (!("name" in n2.callee.object)) return tools.O.none();
604
- const isAt = a({
605
- type: ast.NodeType.MemberExpression,
606
- property: {
607
- type: ast.NodeType.Identifier,
608
- name: "at"
609
- }
610
- }, n2.callee);
611
- const [index] = n2.arguments;
612
- if (!isAt || !index) return tools.O.none();
613
- const initialScope = context.sourceCode.getScope(n2);
614
- const value = astUtils.getStaticValue(index, initialScope);
615
- if (value?.value === 1) return tools.O.fromNullable(n2.callee.object);
616
- return tools.O.none();
617
- }).otherwise(tools.O.none);
618
- return tools.O.exists(topLevelId, isFromUseStateCall);
619
- }
620
- function isThenCall(node) {
621
- if (node.callee.type !== ast.NodeType.MemberExpression) return false;
622
- return a({
623
- type: ast.NodeType.Identifier,
624
- name: "then"
625
- }, node.callee.property);
626
- }
627
- function getCallKind(node) {
628
- return $(node).when(isThenCall, () => "then").when(isSetStateCall, () => "setState").when(isUseStateCallWithAlias, () => "useState").when(isUseLayoutEffectCallWithAlias, () => "useLayoutEffect").otherwise(() => "other");
629
- }
630
- function getFunctionKind(node) {
631
- return $(node).when(isEffectFunction, () => "effect").when(isCleanUpFunction, () => "cleanup").when(ast.isIIFE, () => "immediate").otherwise(() => "other");
632
- }
633
572
  const functionStack = tools.MutList.make();
634
573
  const effectFunctionRef = tools.MutRef.make(null);
635
574
  const effectFunctionIdentifiers = [];
@@ -643,7 +582,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
643
582
  };
644
583
  return {
645
584
  ":function"(node) {
646
- const functionKind = getFunctionKind(node);
585
+ const functionKind = getFunctionKind2(node, context, useLayoutEffectAlias);
647
586
  tools.MutList.append(functionStack, [node, functionKind]);
648
587
  $(functionKind).with("effect", () => {
649
588
  onEffectFunctionEnter(node);
@@ -657,7 +596,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
657
596
  const effectFn = tools.MutRef.get(effectFunctionRef);
658
597
  const [parentFn, parentFnKind] = tools.MutList.tail(functionStack) ?? [];
659
598
  if (parentFn?.async) return;
660
- const callKind = getCallKind(node);
599
+ const callKind = getCallKind2(node, context, useStateAlias, useLayoutEffectAlias);
661
600
  $(callKind).with("setState", () => {
662
601
  if (!parentFn) return;
663
602
  if (parentFn !== effectFn && parentFnKind !== "immediate") {
@@ -735,7 +674,7 @@ var prefer_use_state_lazy_initialization_default = createRule({
735
674
  return {
736
675
  CallExpression(node) {
737
676
  if (!core.isReactHookCall(node)) return;
738
- if (!core.isUseStateCall(node, context) && !alias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node))) return;
677
+ if (!core.isUseStateCall(node, context) && !alias.some(core.isReactHookCallWithNameLoose(node))) return;
739
678
  const [useStateInput] = node.arguments;
740
679
  if (!useStateInput) return;
741
680
  const nestedCallExpressions = ast.getNestedCallExpressions(useStateInput);
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { useHookCollector, isReactHookCall, isUseCallbackCall, isReactHookCallWithNameLoose, isUseMemoCall, isUseStateCall, isUseEffectCall, isUseLayoutEffectCall } from '@eslint-react/core';
1
+ import { useHookCollector, isReactHookCall, isUseCallbackCall, isReactHookCallWithNameLoose, isUseMemoCall, isUseStateCall, isReactHookCallWithNameAlias } from '@eslint-react/core';
2
2
  import { createRuleForPlugin, decodeSettings } from '@eslint-react/shared';
3
3
  import { isFunction, NodeType, is, getNestedIdentifiers, getNestedCallExpressions, isIIFE } from '@eslint-react/ast';
4
4
  import { F, O, MutList, MutRef } from '@eslint-react/tools';
@@ -7,44 +7,22 @@ import { getStaticValue } from '@typescript-eslint/utils/ast-utils';
7
7
 
8
8
  // package.json
9
9
  var name = "eslint-plugin-react-hooks-extra";
10
- var version = "1.6.1-next.1";
10
+ var version = "1.6.1-next.3";
11
11
  var createRule = createRuleForPlugin("hooks-extra");
12
-
13
- // src/rules/ensure-custom-hooks-using-other-hooks.ts
14
- var RULE_NAME = "ensure-custom-hooks-using-other-hooks";
15
- var ensure_custom_hooks_using_other_hooks_default = createRule({
16
- meta: {
17
- type: "problem",
18
- docs: {
19
- description: "enforce custom hooks using other hooks"
20
- },
21
- messages: {
22
- ENSURE_CUSTOM_HOOKS_USING_OTHER_HOOKS: "A custom hook '{{name}}' should use other hooks."
23
- },
24
- schema: []
25
- },
26
- name: RULE_NAME,
27
- create(context) {
28
- const { ctx, listeners } = useHookCollector();
29
- return {
30
- ...listeners,
31
- "Program:exit"(node) {
32
- const allHooks = ctx.getAllHooks(node);
33
- for (const { name: name2, hookCalls, node: node2 } of allHooks.values()) {
34
- if (hookCalls.length > 0) continue;
35
- context.report({
36
- data: {
37
- name: name2.value
38
- },
39
- messageId: "ENSURE_CUSTOM_HOOKS_USING_OTHER_HOOKS",
40
- node: node2
41
- });
42
- }
43
- }
44
- };
45
- },
46
- defaultOptions: []
47
- });
12
+ function isFromUseStateCall(context, useStateAlias) {
13
+ return (topLevelId) => {
14
+ const useStateCall = F.pipe(
15
+ findVariable(topLevelId, context.sourceCode.getScope(topLevelId)),
16
+ O.flatMap(getVariableNode(0)),
17
+ O.filter(is(NodeType.CallExpression)),
18
+ O.filter(isReactHookCallWithNameAlias("useState", context, useStateAlias))
19
+ );
20
+ if (O.isNone(useStateCall)) return false;
21
+ const { parent } = useStateCall.value;
22
+ if (!("id" in parent && parent.id?.type === NodeType.ArrayPattern)) return true;
23
+ return parent.id.elements.findIndex((e2) => e2?.type === NodeType.Identifier && e2.name === topLevelId.name) === 1;
24
+ };
25
+ }
48
26
 
49
27
  // ../../../node_modules/.pnpm/ts-pattern@5.2.0/node_modules/ts-pattern/dist/index.js
50
28
  var t = Symbol.for("@ts-pattern/matcher");
@@ -229,7 +207,74 @@ var z = class _z {
229
207
  }
230
208
  };
231
209
 
232
- // src/rules/ensure-use-callback-has-non-empty-deps.ts
210
+ // src/utils/is-set-state-call.ts
211
+ function isSetStateCall(context, useStateAlias) {
212
+ return (node) => {
213
+ const topLevelId = $(node.callee).with({ type: NodeType.Identifier }, O.some).with({ type: NodeType.MemberExpression }, (n2) => {
214
+ if (!("name" in n2.object)) return O.none();
215
+ const initialScope = context.sourceCode.getScope(n2);
216
+ const property = getStaticValue(n2.property, initialScope);
217
+ if (property?.value === 1) return O.fromNullable(n2.object);
218
+ return O.none();
219
+ }).with({ type: NodeType.CallExpression }, (n2) => {
220
+ if (!is(NodeType.MemberExpression)(n2.callee)) return O.none();
221
+ if (!("name" in n2.callee.object)) return O.none();
222
+ const isAt = a({
223
+ type: NodeType.MemberExpression,
224
+ property: {
225
+ type: NodeType.Identifier,
226
+ name: "at"
227
+ }
228
+ }, n2.callee);
229
+ const [index] = n2.arguments;
230
+ if (!isAt || !index) return O.none();
231
+ const initialScope = context.sourceCode.getScope(n2);
232
+ const value = getStaticValue(index, initialScope);
233
+ if (value?.value === 1) return O.fromNullable(n2.callee.object);
234
+ return O.none();
235
+ }).otherwise(O.none);
236
+ return O.exists(topLevelId, isFromUseStateCall(context, useStateAlias));
237
+ };
238
+ }
239
+ function isThenCall(node) {
240
+ return node.callee.type === NodeType.MemberExpression && node.callee.property.type === NodeType.Identifier && node.callee.property.name === "then";
241
+ }
242
+
243
+ // src/rules/ensure-custom-hooks-using-other-hooks.ts
244
+ var RULE_NAME = "ensure-custom-hooks-using-other-hooks";
245
+ var ensure_custom_hooks_using_other_hooks_default = createRule({
246
+ meta: {
247
+ type: "problem",
248
+ docs: {
249
+ description: "enforce custom hooks using other hooks"
250
+ },
251
+ messages: {
252
+ ENSURE_CUSTOM_HOOKS_USING_OTHER_HOOKS: "A custom hook '{{name}}' should use other hooks."
253
+ },
254
+ schema: []
255
+ },
256
+ name: RULE_NAME,
257
+ create(context) {
258
+ const { ctx, listeners } = useHookCollector();
259
+ return {
260
+ ...listeners,
261
+ "Program:exit"(node) {
262
+ const allHooks = ctx.getAllHooks(node);
263
+ for (const { name: name2, hookCalls, node: node2 } of allHooks.values()) {
264
+ if (hookCalls.length > 0) continue;
265
+ context.report({
266
+ data: {
267
+ name: name2.value
268
+ },
269
+ messageId: "ENSURE_CUSTOM_HOOKS_USING_OTHER_HOOKS",
270
+ node: node2
271
+ });
272
+ }
273
+ }
274
+ };
275
+ },
276
+ defaultOptions: []
277
+ });
233
278
  var RULE_NAME2 = "ensure-use-callback-has-non-empty-deps";
234
279
  var ensure_use_callback_has_non_empty_deps_default = createRule({
235
280
  meta: {
@@ -249,7 +294,7 @@ var ensure_use_callback_has_non_empty_deps_default = createRule({
249
294
  CallExpression(node) {
250
295
  if (!isReactHookCall(node)) return;
251
296
  const initialScope = context.sourceCode.getScope(node);
252
- if (!isUseCallbackCall(node, context) && !alias.some(F.flip(isReactHookCallWithNameLoose)(node))) {
297
+ if (!isUseCallbackCall(node, context) && !alias.some(isReactHookCallWithNameLoose(node))) {
253
298
  return;
254
299
  }
255
300
  const scope = context.sourceCode.getScope(node);
@@ -327,7 +372,7 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
327
372
  CallExpression(node) {
328
373
  if (!isReactHookCall(node)) return;
329
374
  const initialScope = context.sourceCode.getScope(node);
330
- if (!isUseMemoCall(node, context) && !alias.some(F.flip(isReactHookCallWithNameLoose)(node))) {
375
+ if (!isUseMemoCall(node, context) && !alias.some(isReactHookCallWithNameLoose(node))) {
331
376
  return;
332
377
  }
333
378
  const scope = context.sourceCode.getScope(node);
@@ -387,6 +432,15 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
387
432
  defaultOptions: []
388
433
  });
389
434
  var RULE_NAME4 = "no-direct-set-state-in-use-effect";
435
+ function isEffectFunction(context, useSomeEffectAlias) {
436
+ return (node) => node.parent?.type === NodeType.CallExpression && node.parent.callee !== node && isReactHookCallWithNameAlias("useEffect", context, useSomeEffectAlias)(node.parent);
437
+ }
438
+ function getCallKind(node, context, useStateAlias, useEffectAlias) {
439
+ return $(node).when(isThenCall, () => "then").when(isSetStateCall(context, useStateAlias), () => "setState").when(isReactHookCallWithNameAlias("useState", context, useStateAlias), () => "useState").when(isReactHookCallWithNameAlias("useEffect", context, useEffectAlias), () => "useEffect").otherwise(() => "other");
440
+ }
441
+ function getFunctionKind(node, context, useEffectAlias) {
442
+ return $(node).when(isEffectFunction(context, useEffectAlias), () => "effect").when(isIIFE, () => "immediate").otherwise(() => "other");
443
+ }
390
444
  var no_direct_set_state_in_use_effect_default = createRule({
391
445
  meta: {
392
446
  type: "problem",
@@ -402,68 +456,6 @@ var no_direct_set_state_in_use_effect_default = createRule({
402
456
  create(context) {
403
457
  const settings = decodeSettings(context.settings);
404
458
  const { useEffect: useEffectAlias = [], useState: useStateAlias = [] } = settings.additionalHooks ?? {};
405
- function isUseEffectCallWithAlias(node) {
406
- return isUseEffectCall(node, context) || useEffectAlias.some(F.flip(isReactHookCallWithNameLoose)(node));
407
- }
408
- function isUseStateCallWithAlias(node) {
409
- return isUseStateCall(node, context) || useStateAlias.some(F.flip(isReactHookCallWithNameLoose)(node));
410
- }
411
- function isEffectFunction(node) {
412
- return node.parent?.type === NodeType.CallExpression && node.parent.callee !== node && isUseEffectCallWithAlias(node.parent);
413
- }
414
- function isCleanUpFunction(_2) {
415
- }
416
- function isFromUseStateCall(topLevelId) {
417
- const useStateCall = F.pipe(
418
- findVariable(topLevelId, context.sourceCode.getScope(topLevelId)),
419
- O.flatMap(getVariableNode(0)),
420
- O.filter(is(NodeType.CallExpression)),
421
- O.filter(isUseStateCallWithAlias)
422
- );
423
- if (O.isNone(useStateCall)) return false;
424
- const { parent } = useStateCall.value;
425
- if (!a({ id: { type: NodeType.ArrayPattern }, type: NodeType.VariableDeclarator }, parent)) return true;
426
- return parent.id.elements.findIndex((e2) => e2?.type === NodeType.Identifier && e2.name === topLevelId.name) === 1;
427
- }
428
- function isSetStateCall(node) {
429
- const topLevelId = $(node.callee).with({ type: NodeType.Identifier }, O.some).with({ type: NodeType.MemberExpression }, (n2) => {
430
- if (!("name" in n2.object)) return O.none();
431
- const initialScope = context.sourceCode.getScope(n2);
432
- const property = getStaticValue(n2.property, initialScope);
433
- if (property?.value === 1) return O.fromNullable(n2.object);
434
- return O.none();
435
- }).with({ type: NodeType.CallExpression }, (n2) => {
436
- if (!is(NodeType.MemberExpression)(n2.callee)) return O.none();
437
- if (!("name" in n2.callee.object)) return O.none();
438
- const isAt = a({
439
- type: NodeType.MemberExpression,
440
- property: {
441
- type: NodeType.Identifier,
442
- name: "at"
443
- }
444
- }, n2.callee);
445
- const [index] = n2.arguments;
446
- if (!isAt || !index) return O.none();
447
- const initialScope = context.sourceCode.getScope(n2);
448
- const value = getStaticValue(index, initialScope);
449
- if (value?.value === 1) return O.fromNullable(n2.callee.object);
450
- return O.none();
451
- }).otherwise(O.none);
452
- return O.exists(topLevelId, isFromUseStateCall);
453
- }
454
- function isThenCall(node) {
455
- if (node.callee.type !== NodeType.MemberExpression) return false;
456
- return a({
457
- type: NodeType.Identifier,
458
- name: "then"
459
- }, node.callee.property);
460
- }
461
- function getCallKind(node) {
462
- return $(node).when(isThenCall, () => "then").when(isSetStateCall, () => "setState").when(isUseStateCallWithAlias, () => "useState").when(isUseEffectCallWithAlias, () => "useEffect").otherwise(() => "other");
463
- }
464
- function getFunctionKind(node) {
465
- return $(node).when(isEffectFunction, () => "effect").when(isCleanUpFunction, () => "cleanup").when(isIIFE, () => "immediate").otherwise(() => "other");
466
- }
467
459
  const functionStack = MutList.make();
468
460
  const effectFunctionRef = MutRef.make(null);
469
461
  const effectFunctionIdentifiers = [];
@@ -477,7 +469,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
477
469
  };
478
470
  return {
479
471
  ":function"(node) {
480
- const functionKind = getFunctionKind(node);
472
+ const functionKind = getFunctionKind(node, context, useEffectAlias);
481
473
  MutList.append(functionStack, [node, functionKind]);
482
474
  $(functionKind).with("effect", () => {
483
475
  onEffectFunctionEnter(node);
@@ -491,7 +483,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
491
483
  const effectFn = MutRef.get(effectFunctionRef);
492
484
  const [parentFn, parentFnKind] = MutList.tail(functionStack) ?? [];
493
485
  if (parentFn?.async) return;
494
- const callKind = getCallKind(node);
486
+ const callKind = getCallKind(node, context, useStateAlias, useEffectAlias);
495
487
  $(callKind).with("setState", () => {
496
488
  if (!parentFn) return;
497
489
  if (parentFn !== effectFn && parentFnKind !== "immediate") {
@@ -551,6 +543,15 @@ var no_direct_set_state_in_use_effect_default = createRule({
551
543
  defaultOptions: []
552
544
  });
553
545
  var RULE_NAME5 = "no-direct-set-state-in-use-layout-effect";
546
+ function isLayoutEffectFunction(context, useSomeEffectAlias) {
547
+ return (node) => node.parent?.type === NodeType.CallExpression && node.parent.callee !== node && isReactHookCallWithNameAlias("useLayoutEffect", context, useSomeEffectAlias)(node.parent);
548
+ }
549
+ function getCallKind2(node, context, useStateAlias, useLayoutEffectAlias) {
550
+ return $(node).when(isThenCall, () => "then").when(isSetStateCall(context, useStateAlias), () => "setState").when(isReactHookCallWithNameAlias("useState", context, useStateAlias), () => "useState").when(isReactHookCallWithNameAlias("useLayoutEffect", context, useLayoutEffectAlias), () => "useLayoutEffect").otherwise(() => "other");
551
+ }
552
+ function getFunctionKind2(node, context, useLayoutEffectAlias) {
553
+ return $(node).when(isLayoutEffectFunction(context, useLayoutEffectAlias), () => "effect").when(isIIFE, () => "immediate").otherwise(() => "other");
554
+ }
554
555
  var no_direct_set_state_in_use_layout_effect_default = createRule({
555
556
  meta: {
556
557
  type: "problem",
@@ -566,68 +567,6 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
566
567
  create(context) {
567
568
  const settings = decodeSettings(context.settings);
568
569
  const { useLayoutEffect: useLayoutEffectAlias = [], useState: useStateAlias = [] } = settings.additionalHooks ?? {};
569
- function isUseLayoutEffectCallWithAlias(node) {
570
- return isUseLayoutEffectCall(node, context) || useLayoutEffectAlias.some(F.flip(isReactHookCallWithNameLoose)(node));
571
- }
572
- function isUseStateCallWithAlias(node) {
573
- return isUseStateCall(node, context) || useStateAlias.some(F.flip(isReactHookCallWithNameLoose)(node));
574
- }
575
- function isEffectFunction(node) {
576
- return node.parent?.type === NodeType.CallExpression && node.parent.callee !== node && isUseLayoutEffectCallWithAlias(node.parent);
577
- }
578
- function isCleanUpFunction(_2) {
579
- }
580
- function isFromUseStateCall(topLevelId) {
581
- const useStateCall = F.pipe(
582
- findVariable(topLevelId, context.sourceCode.getScope(topLevelId)),
583
- O.flatMap(getVariableNode(0)),
584
- O.filter(is(NodeType.CallExpression)),
585
- O.filter(isUseStateCallWithAlias)
586
- );
587
- if (O.isNone(useStateCall)) return false;
588
- const { parent } = useStateCall.value;
589
- if (!a({ id: { type: NodeType.ArrayPattern }, type: NodeType.VariableDeclarator }, parent)) return true;
590
- return parent.id.elements.findIndex((e2) => e2?.type === NodeType.Identifier && e2.name === topLevelId.name) === 1;
591
- }
592
- function isSetStateCall(node) {
593
- const topLevelId = $(node.callee).with({ type: NodeType.Identifier }, O.some).with({ type: NodeType.MemberExpression }, (n2) => {
594
- if (!("name" in n2.object)) return O.none();
595
- const initialScope = context.sourceCode.getScope(n2);
596
- const property = getStaticValue(n2.property, initialScope);
597
- if (property?.value === 1) return O.fromNullable(n2.object);
598
- return O.none();
599
- }).with({ type: NodeType.CallExpression }, (n2) => {
600
- if (!is(NodeType.MemberExpression)(n2.callee)) return O.none();
601
- if (!("name" in n2.callee.object)) return O.none();
602
- const isAt = a({
603
- type: NodeType.MemberExpression,
604
- property: {
605
- type: NodeType.Identifier,
606
- name: "at"
607
- }
608
- }, n2.callee);
609
- const [index] = n2.arguments;
610
- if (!isAt || !index) return O.none();
611
- const initialScope = context.sourceCode.getScope(n2);
612
- const value = getStaticValue(index, initialScope);
613
- if (value?.value === 1) return O.fromNullable(n2.callee.object);
614
- return O.none();
615
- }).otherwise(O.none);
616
- return O.exists(topLevelId, isFromUseStateCall);
617
- }
618
- function isThenCall(node) {
619
- if (node.callee.type !== NodeType.MemberExpression) return false;
620
- return a({
621
- type: NodeType.Identifier,
622
- name: "then"
623
- }, node.callee.property);
624
- }
625
- function getCallKind(node) {
626
- return $(node).when(isThenCall, () => "then").when(isSetStateCall, () => "setState").when(isUseStateCallWithAlias, () => "useState").when(isUseLayoutEffectCallWithAlias, () => "useLayoutEffect").otherwise(() => "other");
627
- }
628
- function getFunctionKind(node) {
629
- return $(node).when(isEffectFunction, () => "effect").when(isCleanUpFunction, () => "cleanup").when(isIIFE, () => "immediate").otherwise(() => "other");
630
- }
631
570
  const functionStack = MutList.make();
632
571
  const effectFunctionRef = MutRef.make(null);
633
572
  const effectFunctionIdentifiers = [];
@@ -641,7 +580,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
641
580
  };
642
581
  return {
643
582
  ":function"(node) {
644
- const functionKind = getFunctionKind(node);
583
+ const functionKind = getFunctionKind2(node, context, useLayoutEffectAlias);
645
584
  MutList.append(functionStack, [node, functionKind]);
646
585
  $(functionKind).with("effect", () => {
647
586
  onEffectFunctionEnter(node);
@@ -655,7 +594,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
655
594
  const effectFn = MutRef.get(effectFunctionRef);
656
595
  const [parentFn, parentFnKind] = MutList.tail(functionStack) ?? [];
657
596
  if (parentFn?.async) return;
658
- const callKind = getCallKind(node);
597
+ const callKind = getCallKind2(node, context, useStateAlias, useLayoutEffectAlias);
659
598
  $(callKind).with("setState", () => {
660
599
  if (!parentFn) return;
661
600
  if (parentFn !== effectFn && parentFnKind !== "immediate") {
@@ -733,7 +672,7 @@ var prefer_use_state_lazy_initialization_default = createRule({
733
672
  return {
734
673
  CallExpression(node) {
735
674
  if (!isReactHookCall(node)) return;
736
- if (!isUseStateCall(node, context) && !alias.some(F.flip(isReactHookCallWithNameLoose)(node))) return;
675
+ if (!isUseStateCall(node, context) && !alias.some(isReactHookCallWithNameLoose(node))) return;
737
676
  const [useStateInput] = node.arguments;
738
677
  if (!useStateInput) return;
739
678
  const nestedCallExpressions = getNestedCallExpressions(useStateInput);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-hooks-extra",
3
- "version": "1.6.1-next.1",
3
+ "version": "1.6.1-next.3",
4
4
  "description": "ESLint React's ESLint plugin for React Hooks related rules.",
5
5
  "homepage": "https://github.com/rel1cx/eslint-react",
6
6
  "bugs": {
@@ -39,13 +39,13 @@
39
39
  "@typescript-eslint/type-utils": "^7.17.0",
40
40
  "@typescript-eslint/types": "^7.17.0",
41
41
  "@typescript-eslint/utils": "^7.17.0",
42
- "@eslint-react/ast": "1.6.1-next.1",
43
- "@eslint-react/jsx": "1.6.1-next.1",
44
- "@eslint-react/core": "1.6.1-next.1",
45
- "@eslint-react/types": "1.6.1-next.1",
46
- "@eslint-react/var": "1.6.1-next.1",
47
- "@eslint-react/shared": "1.6.1-next.1",
48
- "@eslint-react/tools": "1.6.1-next.1"
42
+ "@eslint-react/ast": "1.6.1-next.3",
43
+ "@eslint-react/core": "1.6.1-next.3",
44
+ "@eslint-react/jsx": "1.6.1-next.3",
45
+ "@eslint-react/shared": "1.6.1-next.3",
46
+ "@eslint-react/types": "1.6.1-next.3",
47
+ "@eslint-react/tools": "1.6.1-next.3",
48
+ "@eslint-react/var": "1.6.1-next.3"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/react": "^18.3.3",