Skip to content

Commit d42486f

Browse files
committed
refactor(plugins/web-api): simplify function definitions
1 parent add7ef8 commit d42486f

File tree

4 files changed

+58
-69
lines changed

4 files changed

+58
-69
lines changed

eslint.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ export default tseslint.config(
240240
partitionByComment: "^Part:.*",
241241
},
242242
],
243+
"perfectionist/sort-switch-case": "off",
243244
"perfectionist/sort-union-types": "warn",
244245
// Part: unicorn rules
245246
"unicorn/template-indent": [

packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-event-listener.ts

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -164,24 +164,7 @@ export default createRule<[], MessageID>({
164164
const aEntries: AEntry[] = [];
165165
const rEntries: REntry[] = [];
166166
const abortedSignals: TSESTree.Expression[] = [];
167-
function checkInlineFunction(
168-
node: TSESTree.CallExpression,
169-
callKind: EventMethodKind,
170-
options: typeof defaultOptions,
171-
): O.Option<ReportDescriptor<MessageID>> {
172-
const [_, listener] = node.arguments;
173-
if (!AST.isFunction(listener)) return O.none();
174-
if (O.isSome(options.signal)) return O.none();
175-
return O.some({
176-
messageId: "noLeakedEventListenerOfInlineFunction",
177-
node: listener,
178-
data: { eventMethodKind: callKind },
179-
});
180-
}
181-
const isSameObject: {
182-
(a: TSESTree.Node): (b: TSESTree.Node) => boolean;
183-
(a: TSESTree.Node, b: TSESTree.Node): boolean;
184-
} = F.dual(2, (a: TSESTree.Node, b: TSESTree.Node) => {
167+
function isSameObject(a: TSESTree.Node, b: TSESTree.Node) {
185168
switch (true) {
186169
case a.type === AST_NODE_TYPES.MemberExpression
187170
&& b.type === AST_NODE_TYPES.MemberExpression:
@@ -190,11 +173,8 @@ export default createRule<[], MessageID>({
190173
default:
191174
return false;
192175
}
193-
});
194-
const isInverseEntry: {
195-
(aEntry: AEntry): (rEntry: REntry) => boolean;
196-
(aEntry: AEntry, rEntry: REntry): boolean;
197-
} = F.dual(2, (aEntry: AEntry, rEntry: REntry) => {
176+
}
177+
function isInverseEntry(aEntry: AEntry, rEntry: REntry) {
198178
const { type: aType, callee: aCallee, capture: aCapture, listener: aListener, phase: aPhase } = aEntry;
199179
const { type: rType, callee: rCallee, capture: rCapture, listener: rListener, phase: rPhase } = rEntry;
200180
if (!isInversePhase(aPhase, rPhase)) return false;
@@ -205,7 +185,21 @@ export default createRule<[], MessageID>({
205185
context.sourceCode.getScope(rType),
206186
])
207187
&& O.getOrElse(aCapture, F.constFalse) === O.getOrElse(rCapture, F.constFalse);
208-
});
188+
}
189+
function checkInlineFunction(
190+
node: TSESTree.CallExpression,
191+
callKind: EventMethodKind,
192+
options: typeof defaultOptions,
193+
): O.Option<ReportDescriptor<MessageID>> {
194+
const [_, listener] = node.arguments;
195+
if (!AST.isFunction(listener)) return O.none();
196+
if (O.isSome(options.signal)) return O.none();
197+
return O.some({
198+
messageId: "noLeakedEventListenerOfInlineFunction",
199+
node: listener,
200+
data: { eventMethodKind: callKind },
201+
});
202+
}
209203
return {
210204
[":function"](node: AST.TSESTreeFunction) {
211205
const functionKind = getFunctionKind(node);
@@ -256,11 +250,11 @@ export default createRule<[], MessageID>({
256250
},
257251
["Program:exit"]() {
258252
for (const aEntry of aEntries) {
259-
if (O.exists(aEntry.signal, signal => abortedSignals.some(isSameObject(signal)))) continue;
260-
if (rEntries.some(isInverseEntry(aEntry))) continue;
253+
if (O.exists(aEntry.signal, signal => abortedSignals.some(as => isSameObject(as, signal)))) continue;
254+
if (rEntries.some(rEntry => isInverseEntry(aEntry, rEntry))) continue;
261255
switch (aEntry.phase) {
262-
case "cleanup":
263256
case "setup":
257+
case "cleanup":
264258
context.report({
265259
messageId: "noLeakedEventListenerInEffect",
266260
node: aEntry.node,

packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-interval.ts

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type * as AST from "@eslint-react/ast";
22
import type { EREffectMethodKind, ERLifecycleMethodKind, ERPhaseKind } from "@eslint-react/core";
33
import { ERPhaseRelevance } from "@eslint-react/core";
4-
import { F, O } from "@eslint-react/eff";
4+
import { O } from "@eslint-react/eff";
55
import type { RuleFeature } from "@eslint-react/types";
66
import * as VAR from "@eslint-react/var";
77
import type { TSESTree } from "@typescript-eslint/utils";
@@ -79,12 +79,9 @@ export default createRule<[], MessageID>({
7979
const fStack: [node: AST.TSESTreeFunction, kind: FunctionKind][] = [];
8080
const sEntries: TimerEntry[] = [];
8181
const cEntries: TimerEntry[] = [];
82-
const isInverseEntry: {
83-
(a: TimerEntry): (b: TimerEntry) => boolean;
84-
(a: TimerEntry, b: TimerEntry): boolean;
85-
} = F.dual(2, (a: TimerEntry, b: TimerEntry) => {
82+
function isInverseEntry(a: TimerEntry, b: TimerEntry) {
8683
return isInstanceIDEqual(a.timerID, b.timerID, context);
87-
});
84+
}
8885
return {
8986
[":function"](node: AST.TSESTreeFunction) {
9087
const fKind = O.getOrElse(getPhaseKindOfFunction(node), () => "other" as const);
@@ -95,13 +92,19 @@ export default createRule<[], MessageID>({
9592
},
9693
["CallExpression"](node) {
9794
switch (getCallKind(node)) {
98-
case "clearInterval": {
95+
case "setInterval": {
9996
const [fNode, fKind] = fStack.findLast(f => f.at(1) !== "other") ?? [];
10097
if (!fNode || !fKind) break;
10198
if (!ERPhaseRelevance.has(fKind)) break;
102-
const [intervalIdNode] = node.arguments;
103-
if (!intervalIdNode) break;
104-
cEntries.push({
99+
const intervalIdNode = O.getOrNull(VAR.getVariableDeclaratorID(node));
100+
if (!intervalIdNode) {
101+
context.report({
102+
messageId: "noLeakedIntervalNoIntervalId",
103+
node,
104+
});
105+
break;
106+
}
107+
sEntries.push({
105108
kind: "interval",
106109
node,
107110
callee: node.callee,
@@ -110,19 +113,13 @@ export default createRule<[], MessageID>({
110113
});
111114
break;
112115
}
113-
case "setInterval": {
116+
case "clearInterval": {
114117
const [fNode, fKind] = fStack.findLast(f => f.at(1) !== "other") ?? [];
115118
if (!fNode || !fKind) break;
116119
if (!ERPhaseRelevance.has(fKind)) break;
117-
const intervalIdNode = O.getOrNull(VAR.getVariableDeclaratorID(node));
118-
if (!intervalIdNode) {
119-
context.report({
120-
messageId: "noLeakedIntervalNoIntervalId",
121-
node,
122-
});
123-
break;
124-
}
125-
sEntries.push({
120+
const [intervalIdNode] = node.arguments;
121+
if (!intervalIdNode) break;
122+
cEntries.push({
126123
kind: "interval",
127124
node,
128125
callee: node.callee,
@@ -135,10 +132,10 @@ export default createRule<[], MessageID>({
135132
},
136133
["Program:exit"]() {
137134
for (const sEntry of sEntries) {
138-
if (cEntries.some(isInverseEntry(sEntry))) continue;
135+
if (cEntries.some(cEntry => isInverseEntry(sEntry, cEntry))) continue;
139136
switch (sEntry.phase) {
140-
case "cleanup":
141137
case "setup":
138+
case "cleanup":
142139
context.report({
143140
messageId: "noLeakedIntervalInEffect",
144141
node: sEntry.node,

packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-timeout.ts

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type * as AST from "@eslint-react/ast";
22
import type { EREffectMethodKind, ERLifecycleMethodKind, ERPhaseKind } from "@eslint-react/core";
33
import { ERPhaseRelevance } from "@eslint-react/core";
4-
import { F, O } from "@eslint-react/eff";
4+
import { O } from "@eslint-react/eff";
55
import type { RuleFeature } from "@eslint-react/types";
66
import * as VAR from "@eslint-react/var";
77
import type { TSESTree } from "@typescript-eslint/utils";
@@ -78,12 +78,9 @@ export default createRule<[], MessageID>({
7878
const fStack: [node: AST.TSESTreeFunction, kind: FunctionKind][] = [];
7979
const sEntries: TimerEntry[] = [];
8080
const rEntries: TimerEntry[] = [];
81-
const isInverseEntry: {
82-
(a: TimerEntry): (b: TimerEntry) => boolean;
83-
(a: TimerEntry, b: TimerEntry): boolean;
84-
} = F.dual(2, (a: TimerEntry, b: TimerEntry) => {
81+
function isInverseEntry(a: TimerEntry, b: TimerEntry) {
8582
return isInstanceIDEqual(a.timerID, b.timerID, context);
86-
});
83+
}
8784
return {
8885
[":function"](node: AST.TSESTreeFunction) {
8986
const fKind = O.getOrElse(getPhaseKindOfFunction(node), () => "other" as const);
@@ -97,18 +94,6 @@ export default createRule<[], MessageID>({
9794
if (!fNode || !fKind) return;
9895
if (!ERPhaseRelevance.has(fKind)) return;
9996
switch (getCallKind(node)) {
100-
case "clearTimeout": {
101-
const [timeoutIdNode] = node.arguments;
102-
if (!timeoutIdNode) break;
103-
rEntries.push({
104-
kind: "timeout",
105-
node,
106-
callee: node.callee,
107-
phase: fKind,
108-
timerID: timeoutIdNode,
109-
});
110-
break;
111-
}
11297
case "setTimeout": {
11398
const timeoutIdNode = O.getOrNull(VAR.getVariableDeclaratorID(node));
11499
if (!timeoutIdNode) {
@@ -127,14 +112,26 @@ export default createRule<[], MessageID>({
127112
});
128113
break;
129114
}
115+
case "clearTimeout": {
116+
const [timeoutIdNode] = node.arguments;
117+
if (!timeoutIdNode) break;
118+
rEntries.push({
119+
kind: "timeout",
120+
node,
121+
callee: node.callee,
122+
phase: fKind,
123+
timerID: timeoutIdNode,
124+
});
125+
break;
126+
}
130127
}
131128
},
132129
["Program:exit"]() {
133130
for (const sEntry of sEntries) {
134-
if (rEntries.some(isInverseEntry(sEntry))) continue;
131+
if (rEntries.some(rEntry => isInverseEntry(sEntry, rEntry))) continue;
135132
switch (sEntry.phase) {
136-
case "cleanup":
137133
case "setup":
134+
case "cleanup":
138135
context.report({
139136
messageId: "noLeakedTimeoutInEffect",
140137
node: sEntry.node,

0 commit comments

Comments
 (0)