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 +7 -9
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +19 -190
- package/dist/index.mjs +19 -190
- package/package.json +9 -9
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
|
|
46
|
-
|
|
|
47
|
-
| `ensure-custom-hooks-using-other-hooks`
|
|
48
|
-
| `ensure-use-callback-has-non-empty-deps`
|
|
49
|
-
| `ensure-use-memo-has-non-empty-deps`
|
|
50
|
-
| `no-direct-set-state-in-use-effect`
|
|
51
|
-
| `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 &&
|
|
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(
|
|
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 (
|
|
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 = "
|
|
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:
|
|
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
|
-
|
|
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
|
|
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 &&
|
|
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(
|
|
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 (
|
|
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 = "
|
|
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:
|
|
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
|
-
|
|
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/
|
|
52
|
-
"@eslint-react/
|
|
53
|
-
"@eslint-react/
|
|
54
|
-
"@eslint-react/
|
|
55
|
-
"@eslint-react/
|
|
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.
|
|
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"
|