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