eslint-plugin-react-hooks-extra 1.5.28-beta.1 → 1.5.28-beta.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 +301 -116
  2. package/dist/index.mjs +303 -118
  3. package/package.json +9 -9
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ 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.5.28-beta.1";
12
+ var version = "1.5.28-beta.3";
13
13
  var createRule = shared.createRuleForPlugin("hooks-extra");
14
14
 
15
15
  // src/rules/ensure-custom-hooks-using-other-hooks.ts
@@ -63,32 +63,45 @@ var o = (n2, s2, c2) => {
63
63
  if (!r(s2)) return false;
64
64
  if (Array.isArray(n2)) {
65
65
  if (!Array.isArray(s2)) return false;
66
- let t2 = [], r2 = [], a = [];
66
+ let t2 = [], r2 = [], a2 = [];
67
67
  for (const o2 of n2.keys()) {
68
68
  const s3 = n2[o2];
69
- i(s3) && s3[e] ? a.push(s3) : a.length ? r2.push(s3) : t2.push(s3);
69
+ i(s3) && s3[e] ? a2.push(s3) : a2.length ? r2.push(s3) : t2.push(s3);
70
70
  }
71
- if (a.length) {
72
- if (a.length > 1) throw new Error("Pattern error: Using `...P.array(...)` several times in a single pattern is not allowed.");
71
+ if (a2.length) {
72
+ if (a2.length > 1) throw new Error("Pattern error: Using `...P.array(...)` several times in a single pattern is not allowed.");
73
73
  if (s2.length < t2.length + r2.length) return false;
74
74
  const e2 = s2.slice(0, t2.length), n3 = 0 === r2.length ? [] : s2.slice(-r2.length), i2 = s2.slice(t2.length, 0 === r2.length ? Infinity : -r2.length);
75
- return t2.every((t3, n4) => o(t3, e2[n4], c2)) && r2.every((t3, e3) => o(t3, n3[e3], c2)) && (0 === a.length || o(a[0], i2, c2));
75
+ return t2.every((t3, n4) => o(t3, e2[n4], c2)) && r2.every((t3, e3) => o(t3, n3[e3], c2)) && (0 === a2.length || o(a2[0], i2, c2));
76
76
  }
77
77
  return n2.length === s2.length && n2.every((t3, e2) => o(t3, s2[e2], c2));
78
78
  }
79
79
  return Object.keys(n2).every((e2) => {
80
80
  const r2 = n2[e2];
81
- return (e2 in s2 || i(a = r2) && "optional" === a[t]().matcherType) && o(r2, s2[e2], c2);
82
- var a;
81
+ return (e2 in s2 || i(a2 = r2) && "optional" === a2[t]().matcherType) && o(r2, s2[e2], c2);
82
+ var a2;
83
83
  });
84
84
  }
85
85
  return Object.is(s2, n2);
86
86
  };
87
87
  var s = (e2) => {
88
- var n2, o2, a;
89
- return r(e2) ? i(e2) ? null != (n2 = null == (o2 = (a = e2[t]()).getSelectionKeys) ? void 0 : o2.call(a)) ? n2 : [] : Array.isArray(e2) ? c(e2, s) : c(Object.values(e2), s) : [];
88
+ var n2, o2, a2;
89
+ return r(e2) ? i(e2) ? null != (n2 = null == (o2 = (a2 = e2[t]()).getSelectionKeys) ? void 0 : o2.call(a2)) ? n2 : [] : Array.isArray(e2) ? c(e2, s) : c(Object.values(e2), s) : [];
90
90
  };
91
91
  var c = (t2, e2) => t2.reduce((t3, n2) => t3.concat(e2(n2)), []);
92
+ function a(...t2) {
93
+ if (1 === t2.length) {
94
+ const [e2] = t2;
95
+ return (t3) => o(e2, t3, () => {
96
+ });
97
+ }
98
+ if (2 === t2.length) {
99
+ const [e2, n2] = t2;
100
+ return o(e2, n2, () => {
101
+ });
102
+ }
103
+ throw new Error(`isMatching wasn't given the right number of arguments: expected 1 or 2, received ${t2.length}.`);
104
+ }
92
105
  function u(t2) {
93
106
  return Object.assign(t2, { optional: () => l(t2), and: (e2) => m(t2, e2), or: (e2) => d(t2, e2), select: (e2) => void 0 === e2 ? p(t2) : p(e2, t2) });
94
107
  }
@@ -187,9 +200,9 @@ var z = class _z {
187
200
  let i2;
188
201
  3 === t2.length && "function" == typeof t2[1] ? i2 = t2[1] : t2.length > 2 && r2.push(...t2.slice(1, t2.length - 1));
189
202
  let s2 = false, c2 = {};
190
- const a = (t3, e3) => {
203
+ const a2 = (t3, e3) => {
191
204
  s2 = true, c2[t3] = e3;
192
- }, u2 = !r2.some((t3) => o(t3, this.input, a)) || i2 && !Boolean(i2(this.input)) ? W : { matched: true, value: e2(s2 ? n in c2 ? c2[n] : c2 : this.input, this.input) };
205
+ }, u2 = !r2.some((t3) => o(t3, this.input, a2)) || i2 && !Boolean(i2(this.input)) ? W : { matched: true, value: e2(s2 ? n in c2 ? c2[n] : c2 : this.input, this.input) };
193
206
  return new _z(this.input, u2);
194
207
  }
195
208
  when(t2, e2) {
@@ -391,64 +404,150 @@ var no_direct_set_state_in_use_effect_default = createRule({
391
404
  create(context) {
392
405
  const settings = shared.getESLintReactSettings(context.settings);
393
406
  const { useEffect: useEffectAlias = [], useState: useStateAlias = [] } = settings.additionalHooks ?? {};
394
- function isUseEffectCallWithAlias(node, context2) {
395
- return core.isUseEffectCall(node, context2) || useEffectAlias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node));
407
+ function isUseEffectCallWithAlias(node) {
408
+ return core.isUseEffectCall(node, context) || useEffectAlias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node));
396
409
  }
397
- function isUseStateCallWithAlias(node, context2) {
398
- return core.isUseStateCall(node, context2) || useStateAlias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node));
410
+ function isUseStateCallWithAlias(node) {
411
+ return core.isUseStateCall(node, context) || useStateAlias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node));
399
412
  }
400
413
  function isEffectFunction(node) {
401
- return node.parent?.type === ast.NodeType.CallExpression && isUseEffectCallWithAlias(node.parent, context);
414
+ return node.parent?.type === ast.NodeType.CallExpression && node.parent.callee !== node && isUseEffectCallWithAlias(node.parent);
415
+ }
416
+ function isCleanUpFunction(_2) {
417
+ }
418
+ function isFromUseStateCall(id) {
419
+ return tools.F.pipe(
420
+ $(id).with({ type: ast.NodeType.Identifier }, (n2) => tools.O.some(n2.name)).with({
421
+ type: ast.NodeType.MemberExpression,
422
+ object: { type: ast.NodeType.Identifier }
423
+ }, (n2) => tools.O.some(n2.object.name)).otherwise(tools.O.none),
424
+ tools.O.flatMap(_var.findVariable(context.sourceCode.getScope(id))),
425
+ tools.O.flatMap(_var.getVariableInit(0)),
426
+ tools.O.filter(ast.is(ast.NodeType.CallExpression)),
427
+ tools.O.exists(isUseStateCallWithAlias)
428
+ );
429
+ }
430
+ function isSetStateCall(node) {
431
+ const id = $(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(id, 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");
402
468
  }
469
+ const useEffectCallRef = tools.MutRef.make(null);
470
+ const functionStack = tools.MutList.make();
471
+ const effectFunctionRef = tools.MutRef.make(null);
472
+ const cleanUpFunctionRef = tools.MutRef.make(null);
473
+ const indirectFunctionCalls = [];
474
+ const indirectSetStateCalls = /* @__PURE__ */ new Map();
403
475
  return {
476
+ Identifier(node) {
477
+ const isInUseEffectCall = tools.MutRef.get(useEffectCallRef) !== null;
478
+ const parentFn = tools.MutList.tail(functionStack)?.[0];
479
+ const useEffectCall = tools.MutRef.get(useEffectCallRef);
480
+ const isEffectFunction2 = useEffectCall && tools.O.isSome(ast.traverseUp(useEffectCall, (n2) => n2 === parentFn));
481
+ if (isFromUseStateCall(node) && isInUseEffectCall && isEffectFunction2) {
482
+ context.report({
483
+ node,
484
+ messageId: "NO_DIRECT_SET_STATE_IN_USE_EFFECT"
485
+ });
486
+ }
487
+ },
404
488
  CallExpression(node) {
405
- const effectFunction = ast.traverseUp(node, isEffectFunction);
406
- const parentFunction = ast.traverseUp(node, ast.isFunction);
407
- if (tools.O.isNone(effectFunction) || tools.O.isNone(parentFunction)) return;
408
- if (parentFunction.value !== effectFunction.value) return;
409
- const callScope = context.sourceCode.getScope(node);
410
- const name2 = $(node.callee).with({ type: ast.NodeType.Identifier }, (n2) => tools.O.some(n2.name)).with({ type: ast.NodeType.MemberExpression }, (n2) => {
411
- if (!("name" in n2.object)) return tools.O.none();
412
- const initialScope = context.sourceCode.getScope(n2);
413
- const property = astUtils.getStaticValue(n2.property, initialScope);
414
- if (property?.value === 1) return tools.O.fromNullable(n2.object.name);
415
- return tools.O.none();
416
- }).with({ type: ast.NodeType.CallExpression }, (n2) => {
417
- if (!ast.is(ast.NodeType.MemberExpression)(n2.callee)) return tools.O.none();
418
- if (!("name" in n2.callee.object)) return tools.O.none();
419
- const isAt = $(n2.callee).with(
420
- {
421
- type: ast.NodeType.MemberExpression,
422
- property: {
423
- type: ast.NodeType.Identifier,
424
- name: "at"
425
- }
426
- },
427
- tools.F.constTrue
428
- ).otherwise(tools.F.constFalse);
429
- if (!isAt) return tools.O.none();
430
- const [index] = n2.arguments;
431
- if (!index) return tools.O.none();
432
- const initialScope = context.sourceCode.getScope(n2);
433
- const value = astUtils.getStaticValue(index, initialScope);
434
- if (value?.value === 1) return tools.O.fromNullable(n2.callee.object.name);
435
- return tools.O.none();
436
- }).otherwise(tools.O.none);
437
- tools.F.pipe(
438
- name2,
439
- tools.O.flatMap(_var.findVariable(callScope)),
440
- tools.O.flatMap(_var.getVariableInit(0)),
441
- tools.O.filter(ast.is(ast.NodeType.CallExpression)),
442
- tools.O.filter((name3) => isUseStateCallWithAlias(name3, context)),
443
- tools.O.map((name3) => ({
444
- data: {
445
- setState: name3
446
- },
447
- messageId: "NO_DIRECT_SET_STATE_IN_USE_EFFECT",
448
- node
449
- })),
450
- tools.O.map(context.report)
451
- );
489
+ const effectFn = tools.MutRef.get(effectFunctionRef);
490
+ const [parentFn, parentFnKind] = tools.MutList.tail(functionStack) ?? [];
491
+ if (parentFn?.async) return;
492
+ const callKind = getCallKind(node);
493
+ $(callKind).with("setState", () => {
494
+ if (!parentFn) return;
495
+ if (parentFn !== effectFn && parentFnKind !== "immediate") {
496
+ indirectSetStateCalls.set(parentFn, [...indirectSetStateCalls.get(parentFn) ?? [], node]);
497
+ return;
498
+ }
499
+ context.report({
500
+ node,
501
+ messageId: "NO_DIRECT_SET_STATE_IN_USE_EFFECT"
502
+ });
503
+ }).with("useEffect", () => {
504
+ tools.MutRef.set(useEffectCallRef, node);
505
+ }).with("other", () => {
506
+ indirectFunctionCalls.push(node);
507
+ }).otherwise(tools.F.constVoid);
508
+ },
509
+ ":function"(node) {
510
+ const functionKind = getFunctionKind(node);
511
+ tools.MutList.append(functionStack, [node, functionKind]);
512
+ $(functionKind).with("effect", () => {
513
+ tools.MutRef.set(effectFunctionRef, node);
514
+ }).with("cleanup", () => {
515
+ tools.MutRef.set(cleanUpFunctionRef, node);
516
+ }).otherwise(tools.F.constVoid);
517
+ },
518
+ ":function:exit"(node) {
519
+ const effectFn = tools.MutRef.get(effectFunctionRef);
520
+ if (effectFn === node) {
521
+ tools.MutRef.set(effectFunctionRef, null);
522
+ }
523
+ tools.MutList.pop(functionStack);
524
+ },
525
+ "CallExpression:exit"(node) {
526
+ if (tools.MutRef.get(useEffectCallRef) === node) {
527
+ tools.MutRef.set(useEffectCallRef, null);
528
+ }
529
+ },
530
+ "Program:exit"() {
531
+ for (const call of indirectFunctionCalls) {
532
+ if (!("name" in call.callee)) continue;
533
+ const { name: name2 } = call.callee;
534
+ const setStateCalls = tools.F.pipe(
535
+ _var.findVariable(name2, context.sourceCode.getScope(call)),
536
+ tools.O.flatMap(_var.getVariableInit(0)),
537
+ tools.O.filter(ast.isFunction),
538
+ tools.O.flatMapNullable((init) => indirectSetStateCalls.get(init)),
539
+ tools.O.getOrElse(() => [])
540
+ );
541
+ for (const setStateCall of setStateCalls) {
542
+ context.report({
543
+ data: {
544
+ name: name2
545
+ },
546
+ messageId: "NO_DIRECT_SET_STATE_IN_USE_EFFECT",
547
+ node: setStateCall
548
+ });
549
+ }
550
+ }
452
551
  }
453
552
  };
454
553
  },
@@ -470,64 +569,150 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
470
569
  create(context) {
471
570
  const settings = shared.getESLintReactSettings(context.settings);
472
571
  const { useLayoutEffect: useLayoutEffectAlias = [], useState: useStateAlias = [] } = settings.additionalHooks ?? {};
473
- function isUseLayoutEffectCallWithAlias(node, context2) {
474
- return core.isUseLayoutEffectCall(node, context2) || useLayoutEffectAlias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node));
572
+ function isUseLayoutEffectCallWithAlias(node) {
573
+ return core.isUseLayoutEffectCall(node, context) || useLayoutEffectAlias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node));
475
574
  }
476
- function isUseStateCallWithAlias(node, context2) {
477
- return core.isUseStateCall(node, context2) || useStateAlias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node));
575
+ function isUseStateCallWithAlias(node) {
576
+ return core.isUseStateCall(node, context) || useStateAlias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node));
478
577
  }
479
578
  function isEffectFunction(node) {
480
- return node.parent?.type === ast.NodeType.CallExpression && isUseLayoutEffectCallWithAlias(node.parent, context);
579
+ return node.parent?.type === ast.NodeType.CallExpression && node.parent.callee !== node && isUseLayoutEffectCallWithAlias(node.parent);
580
+ }
581
+ function isCleanUpFunction(_2) {
582
+ }
583
+ function isFromUseStateCall(id) {
584
+ return tools.F.pipe(
585
+ $(id).with({ type: ast.NodeType.Identifier }, (n2) => tools.O.some(n2.name)).with({
586
+ type: ast.NodeType.MemberExpression,
587
+ object: { type: ast.NodeType.Identifier }
588
+ }, (n2) => tools.O.some(n2.object.name)).otherwise(tools.O.none),
589
+ tools.O.flatMap(_var.findVariable(context.sourceCode.getScope(id))),
590
+ tools.O.flatMap(_var.getVariableInit(0)),
591
+ tools.O.filter(ast.is(ast.NodeType.CallExpression)),
592
+ tools.O.exists(isUseStateCallWithAlias)
593
+ );
594
+ }
595
+ function isSetStateCall(node) {
596
+ const id = $(node.callee).with({ type: ast.NodeType.Identifier }, tools.O.some).with({ type: ast.NodeType.MemberExpression }, (n2) => {
597
+ if (!("name" in n2.object)) return tools.O.none();
598
+ const initialScope = context.sourceCode.getScope(n2);
599
+ const property = astUtils.getStaticValue(n2.property, initialScope);
600
+ if (property?.value === 1) return tools.O.fromNullable(n2.object);
601
+ return tools.O.none();
602
+ }).with({ type: ast.NodeType.CallExpression }, (n2) => {
603
+ if (!ast.is(ast.NodeType.MemberExpression)(n2.callee)) return tools.O.none();
604
+ if (!("name" in n2.callee.object)) return tools.O.none();
605
+ const isAt = a({
606
+ type: ast.NodeType.MemberExpression,
607
+ property: {
608
+ type: ast.NodeType.Identifier,
609
+ name: "at"
610
+ }
611
+ }, n2.callee);
612
+ const [index] = n2.arguments;
613
+ if (!isAt || !index) return tools.O.none();
614
+ const initialScope = context.sourceCode.getScope(n2);
615
+ const value = astUtils.getStaticValue(index, initialScope);
616
+ if (value?.value === 1) return tools.O.fromNullable(n2.callee.object);
617
+ return tools.O.none();
618
+ }).otherwise(tools.O.none);
619
+ return tools.O.exists(id, isFromUseStateCall);
620
+ }
621
+ function isThenCall(node) {
622
+ if (node.callee.type !== ast.NodeType.MemberExpression) return false;
623
+ return a({
624
+ type: ast.NodeType.Identifier,
625
+ name: "then"
626
+ }, node.callee.property);
481
627
  }
628
+ function getCallKind(node) {
629
+ return $(node).when(isThenCall, () => "then").when(isSetStateCall, () => "setState").when(isUseStateCallWithAlias, () => "useState").when(isUseLayoutEffectCallWithAlias, () => "useLayoutEffect").otherwise(() => "other");
630
+ }
631
+ function getFunctionKind(node) {
632
+ return $(node).when(isEffectFunction, () => "effect").when(isCleanUpFunction, () => "cleanup").when(ast.isIIFE, () => "immediate").otherwise(() => "other");
633
+ }
634
+ const useLayoutEffectCallRef = tools.MutRef.make(null);
635
+ const functionStack = tools.MutList.make();
636
+ const effectFunctionRef = tools.MutRef.make(null);
637
+ const cleanUpFunctionRef = tools.MutRef.make(null);
638
+ const indirectFunctionCalls = [];
639
+ const indirectSetStateCalls = /* @__PURE__ */ new Map();
482
640
  return {
641
+ Identifier(node) {
642
+ const isInUseLayoutEffectCall = tools.MutRef.get(useLayoutEffectCallRef) !== null;
643
+ const parentFn = tools.MutList.tail(functionStack)?.[0];
644
+ const useLayoutEffectCall = tools.MutRef.get(useLayoutEffectCallRef);
645
+ const isEffectFunction2 = useLayoutEffectCall && tools.O.isSome(ast.traverseUp(useLayoutEffectCall, (n2) => n2 === parentFn));
646
+ if (isFromUseStateCall(node) && isInUseLayoutEffectCall && isEffectFunction2) {
647
+ context.report({
648
+ node,
649
+ messageId: "NO_DIRECT_SET_STATE_IN_USE_LAYOUT_EFFECT"
650
+ });
651
+ }
652
+ },
483
653
  CallExpression(node) {
484
- const effectFunction = ast.traverseUp(node, isEffectFunction);
485
- const parentFunction = ast.traverseUp(node, ast.isFunction);
486
- if (tools.O.isNone(effectFunction) || tools.O.isNone(parentFunction)) return;
487
- if (parentFunction.value !== effectFunction.value) return;
488
- const callScope = context.sourceCode.getScope(node);
489
- const name2 = $(node.callee).with({ type: ast.NodeType.Identifier }, (n2) => tools.O.some(n2.name)).with({ type: ast.NodeType.MemberExpression }, (n2) => {
490
- if (!("name" in n2.object)) return tools.O.none();
491
- const initialScope = context.sourceCode.getScope(n2);
492
- const property = astUtils.getStaticValue(n2.property, initialScope);
493
- if (property?.value === 1) return tools.O.fromNullable(n2.object.name);
494
- return tools.O.none();
495
- }).with({ type: ast.NodeType.CallExpression }, (n2) => {
496
- if (!ast.is(ast.NodeType.MemberExpression)(n2.callee)) return tools.O.none();
497
- if (!("name" in n2.callee.object)) return tools.O.none();
498
- const isAt = $(n2.callee).with(
499
- {
500
- type: ast.NodeType.MemberExpression,
501
- property: {
502
- type: ast.NodeType.Identifier,
503
- name: "at"
504
- }
505
- },
506
- tools.F.constTrue
507
- ).otherwise(tools.F.constFalse);
508
- if (!isAt) return tools.O.none();
509
- const [index] = n2.arguments;
510
- if (!index) return tools.O.none();
511
- const initialScope = context.sourceCode.getScope(n2);
512
- const value = astUtils.getStaticValue(index, initialScope);
513
- if (value?.value === 1) return tools.O.fromNullable(n2.callee.object.name);
514
- return tools.O.none();
515
- }).otherwise(tools.O.none);
516
- tools.F.pipe(
517
- name2,
518
- tools.O.flatMap(_var.findVariable(callScope)),
519
- tools.O.flatMap(_var.getVariableInit(0)),
520
- tools.O.filter(ast.is(ast.NodeType.CallExpression)),
521
- tools.O.filter((name3) => isUseStateCallWithAlias(name3, context)),
522
- tools.O.map((name3) => ({
523
- data: {
524
- setState: name3
525
- },
526
- messageId: "NO_DIRECT_SET_STATE_IN_USE_LAYOUT_EFFECT",
527
- node
528
- })),
529
- tools.O.map(context.report)
530
- );
654
+ const effectFn = tools.MutRef.get(effectFunctionRef);
655
+ const [parentFn, parentFnKind] = tools.MutList.tail(functionStack) ?? [];
656
+ if (parentFn?.async) return;
657
+ const callKind = getCallKind(node);
658
+ $(callKind).with("setState", () => {
659
+ if (!parentFn) return;
660
+ if (parentFn !== effectFn && parentFnKind !== "immediate") {
661
+ indirectSetStateCalls.set(parentFn, [...indirectSetStateCalls.get(parentFn) ?? [], node]);
662
+ return;
663
+ }
664
+ context.report({
665
+ node,
666
+ messageId: "NO_DIRECT_SET_STATE_IN_USE_LAYOUT_EFFECT"
667
+ });
668
+ }).with("useLayoutEffect", () => {
669
+ tools.MutRef.set(useLayoutEffectCallRef, node);
670
+ }).with("other", () => {
671
+ indirectFunctionCalls.push(node);
672
+ }).otherwise(tools.F.constVoid);
673
+ },
674
+ ":function"(node) {
675
+ const functionKind = getFunctionKind(node);
676
+ tools.MutList.append(functionStack, [node, functionKind]);
677
+ $(functionKind).with("effect", () => {
678
+ tools.MutRef.set(effectFunctionRef, node);
679
+ }).with("cleanup", () => {
680
+ tools.MutRef.set(cleanUpFunctionRef, node);
681
+ }).otherwise(tools.F.constVoid);
682
+ },
683
+ ":function:exit"(node) {
684
+ const effectFn = tools.MutRef.get(effectFunctionRef);
685
+ if (effectFn === node) {
686
+ tools.MutRef.set(effectFunctionRef, null);
687
+ }
688
+ tools.MutList.pop(functionStack);
689
+ },
690
+ "CallExpression:exit"(node) {
691
+ if (tools.MutRef.get(useLayoutEffectCallRef) === node) {
692
+ tools.MutRef.set(useLayoutEffectCallRef, null);
693
+ }
694
+ },
695
+ "Program:exit"() {
696
+ for (const call of indirectFunctionCalls) {
697
+ if (!("name" in call.callee)) continue;
698
+ const { name: name2 } = call.callee;
699
+ const setStateCalls = tools.F.pipe(
700
+ _var.findVariable(name2, context.sourceCode.getScope(call)),
701
+ tools.O.flatMap(_var.getVariableInit(0)),
702
+ tools.O.filter(ast.isFunction),
703
+ tools.O.flatMapNullable((init) => indirectSetStateCalls.get(init)),
704
+ tools.O.getOrElse(() => [])
705
+ );
706
+ for (const setStateCall of setStateCalls) {
707
+ context.report({
708
+ data: {
709
+ name: name2
710
+ },
711
+ messageId: "NO_DIRECT_SET_STATE_IN_USE_LAYOUT_EFFECT",
712
+ node: setStateCall
713
+ });
714
+ }
715
+ }
531
716
  }
532
717
  };
533
718
  },
package/dist/index.mjs CHANGED
@@ -1,13 +1,13 @@
1
1
  import { useHookCollector, isReactHookCall, isUseCallbackCall, isReactHookCallWithNameLoose, isUseMemoCall, isUseStateCall, isUseEffectCall, isUseLayoutEffectCall } from '@eslint-react/core';
2
2
  import { createRuleForPlugin, getESLintReactSettings } from '@eslint-react/shared';
3
- import { isFunction, NodeType, is, traverseUp, getNestedCallExpressions } from '@eslint-react/ast';
4
- import { F, O } from '@eslint-react/tools';
3
+ import { isFunction, NodeType, is, traverseUp, getNestedCallExpressions, isIIFE } from '@eslint-react/ast';
4
+ import { F, O, MutRef, MutList } from '@eslint-react/tools';
5
5
  import { findVariable, getVariableInit } from '@eslint-react/var';
6
6
  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.5.28-beta.1";
10
+ var version = "1.5.28-beta.3";
11
11
  var createRule = createRuleForPlugin("hooks-extra");
12
12
 
13
13
  // src/rules/ensure-custom-hooks-using-other-hooks.ts
@@ -61,32 +61,45 @@ var o = (n2, s2, c2) => {
61
61
  if (!r(s2)) return false;
62
62
  if (Array.isArray(n2)) {
63
63
  if (!Array.isArray(s2)) return false;
64
- let t2 = [], r2 = [], a = [];
64
+ let t2 = [], r2 = [], a2 = [];
65
65
  for (const o2 of n2.keys()) {
66
66
  const s3 = n2[o2];
67
- i(s3) && s3[e] ? a.push(s3) : a.length ? r2.push(s3) : t2.push(s3);
67
+ i(s3) && s3[e] ? a2.push(s3) : a2.length ? r2.push(s3) : t2.push(s3);
68
68
  }
69
- if (a.length) {
70
- if (a.length > 1) throw new Error("Pattern error: Using `...P.array(...)` several times in a single pattern is not allowed.");
69
+ if (a2.length) {
70
+ if (a2.length > 1) throw new Error("Pattern error: Using `...P.array(...)` several times in a single pattern is not allowed.");
71
71
  if (s2.length < t2.length + r2.length) return false;
72
72
  const e2 = s2.slice(0, t2.length), n3 = 0 === r2.length ? [] : s2.slice(-r2.length), i2 = s2.slice(t2.length, 0 === r2.length ? Infinity : -r2.length);
73
- return t2.every((t3, n4) => o(t3, e2[n4], c2)) && r2.every((t3, e3) => o(t3, n3[e3], c2)) && (0 === a.length || o(a[0], i2, c2));
73
+ return t2.every((t3, n4) => o(t3, e2[n4], c2)) && r2.every((t3, e3) => o(t3, n3[e3], c2)) && (0 === a2.length || o(a2[0], i2, c2));
74
74
  }
75
75
  return n2.length === s2.length && n2.every((t3, e2) => o(t3, s2[e2], c2));
76
76
  }
77
77
  return Object.keys(n2).every((e2) => {
78
78
  const r2 = n2[e2];
79
- return (e2 in s2 || i(a = r2) && "optional" === a[t]().matcherType) && o(r2, s2[e2], c2);
80
- var a;
79
+ return (e2 in s2 || i(a2 = r2) && "optional" === a2[t]().matcherType) && o(r2, s2[e2], c2);
80
+ var a2;
81
81
  });
82
82
  }
83
83
  return Object.is(s2, n2);
84
84
  };
85
85
  var s = (e2) => {
86
- var n2, o2, a;
87
- return r(e2) ? i(e2) ? null != (n2 = null == (o2 = (a = e2[t]()).getSelectionKeys) ? void 0 : o2.call(a)) ? n2 : [] : Array.isArray(e2) ? c(e2, s) : c(Object.values(e2), s) : [];
86
+ var n2, o2, a2;
87
+ return r(e2) ? i(e2) ? null != (n2 = null == (o2 = (a2 = e2[t]()).getSelectionKeys) ? void 0 : o2.call(a2)) ? n2 : [] : Array.isArray(e2) ? c(e2, s) : c(Object.values(e2), s) : [];
88
88
  };
89
89
  var c = (t2, e2) => t2.reduce((t3, n2) => t3.concat(e2(n2)), []);
90
+ function a(...t2) {
91
+ if (1 === t2.length) {
92
+ const [e2] = t2;
93
+ return (t3) => o(e2, t3, () => {
94
+ });
95
+ }
96
+ if (2 === t2.length) {
97
+ const [e2, n2] = t2;
98
+ return o(e2, n2, () => {
99
+ });
100
+ }
101
+ throw new Error(`isMatching wasn't given the right number of arguments: expected 1 or 2, received ${t2.length}.`);
102
+ }
90
103
  function u(t2) {
91
104
  return Object.assign(t2, { optional: () => l(t2), and: (e2) => m(t2, e2), or: (e2) => d(t2, e2), select: (e2) => void 0 === e2 ? p(t2) : p(e2, t2) });
92
105
  }
@@ -185,9 +198,9 @@ var z = class _z {
185
198
  let i2;
186
199
  3 === t2.length && "function" == typeof t2[1] ? i2 = t2[1] : t2.length > 2 && r2.push(...t2.slice(1, t2.length - 1));
187
200
  let s2 = false, c2 = {};
188
- const a = (t3, e3) => {
201
+ const a2 = (t3, e3) => {
189
202
  s2 = true, c2[t3] = e3;
190
- }, u2 = !r2.some((t3) => o(t3, this.input, a)) || i2 && !Boolean(i2(this.input)) ? W : { matched: true, value: e2(s2 ? n in c2 ? c2[n] : c2 : this.input, this.input) };
203
+ }, u2 = !r2.some((t3) => o(t3, this.input, a2)) || i2 && !Boolean(i2(this.input)) ? W : { matched: true, value: e2(s2 ? n in c2 ? c2[n] : c2 : this.input, this.input) };
191
204
  return new _z(this.input, u2);
192
205
  }
193
206
  when(t2, e2) {
@@ -389,64 +402,150 @@ var no_direct_set_state_in_use_effect_default = createRule({
389
402
  create(context) {
390
403
  const settings = getESLintReactSettings(context.settings);
391
404
  const { useEffect: useEffectAlias = [], useState: useStateAlias = [] } = settings.additionalHooks ?? {};
392
- function isUseEffectCallWithAlias(node, context2) {
393
- return isUseEffectCall(node, context2) || useEffectAlias.some(F.flip(isReactHookCallWithNameLoose)(node));
405
+ function isUseEffectCallWithAlias(node) {
406
+ return isUseEffectCall(node, context) || useEffectAlias.some(F.flip(isReactHookCallWithNameLoose)(node));
394
407
  }
395
- function isUseStateCallWithAlias(node, context2) {
396
- return isUseStateCall(node, context2) || useStateAlias.some(F.flip(isReactHookCallWithNameLoose)(node));
408
+ function isUseStateCallWithAlias(node) {
409
+ return isUseStateCall(node, context) || useStateAlias.some(F.flip(isReactHookCallWithNameLoose)(node));
397
410
  }
398
411
  function isEffectFunction(node) {
399
- return node.parent?.type === NodeType.CallExpression && isUseEffectCallWithAlias(node.parent, context);
412
+ return node.parent?.type === NodeType.CallExpression && node.parent.callee !== node && isUseEffectCallWithAlias(node.parent);
413
+ }
414
+ function isCleanUpFunction(_2) {
415
+ }
416
+ function isFromUseStateCall(id) {
417
+ return F.pipe(
418
+ $(id).with({ type: NodeType.Identifier }, (n2) => O.some(n2.name)).with({
419
+ type: NodeType.MemberExpression,
420
+ object: { type: NodeType.Identifier }
421
+ }, (n2) => O.some(n2.object.name)).otherwise(O.none),
422
+ O.flatMap(findVariable(context.sourceCode.getScope(id))),
423
+ O.flatMap(getVariableInit(0)),
424
+ O.filter(is(NodeType.CallExpression)),
425
+ O.exists(isUseStateCallWithAlias)
426
+ );
427
+ }
428
+ function isSetStateCall(node) {
429
+ const id = $(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(id, 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");
400
466
  }
467
+ const useEffectCallRef = MutRef.make(null);
468
+ const functionStack = MutList.make();
469
+ const effectFunctionRef = MutRef.make(null);
470
+ const cleanUpFunctionRef = MutRef.make(null);
471
+ const indirectFunctionCalls = [];
472
+ const indirectSetStateCalls = /* @__PURE__ */ new Map();
401
473
  return {
474
+ Identifier(node) {
475
+ const isInUseEffectCall = MutRef.get(useEffectCallRef) !== null;
476
+ const parentFn = MutList.tail(functionStack)?.[0];
477
+ const useEffectCall = MutRef.get(useEffectCallRef);
478
+ const isEffectFunction2 = useEffectCall && O.isSome(traverseUp(useEffectCall, (n2) => n2 === parentFn));
479
+ if (isFromUseStateCall(node) && isInUseEffectCall && isEffectFunction2) {
480
+ context.report({
481
+ node,
482
+ messageId: "NO_DIRECT_SET_STATE_IN_USE_EFFECT"
483
+ });
484
+ }
485
+ },
402
486
  CallExpression(node) {
403
- const effectFunction = traverseUp(node, isEffectFunction);
404
- const parentFunction = traverseUp(node, isFunction);
405
- if (O.isNone(effectFunction) || O.isNone(parentFunction)) return;
406
- if (parentFunction.value !== effectFunction.value) return;
407
- const callScope = context.sourceCode.getScope(node);
408
- const name2 = $(node.callee).with({ type: NodeType.Identifier }, (n2) => O.some(n2.name)).with({ type: NodeType.MemberExpression }, (n2) => {
409
- if (!("name" in n2.object)) return O.none();
410
- const initialScope = context.sourceCode.getScope(n2);
411
- const property = getStaticValue(n2.property, initialScope);
412
- if (property?.value === 1) return O.fromNullable(n2.object.name);
413
- return O.none();
414
- }).with({ type: NodeType.CallExpression }, (n2) => {
415
- if (!is(NodeType.MemberExpression)(n2.callee)) return O.none();
416
- if (!("name" in n2.callee.object)) return O.none();
417
- const isAt = $(n2.callee).with(
418
- {
419
- type: NodeType.MemberExpression,
420
- property: {
421
- type: NodeType.Identifier,
422
- name: "at"
423
- }
424
- },
425
- F.constTrue
426
- ).otherwise(F.constFalse);
427
- if (!isAt) return O.none();
428
- const [index] = n2.arguments;
429
- if (!index) return O.none();
430
- const initialScope = context.sourceCode.getScope(n2);
431
- const value = getStaticValue(index, initialScope);
432
- if (value?.value === 1) return O.fromNullable(n2.callee.object.name);
433
- return O.none();
434
- }).otherwise(O.none);
435
- F.pipe(
436
- name2,
437
- O.flatMap(findVariable(callScope)),
438
- O.flatMap(getVariableInit(0)),
439
- O.filter(is(NodeType.CallExpression)),
440
- O.filter((name3) => isUseStateCallWithAlias(name3, context)),
441
- O.map((name3) => ({
442
- data: {
443
- setState: name3
444
- },
445
- messageId: "NO_DIRECT_SET_STATE_IN_USE_EFFECT",
446
- node
447
- })),
448
- O.map(context.report)
449
- );
487
+ const effectFn = MutRef.get(effectFunctionRef);
488
+ const [parentFn, parentFnKind] = MutList.tail(functionStack) ?? [];
489
+ if (parentFn?.async) return;
490
+ const callKind = getCallKind(node);
491
+ $(callKind).with("setState", () => {
492
+ if (!parentFn) return;
493
+ if (parentFn !== effectFn && parentFnKind !== "immediate") {
494
+ indirectSetStateCalls.set(parentFn, [...indirectSetStateCalls.get(parentFn) ?? [], node]);
495
+ return;
496
+ }
497
+ context.report({
498
+ node,
499
+ messageId: "NO_DIRECT_SET_STATE_IN_USE_EFFECT"
500
+ });
501
+ }).with("useEffect", () => {
502
+ MutRef.set(useEffectCallRef, node);
503
+ }).with("other", () => {
504
+ indirectFunctionCalls.push(node);
505
+ }).otherwise(F.constVoid);
506
+ },
507
+ ":function"(node) {
508
+ const functionKind = getFunctionKind(node);
509
+ MutList.append(functionStack, [node, functionKind]);
510
+ $(functionKind).with("effect", () => {
511
+ MutRef.set(effectFunctionRef, node);
512
+ }).with("cleanup", () => {
513
+ MutRef.set(cleanUpFunctionRef, node);
514
+ }).otherwise(F.constVoid);
515
+ },
516
+ ":function:exit"(node) {
517
+ const effectFn = MutRef.get(effectFunctionRef);
518
+ if (effectFn === node) {
519
+ MutRef.set(effectFunctionRef, null);
520
+ }
521
+ MutList.pop(functionStack);
522
+ },
523
+ "CallExpression:exit"(node) {
524
+ if (MutRef.get(useEffectCallRef) === node) {
525
+ MutRef.set(useEffectCallRef, null);
526
+ }
527
+ },
528
+ "Program:exit"() {
529
+ for (const call of indirectFunctionCalls) {
530
+ if (!("name" in call.callee)) continue;
531
+ const { name: name2 } = call.callee;
532
+ const setStateCalls = F.pipe(
533
+ findVariable(name2, context.sourceCode.getScope(call)),
534
+ O.flatMap(getVariableInit(0)),
535
+ O.filter(isFunction),
536
+ O.flatMapNullable((init) => indirectSetStateCalls.get(init)),
537
+ O.getOrElse(() => [])
538
+ );
539
+ for (const setStateCall of setStateCalls) {
540
+ context.report({
541
+ data: {
542
+ name: name2
543
+ },
544
+ messageId: "NO_DIRECT_SET_STATE_IN_USE_EFFECT",
545
+ node: setStateCall
546
+ });
547
+ }
548
+ }
450
549
  }
451
550
  };
452
551
  },
@@ -468,64 +567,150 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
468
567
  create(context) {
469
568
  const settings = getESLintReactSettings(context.settings);
470
569
  const { useLayoutEffect: useLayoutEffectAlias = [], useState: useStateAlias = [] } = settings.additionalHooks ?? {};
471
- function isUseLayoutEffectCallWithAlias(node, context2) {
472
- return isUseLayoutEffectCall(node, context2) || useLayoutEffectAlias.some(F.flip(isReactHookCallWithNameLoose)(node));
570
+ function isUseLayoutEffectCallWithAlias(node) {
571
+ return isUseLayoutEffectCall(node, context) || useLayoutEffectAlias.some(F.flip(isReactHookCallWithNameLoose)(node));
473
572
  }
474
- function isUseStateCallWithAlias(node, context2) {
475
- return isUseStateCall(node, context2) || useStateAlias.some(F.flip(isReactHookCallWithNameLoose)(node));
573
+ function isUseStateCallWithAlias(node) {
574
+ return isUseStateCall(node, context) || useStateAlias.some(F.flip(isReactHookCallWithNameLoose)(node));
476
575
  }
477
576
  function isEffectFunction(node) {
478
- return node.parent?.type === NodeType.CallExpression && isUseLayoutEffectCallWithAlias(node.parent, context);
577
+ return node.parent?.type === NodeType.CallExpression && node.parent.callee !== node && isUseLayoutEffectCallWithAlias(node.parent);
578
+ }
579
+ function isCleanUpFunction(_2) {
580
+ }
581
+ function isFromUseStateCall(id) {
582
+ return F.pipe(
583
+ $(id).with({ type: NodeType.Identifier }, (n2) => O.some(n2.name)).with({
584
+ type: NodeType.MemberExpression,
585
+ object: { type: NodeType.Identifier }
586
+ }, (n2) => O.some(n2.object.name)).otherwise(O.none),
587
+ O.flatMap(findVariable(context.sourceCode.getScope(id))),
588
+ O.flatMap(getVariableInit(0)),
589
+ O.filter(is(NodeType.CallExpression)),
590
+ O.exists(isUseStateCallWithAlias)
591
+ );
592
+ }
593
+ function isSetStateCall(node) {
594
+ const id = $(node.callee).with({ type: NodeType.Identifier }, O.some).with({ type: NodeType.MemberExpression }, (n2) => {
595
+ if (!("name" in n2.object)) return O.none();
596
+ const initialScope = context.sourceCode.getScope(n2);
597
+ const property = getStaticValue(n2.property, initialScope);
598
+ if (property?.value === 1) return O.fromNullable(n2.object);
599
+ return O.none();
600
+ }).with({ type: NodeType.CallExpression }, (n2) => {
601
+ if (!is(NodeType.MemberExpression)(n2.callee)) return O.none();
602
+ if (!("name" in n2.callee.object)) return O.none();
603
+ const isAt = a({
604
+ type: NodeType.MemberExpression,
605
+ property: {
606
+ type: NodeType.Identifier,
607
+ name: "at"
608
+ }
609
+ }, n2.callee);
610
+ const [index] = n2.arguments;
611
+ if (!isAt || !index) return O.none();
612
+ const initialScope = context.sourceCode.getScope(n2);
613
+ const value = getStaticValue(index, initialScope);
614
+ if (value?.value === 1) return O.fromNullable(n2.callee.object);
615
+ return O.none();
616
+ }).otherwise(O.none);
617
+ return O.exists(id, isFromUseStateCall);
618
+ }
619
+ function isThenCall(node) {
620
+ if (node.callee.type !== NodeType.MemberExpression) return false;
621
+ return a({
622
+ type: NodeType.Identifier,
623
+ name: "then"
624
+ }, node.callee.property);
479
625
  }
626
+ function getCallKind(node) {
627
+ return $(node).when(isThenCall, () => "then").when(isSetStateCall, () => "setState").when(isUseStateCallWithAlias, () => "useState").when(isUseLayoutEffectCallWithAlias, () => "useLayoutEffect").otherwise(() => "other");
628
+ }
629
+ function getFunctionKind(node) {
630
+ return $(node).when(isEffectFunction, () => "effect").when(isCleanUpFunction, () => "cleanup").when(isIIFE, () => "immediate").otherwise(() => "other");
631
+ }
632
+ const useLayoutEffectCallRef = MutRef.make(null);
633
+ const functionStack = MutList.make();
634
+ const effectFunctionRef = MutRef.make(null);
635
+ const cleanUpFunctionRef = MutRef.make(null);
636
+ const indirectFunctionCalls = [];
637
+ const indirectSetStateCalls = /* @__PURE__ */ new Map();
480
638
  return {
639
+ Identifier(node) {
640
+ const isInUseLayoutEffectCall = MutRef.get(useLayoutEffectCallRef) !== null;
641
+ const parentFn = MutList.tail(functionStack)?.[0];
642
+ const useLayoutEffectCall = MutRef.get(useLayoutEffectCallRef);
643
+ const isEffectFunction2 = useLayoutEffectCall && O.isSome(traverseUp(useLayoutEffectCall, (n2) => n2 === parentFn));
644
+ if (isFromUseStateCall(node) && isInUseLayoutEffectCall && isEffectFunction2) {
645
+ context.report({
646
+ node,
647
+ messageId: "NO_DIRECT_SET_STATE_IN_USE_LAYOUT_EFFECT"
648
+ });
649
+ }
650
+ },
481
651
  CallExpression(node) {
482
- const effectFunction = traverseUp(node, isEffectFunction);
483
- const parentFunction = traverseUp(node, isFunction);
484
- if (O.isNone(effectFunction) || O.isNone(parentFunction)) return;
485
- if (parentFunction.value !== effectFunction.value) return;
486
- const callScope = context.sourceCode.getScope(node);
487
- const name2 = $(node.callee).with({ type: NodeType.Identifier }, (n2) => O.some(n2.name)).with({ type: NodeType.MemberExpression }, (n2) => {
488
- if (!("name" in n2.object)) return O.none();
489
- const initialScope = context.sourceCode.getScope(n2);
490
- const property = getStaticValue(n2.property, initialScope);
491
- if (property?.value === 1) return O.fromNullable(n2.object.name);
492
- return O.none();
493
- }).with({ type: NodeType.CallExpression }, (n2) => {
494
- if (!is(NodeType.MemberExpression)(n2.callee)) return O.none();
495
- if (!("name" in n2.callee.object)) return O.none();
496
- const isAt = $(n2.callee).with(
497
- {
498
- type: NodeType.MemberExpression,
499
- property: {
500
- type: NodeType.Identifier,
501
- name: "at"
502
- }
503
- },
504
- F.constTrue
505
- ).otherwise(F.constFalse);
506
- if (!isAt) return O.none();
507
- const [index] = n2.arguments;
508
- if (!index) return O.none();
509
- const initialScope = context.sourceCode.getScope(n2);
510
- const value = getStaticValue(index, initialScope);
511
- if (value?.value === 1) return O.fromNullable(n2.callee.object.name);
512
- return O.none();
513
- }).otherwise(O.none);
514
- F.pipe(
515
- name2,
516
- O.flatMap(findVariable(callScope)),
517
- O.flatMap(getVariableInit(0)),
518
- O.filter(is(NodeType.CallExpression)),
519
- O.filter((name3) => isUseStateCallWithAlias(name3, context)),
520
- O.map((name3) => ({
521
- data: {
522
- setState: name3
523
- },
524
- messageId: "NO_DIRECT_SET_STATE_IN_USE_LAYOUT_EFFECT",
525
- node
526
- })),
527
- O.map(context.report)
528
- );
652
+ const effectFn = MutRef.get(effectFunctionRef);
653
+ const [parentFn, parentFnKind] = MutList.tail(functionStack) ?? [];
654
+ if (parentFn?.async) return;
655
+ const callKind = getCallKind(node);
656
+ $(callKind).with("setState", () => {
657
+ if (!parentFn) return;
658
+ if (parentFn !== effectFn && parentFnKind !== "immediate") {
659
+ indirectSetStateCalls.set(parentFn, [...indirectSetStateCalls.get(parentFn) ?? [], node]);
660
+ return;
661
+ }
662
+ context.report({
663
+ node,
664
+ messageId: "NO_DIRECT_SET_STATE_IN_USE_LAYOUT_EFFECT"
665
+ });
666
+ }).with("useLayoutEffect", () => {
667
+ MutRef.set(useLayoutEffectCallRef, node);
668
+ }).with("other", () => {
669
+ indirectFunctionCalls.push(node);
670
+ }).otherwise(F.constVoid);
671
+ },
672
+ ":function"(node) {
673
+ const functionKind = getFunctionKind(node);
674
+ MutList.append(functionStack, [node, functionKind]);
675
+ $(functionKind).with("effect", () => {
676
+ MutRef.set(effectFunctionRef, node);
677
+ }).with("cleanup", () => {
678
+ MutRef.set(cleanUpFunctionRef, node);
679
+ }).otherwise(F.constVoid);
680
+ },
681
+ ":function:exit"(node) {
682
+ const effectFn = MutRef.get(effectFunctionRef);
683
+ if (effectFn === node) {
684
+ MutRef.set(effectFunctionRef, null);
685
+ }
686
+ MutList.pop(functionStack);
687
+ },
688
+ "CallExpression:exit"(node) {
689
+ if (MutRef.get(useLayoutEffectCallRef) === node) {
690
+ MutRef.set(useLayoutEffectCallRef, null);
691
+ }
692
+ },
693
+ "Program:exit"() {
694
+ for (const call of indirectFunctionCalls) {
695
+ if (!("name" in call.callee)) continue;
696
+ const { name: name2 } = call.callee;
697
+ const setStateCalls = F.pipe(
698
+ findVariable(name2, context.sourceCode.getScope(call)),
699
+ O.flatMap(getVariableInit(0)),
700
+ O.filter(isFunction),
701
+ O.flatMapNullable((init) => indirectSetStateCalls.get(init)),
702
+ O.getOrElse(() => [])
703
+ );
704
+ for (const setStateCall of setStateCalls) {
705
+ context.report({
706
+ data: {
707
+ name: name2
708
+ },
709
+ messageId: "NO_DIRECT_SET_STATE_IN_USE_LAYOUT_EFFECT",
710
+ node: setStateCall
711
+ });
712
+ }
713
+ }
529
714
  }
530
715
  };
531
716
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-hooks-extra",
3
- "version": "1.5.28-beta.1",
3
+ "version": "1.5.28-beta.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,17 +39,17 @@
39
39
  "@typescript-eslint/type-utils": "^7.16.1",
40
40
  "@typescript-eslint/types": "^7.16.1",
41
41
  "@typescript-eslint/utils": "^7.16.1",
42
- "@eslint-react/ast": "1.5.28-beta.1",
43
- "@eslint-react/core": "1.5.28-beta.1",
44
- "@eslint-react/jsx": "1.5.28-beta.1",
45
- "@eslint-react/shared": "1.5.28-beta.1",
46
- "@eslint-react/tools": "1.5.28-beta.1",
47
- "@eslint-react/var": "1.5.28-beta.1",
48
- "@eslint-react/types": "1.5.28-beta.1"
42
+ "@eslint-react/ast": "1.5.28-beta.3",
43
+ "@eslint-react/core": "1.5.28-beta.3",
44
+ "@eslint-react/jsx": "1.5.28-beta.3",
45
+ "@eslint-react/shared": "1.5.28-beta.3",
46
+ "@eslint-react/tools": "1.5.28-beta.3",
47
+ "@eslint-react/types": "1.5.28-beta.3",
48
+ "@eslint-react/var": "1.5.28-beta.3"
49
49
  },
50
50
  "devDependencies": {
51
51
  "string-ts": "2.2.0",
52
- "tsup": "8.1.1"
52
+ "tsup": "8.2.0"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "eslint": "^8.57.0 || ^9.0.0",