eslint-plugin-react-hooks-extra 1.6.1-beta.0 → 1.6.1-beta.2

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 +112 -169
  2. package/dist/index.mjs +113 -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-beta.0";
12
+ var version = "1.6.1-beta.2";
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,78 @@ 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
+ if (node.callee.type !== ast.NodeType.MemberExpression) return false;
243
+ return a({
244
+ type: ast.NodeType.Identifier,
245
+ name: "then"
246
+ }, node.callee.property);
247
+ }
248
+
249
+ // src/rules/ensure-custom-hooks-using-other-hooks.ts
250
+ var RULE_NAME = "ensure-custom-hooks-using-other-hooks";
251
+ var ensure_custom_hooks_using_other_hooks_default = createRule({
252
+ meta: {
253
+ type: "problem",
254
+ docs: {
255
+ description: "enforce custom hooks using other hooks"
256
+ },
257
+ messages: {
258
+ ENSURE_CUSTOM_HOOKS_USING_OTHER_HOOKS: "A custom hook '{{name}}' should use other hooks."
259
+ },
260
+ schema: []
261
+ },
262
+ name: RULE_NAME,
263
+ create(context) {
264
+ const { ctx, listeners } = core.useHookCollector();
265
+ return {
266
+ ...listeners,
267
+ "Program:exit"(node) {
268
+ const allHooks = ctx.getAllHooks(node);
269
+ for (const { name: name2, hookCalls, node: node2 } of allHooks.values()) {
270
+ if (hookCalls.length > 0) continue;
271
+ context.report({
272
+ data: {
273
+ name: name2.value
274
+ },
275
+ messageId: "ENSURE_CUSTOM_HOOKS_USING_OTHER_HOOKS",
276
+ node: node2
277
+ });
278
+ }
279
+ }
280
+ };
281
+ },
282
+ defaultOptions: []
283
+ });
235
284
  var RULE_NAME2 = "ensure-use-callback-has-non-empty-deps";
236
285
  var ensure_use_callback_has_non_empty_deps_default = createRule({
237
286
  meta: {
@@ -251,7 +300,7 @@ var ensure_use_callback_has_non_empty_deps_default = createRule({
251
300
  CallExpression(node) {
252
301
  if (!core.isReactHookCall(node)) return;
253
302
  const initialScope = context.sourceCode.getScope(node);
254
- if (!core.isUseCallbackCall(node, context) && !alias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node))) {
303
+ if (!core.isUseCallbackCall(node, context) && !alias.some(core.isReactHookCallWithNameLoose(node))) {
255
304
  return;
256
305
  }
257
306
  const scope = context.sourceCode.getScope(node);
@@ -329,7 +378,7 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
329
378
  CallExpression(node) {
330
379
  if (!core.isReactHookCall(node)) return;
331
380
  const initialScope = context.sourceCode.getScope(node);
332
- if (!core.isUseMemoCall(node, context) && !alias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node))) {
381
+ if (!core.isUseMemoCall(node, context) && !alias.some(core.isReactHookCallWithNameLoose(node))) {
333
382
  return;
334
383
  }
335
384
  const scope = context.sourceCode.getScope(node);
@@ -389,6 +438,15 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
389
438
  defaultOptions: []
390
439
  });
391
440
  var RULE_NAME4 = "no-direct-set-state-in-use-effect";
441
+ function isEffectFunction(context, useSomeEffectAlias) {
442
+ return (node) => node.parent?.type === ast.NodeType.CallExpression && node.parent.callee !== node && core.isReactHookCallWithNameAlias("useEffect", context, useSomeEffectAlias)(node.parent);
443
+ }
444
+ function getCallKind(node, context, useStateAlias, useEffectAlias) {
445
+ 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");
446
+ }
447
+ function getFunctionKind(node, context, useEffectAlias) {
448
+ return $(node).when(isEffectFunction(context, useEffectAlias), () => "effect").when(ast.isIIFE, () => "immediate").otherwise(() => "other");
449
+ }
392
450
  var no_direct_set_state_in_use_effect_default = createRule({
393
451
  meta: {
394
452
  type: "problem",
@@ -404,68 +462,6 @@ var no_direct_set_state_in_use_effect_default = createRule({
404
462
  create(context) {
405
463
  const settings = shared.decodeSettings(context.settings);
406
464
  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
465
  const functionStack = tools.MutList.make();
470
466
  const effectFunctionRef = tools.MutRef.make(null);
471
467
  const effectFunctionIdentifiers = [];
@@ -479,7 +475,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
479
475
  };
480
476
  return {
481
477
  ":function"(node) {
482
- const functionKind = getFunctionKind(node);
478
+ const functionKind = getFunctionKind(node, context, useEffectAlias);
483
479
  tools.MutList.append(functionStack, [node, functionKind]);
484
480
  $(functionKind).with("effect", () => {
485
481
  onEffectFunctionEnter(node);
@@ -493,7 +489,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
493
489
  const effectFn = tools.MutRef.get(effectFunctionRef);
494
490
  const [parentFn, parentFnKind] = tools.MutList.tail(functionStack) ?? [];
495
491
  if (parentFn?.async) return;
496
- const callKind = getCallKind(node);
492
+ const callKind = getCallKind(node, context, useStateAlias, useEffectAlias);
497
493
  $(callKind).with("setState", () => {
498
494
  if (!parentFn) return;
499
495
  if (parentFn !== effectFn && parentFnKind !== "immediate") {
@@ -553,6 +549,15 @@ var no_direct_set_state_in_use_effect_default = createRule({
553
549
  defaultOptions: []
554
550
  });
555
551
  var RULE_NAME5 = "no-direct-set-state-in-use-layout-effect";
552
+ function isLayoutEffectFunction(context, useSomeEffectAlias) {
553
+ return (node) => node.parent?.type === ast.NodeType.CallExpression && node.parent.callee !== node && core.isReactHookCallWithNameAlias("useLayoutEffect", context, useSomeEffectAlias)(node.parent);
554
+ }
555
+ function getCallKind2(node, context, useStateAlias, useLayoutEffectAlias) {
556
+ 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");
557
+ }
558
+ function getFunctionKind2(node, context, useLayoutEffectAlias) {
559
+ return $(node).when(isLayoutEffectFunction(context, useLayoutEffectAlias), () => "effect").when(ast.isIIFE, () => "immediate").otherwise(() => "other");
560
+ }
556
561
  var no_direct_set_state_in_use_layout_effect_default = createRule({
557
562
  meta: {
558
563
  type: "problem",
@@ -568,68 +573,6 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
568
573
  create(context) {
569
574
  const settings = shared.decodeSettings(context.settings);
570
575
  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
576
  const functionStack = tools.MutList.make();
634
577
  const effectFunctionRef = tools.MutRef.make(null);
635
578
  const effectFunctionIdentifiers = [];
@@ -643,7 +586,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
643
586
  };
644
587
  return {
645
588
  ":function"(node) {
646
- const functionKind = getFunctionKind(node);
589
+ const functionKind = getFunctionKind2(node, context, useLayoutEffectAlias);
647
590
  tools.MutList.append(functionStack, [node, functionKind]);
648
591
  $(functionKind).with("effect", () => {
649
592
  onEffectFunctionEnter(node);
@@ -657,7 +600,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
657
600
  const effectFn = tools.MutRef.get(effectFunctionRef);
658
601
  const [parentFn, parentFnKind] = tools.MutList.tail(functionStack) ?? [];
659
602
  if (parentFn?.async) return;
660
- const callKind = getCallKind(node);
603
+ const callKind = getCallKind2(node, context, useStateAlias, useLayoutEffectAlias);
661
604
  $(callKind).with("setState", () => {
662
605
  if (!parentFn) return;
663
606
  if (parentFn !== effectFn && parentFnKind !== "immediate") {
@@ -735,7 +678,7 @@ var prefer_use_state_lazy_initialization_default = createRule({
735
678
  return {
736
679
  CallExpression(node) {
737
680
  if (!core.isReactHookCall(node)) return;
738
- if (!core.isUseStateCall(node, context) && !alias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node))) return;
681
+ if (!core.isUseStateCall(node, context) && !alias.some(core.isReactHookCallWithNameLoose(node))) return;
739
682
  const [useStateInput] = node.arguments;
740
683
  if (!useStateInput) return;
741
684
  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-beta.0";
10
+ var version = "1.6.1-beta.2";
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,78 @@ 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
+ if (node.callee.type !== NodeType.MemberExpression) return false;
241
+ return a({
242
+ type: NodeType.Identifier,
243
+ name: "then"
244
+ }, node.callee.property);
245
+ }
246
+
247
+ // src/rules/ensure-custom-hooks-using-other-hooks.ts
248
+ var RULE_NAME = "ensure-custom-hooks-using-other-hooks";
249
+ var ensure_custom_hooks_using_other_hooks_default = createRule({
250
+ meta: {
251
+ type: "problem",
252
+ docs: {
253
+ description: "enforce custom hooks using other hooks"
254
+ },
255
+ messages: {
256
+ ENSURE_CUSTOM_HOOKS_USING_OTHER_HOOKS: "A custom hook '{{name}}' should use other hooks."
257
+ },
258
+ schema: []
259
+ },
260
+ name: RULE_NAME,
261
+ create(context) {
262
+ const { ctx, listeners } = useHookCollector();
263
+ return {
264
+ ...listeners,
265
+ "Program:exit"(node) {
266
+ const allHooks = ctx.getAllHooks(node);
267
+ for (const { name: name2, hookCalls, node: node2 } of allHooks.values()) {
268
+ if (hookCalls.length > 0) continue;
269
+ context.report({
270
+ data: {
271
+ name: name2.value
272
+ },
273
+ messageId: "ENSURE_CUSTOM_HOOKS_USING_OTHER_HOOKS",
274
+ node: node2
275
+ });
276
+ }
277
+ }
278
+ };
279
+ },
280
+ defaultOptions: []
281
+ });
233
282
  var RULE_NAME2 = "ensure-use-callback-has-non-empty-deps";
234
283
  var ensure_use_callback_has_non_empty_deps_default = createRule({
235
284
  meta: {
@@ -249,7 +298,7 @@ var ensure_use_callback_has_non_empty_deps_default = createRule({
249
298
  CallExpression(node) {
250
299
  if (!isReactHookCall(node)) return;
251
300
  const initialScope = context.sourceCode.getScope(node);
252
- if (!isUseCallbackCall(node, context) && !alias.some(F.flip(isReactHookCallWithNameLoose)(node))) {
301
+ if (!isUseCallbackCall(node, context) && !alias.some(isReactHookCallWithNameLoose(node))) {
253
302
  return;
254
303
  }
255
304
  const scope = context.sourceCode.getScope(node);
@@ -327,7 +376,7 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
327
376
  CallExpression(node) {
328
377
  if (!isReactHookCall(node)) return;
329
378
  const initialScope = context.sourceCode.getScope(node);
330
- if (!isUseMemoCall(node, context) && !alias.some(F.flip(isReactHookCallWithNameLoose)(node))) {
379
+ if (!isUseMemoCall(node, context) && !alias.some(isReactHookCallWithNameLoose(node))) {
331
380
  return;
332
381
  }
333
382
  const scope = context.sourceCode.getScope(node);
@@ -387,6 +436,15 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
387
436
  defaultOptions: []
388
437
  });
389
438
  var RULE_NAME4 = "no-direct-set-state-in-use-effect";
439
+ function isEffectFunction(context, useSomeEffectAlias) {
440
+ return (node) => node.parent?.type === NodeType.CallExpression && node.parent.callee !== node && isReactHookCallWithNameAlias("useEffect", context, useSomeEffectAlias)(node.parent);
441
+ }
442
+ function getCallKind(node, context, useStateAlias, useEffectAlias) {
443
+ return $(node).when(isThenCall, () => "then").when(isSetStateCall(context, useStateAlias), () => "setState").when(isReactHookCallWithNameAlias("useState", context, useStateAlias), () => "useState").when(isReactHookCallWithNameAlias("useEffect", context, useEffectAlias), () => "useEffect").otherwise(() => "other");
444
+ }
445
+ function getFunctionKind(node, context, useEffectAlias) {
446
+ return $(node).when(isEffectFunction(context, useEffectAlias), () => "effect").when(isIIFE, () => "immediate").otherwise(() => "other");
447
+ }
390
448
  var no_direct_set_state_in_use_effect_default = createRule({
391
449
  meta: {
392
450
  type: "problem",
@@ -402,68 +460,6 @@ var no_direct_set_state_in_use_effect_default = createRule({
402
460
  create(context) {
403
461
  const settings = decodeSettings(context.settings);
404
462
  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
463
  const functionStack = MutList.make();
468
464
  const effectFunctionRef = MutRef.make(null);
469
465
  const effectFunctionIdentifiers = [];
@@ -477,7 +473,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
477
473
  };
478
474
  return {
479
475
  ":function"(node) {
480
- const functionKind = getFunctionKind(node);
476
+ const functionKind = getFunctionKind(node, context, useEffectAlias);
481
477
  MutList.append(functionStack, [node, functionKind]);
482
478
  $(functionKind).with("effect", () => {
483
479
  onEffectFunctionEnter(node);
@@ -491,7 +487,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
491
487
  const effectFn = MutRef.get(effectFunctionRef);
492
488
  const [parentFn, parentFnKind] = MutList.tail(functionStack) ?? [];
493
489
  if (parentFn?.async) return;
494
- const callKind = getCallKind(node);
490
+ const callKind = getCallKind(node, context, useStateAlias, useEffectAlias);
495
491
  $(callKind).with("setState", () => {
496
492
  if (!parentFn) return;
497
493
  if (parentFn !== effectFn && parentFnKind !== "immediate") {
@@ -551,6 +547,15 @@ var no_direct_set_state_in_use_effect_default = createRule({
551
547
  defaultOptions: []
552
548
  });
553
549
  var RULE_NAME5 = "no-direct-set-state-in-use-layout-effect";
550
+ function isLayoutEffectFunction(context, useSomeEffectAlias) {
551
+ return (node) => node.parent?.type === NodeType.CallExpression && node.parent.callee !== node && isReactHookCallWithNameAlias("useLayoutEffect", context, useSomeEffectAlias)(node.parent);
552
+ }
553
+ function getCallKind2(node, context, useStateAlias, useLayoutEffectAlias) {
554
+ return $(node).when(isThenCall, () => "then").when(isSetStateCall(context, useStateAlias), () => "setState").when(isReactHookCallWithNameAlias("useState", context, useStateAlias), () => "useState").when(isReactHookCallWithNameAlias("useLayoutEffect", context, useLayoutEffectAlias), () => "useLayoutEffect").otherwise(() => "other");
555
+ }
556
+ function getFunctionKind2(node, context, useLayoutEffectAlias) {
557
+ return $(node).when(isLayoutEffectFunction(context, useLayoutEffectAlias), () => "effect").when(isIIFE, () => "immediate").otherwise(() => "other");
558
+ }
554
559
  var no_direct_set_state_in_use_layout_effect_default = createRule({
555
560
  meta: {
556
561
  type: "problem",
@@ -566,68 +571,6 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
566
571
  create(context) {
567
572
  const settings = decodeSettings(context.settings);
568
573
  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
574
  const functionStack = MutList.make();
632
575
  const effectFunctionRef = MutRef.make(null);
633
576
  const effectFunctionIdentifiers = [];
@@ -641,7 +584,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
641
584
  };
642
585
  return {
643
586
  ":function"(node) {
644
- const functionKind = getFunctionKind(node);
587
+ const functionKind = getFunctionKind2(node, context, useLayoutEffectAlias);
645
588
  MutList.append(functionStack, [node, functionKind]);
646
589
  $(functionKind).with("effect", () => {
647
590
  onEffectFunctionEnter(node);
@@ -655,7 +598,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
655
598
  const effectFn = MutRef.get(effectFunctionRef);
656
599
  const [parentFn, parentFnKind] = MutList.tail(functionStack) ?? [];
657
600
  if (parentFn?.async) return;
658
- const callKind = getCallKind(node);
601
+ const callKind = getCallKind2(node, context, useStateAlias, useLayoutEffectAlias);
659
602
  $(callKind).with("setState", () => {
660
603
  if (!parentFn) return;
661
604
  if (parentFn !== effectFn && parentFnKind !== "immediate") {
@@ -733,7 +676,7 @@ var prefer_use_state_lazy_initialization_default = createRule({
733
676
  return {
734
677
  CallExpression(node) {
735
678
  if (!isReactHookCall(node)) return;
736
- if (!isUseStateCall(node, context) && !alias.some(F.flip(isReactHookCallWithNameLoose)(node))) return;
679
+ if (!isUseStateCall(node, context) && !alias.some(isReactHookCallWithNameLoose(node))) return;
737
680
  const [useStateInput] = node.arguments;
738
681
  if (!useStateInput) return;
739
682
  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-beta.0",
3
+ "version": "1.6.1-beta.2",
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-beta.0",
43
- "@eslint-react/core": "1.6.1-beta.0",
44
- "@eslint-react/jsx": "1.6.1-beta.0",
45
- "@eslint-react/shared": "1.6.1-beta.0",
46
- "@eslint-react/types": "1.6.1-beta.0",
47
- "@eslint-react/tools": "1.6.1-beta.0",
48
- "@eslint-react/var": "1.6.1-beta.0"
42
+ "@eslint-react/ast": "1.6.1-beta.2",
43
+ "@eslint-react/core": "1.6.1-beta.2",
44
+ "@eslint-react/jsx": "1.6.1-beta.2",
45
+ "@eslint-react/tools": "1.6.1-beta.2",
46
+ "@eslint-react/shared": "1.6.1-beta.2",
47
+ "@eslint-react/types": "1.6.1-beta.2",
48
+ "@eslint-react/var": "1.6.1-beta.2"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/react": "^18.3.3",