eslint-plugin-react-hooks-extra 1.12.3 → 1.12.4-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.
package/README.md CHANGED
@@ -33,7 +33,6 @@ export default [
33
33
  // react-hooks-extra recommended rules
34
34
  "hooks-extra/ensure-custom-hooks-using-other-hooks": "warn",
35
35
  "hooks-extra/no-direct-set-state-in-use-effect": "warn",
36
- "hooks-extra/no-direct-set-state-in-use-layout-effect": "warn",
37
36
  "hooks-extra/prefer-use-state-lazy-initialization": "warn",
38
37
  },
39
38
  },
@@ -42,11 +41,10 @@ export default [
42
41
 
43
42
  ## Rules
44
43
 
45
- | Rule | Description | 💼 | 💭 | |
46
- | :----------------------------------------- | :------------------------------------------------------------------------------ | :-: | :-: | :-: |
47
- | `ensure-custom-hooks-using-other-hooks` | Warns when custom Hooks that don't use other Hooks. | ✔️ | | |
48
- | `ensure-use-callback-has-non-empty-deps` | Warns when `useCallback` is called with empty dependencies array. | 🧐 | | |
49
- | `ensure-use-memo-has-non-empty-deps` | Warns when `useMemo` is called with empty dependencies array. | 🧐 | | |
50
- | `no-direct-set-state-in-use-effect` | Disallow direct calls to the `set` function of `useState` in `useEffect`. | ✔️ | | |
51
- | `no-direct-set-state-in-use-layout-effect` | Disallow direct calls to the `set` function of `useState` in `useLayoutEffect`. | ✔️ | | |
52
- | `prefer-use-state-lazy-initialization` | Warns function calls made inside `useState` calls. | 🚀 | | |
44
+ | Rule | Description | 💼 | 💭 | |
45
+ | :--------------------------------------- | :------------------------------------------------------------------------ | :-: | :-: | :-: |
46
+ | `ensure-custom-hooks-using-other-hooks` | Warns when custom Hooks that don't use other Hooks. | ✔️ | | |
47
+ | `ensure-use-callback-has-non-empty-deps` | Warns when `useCallback` is called with empty dependencies array. | 🧐 | | |
48
+ | `ensure-use-memo-has-non-empty-deps` | Warns when `useMemo` is called with empty dependencies array. | 🧐 | | |
49
+ | `no-direct-set-state-in-use-effect` | Disallow direct calls to the `set` function of `useState` in `useEffect`. | ✔️ | | |
50
+ | `prefer-use-state-lazy-initialization` | Warns function calls made inside `useState` calls. | 🚀 | | |
package/dist/index.d.mts CHANGED
@@ -9,7 +9,8 @@ declare const rules: {
9
9
  readonly "ensure-use-callback-has-non-empty-deps": _typescript_eslint_utils_ts_eslint.RuleModule<"ensureUseCallbackHasNonEmptyDeps", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
10
10
  readonly "ensure-use-memo-has-non-empty-deps": _typescript_eslint_utils_ts_eslint.RuleModule<"ensureUseMemoHasNonEmptyDeps", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
11
11
  readonly "no-direct-set-state-in-use-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"noDirectSetStateInUseEffect", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
12
- readonly "no-direct-set-state-in-use-layout-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"noDirectSetStateInUseLayoutEffect", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
12
+ /** @deprecated Use `no-direct-set-state-in-use-effect` instead */
13
+ readonly "no-direct-set-state-in-use-layout-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"noDirectSetStateInUseEffect", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
13
14
  readonly "prefer-use-state-lazy-initialization": _typescript_eslint_utils_ts_eslint.RuleModule<"preferUseStateLazyInitialization", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
14
15
  };
15
16
 
package/dist/index.d.ts CHANGED
@@ -9,7 +9,8 @@ declare const rules: {
9
9
  readonly "ensure-use-callback-has-non-empty-deps": _typescript_eslint_utils_ts_eslint.RuleModule<"ensureUseCallbackHasNonEmptyDeps", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
10
10
  readonly "ensure-use-memo-has-non-empty-deps": _typescript_eslint_utils_ts_eslint.RuleModule<"ensureUseMemoHasNonEmptyDeps", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
11
11
  readonly "no-direct-set-state-in-use-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"noDirectSetStateInUseEffect", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
12
- readonly "no-direct-set-state-in-use-layout-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"noDirectSetStateInUseLayoutEffect", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
12
+ /** @deprecated Use `no-direct-set-state-in-use-effect` instead */
13
+ readonly "no-direct-set-state-in-use-layout-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"noDirectSetStateInUseEffect", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
13
14
  readonly "prefer-use-state-lazy-initialization": _typescript_eslint_utils_ts_eslint.RuleModule<"preferUseStateLazyInitialization", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
14
15
  };
15
16
 
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ var tsPattern = require('ts-pattern');
11
11
 
12
12
  // package.json
13
13
  var name = "eslint-plugin-react-hooks-extra";
14
- var version = "1.12.3";
14
+ var version = "1.12.4-beta.3";
15
15
  var createRule = shared.createRuleForPlugin("hooks-extra");
16
16
  function isFromHookCall(name2, context, settings, predicate = tools.F.constTrue) {
17
17
  const hookAlias = settings.additionalHooks?.[name2] ?? [];
@@ -303,7 +303,17 @@ var no_direct_set_state_in_use_effect_default = createRule({
303
303
  if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
304
304
  const settings = shared.decodeSettings(context.settings);
305
305
  const additionalHooks = settings.additionalHooks ?? {};
306
- const isUseEffectCall = core.isReactHookCallWithNameAlias("useEffect", context, additionalHooks.useEffect ?? []);
306
+ const isUseEffectLikeCall = core.isReactHookCallWithNameAlias(
307
+ "useEffect",
308
+ context,
309
+ [
310
+ "useLayoutEffect",
311
+ "useInsertionEffect",
312
+ ...additionalHooks.useEffect ?? [],
313
+ ...additionalHooks.useLayoutEffect ?? [],
314
+ ...additionalHooks.useInsertionEffect ?? []
315
+ ]
316
+ );
307
317
  const isUseStateCall2 = core.isReactHookCallWithNameAlias("useState", context, additionalHooks.useState ?? []);
308
318
  const isUseMemoCall2 = core.isReactHookCallWithNameAlias("useMemo", context, additionalHooks.useMemo ?? []);
309
319
  const isUseCallbackCall2 = core.isReactHookCallWithNameAlias("useCallback", context, additionalHooks.useCallback ?? []);
@@ -324,10 +334,10 @@ var no_direct_set_state_in_use_effect_default = createRule({
324
334
  tools.MutRef.update(setupFunctionRef, (current) => current === node ? null : current);
325
335
  };
326
336
  function isSetupFunction(node) {
327
- return node.parent?.type === types.AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseEffectCall(node.parent);
337
+ return node.parent?.type === types.AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseEffectLikeCall(node.parent);
328
338
  }
329
339
  function getCallKind(node) {
330
- return tsPattern.match(node).when(isUseStateCall2, () => "useState").when(isUseEffectCall, () => "useEffect").when(isSetStateCall, () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
340
+ return tsPattern.match(node).when(isUseStateCall2, () => "useState").when(isUseEffectLikeCall, () => "useEffect").when(isSetStateCall, () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
331
341
  }
332
342
  function getFunctionKind(node) {
333
343
  return tsPattern.match(node).when(isSetupFunction, () => "setup").when(ast.isFunctionOfImmediatelyInvoked, () => "immediate").otherwise(() => "other");
@@ -391,7 +401,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
391
401
  const calls = indirectSetStateCallsAsArgs.get(vd.init) ?? [];
392
402
  indirectSetStateCallsAsArgs.set(vd.init, [...calls, node]);
393
403
  }
394
- if (isUseEffectCall(node.parent)) {
404
+ if (isUseEffectLikeCall(node.parent)) {
395
405
  const calls = indirectSetStateCallsAsArgs.get(node.parent) ?? [];
396
406
  indirectSetStateCallsAsSetups.set(node.parent, [...calls, node]);
397
407
  }
@@ -462,189 +472,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
462
472
  },
463
473
  defaultOptions: []
464
474
  });
465
- var RULE_NAME5 = "no-direct-set-state-in-use-layout-effect";
466
- var no_direct_set_state_in_use_layout_effect_default = createRule({
467
- meta: {
468
- type: "problem",
469
- docs: {
470
- description: "disallow direct calls to the 'set' function of 'useState' in 'useLayoutEffect'"
471
- },
472
- messages: {
473
- noDirectSetStateInUseLayoutEffect: "Do not call the 'set' function '{{name}}' of 'useState' directly in 'useLayoutEffect'."
474
- },
475
- schema: []
476
- },
477
- name: RULE_NAME5,
478
- create(context) {
479
- if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
480
- const settings = shared.decodeSettings(context.settings);
481
- const additionalHooks = settings.additionalHooks ?? {};
482
- const isUseEffectCall = core.isReactHookCallWithNameAlias(
483
- "useLayoutEffect",
484
- context,
485
- additionalHooks.useLayoutEffect ?? []
486
- );
487
- const isUseStateCall2 = core.isReactHookCallWithNameAlias("useState", context, additionalHooks.useState ?? []);
488
- const isUseMemoCall2 = core.isReactHookCallWithNameAlias("useMemo", context, additionalHooks.useMemo ?? []);
489
- const isUseCallbackCall2 = core.isReactHookCallWithNameAlias("useCallback", context, additionalHooks.useCallback ?? []);
490
- const isSetStateCall = isSetFunctionCall(context, settings);
491
- const isIdFromUseStateCall = isFromUseStateCall(context, settings);
492
- const functionStack = [];
493
- const setupFunctionRef = tools.MutRef.make(null);
494
- const setupFunctionIdentifiers = [];
495
- const indirectFunctionCalls = [];
496
- const indirectSetStateCalls = /* @__PURE__ */ new WeakMap();
497
- const indirectSetStateCallsAsSetups = /* @__PURE__ */ new Map();
498
- const indirectSetStateCallsAsArgs = /* @__PURE__ */ new WeakMap();
499
- const indirectSetStateCallsInHooks = /* @__PURE__ */ new WeakMap();
500
- const onSetupFunctionEnter = (node) => {
501
- tools.MutRef.set(setupFunctionRef, node);
502
- };
503
- const onSetupFunctionExit = (node) => {
504
- tools.MutRef.update(setupFunctionRef, (current) => current === node ? null : current);
505
- };
506
- function isSetupFunction(node) {
507
- return node.parent?.type === types.AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseEffectCall(node.parent);
508
- }
509
- function getCallKind(node) {
510
- return tsPattern.match(node).when(isUseStateCall2, () => "useState").when(isUseEffectCall, () => "useLayoutEffect").when(isSetStateCall, () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
511
- }
512
- function getFunctionKind(node) {
513
- return tsPattern.match(node).when(isSetupFunction, () => "setup").when(ast.isFunctionOfImmediatelyInvoked, () => "immediate").otherwise(() => "other");
514
- }
515
- return {
516
- ":function"(node) {
517
- const functionKind = getFunctionKind(node);
518
- functionStack.push([node, functionKind]);
519
- if (functionKind === "setup") onSetupFunctionEnter(node);
520
- },
521
- ":function:exit"(node) {
522
- const [_, functionKind] = functionStack.at(-1) ?? [];
523
- functionStack.pop();
524
- if (functionKind === "setup") onSetupFunctionExit(node);
525
- },
526
- CallExpression(node) {
527
- const effectFn = tools.MutRef.get(setupFunctionRef);
528
- const [parentFn, parentFnKind] = functionStack.at(-1) ?? [];
529
- if (parentFn?.async) return;
530
- tsPattern.match(getCallKind(node)).with("setState", () => {
531
- if (!parentFn) return;
532
- if (parentFn !== effectFn && parentFnKind !== "immediate") {
533
- const maybeVd = ast.traverseUpGuard(node, isVariableDeclaratorFromHookCall);
534
- if (tools.O.isSome(maybeVd)) {
535
- const vd = maybeVd.value;
536
- const calls2 = indirectSetStateCallsInHooks.get(vd.init) ?? [];
537
- indirectSetStateCallsInHooks.set(vd.init, [...calls2, node]);
538
- return;
539
- }
540
- const calls = indirectSetStateCalls.get(parentFn) ?? [];
541
- indirectSetStateCalls.set(parentFn, [...calls, node]);
542
- return;
543
- }
544
- context.report({
545
- messageId: "noDirectSetStateInUseLayoutEffect",
546
- node,
547
- data: {
548
- name: context.sourceCode.getText(node.callee)
549
- }
550
- });
551
- }).with("useLayoutEffect", () => {
552
- const [firstArg] = node.arguments;
553
- if (ast.isFunction(firstArg)) return;
554
- const identifiers = ast.getNestedIdentifiers(node);
555
- setupFunctionIdentifiers.push(...identifiers);
556
- }).with("other", () => {
557
- const isInSetupFunction = effectFn === parentFn;
558
- if (!isInSetupFunction) return;
559
- indirectFunctionCalls.push(node);
560
- }).otherwise(tools.F.constVoid);
561
- },
562
- Identifier(node) {
563
- if (node.parent.type === types.AST_NODE_TYPES.CallExpression && node.parent.callee === node) return;
564
- if (!isIdFromUseStateCall(node)) return;
565
- switch (node.parent.type) {
566
- case types.AST_NODE_TYPES.CallExpression: {
567
- const [firstArg] = node.parent.arguments;
568
- if (node !== firstArg) break;
569
- if (isUseCallbackCall2(node.parent)) {
570
- const maybeVd = ast.traverseUpGuard(node.parent, isVariableDeclaratorFromHookCall);
571
- if (tools.O.isNone(maybeVd)) break;
572
- const vd = maybeVd.value;
573
- const calls = indirectSetStateCallsAsArgs.get(vd.init) ?? [];
574
- indirectSetStateCallsAsArgs.set(vd.init, [...calls, node]);
575
- }
576
- if (isUseEffectCall(node.parent)) {
577
- const calls = indirectSetStateCallsAsArgs.get(node.parent) ?? [];
578
- indirectSetStateCallsAsSetups.set(node.parent, [...calls, node]);
579
- }
580
- break;
581
- }
582
- case types.AST_NODE_TYPES.ArrowFunctionExpression: {
583
- const parent = node.parent.parent;
584
- if (parent.type !== types.AST_NODE_TYPES.CallExpression) break;
585
- if (!isUseMemoCall2(parent)) break;
586
- const maybeVd = ast.traverseUpGuard(parent, isVariableDeclaratorFromHookCall);
587
- if (tools.O.isNone(maybeVd)) break;
588
- const vd = maybeVd.value;
589
- const calls = indirectSetStateCallsAsArgs.get(vd.init) ?? [];
590
- indirectSetStateCallsAsArgs.set(vd.init, [...calls, node]);
591
- }
592
- }
593
- },
594
- "Program:exit"() {
595
- const getSetStateCalls = (id, initialScope) => {
596
- const node = tools.O.flatMap(_var.findVariable(id, initialScope), _var.getVariableNode(0)).pipe(tools.O.getOrNull);
597
- switch (node?.type) {
598
- case types.AST_NODE_TYPES.FunctionDeclaration:
599
- case types.AST_NODE_TYPES.FunctionExpression:
600
- case types.AST_NODE_TYPES.ArrowFunctionExpression:
601
- return indirectSetStateCalls.get(node) ?? [];
602
- case types.AST_NODE_TYPES.CallExpression:
603
- return indirectSetStateCallsInHooks.get(node) ?? indirectSetStateCallsAsArgs.get(node) ?? [];
604
- }
605
- return [];
606
- };
607
- for (const [_, calls] of indirectSetStateCallsAsSetups) {
608
- for (const call of calls) {
609
- context.report({
610
- messageId: "noDirectSetStateInUseLayoutEffect",
611
- node: call,
612
- data: { name: call.name }
613
- });
614
- }
615
- }
616
- for (const { callee } of indirectFunctionCalls) {
617
- if (!("name" in callee)) continue;
618
- const { name: name2 } = callee;
619
- const setStateCalls = getSetStateCalls(name2, context.sourceCode.getScope(callee));
620
- for (const setStateCall of setStateCalls) {
621
- context.report({
622
- messageId: "noDirectSetStateInUseLayoutEffect",
623
- node: setStateCall,
624
- data: {
625
- name: ast.toReadableNodeName(setStateCall, (n) => context.sourceCode.getText(n))
626
- }
627
- });
628
- }
629
- }
630
- for (const id of setupFunctionIdentifiers) {
631
- const setStateCalls = getSetStateCalls(id.name, context.sourceCode.getScope(id));
632
- for (const setStateCall of setStateCalls) {
633
- context.report({
634
- messageId: "noDirectSetStateInUseLayoutEffect",
635
- node: setStateCall,
636
- data: {
637
- name: ast.toReadableNodeName(setStateCall, (n) => context.sourceCode.getText(n))
638
- }
639
- });
640
- }
641
- }
642
- }
643
- };
644
- },
645
- defaultOptions: []
646
- });
647
- var RULE_NAME6 = "prefer-use-state-lazy-initialization";
475
+ var RULE_NAME5 = "prefer-use-state-lazy-initialization";
648
476
  var ALLOW_LIST = ["Boolean", "String", "Number"];
649
477
  var prefer_use_state_lazy_initialization_default = createRule({
650
478
  meta: {
@@ -657,7 +485,7 @@ var prefer_use_state_lazy_initialization_default = createRule({
657
485
  },
658
486
  schema: []
659
487
  },
660
- name: RULE_NAME6,
488
+ name: RULE_NAME5,
661
489
  create(context) {
662
490
  if (!context.sourceCode.text.includes("use")) return {};
663
491
  const alias = shared.decodeSettings(context.settings).additionalHooks?.useState ?? [];
@@ -692,7 +520,8 @@ var rules = {
692
520
  "ensure-use-callback-has-non-empty-deps": ensure_use_callback_has_non_empty_deps_default,
693
521
  "ensure-use-memo-has-non-empty-deps": ensure_use_memo_has_non_empty_deps_default,
694
522
  "no-direct-set-state-in-use-effect": no_direct_set_state_in_use_effect_default,
695
- "no-direct-set-state-in-use-layout-effect": no_direct_set_state_in_use_layout_effect_default,
523
+ /** @deprecated Use `no-direct-set-state-in-use-effect` instead */
524
+ "no-direct-set-state-in-use-layout-effect": no_direct_set_state_in_use_effect_default,
696
525
  "prefer-use-state-lazy-initialization": prefer_use_state_lazy_initialization_default
697
526
  };
698
527
 
package/dist/index.mjs CHANGED
@@ -9,7 +9,7 @@ import { match, isMatching } from 'ts-pattern';
9
9
 
10
10
  // package.json
11
11
  var name = "eslint-plugin-react-hooks-extra";
12
- var version = "1.12.3";
12
+ var version = "1.12.4-beta.3";
13
13
  var createRule = createRuleForPlugin("hooks-extra");
14
14
  function isFromHookCall(name2, context, settings, predicate = F.constTrue) {
15
15
  const hookAlias = settings.additionalHooks?.[name2] ?? [];
@@ -301,7 +301,17 @@ var no_direct_set_state_in_use_effect_default = createRule({
301
301
  if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
302
302
  const settings = decodeSettings(context.settings);
303
303
  const additionalHooks = settings.additionalHooks ?? {};
304
- const isUseEffectCall = isReactHookCallWithNameAlias("useEffect", context, additionalHooks.useEffect ?? []);
304
+ const isUseEffectLikeCall = isReactHookCallWithNameAlias(
305
+ "useEffect",
306
+ context,
307
+ [
308
+ "useLayoutEffect",
309
+ "useInsertionEffect",
310
+ ...additionalHooks.useEffect ?? [],
311
+ ...additionalHooks.useLayoutEffect ?? [],
312
+ ...additionalHooks.useInsertionEffect ?? []
313
+ ]
314
+ );
305
315
  const isUseStateCall2 = isReactHookCallWithNameAlias("useState", context, additionalHooks.useState ?? []);
306
316
  const isUseMemoCall2 = isReactHookCallWithNameAlias("useMemo", context, additionalHooks.useMemo ?? []);
307
317
  const isUseCallbackCall2 = isReactHookCallWithNameAlias("useCallback", context, additionalHooks.useCallback ?? []);
@@ -322,10 +332,10 @@ var no_direct_set_state_in_use_effect_default = createRule({
322
332
  MutRef.update(setupFunctionRef, (current) => current === node ? null : current);
323
333
  };
324
334
  function isSetupFunction(node) {
325
- return node.parent?.type === AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseEffectCall(node.parent);
335
+ return node.parent?.type === AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseEffectLikeCall(node.parent);
326
336
  }
327
337
  function getCallKind(node) {
328
- return match(node).when(isUseStateCall2, () => "useState").when(isUseEffectCall, () => "useEffect").when(isSetStateCall, () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
338
+ return match(node).when(isUseStateCall2, () => "useState").when(isUseEffectLikeCall, () => "useEffect").when(isSetStateCall, () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
329
339
  }
330
340
  function getFunctionKind(node) {
331
341
  return match(node).when(isSetupFunction, () => "setup").when(isFunctionOfImmediatelyInvoked, () => "immediate").otherwise(() => "other");
@@ -389,7 +399,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
389
399
  const calls = indirectSetStateCallsAsArgs.get(vd.init) ?? [];
390
400
  indirectSetStateCallsAsArgs.set(vd.init, [...calls, node]);
391
401
  }
392
- if (isUseEffectCall(node.parent)) {
402
+ if (isUseEffectLikeCall(node.parent)) {
393
403
  const calls = indirectSetStateCallsAsArgs.get(node.parent) ?? [];
394
404
  indirectSetStateCallsAsSetups.set(node.parent, [...calls, node]);
395
405
  }
@@ -460,189 +470,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
460
470
  },
461
471
  defaultOptions: []
462
472
  });
463
- var RULE_NAME5 = "no-direct-set-state-in-use-layout-effect";
464
- var no_direct_set_state_in_use_layout_effect_default = createRule({
465
- meta: {
466
- type: "problem",
467
- docs: {
468
- description: "disallow direct calls to the 'set' function of 'useState' in 'useLayoutEffect'"
469
- },
470
- messages: {
471
- noDirectSetStateInUseLayoutEffect: "Do not call the 'set' function '{{name}}' of 'useState' directly in 'useLayoutEffect'."
472
- },
473
- schema: []
474
- },
475
- name: RULE_NAME5,
476
- create(context) {
477
- if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
478
- const settings = decodeSettings(context.settings);
479
- const additionalHooks = settings.additionalHooks ?? {};
480
- const isUseEffectCall = isReactHookCallWithNameAlias(
481
- "useLayoutEffect",
482
- context,
483
- additionalHooks.useLayoutEffect ?? []
484
- );
485
- const isUseStateCall2 = isReactHookCallWithNameAlias("useState", context, additionalHooks.useState ?? []);
486
- const isUseMemoCall2 = isReactHookCallWithNameAlias("useMemo", context, additionalHooks.useMemo ?? []);
487
- const isUseCallbackCall2 = isReactHookCallWithNameAlias("useCallback", context, additionalHooks.useCallback ?? []);
488
- const isSetStateCall = isSetFunctionCall(context, settings);
489
- const isIdFromUseStateCall = isFromUseStateCall(context, settings);
490
- const functionStack = [];
491
- const setupFunctionRef = MutRef.make(null);
492
- const setupFunctionIdentifiers = [];
493
- const indirectFunctionCalls = [];
494
- const indirectSetStateCalls = /* @__PURE__ */ new WeakMap();
495
- const indirectSetStateCallsAsSetups = /* @__PURE__ */ new Map();
496
- const indirectSetStateCallsAsArgs = /* @__PURE__ */ new WeakMap();
497
- const indirectSetStateCallsInHooks = /* @__PURE__ */ new WeakMap();
498
- const onSetupFunctionEnter = (node) => {
499
- MutRef.set(setupFunctionRef, node);
500
- };
501
- const onSetupFunctionExit = (node) => {
502
- MutRef.update(setupFunctionRef, (current) => current === node ? null : current);
503
- };
504
- function isSetupFunction(node) {
505
- return node.parent?.type === AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseEffectCall(node.parent);
506
- }
507
- function getCallKind(node) {
508
- return match(node).when(isUseStateCall2, () => "useState").when(isUseEffectCall, () => "useLayoutEffect").when(isSetStateCall, () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
509
- }
510
- function getFunctionKind(node) {
511
- return match(node).when(isSetupFunction, () => "setup").when(isFunctionOfImmediatelyInvoked, () => "immediate").otherwise(() => "other");
512
- }
513
- return {
514
- ":function"(node) {
515
- const functionKind = getFunctionKind(node);
516
- functionStack.push([node, functionKind]);
517
- if (functionKind === "setup") onSetupFunctionEnter(node);
518
- },
519
- ":function:exit"(node) {
520
- const [_, functionKind] = functionStack.at(-1) ?? [];
521
- functionStack.pop();
522
- if (functionKind === "setup") onSetupFunctionExit(node);
523
- },
524
- CallExpression(node) {
525
- const effectFn = MutRef.get(setupFunctionRef);
526
- const [parentFn, parentFnKind] = functionStack.at(-1) ?? [];
527
- if (parentFn?.async) return;
528
- match(getCallKind(node)).with("setState", () => {
529
- if (!parentFn) return;
530
- if (parentFn !== effectFn && parentFnKind !== "immediate") {
531
- const maybeVd = traverseUpGuard(node, isVariableDeclaratorFromHookCall);
532
- if (O.isSome(maybeVd)) {
533
- const vd = maybeVd.value;
534
- const calls2 = indirectSetStateCallsInHooks.get(vd.init) ?? [];
535
- indirectSetStateCallsInHooks.set(vd.init, [...calls2, node]);
536
- return;
537
- }
538
- const calls = indirectSetStateCalls.get(parentFn) ?? [];
539
- indirectSetStateCalls.set(parentFn, [...calls, node]);
540
- return;
541
- }
542
- context.report({
543
- messageId: "noDirectSetStateInUseLayoutEffect",
544
- node,
545
- data: {
546
- name: context.sourceCode.getText(node.callee)
547
- }
548
- });
549
- }).with("useLayoutEffect", () => {
550
- const [firstArg] = node.arguments;
551
- if (isFunction(firstArg)) return;
552
- const identifiers = getNestedIdentifiers(node);
553
- setupFunctionIdentifiers.push(...identifiers);
554
- }).with("other", () => {
555
- const isInSetupFunction = effectFn === parentFn;
556
- if (!isInSetupFunction) return;
557
- indirectFunctionCalls.push(node);
558
- }).otherwise(F.constVoid);
559
- },
560
- Identifier(node) {
561
- if (node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee === node) return;
562
- if (!isIdFromUseStateCall(node)) return;
563
- switch (node.parent.type) {
564
- case AST_NODE_TYPES.CallExpression: {
565
- const [firstArg] = node.parent.arguments;
566
- if (node !== firstArg) break;
567
- if (isUseCallbackCall2(node.parent)) {
568
- const maybeVd = traverseUpGuard(node.parent, isVariableDeclaratorFromHookCall);
569
- if (O.isNone(maybeVd)) break;
570
- const vd = maybeVd.value;
571
- const calls = indirectSetStateCallsAsArgs.get(vd.init) ?? [];
572
- indirectSetStateCallsAsArgs.set(vd.init, [...calls, node]);
573
- }
574
- if (isUseEffectCall(node.parent)) {
575
- const calls = indirectSetStateCallsAsArgs.get(node.parent) ?? [];
576
- indirectSetStateCallsAsSetups.set(node.parent, [...calls, node]);
577
- }
578
- break;
579
- }
580
- case AST_NODE_TYPES.ArrowFunctionExpression: {
581
- const parent = node.parent.parent;
582
- if (parent.type !== AST_NODE_TYPES.CallExpression) break;
583
- if (!isUseMemoCall2(parent)) break;
584
- const maybeVd = traverseUpGuard(parent, isVariableDeclaratorFromHookCall);
585
- if (O.isNone(maybeVd)) break;
586
- const vd = maybeVd.value;
587
- const calls = indirectSetStateCallsAsArgs.get(vd.init) ?? [];
588
- indirectSetStateCallsAsArgs.set(vd.init, [...calls, node]);
589
- }
590
- }
591
- },
592
- "Program:exit"() {
593
- const getSetStateCalls = (id, initialScope) => {
594
- const node = O.flatMap(findVariable(id, initialScope), getVariableNode(0)).pipe(O.getOrNull);
595
- switch (node?.type) {
596
- case AST_NODE_TYPES.FunctionDeclaration:
597
- case AST_NODE_TYPES.FunctionExpression:
598
- case AST_NODE_TYPES.ArrowFunctionExpression:
599
- return indirectSetStateCalls.get(node) ?? [];
600
- case AST_NODE_TYPES.CallExpression:
601
- return indirectSetStateCallsInHooks.get(node) ?? indirectSetStateCallsAsArgs.get(node) ?? [];
602
- }
603
- return [];
604
- };
605
- for (const [_, calls] of indirectSetStateCallsAsSetups) {
606
- for (const call of calls) {
607
- context.report({
608
- messageId: "noDirectSetStateInUseLayoutEffect",
609
- node: call,
610
- data: { name: call.name }
611
- });
612
- }
613
- }
614
- for (const { callee } of indirectFunctionCalls) {
615
- if (!("name" in callee)) continue;
616
- const { name: name2 } = callee;
617
- const setStateCalls = getSetStateCalls(name2, context.sourceCode.getScope(callee));
618
- for (const setStateCall of setStateCalls) {
619
- context.report({
620
- messageId: "noDirectSetStateInUseLayoutEffect",
621
- node: setStateCall,
622
- data: {
623
- name: toReadableNodeName(setStateCall, (n) => context.sourceCode.getText(n))
624
- }
625
- });
626
- }
627
- }
628
- for (const id of setupFunctionIdentifiers) {
629
- const setStateCalls = getSetStateCalls(id.name, context.sourceCode.getScope(id));
630
- for (const setStateCall of setStateCalls) {
631
- context.report({
632
- messageId: "noDirectSetStateInUseLayoutEffect",
633
- node: setStateCall,
634
- data: {
635
- name: toReadableNodeName(setStateCall, (n) => context.sourceCode.getText(n))
636
- }
637
- });
638
- }
639
- }
640
- }
641
- };
642
- },
643
- defaultOptions: []
644
- });
645
- var RULE_NAME6 = "prefer-use-state-lazy-initialization";
473
+ var RULE_NAME5 = "prefer-use-state-lazy-initialization";
646
474
  var ALLOW_LIST = ["Boolean", "String", "Number"];
647
475
  var prefer_use_state_lazy_initialization_default = createRule({
648
476
  meta: {
@@ -655,7 +483,7 @@ var prefer_use_state_lazy_initialization_default = createRule({
655
483
  },
656
484
  schema: []
657
485
  },
658
- name: RULE_NAME6,
486
+ name: RULE_NAME5,
659
487
  create(context) {
660
488
  if (!context.sourceCode.text.includes("use")) return {};
661
489
  const alias = decodeSettings(context.settings).additionalHooks?.useState ?? [];
@@ -690,7 +518,8 @@ var rules = {
690
518
  "ensure-use-callback-has-non-empty-deps": ensure_use_callback_has_non_empty_deps_default,
691
519
  "ensure-use-memo-has-non-empty-deps": ensure_use_memo_has_non_empty_deps_default,
692
520
  "no-direct-set-state-in-use-effect": no_direct_set_state_in_use_effect_default,
693
- "no-direct-set-state-in-use-layout-effect": no_direct_set_state_in_use_layout_effect_default,
521
+ /** @deprecated Use `no-direct-set-state-in-use-effect` instead */
522
+ "no-direct-set-state-in-use-layout-effect": no_direct_set_state_in_use_effect_default,
694
523
  "prefer-use-state-lazy-initialization": prefer_use_state_lazy_initialization_default
695
524
  };
696
525
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-hooks-extra",
3
- "version": "1.12.3",
3
+ "version": "1.12.4-beta.3",
4
4
  "description": "ESLint React's ESLint plugin for React Hooks related rules.",
5
5
  "keywords": [
6
6
  "eslint",
@@ -46,16 +46,16 @@
46
46
  "@typescript-eslint/types": "^8.3.0",
47
47
  "@typescript-eslint/utils": "^8.3.0",
48
48
  "ts-pattern": "^5.3.1",
49
- "@eslint-react/ast": "1.12.3",
50
- "@eslint-react/core": "1.12.3",
51
- "@eslint-react/shared": "1.12.3",
52
- "@eslint-react/tools": "1.12.3",
53
- "@eslint-react/jsx": "1.12.3",
54
- "@eslint-react/types": "1.12.3",
55
- "@eslint-react/var": "1.12.3"
49
+ "@eslint-react/ast": "1.12.4-beta.3",
50
+ "@eslint-react/core": "1.12.4-beta.3",
51
+ "@eslint-react/jsx": "1.12.4-beta.3",
52
+ "@eslint-react/shared": "1.12.4-beta.3",
53
+ "@eslint-react/tools": "1.12.4-beta.3",
54
+ "@eslint-react/var": "1.12.4-beta.3",
55
+ "@eslint-react/types": "1.12.4-beta.3"
56
56
  },
57
57
  "devDependencies": {
58
- "@types/react": "^18.3.4",
58
+ "@types/react": "^18.3.5",
59
59
  "@types/react-dom": "^18.3.0",
60
60
  "string-ts": "^2.2.0",
61
61
  "tsup": "^8.2.4"