eslint-plugin-react-hooks-extra 1.8.2 → 1.8.3-next.1
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 +1 -1
- package/dist/index.d.mts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +93 -73
- package/dist/index.mjs +93 -73
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ export default [
|
|
|
41
41
|
|
|
42
42
|
## Rules
|
|
43
43
|
|
|
44
|
-
| Rule | Description | 💼 | 💭 |
|
|
44
|
+
| Rule | Description | 💼 | 💭 | |
|
|
45
45
|
| :----------------------------------------- | :------------------------------------------------------------------------------ | :-: | :-: | :-: |
|
|
46
46
|
| `ensure-custom-hooks-using-other-hooks` | Warns when custom Hooks that don't use other Hooks. | ✔️ | | |
|
|
47
47
|
| `ensure-use-callback-has-non-empty-deps` | Warns when `useCallback` is called with empty dependencies array. | 🧐 | | |
|
package/dist/index.d.mts
CHANGED
|
@@ -5,12 +5,12 @@ declare const meta: {
|
|
|
5
5
|
readonly version: string;
|
|
6
6
|
};
|
|
7
7
|
declare const rules: {
|
|
8
|
-
readonly "ensure-custom-hooks-using-other-hooks": _typescript_eslint_utils_ts_eslint.RuleModule<"
|
|
9
|
-
readonly "ensure-use-callback-has-non-empty-deps": _typescript_eslint_utils_ts_eslint.RuleModule<"
|
|
10
|
-
readonly "ensure-use-memo-has-non-empty-deps": _typescript_eslint_utils_ts_eslint.RuleModule<"
|
|
11
|
-
readonly "no-direct-set-state-in-use-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"
|
|
12
|
-
readonly "no-direct-set-state-in-use-layout-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"
|
|
13
|
-
readonly "prefer-use-state-lazy-initialization": _typescript_eslint_utils_ts_eslint.RuleModule<"
|
|
8
|
+
readonly "ensure-custom-hooks-using-other-hooks": _typescript_eslint_utils_ts_eslint.RuleModule<"ensureCustomHooksUsingOtherHooks", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
9
|
+
readonly "ensure-use-callback-has-non-empty-deps": _typescript_eslint_utils_ts_eslint.RuleModule<"ensureUseCallbackHasNonEmptyDeps", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
10
|
+
readonly "ensure-use-memo-has-non-empty-deps": _typescript_eslint_utils_ts_eslint.RuleModule<"ensureUseMemoHasNonEmptyDeps", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
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>;
|
|
13
|
+
readonly "prefer-use-state-lazy-initialization": _typescript_eslint_utils_ts_eslint.RuleModule<"preferUseStateLazyInitialization", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
export { meta, rules };
|
package/dist/index.d.ts
CHANGED
|
@@ -5,12 +5,12 @@ declare const meta: {
|
|
|
5
5
|
readonly version: string;
|
|
6
6
|
};
|
|
7
7
|
declare const rules: {
|
|
8
|
-
readonly "ensure-custom-hooks-using-other-hooks": _typescript_eslint_utils_ts_eslint.RuleModule<"
|
|
9
|
-
readonly "ensure-use-callback-has-non-empty-deps": _typescript_eslint_utils_ts_eslint.RuleModule<"
|
|
10
|
-
readonly "ensure-use-memo-has-non-empty-deps": _typescript_eslint_utils_ts_eslint.RuleModule<"
|
|
11
|
-
readonly "no-direct-set-state-in-use-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"
|
|
12
|
-
readonly "no-direct-set-state-in-use-layout-effect": _typescript_eslint_utils_ts_eslint.RuleModule<"
|
|
13
|
-
readonly "prefer-use-state-lazy-initialization": _typescript_eslint_utils_ts_eslint.RuleModule<"
|
|
8
|
+
readonly "ensure-custom-hooks-using-other-hooks": _typescript_eslint_utils_ts_eslint.RuleModule<"ensureCustomHooksUsingOtherHooks", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
9
|
+
readonly "ensure-use-callback-has-non-empty-deps": _typescript_eslint_utils_ts_eslint.RuleModule<"ensureUseCallbackHasNonEmptyDeps", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
10
|
+
readonly "ensure-use-memo-has-non-empty-deps": _typescript_eslint_utils_ts_eslint.RuleModule<"ensureUseMemoHasNonEmptyDeps", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
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>;
|
|
13
|
+
readonly "prefer-use-state-lazy-initialization": _typescript_eslint_utils_ts_eslint.RuleModule<"preferUseStateLazyInitialization", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
export { meta, rules };
|
package/dist/index.js
CHANGED
|
@@ -30,21 +30,24 @@ var R__namespace = /*#__PURE__*/_interopNamespace(R);
|
|
|
30
30
|
|
|
31
31
|
// package.json
|
|
32
32
|
var name = "eslint-plugin-react-hooks-extra";
|
|
33
|
-
var version = "1.8.
|
|
33
|
+
var version = "1.8.3-next.1";
|
|
34
34
|
var createRule = shared.createRuleForPlugin("hooks-extra");
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
function isFromHookCall(name2, context, settings, predicate = tools.F.constTrue) {
|
|
36
|
+
const hookAlias = settings.additionalHooks?.[name2] ?? [];
|
|
37
|
+
return (topLevelId) => tools.F.pipe(
|
|
38
|
+
_var.findVariable(topLevelId, context.sourceCode.getScope(topLevelId)),
|
|
39
|
+
tools.O.flatMap(_var.getVariableNode(0)),
|
|
40
|
+
tools.O.filter(ast.is(ast.NodeType.CallExpression)),
|
|
41
|
+
tools.O.filter(core.isReactHookCallWithNameAlias(name2, context, hookAlias)),
|
|
42
|
+
tools.O.exists((call) => predicate(topLevelId, call))
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
function isFromUseStateCall(context, settings) {
|
|
46
|
+
return (toplevelId) => isFromHookCall("useState", context, settings, (topLevelId, call) => {
|
|
47
|
+
const { parent } = call;
|
|
45
48
|
if (!("id" in parent && parent.id?.type === ast.NodeType.ArrayPattern)) return true;
|
|
46
49
|
return parent.id.elements.findIndex((e2) => e2?.type === ast.NodeType.Identifier && e2.name === topLevelId.name) === 1;
|
|
47
|
-
};
|
|
50
|
+
})(toplevelId);
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
// ../../../node_modules/.pnpm/ts-pattern@5.2.0/node_modules/ts-pattern/dist/index.js
|
|
@@ -230,18 +233,19 @@ var z = class _z {
|
|
|
230
233
|
}
|
|
231
234
|
};
|
|
232
235
|
|
|
233
|
-
// src/utils/is-set-
|
|
234
|
-
function
|
|
236
|
+
// src/utils/is-set-function-call.ts
|
|
237
|
+
function isSetFunctionCall(context, settings) {
|
|
238
|
+
const isIdFromUseStateCall = isFromUseStateCall(context, settings);
|
|
235
239
|
return (node) => {
|
|
236
240
|
switch (node.callee.type) {
|
|
237
241
|
case ast.NodeType.Identifier: {
|
|
238
|
-
return
|
|
242
|
+
return isIdFromUseStateCall(node.callee);
|
|
239
243
|
}
|
|
240
244
|
case ast.NodeType.MemberExpression: {
|
|
241
245
|
if (!("name" in node.callee.object)) return false;
|
|
242
246
|
const initialScope = context.sourceCode.getScope(node);
|
|
243
247
|
const property = astUtils.getStaticValue(node.callee.property, initialScope);
|
|
244
|
-
if (property?.value === 1) return
|
|
248
|
+
if (property?.value === 1) return isIdFromUseStateCall(node.callee.object);
|
|
245
249
|
return false;
|
|
246
250
|
}
|
|
247
251
|
case ast.NodeType.CallExpression: {
|
|
@@ -259,7 +263,7 @@ function isSetStateCall(context, useStateAlias) {
|
|
|
259
263
|
if (!isAt || !index) return false;
|
|
260
264
|
const initialScope = context.sourceCode.getScope(node);
|
|
261
265
|
const value = astUtils.getStaticValue(index, initialScope);
|
|
262
|
-
if (value?.value === 1) return
|
|
266
|
+
if (value?.value === 1) return isIdFromUseStateCall(callee.object);
|
|
263
267
|
return false;
|
|
264
268
|
}
|
|
265
269
|
default: {
|
|
@@ -294,7 +298,7 @@ var ensure_custom_hooks_using_other_hooks_default = createRule({
|
|
|
294
298
|
description: "enforce custom hooks using other hooks"
|
|
295
299
|
},
|
|
296
300
|
messages: {
|
|
297
|
-
|
|
301
|
+
ensureCustomHooksUsingOtherHooks: "A custom hook '{{name}}' should use other hooks."
|
|
298
302
|
},
|
|
299
303
|
schema: []
|
|
300
304
|
},
|
|
@@ -311,7 +315,7 @@ var ensure_custom_hooks_using_other_hooks_default = createRule({
|
|
|
311
315
|
data: {
|
|
312
316
|
name: name2.value
|
|
313
317
|
},
|
|
314
|
-
messageId: "
|
|
318
|
+
messageId: "ensureCustomHooksUsingOtherHooks",
|
|
315
319
|
node: node2
|
|
316
320
|
});
|
|
317
321
|
}
|
|
@@ -328,7 +332,7 @@ var ensure_use_callback_has_non_empty_deps_default = createRule({
|
|
|
328
332
|
description: "enforce 'useCallback' has non-empty dependencies array"
|
|
329
333
|
},
|
|
330
334
|
messages: {
|
|
331
|
-
|
|
335
|
+
ensureUseCallbackHasNonEmptyDeps: "An 'useCallback' should have a non-empty dependencies array."
|
|
332
336
|
},
|
|
333
337
|
schema: []
|
|
334
338
|
},
|
|
@@ -348,7 +352,7 @@ var ensure_use_callback_has_non_empty_deps_default = createRule({
|
|
|
348
352
|
const [cb, deps] = node.arguments;
|
|
349
353
|
if (!deps) {
|
|
350
354
|
context.report({
|
|
351
|
-
messageId: "
|
|
355
|
+
messageId: "ensureUseCallbackHasNonEmptyDeps",
|
|
352
356
|
node
|
|
353
357
|
});
|
|
354
358
|
return;
|
|
@@ -366,7 +370,7 @@ var ensure_use_callback_has_non_empty_deps_default = createRule({
|
|
|
366
370
|
if (!hasEmptyDeps) return;
|
|
367
371
|
if (!cb) {
|
|
368
372
|
context.report({
|
|
369
|
-
messageId: "
|
|
373
|
+
messageId: "ensureUseCallbackHasNonEmptyDeps",
|
|
370
374
|
node
|
|
371
375
|
});
|
|
372
376
|
return;
|
|
@@ -390,7 +394,7 @@ var ensure_use_callback_has_non_empty_deps_default = createRule({
|
|
|
390
394
|
);
|
|
391
395
|
if (isReferencedToComponentScope) return;
|
|
392
396
|
context.report({
|
|
393
|
-
messageId: "
|
|
397
|
+
messageId: "ensureUseCallbackHasNonEmptyDeps",
|
|
394
398
|
node
|
|
395
399
|
});
|
|
396
400
|
}
|
|
@@ -406,7 +410,7 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
|
|
|
406
410
|
description: "enforce 'useMemo' has non-empty dependencies array"
|
|
407
411
|
},
|
|
408
412
|
messages: {
|
|
409
|
-
|
|
413
|
+
ensureUseMemoHasNonEmptyDeps: "An 'useMemo' should have a non-empty dependencies array."
|
|
410
414
|
},
|
|
411
415
|
schema: []
|
|
412
416
|
},
|
|
@@ -426,7 +430,7 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
|
|
|
426
430
|
const [cb, deps] = node.arguments;
|
|
427
431
|
if (!deps) {
|
|
428
432
|
context.report({
|
|
429
|
-
messageId: "
|
|
433
|
+
messageId: "ensureUseMemoHasNonEmptyDeps",
|
|
430
434
|
node
|
|
431
435
|
});
|
|
432
436
|
return;
|
|
@@ -444,7 +448,7 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
|
|
|
444
448
|
if (!hasEmptyDeps) return;
|
|
445
449
|
if (!cb) {
|
|
446
450
|
context.report({
|
|
447
|
-
messageId: "
|
|
451
|
+
messageId: "ensureUseMemoHasNonEmptyDeps",
|
|
448
452
|
node
|
|
449
453
|
});
|
|
450
454
|
return;
|
|
@@ -468,7 +472,7 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
|
|
|
468
472
|
);
|
|
469
473
|
if (isReferencedToComponentScope) return;
|
|
470
474
|
context.report({
|
|
471
|
-
messageId: "
|
|
475
|
+
messageId: "ensureUseMemoHasNonEmptyDeps",
|
|
472
476
|
node
|
|
473
477
|
});
|
|
474
478
|
}
|
|
@@ -477,16 +481,6 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
|
|
|
477
481
|
defaultOptions: []
|
|
478
482
|
});
|
|
479
483
|
var RULE_NAME4 = "no-direct-set-state-in-use-effect";
|
|
480
|
-
function isEffectFunction(context, useSomeEffectAlias) {
|
|
481
|
-
return (node) => node.parent?.type === ast.NodeType.CallExpression && node.parent.callee !== node && core.isReactHookCallWithNameAlias("useEffect", context, useSomeEffectAlias)(node.parent);
|
|
482
|
-
}
|
|
483
|
-
function getCallKind(node, context, alias) {
|
|
484
|
-
const [useStateAlias, useEffectAlias] = alias;
|
|
485
|
-
return $(node).when(core.isReactHookCallWithNameAlias("useState", context, useStateAlias), () => "useState").when(core.isReactHookCallWithNameAlias("useEffect", context, useEffectAlias), () => "useEffect").when(isSetStateCall(context, useStateAlias), () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
|
|
486
|
-
}
|
|
487
|
-
function getFunctionKind(node, context, useEffectAlias) {
|
|
488
|
-
return $(node).when(isEffectFunction(context, useEffectAlias), () => "effect").when(ast.isIIFE, () => "immediate").otherwise(() => "other");
|
|
489
|
-
}
|
|
490
484
|
var no_direct_set_state_in_use_effect_default = createRule({
|
|
491
485
|
meta: {
|
|
492
486
|
type: "problem",
|
|
@@ -494,14 +488,20 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
494
488
|
description: "disallow direct calls to the 'set' function of 'useState' in 'useEffect'"
|
|
495
489
|
},
|
|
496
490
|
messages: {
|
|
497
|
-
|
|
491
|
+
noDirectSetStateInUseEffect: "Do not call the 'set' function of 'useState' directly in 'useEffect'."
|
|
498
492
|
},
|
|
499
493
|
schema: []
|
|
500
494
|
},
|
|
501
495
|
name: RULE_NAME4,
|
|
502
496
|
create(context) {
|
|
503
497
|
const settings = shared.decodeSettings(context.settings);
|
|
504
|
-
const
|
|
498
|
+
const additionalHooks = settings.additionalHooks ?? {};
|
|
499
|
+
const isUseEffectCall = core.isReactHookCallWithNameAlias("useEffect", context, additionalHooks.useEffect ?? []);
|
|
500
|
+
const isUseStateCall2 = core.isReactHookCallWithNameAlias("useState", context, additionalHooks.useState ?? []);
|
|
501
|
+
const isUseMemoCall2 = core.isReactHookCallWithNameAlias("useMemo", context, additionalHooks.useMemo ?? []);
|
|
502
|
+
const isUseCallbackCall2 = core.isReactHookCallWithNameAlias("useCallback", context, additionalHooks.useCallback ?? []);
|
|
503
|
+
const isSetStateCall = isSetFunctionCall(context, settings);
|
|
504
|
+
const isIdFromUseStateCall = isFromUseStateCall(context, settings);
|
|
505
505
|
const functionStack = [];
|
|
506
506
|
const effectFunctionRef = tools.MutRef.make(null);
|
|
507
507
|
const effectFunctionIdentifiers = [];
|
|
@@ -516,9 +516,18 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
516
516
|
const onEffectFunctionExit = (node) => {
|
|
517
517
|
tools.MutRef.update(effectFunctionRef, (current) => current === node ? null : current);
|
|
518
518
|
};
|
|
519
|
+
function isEffectFunction(node) {
|
|
520
|
+
return node.parent?.type === ast.NodeType.CallExpression && node.parent.callee !== node && isUseEffectCall(node.parent);
|
|
521
|
+
}
|
|
522
|
+
function getCallKind(node) {
|
|
523
|
+
return $(node).when(isUseStateCall2, () => "useState").when(isUseEffectCall, () => "useEffect").when(isSetStateCall, () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
|
|
524
|
+
}
|
|
525
|
+
function getFunctionKind(node) {
|
|
526
|
+
return $(node).when(isEffectFunction, () => "effect").when(ast.isIIFE, () => "immediate").otherwise(() => "other");
|
|
527
|
+
}
|
|
519
528
|
return {
|
|
520
529
|
":function"(node) {
|
|
521
|
-
const functionKind = getFunctionKind(node
|
|
530
|
+
const functionKind = getFunctionKind(node);
|
|
522
531
|
functionStack.push([node, functionKind]);
|
|
523
532
|
$(functionKind).with("effect", () => {
|
|
524
533
|
onEffectFunctionEnter(node);
|
|
@@ -532,7 +541,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
532
541
|
const effectFn = tools.MutRef.get(effectFunctionRef);
|
|
533
542
|
const [parentFn, parentFnKind] = R__namespace.last(functionStack) ?? [];
|
|
534
543
|
if (parentFn?.async) return;
|
|
535
|
-
$(getCallKind(node
|
|
544
|
+
$(getCallKind(node)).with("setState", () => {
|
|
536
545
|
if (!parentFn) return;
|
|
537
546
|
if (parentFn !== effectFn && parentFnKind !== "immediate") {
|
|
538
547
|
const maybeVd = ast.traverseUpGuard(node, isVariableDeclaratorFromHookCall);
|
|
@@ -547,7 +556,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
547
556
|
return;
|
|
548
557
|
}
|
|
549
558
|
context.report({
|
|
550
|
-
messageId: "
|
|
559
|
+
messageId: "noDirectSetStateInUseEffect",
|
|
551
560
|
node
|
|
552
561
|
});
|
|
553
562
|
}).with("useEffect", () => {
|
|
@@ -563,19 +572,20 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
563
572
|
},
|
|
564
573
|
Identifier(node) {
|
|
565
574
|
if (!node.parent) return;
|
|
566
|
-
if (
|
|
575
|
+
if (node.parent.type === ast.NodeType.CallExpression && node.parent.callee === node) return;
|
|
576
|
+
if (!isIdFromUseStateCall(node)) return;
|
|
567
577
|
switch (node.parent.type) {
|
|
568
578
|
case ast.NodeType.CallExpression: {
|
|
569
579
|
const [firstArg] = node.parent.arguments;
|
|
570
580
|
if (node !== firstArg) break;
|
|
571
|
-
if (
|
|
581
|
+
if (isUseCallbackCall2(node.parent)) {
|
|
572
582
|
const maybeVd = ast.traverseUpGuard(node.parent, isVariableDeclaratorFromHookCall);
|
|
573
583
|
if (tools.O.isNone(maybeVd)) break;
|
|
574
584
|
const vd = maybeVd.value;
|
|
575
585
|
const calls = indirectSetStateCallsAsArgs.get(vd.init) ?? [];
|
|
576
586
|
indirectSetStateCallsAsArgs.set(vd.init, [...calls, node]);
|
|
577
587
|
}
|
|
578
|
-
if (
|
|
588
|
+
if (isUseEffectCall(node.parent)) {
|
|
579
589
|
const calls = indirectSetStateCallsAsArgs.get(node.parent) ?? [];
|
|
580
590
|
indirectSetStateCallsAsEFs.set(node.parent, [...calls, node]);
|
|
581
591
|
}
|
|
@@ -584,7 +594,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
584
594
|
case ast.NodeType.ArrowFunctionExpression: {
|
|
585
595
|
const parent = node.parent.parent;
|
|
586
596
|
if (parent?.type !== ast.NodeType.CallExpression) break;
|
|
587
|
-
if (!
|
|
597
|
+
if (!isUseMemoCall2(parent)) break;
|
|
588
598
|
const maybeVd = ast.traverseUpGuard(parent, isVariableDeclaratorFromHookCall);
|
|
589
599
|
if (tools.O.isNone(maybeVd)) break;
|
|
590
600
|
const vd = maybeVd.value;
|
|
@@ -610,7 +620,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
610
620
|
for (const call of calls) {
|
|
611
621
|
context.report({
|
|
612
622
|
data: { name: call.name },
|
|
613
|
-
messageId: "
|
|
623
|
+
messageId: "noDirectSetStateInUseEffect",
|
|
614
624
|
node: call
|
|
615
625
|
});
|
|
616
626
|
}
|
|
@@ -622,7 +632,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
622
632
|
for (const setStateCall of setStateCalls) {
|
|
623
633
|
context.report({
|
|
624
634
|
data: { name: name2 },
|
|
625
|
-
messageId: "
|
|
635
|
+
messageId: "noDirectSetStateInUseEffect",
|
|
626
636
|
node: setStateCall
|
|
627
637
|
});
|
|
628
638
|
}
|
|
@@ -632,7 +642,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
632
642
|
for (const setStateCall of setStateCalls) {
|
|
633
643
|
context.report({
|
|
634
644
|
data: { name: id.name },
|
|
635
|
-
messageId: "
|
|
645
|
+
messageId: "noDirectSetStateInUseEffect",
|
|
636
646
|
node: setStateCall
|
|
637
647
|
});
|
|
638
648
|
}
|
|
@@ -643,16 +653,6 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
643
653
|
defaultOptions: []
|
|
644
654
|
});
|
|
645
655
|
var RULE_NAME5 = "no-direct-set-state-in-use-layout-effect";
|
|
646
|
-
function isEffectFunction2(context, useSomeEffectAlias) {
|
|
647
|
-
return (node) => node.parent?.type === ast.NodeType.CallExpression && node.parent.callee !== node && core.isReactHookCallWithNameAlias("useLayoutEffect", context, useSomeEffectAlias)(node.parent);
|
|
648
|
-
}
|
|
649
|
-
function getCallKind2(node, context, alias) {
|
|
650
|
-
const [useStateAlias, useLayoutEffectAlias] = alias;
|
|
651
|
-
return $(node).when(core.isReactHookCallWithNameAlias("useState", context, useStateAlias), () => "useState").when(core.isReactHookCallWithNameAlias("useLayoutEffect", context, useLayoutEffectAlias), () => "useLayoutEffect").when(isSetStateCall(context, useStateAlias), () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
|
|
652
|
-
}
|
|
653
|
-
function getFunctionKind2(node, context, useLayoutEffectAlias) {
|
|
654
|
-
return $(node).when(isEffectFunction2(context, useLayoutEffectAlias), () => "effect").when(ast.isIIFE, () => "immediate").otherwise(() => "other");
|
|
655
|
-
}
|
|
656
656
|
var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
657
657
|
meta: {
|
|
658
658
|
type: "problem",
|
|
@@ -660,14 +660,24 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
660
660
|
description: "disallow direct calls to the 'set' function of 'useState' in 'useLayoutEffect'"
|
|
661
661
|
},
|
|
662
662
|
messages: {
|
|
663
|
-
|
|
663
|
+
noDirectSetStateInUseLayoutEffect: "Do not call the 'set' function of 'useState' directly in 'useLayoutEffect'."
|
|
664
664
|
},
|
|
665
665
|
schema: []
|
|
666
666
|
},
|
|
667
667
|
name: RULE_NAME5,
|
|
668
668
|
create(context) {
|
|
669
669
|
const settings = shared.decodeSettings(context.settings);
|
|
670
|
-
const
|
|
670
|
+
const additionalHooks = settings.additionalHooks ?? {};
|
|
671
|
+
const isUseEffectCall = core.isReactHookCallWithNameAlias(
|
|
672
|
+
"useLayoutEffect",
|
|
673
|
+
context,
|
|
674
|
+
additionalHooks.useLayoutEffect ?? []
|
|
675
|
+
);
|
|
676
|
+
const isUseStateCall2 = core.isReactHookCallWithNameAlias("useState", context, additionalHooks.useState ?? []);
|
|
677
|
+
const isUseMemoCall2 = core.isReactHookCallWithNameAlias("useMemo", context, additionalHooks.useMemo ?? []);
|
|
678
|
+
const isUseCallbackCall2 = core.isReactHookCallWithNameAlias("useCallback", context, additionalHooks.useCallback ?? []);
|
|
679
|
+
const isSetStateCall = isSetFunctionCall(context, settings);
|
|
680
|
+
const isIdFromUseStateCall = isFromUseStateCall(context, settings);
|
|
671
681
|
const functionStack = [];
|
|
672
682
|
const effectFunctionRef = tools.MutRef.make(null);
|
|
673
683
|
const effectFunctionIdentifiers = [];
|
|
@@ -682,9 +692,18 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
682
692
|
const onEffectFunctionExit = (node) => {
|
|
683
693
|
tools.MutRef.update(effectFunctionRef, (current) => current === node ? null : current);
|
|
684
694
|
};
|
|
695
|
+
function isEffectFunction(node) {
|
|
696
|
+
return node.parent?.type === ast.NodeType.CallExpression && node.parent.callee !== node && isUseEffectCall(node.parent);
|
|
697
|
+
}
|
|
698
|
+
function getCallKind(node) {
|
|
699
|
+
return $(node).when(isUseStateCall2, () => "useState").when(isUseEffectCall, () => "useLayoutEffect").when(isSetStateCall, () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
|
|
700
|
+
}
|
|
701
|
+
function getFunctionKind(node) {
|
|
702
|
+
return $(node).when(isEffectFunction, () => "effect").when(ast.isIIFE, () => "immediate").otherwise(() => "other");
|
|
703
|
+
}
|
|
685
704
|
return {
|
|
686
705
|
":function"(node) {
|
|
687
|
-
const functionKind =
|
|
706
|
+
const functionKind = getFunctionKind(node);
|
|
688
707
|
functionStack.push([node, functionKind]);
|
|
689
708
|
$(functionKind).with("effect", () => {
|
|
690
709
|
onEffectFunctionEnter(node);
|
|
@@ -698,7 +717,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
698
717
|
const effectFn = tools.MutRef.get(effectFunctionRef);
|
|
699
718
|
const [parentFn, parentFnKind] = R__namespace.last(functionStack) ?? [];
|
|
700
719
|
if (parentFn?.async) return;
|
|
701
|
-
$(
|
|
720
|
+
$(getCallKind(node)).with("setState", () => {
|
|
702
721
|
if (!parentFn) return;
|
|
703
722
|
if (parentFn !== effectFn && parentFnKind !== "immediate") {
|
|
704
723
|
const maybeVd = ast.traverseUpGuard(node, isVariableDeclaratorFromHookCall);
|
|
@@ -713,7 +732,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
713
732
|
return;
|
|
714
733
|
}
|
|
715
734
|
context.report({
|
|
716
|
-
messageId: "
|
|
735
|
+
messageId: "noDirectSetStateInUseLayoutEffect",
|
|
717
736
|
node
|
|
718
737
|
});
|
|
719
738
|
}).with("useLayoutEffect", () => {
|
|
@@ -729,19 +748,20 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
729
748
|
},
|
|
730
749
|
Identifier(node) {
|
|
731
750
|
if (!node.parent) return;
|
|
732
|
-
if (
|
|
751
|
+
if (node.parent.type === ast.NodeType.CallExpression && node.parent.callee === node) return;
|
|
752
|
+
if (!isIdFromUseStateCall(node)) return;
|
|
733
753
|
switch (node.parent.type) {
|
|
734
754
|
case ast.NodeType.CallExpression: {
|
|
735
755
|
const [firstArg] = node.parent.arguments;
|
|
736
756
|
if (node !== firstArg) break;
|
|
737
|
-
if (
|
|
757
|
+
if (isUseCallbackCall2(node.parent)) {
|
|
738
758
|
const maybeVd = ast.traverseUpGuard(node.parent, isVariableDeclaratorFromHookCall);
|
|
739
759
|
if (tools.O.isNone(maybeVd)) break;
|
|
740
760
|
const vd = maybeVd.value;
|
|
741
761
|
const calls = indirectSetStateCallsAsArgs.get(vd.init) ?? [];
|
|
742
762
|
indirectSetStateCallsAsArgs.set(vd.init, [...calls, node]);
|
|
743
763
|
}
|
|
744
|
-
if (
|
|
764
|
+
if (isUseEffectCall(node.parent)) {
|
|
745
765
|
const calls = indirectSetStateCallsAsArgs.get(node.parent) ?? [];
|
|
746
766
|
indirectSetStateCallsAsEFs.set(node.parent, [...calls, node]);
|
|
747
767
|
}
|
|
@@ -750,7 +770,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
750
770
|
case ast.NodeType.ArrowFunctionExpression: {
|
|
751
771
|
const parent = node.parent.parent;
|
|
752
772
|
if (parent?.type !== ast.NodeType.CallExpression) break;
|
|
753
|
-
if (!
|
|
773
|
+
if (!isUseMemoCall2(parent)) break;
|
|
754
774
|
const maybeVd = ast.traverseUpGuard(parent, isVariableDeclaratorFromHookCall);
|
|
755
775
|
if (tools.O.isNone(maybeVd)) break;
|
|
756
776
|
const vd = maybeVd.value;
|
|
@@ -776,7 +796,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
776
796
|
for (const call of calls) {
|
|
777
797
|
context.report({
|
|
778
798
|
data: { name: call.name },
|
|
779
|
-
messageId: "
|
|
799
|
+
messageId: "noDirectSetStateInUseLayoutEffect",
|
|
780
800
|
node: call
|
|
781
801
|
});
|
|
782
802
|
}
|
|
@@ -788,7 +808,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
788
808
|
for (const setStateCall of setStateCalls) {
|
|
789
809
|
context.report({
|
|
790
810
|
data: { name: name2 },
|
|
791
|
-
messageId: "
|
|
811
|
+
messageId: "noDirectSetStateInUseLayoutEffect",
|
|
792
812
|
node: setStateCall
|
|
793
813
|
});
|
|
794
814
|
}
|
|
@@ -798,7 +818,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
798
818
|
for (const setStateCall of setStateCalls) {
|
|
799
819
|
context.report({
|
|
800
820
|
data: { name: id.name },
|
|
801
|
-
messageId: "
|
|
821
|
+
messageId: "noDirectSetStateInUseLayoutEffect",
|
|
802
822
|
node: setStateCall
|
|
803
823
|
});
|
|
804
824
|
}
|
|
@@ -817,7 +837,7 @@ var prefer_use_state_lazy_initialization_default = createRule({
|
|
|
817
837
|
description: "disallow function calls in 'useState' that aren't wrapped in an initializer function"
|
|
818
838
|
},
|
|
819
839
|
messages: {
|
|
820
|
-
|
|
840
|
+
preferUseStateLazyInitialization: "To prevent re-computation, consider using lazy initial state for useState calls that involve function calls. Ex: 'useState(() => getValue())'."
|
|
821
841
|
},
|
|
822
842
|
schema: []
|
|
823
843
|
},
|
|
@@ -836,7 +856,7 @@ var prefer_use_state_lazy_initialization_default = createRule({
|
|
|
836
856
|
});
|
|
837
857
|
if (!hasFunctionCall) return;
|
|
838
858
|
context.report({
|
|
839
|
-
messageId: "
|
|
859
|
+
messageId: "preferUseStateLazyInitialization",
|
|
840
860
|
node: useStateInput
|
|
841
861
|
});
|
|
842
862
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -8,21 +8,24 @@ import * as R from 'remeda';
|
|
|
8
8
|
|
|
9
9
|
// package.json
|
|
10
10
|
var name = "eslint-plugin-react-hooks-extra";
|
|
11
|
-
var version = "1.8.
|
|
11
|
+
var version = "1.8.3-next.1";
|
|
12
12
|
var createRule = createRuleForPlugin("hooks-extra");
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
function isFromHookCall(name2, context, settings, predicate = F.constTrue) {
|
|
14
|
+
const hookAlias = settings.additionalHooks?.[name2] ?? [];
|
|
15
|
+
return (topLevelId) => F.pipe(
|
|
16
|
+
findVariable(topLevelId, context.sourceCode.getScope(topLevelId)),
|
|
17
|
+
O.flatMap(getVariableNode(0)),
|
|
18
|
+
O.filter(is(NodeType.CallExpression)),
|
|
19
|
+
O.filter(isReactHookCallWithNameAlias(name2, context, hookAlias)),
|
|
20
|
+
O.exists((call) => predicate(topLevelId, call))
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
function isFromUseStateCall(context, settings) {
|
|
24
|
+
return (toplevelId) => isFromHookCall("useState", context, settings, (topLevelId, call) => {
|
|
25
|
+
const { parent } = call;
|
|
23
26
|
if (!("id" in parent && parent.id?.type === NodeType.ArrayPattern)) return true;
|
|
24
27
|
return parent.id.elements.findIndex((e2) => e2?.type === NodeType.Identifier && e2.name === topLevelId.name) === 1;
|
|
25
|
-
};
|
|
28
|
+
})(toplevelId);
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
// ../../../node_modules/.pnpm/ts-pattern@5.2.0/node_modules/ts-pattern/dist/index.js
|
|
@@ -208,18 +211,19 @@ var z = class _z {
|
|
|
208
211
|
}
|
|
209
212
|
};
|
|
210
213
|
|
|
211
|
-
// src/utils/is-set-
|
|
212
|
-
function
|
|
214
|
+
// src/utils/is-set-function-call.ts
|
|
215
|
+
function isSetFunctionCall(context, settings) {
|
|
216
|
+
const isIdFromUseStateCall = isFromUseStateCall(context, settings);
|
|
213
217
|
return (node) => {
|
|
214
218
|
switch (node.callee.type) {
|
|
215
219
|
case NodeType.Identifier: {
|
|
216
|
-
return
|
|
220
|
+
return isIdFromUseStateCall(node.callee);
|
|
217
221
|
}
|
|
218
222
|
case NodeType.MemberExpression: {
|
|
219
223
|
if (!("name" in node.callee.object)) return false;
|
|
220
224
|
const initialScope = context.sourceCode.getScope(node);
|
|
221
225
|
const property = getStaticValue(node.callee.property, initialScope);
|
|
222
|
-
if (property?.value === 1) return
|
|
226
|
+
if (property?.value === 1) return isIdFromUseStateCall(node.callee.object);
|
|
223
227
|
return false;
|
|
224
228
|
}
|
|
225
229
|
case NodeType.CallExpression: {
|
|
@@ -237,7 +241,7 @@ function isSetStateCall(context, useStateAlias) {
|
|
|
237
241
|
if (!isAt || !index) return false;
|
|
238
242
|
const initialScope = context.sourceCode.getScope(node);
|
|
239
243
|
const value = getStaticValue(index, initialScope);
|
|
240
|
-
if (value?.value === 1) return
|
|
244
|
+
if (value?.value === 1) return isIdFromUseStateCall(callee.object);
|
|
241
245
|
return false;
|
|
242
246
|
}
|
|
243
247
|
default: {
|
|
@@ -272,7 +276,7 @@ var ensure_custom_hooks_using_other_hooks_default = createRule({
|
|
|
272
276
|
description: "enforce custom hooks using other hooks"
|
|
273
277
|
},
|
|
274
278
|
messages: {
|
|
275
|
-
|
|
279
|
+
ensureCustomHooksUsingOtherHooks: "A custom hook '{{name}}' should use other hooks."
|
|
276
280
|
},
|
|
277
281
|
schema: []
|
|
278
282
|
},
|
|
@@ -289,7 +293,7 @@ var ensure_custom_hooks_using_other_hooks_default = createRule({
|
|
|
289
293
|
data: {
|
|
290
294
|
name: name2.value
|
|
291
295
|
},
|
|
292
|
-
messageId: "
|
|
296
|
+
messageId: "ensureCustomHooksUsingOtherHooks",
|
|
293
297
|
node: node2
|
|
294
298
|
});
|
|
295
299
|
}
|
|
@@ -306,7 +310,7 @@ var ensure_use_callback_has_non_empty_deps_default = createRule({
|
|
|
306
310
|
description: "enforce 'useCallback' has non-empty dependencies array"
|
|
307
311
|
},
|
|
308
312
|
messages: {
|
|
309
|
-
|
|
313
|
+
ensureUseCallbackHasNonEmptyDeps: "An 'useCallback' should have a non-empty dependencies array."
|
|
310
314
|
},
|
|
311
315
|
schema: []
|
|
312
316
|
},
|
|
@@ -326,7 +330,7 @@ var ensure_use_callback_has_non_empty_deps_default = createRule({
|
|
|
326
330
|
const [cb, deps] = node.arguments;
|
|
327
331
|
if (!deps) {
|
|
328
332
|
context.report({
|
|
329
|
-
messageId: "
|
|
333
|
+
messageId: "ensureUseCallbackHasNonEmptyDeps",
|
|
330
334
|
node
|
|
331
335
|
});
|
|
332
336
|
return;
|
|
@@ -344,7 +348,7 @@ var ensure_use_callback_has_non_empty_deps_default = createRule({
|
|
|
344
348
|
if (!hasEmptyDeps) return;
|
|
345
349
|
if (!cb) {
|
|
346
350
|
context.report({
|
|
347
|
-
messageId: "
|
|
351
|
+
messageId: "ensureUseCallbackHasNonEmptyDeps",
|
|
348
352
|
node
|
|
349
353
|
});
|
|
350
354
|
return;
|
|
@@ -368,7 +372,7 @@ var ensure_use_callback_has_non_empty_deps_default = createRule({
|
|
|
368
372
|
);
|
|
369
373
|
if (isReferencedToComponentScope) return;
|
|
370
374
|
context.report({
|
|
371
|
-
messageId: "
|
|
375
|
+
messageId: "ensureUseCallbackHasNonEmptyDeps",
|
|
372
376
|
node
|
|
373
377
|
});
|
|
374
378
|
}
|
|
@@ -384,7 +388,7 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
|
|
|
384
388
|
description: "enforce 'useMemo' has non-empty dependencies array"
|
|
385
389
|
},
|
|
386
390
|
messages: {
|
|
387
|
-
|
|
391
|
+
ensureUseMemoHasNonEmptyDeps: "An 'useMemo' should have a non-empty dependencies array."
|
|
388
392
|
},
|
|
389
393
|
schema: []
|
|
390
394
|
},
|
|
@@ -404,7 +408,7 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
|
|
|
404
408
|
const [cb, deps] = node.arguments;
|
|
405
409
|
if (!deps) {
|
|
406
410
|
context.report({
|
|
407
|
-
messageId: "
|
|
411
|
+
messageId: "ensureUseMemoHasNonEmptyDeps",
|
|
408
412
|
node
|
|
409
413
|
});
|
|
410
414
|
return;
|
|
@@ -422,7 +426,7 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
|
|
|
422
426
|
if (!hasEmptyDeps) return;
|
|
423
427
|
if (!cb) {
|
|
424
428
|
context.report({
|
|
425
|
-
messageId: "
|
|
429
|
+
messageId: "ensureUseMemoHasNonEmptyDeps",
|
|
426
430
|
node
|
|
427
431
|
});
|
|
428
432
|
return;
|
|
@@ -446,7 +450,7 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
|
|
|
446
450
|
);
|
|
447
451
|
if (isReferencedToComponentScope) return;
|
|
448
452
|
context.report({
|
|
449
|
-
messageId: "
|
|
453
|
+
messageId: "ensureUseMemoHasNonEmptyDeps",
|
|
450
454
|
node
|
|
451
455
|
});
|
|
452
456
|
}
|
|
@@ -455,16 +459,6 @@ var ensure_use_memo_has_non_empty_deps_default = createRule({
|
|
|
455
459
|
defaultOptions: []
|
|
456
460
|
});
|
|
457
461
|
var RULE_NAME4 = "no-direct-set-state-in-use-effect";
|
|
458
|
-
function isEffectFunction(context, useSomeEffectAlias) {
|
|
459
|
-
return (node) => node.parent?.type === NodeType.CallExpression && node.parent.callee !== node && isReactHookCallWithNameAlias("useEffect", context, useSomeEffectAlias)(node.parent);
|
|
460
|
-
}
|
|
461
|
-
function getCallKind(node, context, alias) {
|
|
462
|
-
const [useStateAlias, useEffectAlias] = alias;
|
|
463
|
-
return $(node).when(isReactHookCallWithNameAlias("useState", context, useStateAlias), () => "useState").when(isReactHookCallWithNameAlias("useEffect", context, useEffectAlias), () => "useEffect").when(isSetStateCall(context, useStateAlias), () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
|
|
464
|
-
}
|
|
465
|
-
function getFunctionKind(node, context, useEffectAlias) {
|
|
466
|
-
return $(node).when(isEffectFunction(context, useEffectAlias), () => "effect").when(isIIFE, () => "immediate").otherwise(() => "other");
|
|
467
|
-
}
|
|
468
462
|
var no_direct_set_state_in_use_effect_default = createRule({
|
|
469
463
|
meta: {
|
|
470
464
|
type: "problem",
|
|
@@ -472,14 +466,20 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
472
466
|
description: "disallow direct calls to the 'set' function of 'useState' in 'useEffect'"
|
|
473
467
|
},
|
|
474
468
|
messages: {
|
|
475
|
-
|
|
469
|
+
noDirectSetStateInUseEffect: "Do not call the 'set' function of 'useState' directly in 'useEffect'."
|
|
476
470
|
},
|
|
477
471
|
schema: []
|
|
478
472
|
},
|
|
479
473
|
name: RULE_NAME4,
|
|
480
474
|
create(context) {
|
|
481
475
|
const settings = decodeSettings(context.settings);
|
|
482
|
-
const
|
|
476
|
+
const additionalHooks = settings.additionalHooks ?? {};
|
|
477
|
+
const isUseEffectCall = isReactHookCallWithNameAlias("useEffect", context, additionalHooks.useEffect ?? []);
|
|
478
|
+
const isUseStateCall2 = isReactHookCallWithNameAlias("useState", context, additionalHooks.useState ?? []);
|
|
479
|
+
const isUseMemoCall2 = isReactHookCallWithNameAlias("useMemo", context, additionalHooks.useMemo ?? []);
|
|
480
|
+
const isUseCallbackCall2 = isReactHookCallWithNameAlias("useCallback", context, additionalHooks.useCallback ?? []);
|
|
481
|
+
const isSetStateCall = isSetFunctionCall(context, settings);
|
|
482
|
+
const isIdFromUseStateCall = isFromUseStateCall(context, settings);
|
|
483
483
|
const functionStack = [];
|
|
484
484
|
const effectFunctionRef = MutRef.make(null);
|
|
485
485
|
const effectFunctionIdentifiers = [];
|
|
@@ -494,9 +494,18 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
494
494
|
const onEffectFunctionExit = (node) => {
|
|
495
495
|
MutRef.update(effectFunctionRef, (current) => current === node ? null : current);
|
|
496
496
|
};
|
|
497
|
+
function isEffectFunction(node) {
|
|
498
|
+
return node.parent?.type === NodeType.CallExpression && node.parent.callee !== node && isUseEffectCall(node.parent);
|
|
499
|
+
}
|
|
500
|
+
function getCallKind(node) {
|
|
501
|
+
return $(node).when(isUseStateCall2, () => "useState").when(isUseEffectCall, () => "useEffect").when(isSetStateCall, () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
|
|
502
|
+
}
|
|
503
|
+
function getFunctionKind(node) {
|
|
504
|
+
return $(node).when(isEffectFunction, () => "effect").when(isIIFE, () => "immediate").otherwise(() => "other");
|
|
505
|
+
}
|
|
497
506
|
return {
|
|
498
507
|
":function"(node) {
|
|
499
|
-
const functionKind = getFunctionKind(node
|
|
508
|
+
const functionKind = getFunctionKind(node);
|
|
500
509
|
functionStack.push([node, functionKind]);
|
|
501
510
|
$(functionKind).with("effect", () => {
|
|
502
511
|
onEffectFunctionEnter(node);
|
|
@@ -510,7 +519,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
510
519
|
const effectFn = MutRef.get(effectFunctionRef);
|
|
511
520
|
const [parentFn, parentFnKind] = R.last(functionStack) ?? [];
|
|
512
521
|
if (parentFn?.async) return;
|
|
513
|
-
$(getCallKind(node
|
|
522
|
+
$(getCallKind(node)).with("setState", () => {
|
|
514
523
|
if (!parentFn) return;
|
|
515
524
|
if (parentFn !== effectFn && parentFnKind !== "immediate") {
|
|
516
525
|
const maybeVd = traverseUpGuard(node, isVariableDeclaratorFromHookCall);
|
|
@@ -525,7 +534,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
525
534
|
return;
|
|
526
535
|
}
|
|
527
536
|
context.report({
|
|
528
|
-
messageId: "
|
|
537
|
+
messageId: "noDirectSetStateInUseEffect",
|
|
529
538
|
node
|
|
530
539
|
});
|
|
531
540
|
}).with("useEffect", () => {
|
|
@@ -541,19 +550,20 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
541
550
|
},
|
|
542
551
|
Identifier(node) {
|
|
543
552
|
if (!node.parent) return;
|
|
544
|
-
if (
|
|
553
|
+
if (node.parent.type === NodeType.CallExpression && node.parent.callee === node) return;
|
|
554
|
+
if (!isIdFromUseStateCall(node)) return;
|
|
545
555
|
switch (node.parent.type) {
|
|
546
556
|
case NodeType.CallExpression: {
|
|
547
557
|
const [firstArg] = node.parent.arguments;
|
|
548
558
|
if (node !== firstArg) break;
|
|
549
|
-
if (
|
|
559
|
+
if (isUseCallbackCall2(node.parent)) {
|
|
550
560
|
const maybeVd = traverseUpGuard(node.parent, isVariableDeclaratorFromHookCall);
|
|
551
561
|
if (O.isNone(maybeVd)) break;
|
|
552
562
|
const vd = maybeVd.value;
|
|
553
563
|
const calls = indirectSetStateCallsAsArgs.get(vd.init) ?? [];
|
|
554
564
|
indirectSetStateCallsAsArgs.set(vd.init, [...calls, node]);
|
|
555
565
|
}
|
|
556
|
-
if (
|
|
566
|
+
if (isUseEffectCall(node.parent)) {
|
|
557
567
|
const calls = indirectSetStateCallsAsArgs.get(node.parent) ?? [];
|
|
558
568
|
indirectSetStateCallsAsEFs.set(node.parent, [...calls, node]);
|
|
559
569
|
}
|
|
@@ -562,7 +572,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
562
572
|
case NodeType.ArrowFunctionExpression: {
|
|
563
573
|
const parent = node.parent.parent;
|
|
564
574
|
if (parent?.type !== NodeType.CallExpression) break;
|
|
565
|
-
if (!
|
|
575
|
+
if (!isUseMemoCall2(parent)) break;
|
|
566
576
|
const maybeVd = traverseUpGuard(parent, isVariableDeclaratorFromHookCall);
|
|
567
577
|
if (O.isNone(maybeVd)) break;
|
|
568
578
|
const vd = maybeVd.value;
|
|
@@ -588,7 +598,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
588
598
|
for (const call of calls) {
|
|
589
599
|
context.report({
|
|
590
600
|
data: { name: call.name },
|
|
591
|
-
messageId: "
|
|
601
|
+
messageId: "noDirectSetStateInUseEffect",
|
|
592
602
|
node: call
|
|
593
603
|
});
|
|
594
604
|
}
|
|
@@ -600,7 +610,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
600
610
|
for (const setStateCall of setStateCalls) {
|
|
601
611
|
context.report({
|
|
602
612
|
data: { name: name2 },
|
|
603
|
-
messageId: "
|
|
613
|
+
messageId: "noDirectSetStateInUseEffect",
|
|
604
614
|
node: setStateCall
|
|
605
615
|
});
|
|
606
616
|
}
|
|
@@ -610,7 +620,7 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
610
620
|
for (const setStateCall of setStateCalls) {
|
|
611
621
|
context.report({
|
|
612
622
|
data: { name: id.name },
|
|
613
|
-
messageId: "
|
|
623
|
+
messageId: "noDirectSetStateInUseEffect",
|
|
614
624
|
node: setStateCall
|
|
615
625
|
});
|
|
616
626
|
}
|
|
@@ -621,16 +631,6 @@ var no_direct_set_state_in_use_effect_default = createRule({
|
|
|
621
631
|
defaultOptions: []
|
|
622
632
|
});
|
|
623
633
|
var RULE_NAME5 = "no-direct-set-state-in-use-layout-effect";
|
|
624
|
-
function isEffectFunction2(context, useSomeEffectAlias) {
|
|
625
|
-
return (node) => node.parent?.type === NodeType.CallExpression && node.parent.callee !== node && isReactHookCallWithNameAlias("useLayoutEffect", context, useSomeEffectAlias)(node.parent);
|
|
626
|
-
}
|
|
627
|
-
function getCallKind2(node, context, alias) {
|
|
628
|
-
const [useStateAlias, useLayoutEffectAlias] = alias;
|
|
629
|
-
return $(node).when(isReactHookCallWithNameAlias("useState", context, useStateAlias), () => "useState").when(isReactHookCallWithNameAlias("useLayoutEffect", context, useLayoutEffectAlias), () => "useLayoutEffect").when(isSetStateCall(context, useStateAlias), () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
|
|
630
|
-
}
|
|
631
|
-
function getFunctionKind2(node, context, useLayoutEffectAlias) {
|
|
632
|
-
return $(node).when(isEffectFunction2(context, useLayoutEffectAlias), () => "effect").when(isIIFE, () => "immediate").otherwise(() => "other");
|
|
633
|
-
}
|
|
634
634
|
var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
635
635
|
meta: {
|
|
636
636
|
type: "problem",
|
|
@@ -638,14 +638,24 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
638
638
|
description: "disallow direct calls to the 'set' function of 'useState' in 'useLayoutEffect'"
|
|
639
639
|
},
|
|
640
640
|
messages: {
|
|
641
|
-
|
|
641
|
+
noDirectSetStateInUseLayoutEffect: "Do not call the 'set' function of 'useState' directly in 'useLayoutEffect'."
|
|
642
642
|
},
|
|
643
643
|
schema: []
|
|
644
644
|
},
|
|
645
645
|
name: RULE_NAME5,
|
|
646
646
|
create(context) {
|
|
647
647
|
const settings = decodeSettings(context.settings);
|
|
648
|
-
const
|
|
648
|
+
const additionalHooks = settings.additionalHooks ?? {};
|
|
649
|
+
const isUseEffectCall = isReactHookCallWithNameAlias(
|
|
650
|
+
"useLayoutEffect",
|
|
651
|
+
context,
|
|
652
|
+
additionalHooks.useLayoutEffect ?? []
|
|
653
|
+
);
|
|
654
|
+
const isUseStateCall2 = isReactHookCallWithNameAlias("useState", context, additionalHooks.useState ?? []);
|
|
655
|
+
const isUseMemoCall2 = isReactHookCallWithNameAlias("useMemo", context, additionalHooks.useMemo ?? []);
|
|
656
|
+
const isUseCallbackCall2 = isReactHookCallWithNameAlias("useCallback", context, additionalHooks.useCallback ?? []);
|
|
657
|
+
const isSetStateCall = isSetFunctionCall(context, settings);
|
|
658
|
+
const isIdFromUseStateCall = isFromUseStateCall(context, settings);
|
|
649
659
|
const functionStack = [];
|
|
650
660
|
const effectFunctionRef = MutRef.make(null);
|
|
651
661
|
const effectFunctionIdentifiers = [];
|
|
@@ -660,9 +670,18 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
660
670
|
const onEffectFunctionExit = (node) => {
|
|
661
671
|
MutRef.update(effectFunctionRef, (current) => current === node ? null : current);
|
|
662
672
|
};
|
|
673
|
+
function isEffectFunction(node) {
|
|
674
|
+
return node.parent?.type === NodeType.CallExpression && node.parent.callee !== node && isUseEffectCall(node.parent);
|
|
675
|
+
}
|
|
676
|
+
function getCallKind(node) {
|
|
677
|
+
return $(node).when(isUseStateCall2, () => "useState").when(isUseEffectCall, () => "useLayoutEffect").when(isSetStateCall, () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
|
|
678
|
+
}
|
|
679
|
+
function getFunctionKind(node) {
|
|
680
|
+
return $(node).when(isEffectFunction, () => "effect").when(isIIFE, () => "immediate").otherwise(() => "other");
|
|
681
|
+
}
|
|
663
682
|
return {
|
|
664
683
|
":function"(node) {
|
|
665
|
-
const functionKind =
|
|
684
|
+
const functionKind = getFunctionKind(node);
|
|
666
685
|
functionStack.push([node, functionKind]);
|
|
667
686
|
$(functionKind).with("effect", () => {
|
|
668
687
|
onEffectFunctionEnter(node);
|
|
@@ -676,7 +695,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
676
695
|
const effectFn = MutRef.get(effectFunctionRef);
|
|
677
696
|
const [parentFn, parentFnKind] = R.last(functionStack) ?? [];
|
|
678
697
|
if (parentFn?.async) return;
|
|
679
|
-
$(
|
|
698
|
+
$(getCallKind(node)).with("setState", () => {
|
|
680
699
|
if (!parentFn) return;
|
|
681
700
|
if (parentFn !== effectFn && parentFnKind !== "immediate") {
|
|
682
701
|
const maybeVd = traverseUpGuard(node, isVariableDeclaratorFromHookCall);
|
|
@@ -691,7 +710,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
691
710
|
return;
|
|
692
711
|
}
|
|
693
712
|
context.report({
|
|
694
|
-
messageId: "
|
|
713
|
+
messageId: "noDirectSetStateInUseLayoutEffect",
|
|
695
714
|
node
|
|
696
715
|
});
|
|
697
716
|
}).with("useLayoutEffect", () => {
|
|
@@ -707,19 +726,20 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
707
726
|
},
|
|
708
727
|
Identifier(node) {
|
|
709
728
|
if (!node.parent) return;
|
|
710
|
-
if (
|
|
729
|
+
if (node.parent.type === NodeType.CallExpression && node.parent.callee === node) return;
|
|
730
|
+
if (!isIdFromUseStateCall(node)) return;
|
|
711
731
|
switch (node.parent.type) {
|
|
712
732
|
case NodeType.CallExpression: {
|
|
713
733
|
const [firstArg] = node.parent.arguments;
|
|
714
734
|
if (node !== firstArg) break;
|
|
715
|
-
if (
|
|
735
|
+
if (isUseCallbackCall2(node.parent)) {
|
|
716
736
|
const maybeVd = traverseUpGuard(node.parent, isVariableDeclaratorFromHookCall);
|
|
717
737
|
if (O.isNone(maybeVd)) break;
|
|
718
738
|
const vd = maybeVd.value;
|
|
719
739
|
const calls = indirectSetStateCallsAsArgs.get(vd.init) ?? [];
|
|
720
740
|
indirectSetStateCallsAsArgs.set(vd.init, [...calls, node]);
|
|
721
741
|
}
|
|
722
|
-
if (
|
|
742
|
+
if (isUseEffectCall(node.parent)) {
|
|
723
743
|
const calls = indirectSetStateCallsAsArgs.get(node.parent) ?? [];
|
|
724
744
|
indirectSetStateCallsAsEFs.set(node.parent, [...calls, node]);
|
|
725
745
|
}
|
|
@@ -728,7 +748,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
728
748
|
case NodeType.ArrowFunctionExpression: {
|
|
729
749
|
const parent = node.parent.parent;
|
|
730
750
|
if (parent?.type !== NodeType.CallExpression) break;
|
|
731
|
-
if (!
|
|
751
|
+
if (!isUseMemoCall2(parent)) break;
|
|
732
752
|
const maybeVd = traverseUpGuard(parent, isVariableDeclaratorFromHookCall);
|
|
733
753
|
if (O.isNone(maybeVd)) break;
|
|
734
754
|
const vd = maybeVd.value;
|
|
@@ -754,7 +774,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
754
774
|
for (const call of calls) {
|
|
755
775
|
context.report({
|
|
756
776
|
data: { name: call.name },
|
|
757
|
-
messageId: "
|
|
777
|
+
messageId: "noDirectSetStateInUseLayoutEffect",
|
|
758
778
|
node: call
|
|
759
779
|
});
|
|
760
780
|
}
|
|
@@ -766,7 +786,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
766
786
|
for (const setStateCall of setStateCalls) {
|
|
767
787
|
context.report({
|
|
768
788
|
data: { name: name2 },
|
|
769
|
-
messageId: "
|
|
789
|
+
messageId: "noDirectSetStateInUseLayoutEffect",
|
|
770
790
|
node: setStateCall
|
|
771
791
|
});
|
|
772
792
|
}
|
|
@@ -776,7 +796,7 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
776
796
|
for (const setStateCall of setStateCalls) {
|
|
777
797
|
context.report({
|
|
778
798
|
data: { name: id.name },
|
|
779
|
-
messageId: "
|
|
799
|
+
messageId: "noDirectSetStateInUseLayoutEffect",
|
|
780
800
|
node: setStateCall
|
|
781
801
|
});
|
|
782
802
|
}
|
|
@@ -795,7 +815,7 @@ var prefer_use_state_lazy_initialization_default = createRule({
|
|
|
795
815
|
description: "disallow function calls in 'useState' that aren't wrapped in an initializer function"
|
|
796
816
|
},
|
|
797
817
|
messages: {
|
|
798
|
-
|
|
818
|
+
preferUseStateLazyInitialization: "To prevent re-computation, consider using lazy initial state for useState calls that involve function calls. Ex: 'useState(() => getValue())'."
|
|
799
819
|
},
|
|
800
820
|
schema: []
|
|
801
821
|
},
|
|
@@ -814,7 +834,7 @@ var prefer_use_state_lazy_initialization_default = createRule({
|
|
|
814
834
|
});
|
|
815
835
|
if (!hasFunctionCall) return;
|
|
816
836
|
context.report({
|
|
817
|
-
messageId: "
|
|
837
|
+
messageId: "preferUseStateLazyInitialization",
|
|
818
838
|
node: useStateInput
|
|
819
839
|
});
|
|
820
840
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-react-hooks-extra",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.3-next.1",
|
|
4
4
|
"description": "ESLint React's ESLint plugin for React Hooks related rules.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -46,13 +46,13 @@
|
|
|
46
46
|
"@typescript-eslint/types": "^8.0.0",
|
|
47
47
|
"@typescript-eslint/utils": "^8.0.0",
|
|
48
48
|
"remeda": "^2.7.0",
|
|
49
|
-
"@eslint-react/ast": "1.8.
|
|
50
|
-
"@eslint-react/core": "1.8.
|
|
51
|
-
"@eslint-react/
|
|
52
|
-
"@eslint-react/
|
|
53
|
-
"@eslint-react/
|
|
54
|
-
"@eslint-react/var": "1.8.
|
|
55
|
-
"@eslint-react/types": "1.8.
|
|
49
|
+
"@eslint-react/ast": "1.8.3-next.1",
|
|
50
|
+
"@eslint-react/core": "1.8.3-next.1",
|
|
51
|
+
"@eslint-react/shared": "1.8.3-next.1",
|
|
52
|
+
"@eslint-react/jsx": "1.8.3-next.1",
|
|
53
|
+
"@eslint-react/tools": "1.8.3-next.1",
|
|
54
|
+
"@eslint-react/var": "1.8.3-next.1",
|
|
55
|
+
"@eslint-react/types": "1.8.3-next.1"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/react": "^18.3.3",
|