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