eslint-plugin-react-hooks-extra 1.5.28-beta.1 → 1.5.28-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/dist/index.js +319 -116
- package/dist/index.mjs +321 -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.2";
|
|
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,159 @@ 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) {
|
|
402
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");
|
|
468
|
+
}
|
|
469
|
+
const useEffectCallRef = tools.MutRef.make(null);
|
|
470
|
+
const callStack = tools.MutList.make();
|
|
471
|
+
const functionStack = tools.MutList.make();
|
|
472
|
+
const effectFunctionRef = tools.MutRef.make(null);
|
|
473
|
+
const cleanUpFunctionRef = tools.MutRef.make(null);
|
|
474
|
+
const indirectFunctionCalls = [];
|
|
475
|
+
const indirectSetStateCalls = /* @__PURE__ */ new Map();
|
|
476
|
+
const onUseEffectCallEnter = (node) => void tools.MutRef.set(useEffectCallRef, node);
|
|
477
|
+
const onUseEffectCallExit = () => void tools.MutRef.set(useEffectCallRef, null);
|
|
403
478
|
return {
|
|
479
|
+
Identifier(node) {
|
|
480
|
+
const isInUseEffectCall = tools.MutRef.get(useEffectCallRef) !== null;
|
|
481
|
+
const parentFn = tools.MutList.tail(functionStack)?.[0];
|
|
482
|
+
const useEffectCall = tools.MutRef.get(useEffectCallRef);
|
|
483
|
+
const isEffectFunction2 = useEffectCall && tools.O.isSome(ast.traverseUp(useEffectCall, (n2) => n2 === parentFn));
|
|
484
|
+
if (isFromUseStateCall(node) && isInUseEffectCall && isEffectFunction2) {
|
|
485
|
+
context.report({
|
|
486
|
+
node,
|
|
487
|
+
messageId: "NO_DIRECT_SET_STATE_IN_USE_EFFECT"
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
},
|
|
404
491
|
CallExpression(node) {
|
|
405
|
-
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
if (
|
|
409
|
-
const
|
|
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
|
-
tools.
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
492
|
+
tools.MutList.append(callStack, node);
|
|
493
|
+
const effectFn = tools.MutRef.get(effectFunctionRef);
|
|
494
|
+
const [parentFn, parentFnKind] = tools.MutList.tail(functionStack) ?? [];
|
|
495
|
+
if (parentFn?.async) return;
|
|
496
|
+
const callKind = getCallKind(node);
|
|
497
|
+
$(callKind).with("setState", () => {
|
|
498
|
+
if (!parentFn) return;
|
|
499
|
+
if (parentFn !== effectFn && parentFnKind !== "immediate") {
|
|
500
|
+
indirectSetStateCalls.set(parentFn, [...indirectSetStateCalls.get(parentFn) ?? [], node]);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
context.report({
|
|
504
|
+
node,
|
|
505
|
+
messageId: "NO_DIRECT_SET_STATE_IN_USE_EFFECT"
|
|
506
|
+
});
|
|
507
|
+
}).with("useEffect", () => {
|
|
508
|
+
onUseEffectCallEnter(node);
|
|
509
|
+
}).with("useState", () => {
|
|
510
|
+
}).with("then", () => {
|
|
511
|
+
}).with("other", () => {
|
|
512
|
+
indirectFunctionCalls.push(node);
|
|
513
|
+
}).exhaustive();
|
|
514
|
+
},
|
|
515
|
+
":function"(node) {
|
|
516
|
+
const functionKind = getFunctionKind(node);
|
|
517
|
+
tools.MutList.append(functionStack, [node, functionKind]);
|
|
518
|
+
$(functionKind).with("effect", () => {
|
|
519
|
+
tools.MutRef.set(effectFunctionRef, node);
|
|
520
|
+
}).with("cleanup", () => {
|
|
521
|
+
tools.MutRef.set(cleanUpFunctionRef, node);
|
|
522
|
+
}).otherwise(() => {
|
|
523
|
+
});
|
|
524
|
+
},
|
|
525
|
+
":function:exit"(node) {
|
|
526
|
+
const effectFn = tools.MutRef.get(effectFunctionRef);
|
|
527
|
+
if (effectFn === node) {
|
|
528
|
+
tools.MutRef.set(effectFunctionRef, null);
|
|
529
|
+
}
|
|
530
|
+
tools.MutList.pop(functionStack);
|
|
531
|
+
},
|
|
532
|
+
"CallExpression:exit"(node) {
|
|
533
|
+
tools.MutList.pop(callStack);
|
|
534
|
+
if (tools.MutRef.get(useEffectCallRef) === node) {
|
|
535
|
+
onUseEffectCallExit();
|
|
536
|
+
tools.MutRef.set(useEffectCallRef, null);
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
"Program:exit"() {
|
|
540
|
+
for (const call of indirectFunctionCalls) {
|
|
541
|
+
if (!("name" in call.callee)) continue;
|
|
542
|
+
const { name: name2 } = call.callee;
|
|
543
|
+
const setStateCalls = tools.F.pipe(
|
|
544
|
+
_var.findVariable(name2, context.sourceCode.getScope(call)),
|
|
545
|
+
tools.O.flatMap(_var.getVariableInit(0)),
|
|
546
|
+
tools.O.filter(ast.isFunction),
|
|
547
|
+
tools.O.flatMapNullable((init) => indirectSetStateCalls.get(init)),
|
|
548
|
+
tools.O.getOrElse(() => [])
|
|
549
|
+
);
|
|
550
|
+
for (const setStateCall of setStateCalls) {
|
|
551
|
+
context.report({
|
|
552
|
+
data: {
|
|
553
|
+
name: name2
|
|
554
|
+
},
|
|
555
|
+
messageId: "NO_DIRECT_SET_STATE_IN_USE_EFFECT",
|
|
556
|
+
node: setStateCall
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
}
|
|
452
560
|
}
|
|
453
561
|
};
|
|
454
562
|
},
|
|
@@ -470,64 +578,159 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
470
578
|
create(context) {
|
|
471
579
|
const settings = shared.getESLintReactSettings(context.settings);
|
|
472
580
|
const { useLayoutEffect: useLayoutEffectAlias = [], useState: useStateAlias = [] } = settings.additionalHooks ?? {};
|
|
473
|
-
function isUseLayoutEffectCallWithAlias(node
|
|
474
|
-
return core.isUseLayoutEffectCall(node,
|
|
581
|
+
function isUseLayoutEffectCallWithAlias(node) {
|
|
582
|
+
return core.isUseLayoutEffectCall(node, context) || useLayoutEffectAlias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node));
|
|
475
583
|
}
|
|
476
|
-
function isUseStateCallWithAlias(node
|
|
477
|
-
return core.isUseStateCall(node,
|
|
584
|
+
function isUseStateCallWithAlias(node) {
|
|
585
|
+
return core.isUseStateCall(node, context) || useStateAlias.some(tools.F.flip(core.isReactHookCallWithNameLoose)(node));
|
|
478
586
|
}
|
|
479
587
|
function isEffectFunction(node) {
|
|
480
|
-
return node.parent?.type === ast.NodeType.CallExpression && isUseLayoutEffectCallWithAlias(node.parent
|
|
588
|
+
return node.parent?.type === ast.NodeType.CallExpression && node.parent.callee !== node && isUseLayoutEffectCallWithAlias(node.parent);
|
|
589
|
+
}
|
|
590
|
+
function isCleanUpFunction(_2) {
|
|
591
|
+
}
|
|
592
|
+
function isFromUseStateCall(id) {
|
|
593
|
+
return tools.F.pipe(
|
|
594
|
+
$(id).with({ type: ast.NodeType.Identifier }, (n2) => tools.O.some(n2.name)).with({
|
|
595
|
+
type: ast.NodeType.MemberExpression,
|
|
596
|
+
object: { type: ast.NodeType.Identifier }
|
|
597
|
+
}, (n2) => tools.O.some(n2.object.name)).otherwise(tools.O.none),
|
|
598
|
+
tools.O.flatMap(_var.findVariable(context.sourceCode.getScope(id))),
|
|
599
|
+
tools.O.flatMap(_var.getVariableInit(0)),
|
|
600
|
+
tools.O.filter(ast.is(ast.NodeType.CallExpression)),
|
|
601
|
+
tools.O.exists(isUseStateCallWithAlias)
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
function isSetStateCall(node) {
|
|
605
|
+
const id = $(node.callee).with({ type: ast.NodeType.Identifier }, tools.O.some).with({ type: ast.NodeType.MemberExpression }, (n2) => {
|
|
606
|
+
if (!("name" in n2.object)) return tools.O.none();
|
|
607
|
+
const initialScope = context.sourceCode.getScope(n2);
|
|
608
|
+
const property = astUtils.getStaticValue(n2.property, initialScope);
|
|
609
|
+
if (property?.value === 1) return tools.O.fromNullable(n2.object);
|
|
610
|
+
return tools.O.none();
|
|
611
|
+
}).with({ type: ast.NodeType.CallExpression }, (n2) => {
|
|
612
|
+
if (!ast.is(ast.NodeType.MemberExpression)(n2.callee)) return tools.O.none();
|
|
613
|
+
if (!("name" in n2.callee.object)) return tools.O.none();
|
|
614
|
+
const isAt = a({
|
|
615
|
+
type: ast.NodeType.MemberExpression,
|
|
616
|
+
property: {
|
|
617
|
+
type: ast.NodeType.Identifier,
|
|
618
|
+
name: "at"
|
|
619
|
+
}
|
|
620
|
+
}, n2.callee);
|
|
621
|
+
const [index] = n2.arguments;
|
|
622
|
+
if (!isAt || !index) return tools.O.none();
|
|
623
|
+
const initialScope = context.sourceCode.getScope(n2);
|
|
624
|
+
const value = astUtils.getStaticValue(index, initialScope);
|
|
625
|
+
if (value?.value === 1) return tools.O.fromNullable(n2.callee.object);
|
|
626
|
+
return tools.O.none();
|
|
627
|
+
}).otherwise(tools.O.none);
|
|
628
|
+
return tools.O.exists(id, isFromUseStateCall);
|
|
629
|
+
}
|
|
630
|
+
function isThenCall(node) {
|
|
631
|
+
if (node.callee.type !== ast.NodeType.MemberExpression) return false;
|
|
632
|
+
return a({
|
|
633
|
+
type: ast.NodeType.Identifier,
|
|
634
|
+
name: "then"
|
|
635
|
+
}, node.callee.property);
|
|
481
636
|
}
|
|
637
|
+
function getCallKind(node) {
|
|
638
|
+
return $(node).when(isThenCall, () => "then").when(isSetStateCall, () => "setState").when(isUseStateCallWithAlias, () => "useState").when(isUseLayoutEffectCallWithAlias, () => "useLayoutEffect").otherwise(() => "other");
|
|
639
|
+
}
|
|
640
|
+
function getFunctionKind(node) {
|
|
641
|
+
return $(node).when(isEffectFunction, () => "effect").when(isCleanUpFunction, () => "cleanup").when(ast.isIIFE, () => "immediate").otherwise(() => "other");
|
|
642
|
+
}
|
|
643
|
+
const useLayoutEffectCallRef = tools.MutRef.make(null);
|
|
644
|
+
const callStack = tools.MutList.make();
|
|
645
|
+
const functionStack = tools.MutList.make();
|
|
646
|
+
const effectFunctionRef = tools.MutRef.make(null);
|
|
647
|
+
const cleanUpFunctionRef = tools.MutRef.make(null);
|
|
648
|
+
const indirectFunctionCalls = [];
|
|
649
|
+
const indirectSetStateCalls = /* @__PURE__ */ new Map();
|
|
650
|
+
const onUseLayoutEffectCallEnter = (node) => void tools.MutRef.set(useLayoutEffectCallRef, node);
|
|
651
|
+
const onUseLayoutEffectCallExit = () => void tools.MutRef.set(useLayoutEffectCallRef, null);
|
|
482
652
|
return {
|
|
653
|
+
Identifier(node) {
|
|
654
|
+
const isInUseLayoutEffectCall = tools.MutRef.get(useLayoutEffectCallRef) !== null;
|
|
655
|
+
const parentFn = tools.MutList.tail(functionStack)?.[0];
|
|
656
|
+
const useLayoutEffectCall = tools.MutRef.get(useLayoutEffectCallRef);
|
|
657
|
+
const isEffectFunction2 = useLayoutEffectCall && tools.O.isSome(ast.traverseUp(useLayoutEffectCall, (n2) => n2 === parentFn));
|
|
658
|
+
if (isFromUseStateCall(node) && isInUseLayoutEffectCall && isEffectFunction2) {
|
|
659
|
+
context.report({
|
|
660
|
+
node,
|
|
661
|
+
messageId: "NO_DIRECT_SET_STATE_IN_USE_LAYOUT_EFFECT"
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
},
|
|
483
665
|
CallExpression(node) {
|
|
484
|
-
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
if (
|
|
488
|
-
const
|
|
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
|
-
tools.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
666
|
+
tools.MutList.append(callStack, node);
|
|
667
|
+
const effectFn = tools.MutRef.get(effectFunctionRef);
|
|
668
|
+
const [parentFn, parentFnKind] = tools.MutList.tail(functionStack) ?? [];
|
|
669
|
+
if (parentFn?.async) return;
|
|
670
|
+
const callKind = getCallKind(node);
|
|
671
|
+
$(callKind).with("setState", () => {
|
|
672
|
+
if (!parentFn) return;
|
|
673
|
+
if (parentFn !== effectFn && parentFnKind !== "immediate") {
|
|
674
|
+
indirectSetStateCalls.set(parentFn, [...indirectSetStateCalls.get(parentFn) ?? [], node]);
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
context.report({
|
|
678
|
+
node,
|
|
679
|
+
messageId: "NO_DIRECT_SET_STATE_IN_USE_LAYOUT_EFFECT"
|
|
680
|
+
});
|
|
681
|
+
}).with("useLayoutEffect", () => {
|
|
682
|
+
onUseLayoutEffectCallEnter(node);
|
|
683
|
+
}).with("useState", () => {
|
|
684
|
+
}).with("then", () => {
|
|
685
|
+
}).with("other", () => {
|
|
686
|
+
indirectFunctionCalls.push(node);
|
|
687
|
+
}).exhaustive();
|
|
688
|
+
},
|
|
689
|
+
":function"(node) {
|
|
690
|
+
const functionKind = getFunctionKind(node);
|
|
691
|
+
tools.MutList.append(functionStack, [node, functionKind]);
|
|
692
|
+
$(functionKind).with("effect", () => {
|
|
693
|
+
tools.MutRef.set(effectFunctionRef, node);
|
|
694
|
+
}).with("cleanup", () => {
|
|
695
|
+
tools.MutRef.set(cleanUpFunctionRef, node);
|
|
696
|
+
}).otherwise(() => {
|
|
697
|
+
});
|
|
698
|
+
},
|
|
699
|
+
":function:exit"(node) {
|
|
700
|
+
const effectFn = tools.MutRef.get(effectFunctionRef);
|
|
701
|
+
if (effectFn === node) {
|
|
702
|
+
tools.MutRef.set(effectFunctionRef, null);
|
|
703
|
+
}
|
|
704
|
+
tools.MutList.pop(functionStack);
|
|
705
|
+
},
|
|
706
|
+
"CallExpression:exit"(node) {
|
|
707
|
+
tools.MutList.pop(callStack);
|
|
708
|
+
if (tools.MutRef.get(useLayoutEffectCallRef) === node) {
|
|
709
|
+
onUseLayoutEffectCallExit();
|
|
710
|
+
tools.MutRef.set(useLayoutEffectCallRef, null);
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
"Program:exit"() {
|
|
714
|
+
for (const call of indirectFunctionCalls) {
|
|
715
|
+
if (!("name" in call.callee)) continue;
|
|
716
|
+
const { name: name2 } = call.callee;
|
|
717
|
+
const setStateCalls = tools.F.pipe(
|
|
718
|
+
_var.findVariable(name2, context.sourceCode.getScope(call)),
|
|
719
|
+
tools.O.flatMap(_var.getVariableInit(0)),
|
|
720
|
+
tools.O.filter(ast.isFunction),
|
|
721
|
+
tools.O.flatMapNullable((init) => indirectSetStateCalls.get(init)),
|
|
722
|
+
tools.O.getOrElse(() => [])
|
|
723
|
+
);
|
|
724
|
+
for (const setStateCall of setStateCalls) {
|
|
725
|
+
context.report({
|
|
726
|
+
data: {
|
|
727
|
+
name: name2
|
|
728
|
+
},
|
|
729
|
+
messageId: "NO_DIRECT_SET_STATE_IN_USE_LAYOUT_EFFECT",
|
|
730
|
+
node: setStateCall
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}
|
|
531
734
|
}
|
|
532
735
|
};
|
|
533
736
|
},
|
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.2";
|
|
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,159 @@ 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) {
|
|
400
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");
|
|
466
|
+
}
|
|
467
|
+
const useEffectCallRef = MutRef.make(null);
|
|
468
|
+
const callStack = MutList.make();
|
|
469
|
+
const functionStack = MutList.make();
|
|
470
|
+
const effectFunctionRef = MutRef.make(null);
|
|
471
|
+
const cleanUpFunctionRef = MutRef.make(null);
|
|
472
|
+
const indirectFunctionCalls = [];
|
|
473
|
+
const indirectSetStateCalls = /* @__PURE__ */ new Map();
|
|
474
|
+
const onUseEffectCallEnter = (node) => void MutRef.set(useEffectCallRef, node);
|
|
475
|
+
const onUseEffectCallExit = () => void MutRef.set(useEffectCallRef, null);
|
|
401
476
|
return {
|
|
477
|
+
Identifier(node) {
|
|
478
|
+
const isInUseEffectCall = MutRef.get(useEffectCallRef) !== null;
|
|
479
|
+
const parentFn = MutList.tail(functionStack)?.[0];
|
|
480
|
+
const useEffectCall = MutRef.get(useEffectCallRef);
|
|
481
|
+
const isEffectFunction2 = useEffectCall && O.isSome(traverseUp(useEffectCall, (n2) => n2 === parentFn));
|
|
482
|
+
if (isFromUseStateCall(node) && isInUseEffectCall && isEffectFunction2) {
|
|
483
|
+
context.report({
|
|
484
|
+
node,
|
|
485
|
+
messageId: "NO_DIRECT_SET_STATE_IN_USE_EFFECT"
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
},
|
|
402
489
|
CallExpression(node) {
|
|
403
|
-
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
if (
|
|
407
|
-
const
|
|
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
|
-
|
|
490
|
+
MutList.append(callStack, node);
|
|
491
|
+
const effectFn = MutRef.get(effectFunctionRef);
|
|
492
|
+
const [parentFn, parentFnKind] = MutList.tail(functionStack) ?? [];
|
|
493
|
+
if (parentFn?.async) return;
|
|
494
|
+
const callKind = getCallKind(node);
|
|
495
|
+
$(callKind).with("setState", () => {
|
|
496
|
+
if (!parentFn) return;
|
|
497
|
+
if (parentFn !== effectFn && parentFnKind !== "immediate") {
|
|
498
|
+
indirectSetStateCalls.set(parentFn, [...indirectSetStateCalls.get(parentFn) ?? [], node]);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
context.report({
|
|
502
|
+
node,
|
|
503
|
+
messageId: "NO_DIRECT_SET_STATE_IN_USE_EFFECT"
|
|
504
|
+
});
|
|
505
|
+
}).with("useEffect", () => {
|
|
506
|
+
onUseEffectCallEnter(node);
|
|
507
|
+
}).with("useState", () => {
|
|
508
|
+
}).with("then", () => {
|
|
509
|
+
}).with("other", () => {
|
|
510
|
+
indirectFunctionCalls.push(node);
|
|
511
|
+
}).exhaustive();
|
|
512
|
+
},
|
|
513
|
+
":function"(node) {
|
|
514
|
+
const functionKind = getFunctionKind(node);
|
|
515
|
+
MutList.append(functionStack, [node, functionKind]);
|
|
516
|
+
$(functionKind).with("effect", () => {
|
|
517
|
+
MutRef.set(effectFunctionRef, node);
|
|
518
|
+
}).with("cleanup", () => {
|
|
519
|
+
MutRef.set(cleanUpFunctionRef, node);
|
|
520
|
+
}).otherwise(() => {
|
|
521
|
+
});
|
|
522
|
+
},
|
|
523
|
+
":function:exit"(node) {
|
|
524
|
+
const effectFn = MutRef.get(effectFunctionRef);
|
|
525
|
+
if (effectFn === node) {
|
|
526
|
+
MutRef.set(effectFunctionRef, null);
|
|
527
|
+
}
|
|
528
|
+
MutList.pop(functionStack);
|
|
529
|
+
},
|
|
530
|
+
"CallExpression:exit"(node) {
|
|
531
|
+
MutList.pop(callStack);
|
|
532
|
+
if (MutRef.get(useEffectCallRef) === node) {
|
|
533
|
+
onUseEffectCallExit();
|
|
534
|
+
MutRef.set(useEffectCallRef, null);
|
|
535
|
+
}
|
|
536
|
+
},
|
|
537
|
+
"Program:exit"() {
|
|
538
|
+
for (const call of indirectFunctionCalls) {
|
|
539
|
+
if (!("name" in call.callee)) continue;
|
|
540
|
+
const { name: name2 } = call.callee;
|
|
541
|
+
const setStateCalls = F.pipe(
|
|
542
|
+
findVariable(name2, context.sourceCode.getScope(call)),
|
|
543
|
+
O.flatMap(getVariableInit(0)),
|
|
544
|
+
O.filter(isFunction),
|
|
545
|
+
O.flatMapNullable((init) => indirectSetStateCalls.get(init)),
|
|
546
|
+
O.getOrElse(() => [])
|
|
547
|
+
);
|
|
548
|
+
for (const setStateCall of setStateCalls) {
|
|
549
|
+
context.report({
|
|
550
|
+
data: {
|
|
551
|
+
name: name2
|
|
552
|
+
},
|
|
553
|
+
messageId: "NO_DIRECT_SET_STATE_IN_USE_EFFECT",
|
|
554
|
+
node: setStateCall
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
}
|
|
450
558
|
}
|
|
451
559
|
};
|
|
452
560
|
},
|
|
@@ -468,64 +576,159 @@ var no_direct_set_state_in_use_layout_effect_default = createRule({
|
|
|
468
576
|
create(context) {
|
|
469
577
|
const settings = getESLintReactSettings(context.settings);
|
|
470
578
|
const { useLayoutEffect: useLayoutEffectAlias = [], useState: useStateAlias = [] } = settings.additionalHooks ?? {};
|
|
471
|
-
function isUseLayoutEffectCallWithAlias(node
|
|
472
|
-
return isUseLayoutEffectCall(node,
|
|
579
|
+
function isUseLayoutEffectCallWithAlias(node) {
|
|
580
|
+
return isUseLayoutEffectCall(node, context) || useLayoutEffectAlias.some(F.flip(isReactHookCallWithNameLoose)(node));
|
|
473
581
|
}
|
|
474
|
-
function isUseStateCallWithAlias(node
|
|
475
|
-
return isUseStateCall(node,
|
|
582
|
+
function isUseStateCallWithAlias(node) {
|
|
583
|
+
return isUseStateCall(node, context) || useStateAlias.some(F.flip(isReactHookCallWithNameLoose)(node));
|
|
476
584
|
}
|
|
477
585
|
function isEffectFunction(node) {
|
|
478
|
-
return node.parent?.type === NodeType.CallExpression && isUseLayoutEffectCallWithAlias(node.parent
|
|
586
|
+
return node.parent?.type === NodeType.CallExpression && node.parent.callee !== node && isUseLayoutEffectCallWithAlias(node.parent);
|
|
587
|
+
}
|
|
588
|
+
function isCleanUpFunction(_2) {
|
|
589
|
+
}
|
|
590
|
+
function isFromUseStateCall(id) {
|
|
591
|
+
return F.pipe(
|
|
592
|
+
$(id).with({ type: NodeType.Identifier }, (n2) => O.some(n2.name)).with({
|
|
593
|
+
type: NodeType.MemberExpression,
|
|
594
|
+
object: { type: NodeType.Identifier }
|
|
595
|
+
}, (n2) => O.some(n2.object.name)).otherwise(O.none),
|
|
596
|
+
O.flatMap(findVariable(context.sourceCode.getScope(id))),
|
|
597
|
+
O.flatMap(getVariableInit(0)),
|
|
598
|
+
O.filter(is(NodeType.CallExpression)),
|
|
599
|
+
O.exists(isUseStateCallWithAlias)
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
function isSetStateCall(node) {
|
|
603
|
+
const id = $(node.callee).with({ type: NodeType.Identifier }, O.some).with({ type: NodeType.MemberExpression }, (n2) => {
|
|
604
|
+
if (!("name" in n2.object)) return O.none();
|
|
605
|
+
const initialScope = context.sourceCode.getScope(n2);
|
|
606
|
+
const property = getStaticValue(n2.property, initialScope);
|
|
607
|
+
if (property?.value === 1) return O.fromNullable(n2.object);
|
|
608
|
+
return O.none();
|
|
609
|
+
}).with({ type: NodeType.CallExpression }, (n2) => {
|
|
610
|
+
if (!is(NodeType.MemberExpression)(n2.callee)) return O.none();
|
|
611
|
+
if (!("name" in n2.callee.object)) return O.none();
|
|
612
|
+
const isAt = a({
|
|
613
|
+
type: NodeType.MemberExpression,
|
|
614
|
+
property: {
|
|
615
|
+
type: NodeType.Identifier,
|
|
616
|
+
name: "at"
|
|
617
|
+
}
|
|
618
|
+
}, n2.callee);
|
|
619
|
+
const [index] = n2.arguments;
|
|
620
|
+
if (!isAt || !index) return O.none();
|
|
621
|
+
const initialScope = context.sourceCode.getScope(n2);
|
|
622
|
+
const value = getStaticValue(index, initialScope);
|
|
623
|
+
if (value?.value === 1) return O.fromNullable(n2.callee.object);
|
|
624
|
+
return O.none();
|
|
625
|
+
}).otherwise(O.none);
|
|
626
|
+
return O.exists(id, isFromUseStateCall);
|
|
627
|
+
}
|
|
628
|
+
function isThenCall(node) {
|
|
629
|
+
if (node.callee.type !== NodeType.MemberExpression) return false;
|
|
630
|
+
return a({
|
|
631
|
+
type: NodeType.Identifier,
|
|
632
|
+
name: "then"
|
|
633
|
+
}, node.callee.property);
|
|
479
634
|
}
|
|
635
|
+
function getCallKind(node) {
|
|
636
|
+
return $(node).when(isThenCall, () => "then").when(isSetStateCall, () => "setState").when(isUseStateCallWithAlias, () => "useState").when(isUseLayoutEffectCallWithAlias, () => "useLayoutEffect").otherwise(() => "other");
|
|
637
|
+
}
|
|
638
|
+
function getFunctionKind(node) {
|
|
639
|
+
return $(node).when(isEffectFunction, () => "effect").when(isCleanUpFunction, () => "cleanup").when(isIIFE, () => "immediate").otherwise(() => "other");
|
|
640
|
+
}
|
|
641
|
+
const useLayoutEffectCallRef = MutRef.make(null);
|
|
642
|
+
const callStack = MutList.make();
|
|
643
|
+
const functionStack = MutList.make();
|
|
644
|
+
const effectFunctionRef = MutRef.make(null);
|
|
645
|
+
const cleanUpFunctionRef = MutRef.make(null);
|
|
646
|
+
const indirectFunctionCalls = [];
|
|
647
|
+
const indirectSetStateCalls = /* @__PURE__ */ new Map();
|
|
648
|
+
const onUseLayoutEffectCallEnter = (node) => void MutRef.set(useLayoutEffectCallRef, node);
|
|
649
|
+
const onUseLayoutEffectCallExit = () => void MutRef.set(useLayoutEffectCallRef, null);
|
|
480
650
|
return {
|
|
651
|
+
Identifier(node) {
|
|
652
|
+
const isInUseLayoutEffectCall = MutRef.get(useLayoutEffectCallRef) !== null;
|
|
653
|
+
const parentFn = MutList.tail(functionStack)?.[0];
|
|
654
|
+
const useLayoutEffectCall = MutRef.get(useLayoutEffectCallRef);
|
|
655
|
+
const isEffectFunction2 = useLayoutEffectCall && O.isSome(traverseUp(useLayoutEffectCall, (n2) => n2 === parentFn));
|
|
656
|
+
if (isFromUseStateCall(node) && isInUseLayoutEffectCall && isEffectFunction2) {
|
|
657
|
+
context.report({
|
|
658
|
+
node,
|
|
659
|
+
messageId: "NO_DIRECT_SET_STATE_IN_USE_LAYOUT_EFFECT"
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
},
|
|
481
663
|
CallExpression(node) {
|
|
482
|
-
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
if (
|
|
486
|
-
const
|
|
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
|
-
|
|
664
|
+
MutList.append(callStack, node);
|
|
665
|
+
const effectFn = MutRef.get(effectFunctionRef);
|
|
666
|
+
const [parentFn, parentFnKind] = MutList.tail(functionStack) ?? [];
|
|
667
|
+
if (parentFn?.async) return;
|
|
668
|
+
const callKind = getCallKind(node);
|
|
669
|
+
$(callKind).with("setState", () => {
|
|
670
|
+
if (!parentFn) return;
|
|
671
|
+
if (parentFn !== effectFn && parentFnKind !== "immediate") {
|
|
672
|
+
indirectSetStateCalls.set(parentFn, [...indirectSetStateCalls.get(parentFn) ?? [], node]);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
context.report({
|
|
676
|
+
node,
|
|
677
|
+
messageId: "NO_DIRECT_SET_STATE_IN_USE_LAYOUT_EFFECT"
|
|
678
|
+
});
|
|
679
|
+
}).with("useLayoutEffect", () => {
|
|
680
|
+
onUseLayoutEffectCallEnter(node);
|
|
681
|
+
}).with("useState", () => {
|
|
682
|
+
}).with("then", () => {
|
|
683
|
+
}).with("other", () => {
|
|
684
|
+
indirectFunctionCalls.push(node);
|
|
685
|
+
}).exhaustive();
|
|
686
|
+
},
|
|
687
|
+
":function"(node) {
|
|
688
|
+
const functionKind = getFunctionKind(node);
|
|
689
|
+
MutList.append(functionStack, [node, functionKind]);
|
|
690
|
+
$(functionKind).with("effect", () => {
|
|
691
|
+
MutRef.set(effectFunctionRef, node);
|
|
692
|
+
}).with("cleanup", () => {
|
|
693
|
+
MutRef.set(cleanUpFunctionRef, node);
|
|
694
|
+
}).otherwise(() => {
|
|
695
|
+
});
|
|
696
|
+
},
|
|
697
|
+
":function:exit"(node) {
|
|
698
|
+
const effectFn = MutRef.get(effectFunctionRef);
|
|
699
|
+
if (effectFn === node) {
|
|
700
|
+
MutRef.set(effectFunctionRef, null);
|
|
701
|
+
}
|
|
702
|
+
MutList.pop(functionStack);
|
|
703
|
+
},
|
|
704
|
+
"CallExpression:exit"(node) {
|
|
705
|
+
MutList.pop(callStack);
|
|
706
|
+
if (MutRef.get(useLayoutEffectCallRef) === node) {
|
|
707
|
+
onUseLayoutEffectCallExit();
|
|
708
|
+
MutRef.set(useLayoutEffectCallRef, null);
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
"Program:exit"() {
|
|
712
|
+
for (const call of indirectFunctionCalls) {
|
|
713
|
+
if (!("name" in call.callee)) continue;
|
|
714
|
+
const { name: name2 } = call.callee;
|
|
715
|
+
const setStateCalls = F.pipe(
|
|
716
|
+
findVariable(name2, context.sourceCode.getScope(call)),
|
|
717
|
+
O.flatMap(getVariableInit(0)),
|
|
718
|
+
O.filter(isFunction),
|
|
719
|
+
O.flatMapNullable((init) => indirectSetStateCalls.get(init)),
|
|
720
|
+
O.getOrElse(() => [])
|
|
721
|
+
);
|
|
722
|
+
for (const setStateCall of setStateCalls) {
|
|
723
|
+
context.report({
|
|
724
|
+
data: {
|
|
725
|
+
name: name2
|
|
726
|
+
},
|
|
727
|
+
messageId: "NO_DIRECT_SET_STATE_IN_USE_LAYOUT_EFFECT",
|
|
728
|
+
node: setStateCall
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
}
|
|
529
732
|
}
|
|
530
733
|
};
|
|
531
734
|
},
|
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.2",
|
|
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/
|
|
45
|
-
"@eslint-react/shared": "1.5.28-beta.
|
|
46
|
-
"@eslint-react/
|
|
47
|
-
"@eslint-react/var": "1.5.28-beta.
|
|
48
|
-
"@eslint-react/
|
|
42
|
+
"@eslint-react/ast": "1.5.28-beta.2",
|
|
43
|
+
"@eslint-react/core": "1.5.28-beta.2",
|
|
44
|
+
"@eslint-react/tools": "1.5.28-beta.2",
|
|
45
|
+
"@eslint-react/shared": "1.5.28-beta.2",
|
|
46
|
+
"@eslint-react/types": "1.5.28-beta.2",
|
|
47
|
+
"@eslint-react/var": "1.5.28-beta.2",
|
|
48
|
+
"@eslint-react/jsx": "1.5.28-beta.2"
|
|
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",
|