eslint-plugin-react-web-api 2.0.0-next.0 → 2.0.0-next.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 +74 -98
- package/package.json +10 -18
- package/dist/index.d.mts +0 -49
- package/dist/index.mjs +0 -737
package/dist/index.js
CHANGED
|
@@ -1,35 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
var tsPattern = require('ts-pattern');
|
|
10
|
-
var types = require('@typescript-eslint/types');
|
|
11
|
-
|
|
12
|
-
function _interopNamespace(e) {
|
|
13
|
-
if (e && e.__esModule) return e;
|
|
14
|
-
var n = Object.create(null);
|
|
15
|
-
if (e) {
|
|
16
|
-
Object.keys(e).forEach(function (k) {
|
|
17
|
-
if (k !== 'default') {
|
|
18
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
19
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
20
|
-
enumerable: true,
|
|
21
|
-
get: function () { return e[k]; }
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
n.default = e;
|
|
27
|
-
return Object.freeze(n);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
var AST__namespace = /*#__PURE__*/_interopNamespace(AST);
|
|
31
|
-
var ER4__namespace = /*#__PURE__*/_interopNamespace(ER4);
|
|
32
|
-
var VAR__namespace = /*#__PURE__*/_interopNamespace(VAR);
|
|
1
|
+
import { getDocsUrl, DEFAULT_ESLINT_REACT_SETTINGS } from '@eslint-react/shared';
|
|
2
|
+
import * as AST from '@eslint-react/ast';
|
|
3
|
+
import * as ER4 from '@eslint-react/core';
|
|
4
|
+
import { _, or } from '@eslint-react/eff';
|
|
5
|
+
import * as VAR from '@eslint-react/var';
|
|
6
|
+
import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
|
|
7
|
+
import { match, isMatching, P } from 'ts-pattern';
|
|
8
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES$1 } from '@typescript-eslint/types';
|
|
33
9
|
|
|
34
10
|
var __defProp = Object.defineProperty;
|
|
35
11
|
var __export = (target, all) => {
|
|
@@ -52,33 +28,33 @@ var rules = {
|
|
|
52
28
|
"react-web-api/no-leaked-timeout": "warn"
|
|
53
29
|
};
|
|
54
30
|
var settings = {
|
|
55
|
-
"react-x":
|
|
31
|
+
"react-x": DEFAULT_ESLINT_REACT_SETTINGS
|
|
56
32
|
};
|
|
57
33
|
|
|
58
34
|
// package.json
|
|
59
35
|
var name2 = "eslint-plugin-react-web-api";
|
|
60
|
-
var version = "2.0.0-next.
|
|
61
|
-
var createRule =
|
|
36
|
+
var version = "2.0.0-next.2";
|
|
37
|
+
var createRule = ESLintUtils.RuleCreator(getDocsUrl("web-api"));
|
|
62
38
|
function getPhaseKindOfFunction(node) {
|
|
63
|
-
return
|
|
39
|
+
return match(node).when(isFunctionOfUseEffectSetup, () => "setup").when(isFunctionOfUseEffectCleanup, () => "cleanup").when(isFunctionOfComponentDidMount, () => "mount").when(isFunctionOfComponentWillUnmount, () => "unmount").otherwise(() => null);
|
|
64
40
|
}
|
|
65
41
|
function isFunctionOfUseEffectSetup(node) {
|
|
66
42
|
if (node == null) return false;
|
|
67
|
-
return node.parent?.type ===
|
|
43
|
+
return node.parent?.type === AST_NODE_TYPES$1.CallExpression && node.parent.callee !== node && node.parent.callee.type === AST_NODE_TYPES$1.Identifier && node.parent.arguments.at(0) === node && ER4.isUseEffectCallLoose(node.parent);
|
|
68
44
|
}
|
|
69
45
|
function isFunctionOfUseEffectCleanup(node) {
|
|
70
46
|
if (node == null) return false;
|
|
71
|
-
const pReturn =
|
|
72
|
-
const pFunction =
|
|
73
|
-
const pFunctionOfReturn =
|
|
47
|
+
const pReturn = AST.findParentNode(node, AST.is(AST_NODE_TYPES$1.ReturnStatement));
|
|
48
|
+
const pFunction = AST.findParentNode(node, AST.isFunction);
|
|
49
|
+
const pFunctionOfReturn = AST.findParentNode(pReturn, AST.isFunction);
|
|
74
50
|
if (pFunction !== pFunctionOfReturn) return false;
|
|
75
51
|
return isFunctionOfUseEffectSetup(pFunction);
|
|
76
52
|
}
|
|
77
53
|
function isFunctionOfComponentDidMount(node) {
|
|
78
|
-
return
|
|
54
|
+
return AST.isFunction(node) && ER4.isComponentDidMount(node.parent) && node.parent.value === node;
|
|
79
55
|
}
|
|
80
56
|
function isFunctionOfComponentWillUnmount(node) {
|
|
81
|
-
return
|
|
57
|
+
return AST.isFunction(node) && ER4.isComponentWillUnmount(node.parent) && node.parent.value === node;
|
|
82
58
|
}
|
|
83
59
|
|
|
84
60
|
// src/rules/no-leaked-event-listener.ts
|
|
@@ -87,13 +63,13 @@ var RULE_FEATURES = [];
|
|
|
87
63
|
var defaultOptions = {
|
|
88
64
|
capture: false,
|
|
89
65
|
// once: false,
|
|
90
|
-
signal:
|
|
66
|
+
signal: _
|
|
91
67
|
};
|
|
92
68
|
function getCallKind(node) {
|
|
93
69
|
switch (true) {
|
|
94
|
-
case (node.callee.type ===
|
|
70
|
+
case (node.callee.type === AST_NODE_TYPES.Identifier && isMatching(P.union("addEventListener", "removeEventListener", "abort"))(node.callee.name)):
|
|
95
71
|
return node.callee.name;
|
|
96
|
-
case (node.callee.type ===
|
|
72
|
+
case (node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier && isMatching(P.union("addEventListener", "removeEventListener", "abort"))(node.callee.property.name)):
|
|
97
73
|
return node.callee.property.name;
|
|
98
74
|
default:
|
|
99
75
|
return "other";
|
|
@@ -103,55 +79,55 @@ function getFunctionKind(node) {
|
|
|
103
79
|
return getPhaseKindOfFunction(node) ?? "other";
|
|
104
80
|
}
|
|
105
81
|
function getSignalValueExpression(node, initialScope) {
|
|
106
|
-
if (node == null) return
|
|
82
|
+
if (node == null) return _;
|
|
107
83
|
switch (node.type) {
|
|
108
|
-
case
|
|
109
|
-
return getSignalValueExpression(
|
|
84
|
+
case AST_NODE_TYPES.Identifier: {
|
|
85
|
+
return getSignalValueExpression(VAR.getVariableInitNode(VAR.findVariable(node, initialScope), 0), initialScope);
|
|
110
86
|
}
|
|
111
|
-
case
|
|
87
|
+
case AST_NODE_TYPES.MemberExpression:
|
|
112
88
|
return node;
|
|
113
89
|
default:
|
|
114
|
-
return
|
|
90
|
+
return _;
|
|
115
91
|
}
|
|
116
92
|
}
|
|
117
93
|
function getOptions(node, initialScope) {
|
|
118
94
|
function findProp(properties, propName) {
|
|
119
|
-
return
|
|
95
|
+
return VAR.findPropertyInProperties(propName, properties, initialScope);
|
|
120
96
|
}
|
|
121
97
|
function getPropValue(prop, filter = (a) => true) {
|
|
122
|
-
if (prop?.type !==
|
|
98
|
+
if (prop?.type !== AST_NODE_TYPES.Property) return _;
|
|
123
99
|
const { value } = prop;
|
|
124
100
|
let v = value;
|
|
125
101
|
switch (value.type) {
|
|
126
|
-
case
|
|
102
|
+
case AST_NODE_TYPES.Literal: {
|
|
127
103
|
v = value.value;
|
|
128
104
|
break;
|
|
129
105
|
}
|
|
130
106
|
default: {
|
|
131
|
-
v =
|
|
107
|
+
v = VAR.toStaticValue({ kind: "lazy", node: value, initialScope }).value;
|
|
132
108
|
break;
|
|
133
109
|
}
|
|
134
110
|
}
|
|
135
|
-
return filter(v) ? v :
|
|
111
|
+
return filter(v) ? v : _;
|
|
136
112
|
}
|
|
137
113
|
function getOpts(node2) {
|
|
138
114
|
switch (node2.type) {
|
|
139
|
-
case
|
|
140
|
-
const variable =
|
|
141
|
-
const variableNode =
|
|
142
|
-
if (variableNode?.type ===
|
|
115
|
+
case AST_NODE_TYPES.Identifier: {
|
|
116
|
+
const variable = VAR.findVariable(node2, initialScope);
|
|
117
|
+
const variableNode = VAR.getVariableInitNode(variable, 0);
|
|
118
|
+
if (variableNode?.type === AST_NODE_TYPES.ObjectExpression) {
|
|
143
119
|
return getOpts(variableNode);
|
|
144
120
|
}
|
|
145
121
|
return defaultOptions;
|
|
146
122
|
}
|
|
147
|
-
case
|
|
123
|
+
case AST_NODE_TYPES.Literal: {
|
|
148
124
|
return { ...defaultOptions, capture: Boolean(node2.value) };
|
|
149
125
|
}
|
|
150
|
-
case
|
|
126
|
+
case AST_NODE_TYPES.ObjectExpression: {
|
|
151
127
|
const pCapture = findProp(node2.properties, "capture");
|
|
152
128
|
const vCapture = !!getPropValue(pCapture);
|
|
153
129
|
const pSignal = findProp(node2.properties, "signal");
|
|
154
|
-
const vSignal = pSignal?.type ===
|
|
130
|
+
const vSignal = pSignal?.type === AST_NODE_TYPES.Property ? getSignalValueExpression(pSignal.value, initialScope) : _;
|
|
155
131
|
return { capture: vCapture, signal: vSignal };
|
|
156
132
|
}
|
|
157
133
|
default: {
|
|
@@ -192,8 +168,8 @@ function create(context) {
|
|
|
192
168
|
const abortedSignals = [];
|
|
193
169
|
function isSameObject(a, b) {
|
|
194
170
|
switch (true) {
|
|
195
|
-
case (a.type ===
|
|
196
|
-
return
|
|
171
|
+
case (a.type === AST_NODE_TYPES.MemberExpression && b.type === AST_NODE_TYPES.MemberExpression):
|
|
172
|
+
return AST.isNodeEqual(a.object, b.object);
|
|
197
173
|
// TODO: Maybe there other cases to consider here.
|
|
198
174
|
default:
|
|
199
175
|
return false;
|
|
@@ -202,17 +178,17 @@ function create(context) {
|
|
|
202
178
|
function isInverseEntry(aEntry, rEntry) {
|
|
203
179
|
const { type: aType, callee: aCallee, capture: aCapture, listener: aListener, phase: aPhase } = aEntry;
|
|
204
180
|
const { type: rType, callee: rCallee, capture: rCapture, listener: rListener, phase: rPhase } = rEntry;
|
|
205
|
-
if (!
|
|
181
|
+
if (!ER4.isInversePhase(aPhase, rPhase)) {
|
|
206
182
|
return false;
|
|
207
183
|
}
|
|
208
|
-
return isSameObject(aCallee, rCallee) &&
|
|
184
|
+
return isSameObject(aCallee, rCallee) && AST.isNodeEqual(aListener, rListener) && VAR.isNodeValueEqual(aType, rType, [
|
|
209
185
|
context.sourceCode.getScope(aType),
|
|
210
186
|
context.sourceCode.getScope(rType)
|
|
211
187
|
]) && aCapture === rCapture;
|
|
212
188
|
}
|
|
213
189
|
function checkInlineFunction(node, callKind, options) {
|
|
214
190
|
const listener = node.arguments.at(1);
|
|
215
|
-
if (!
|
|
191
|
+
if (!AST.isFunction(listener)) {
|
|
216
192
|
return;
|
|
217
193
|
}
|
|
218
194
|
if (options.signal != null) {
|
|
@@ -237,10 +213,10 @@ function create(context) {
|
|
|
237
213
|
if (fKind == null) {
|
|
238
214
|
return;
|
|
239
215
|
}
|
|
240
|
-
if (!
|
|
216
|
+
if (!ER4.ComponentPhaseRelevance.has(fKind)) {
|
|
241
217
|
return;
|
|
242
218
|
}
|
|
243
|
-
|
|
219
|
+
match(getCallKind(node)).with("addEventListener", (callKind) => {
|
|
244
220
|
const [type, listener, options] = node.arguments;
|
|
245
221
|
if (type == null || listener == null) {
|
|
246
222
|
return;
|
|
@@ -314,9 +290,9 @@ var RULE_NAME2 = "no-leaked-interval";
|
|
|
314
290
|
var RULE_FEATURES2 = [];
|
|
315
291
|
function getCallKind2(node) {
|
|
316
292
|
switch (true) {
|
|
317
|
-
case (node.callee.type ===
|
|
293
|
+
case (node.callee.type === AST_NODE_TYPES.Identifier && isMatching(P.union("setInterval", "clearInterval"))(node.callee.name)):
|
|
318
294
|
return node.callee.name;
|
|
319
|
-
case (node.callee.type ===
|
|
295
|
+
case (node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier && isMatching(P.union("setInterval", "clearInterval"))(node.callee.property.name)):
|
|
320
296
|
return node.callee.property.name;
|
|
321
297
|
default:
|
|
322
298
|
return "other";
|
|
@@ -348,7 +324,7 @@ function create2(context) {
|
|
|
348
324
|
const sEntries = [];
|
|
349
325
|
const cEntries = [];
|
|
350
326
|
function isInverseEntry(a, b) {
|
|
351
|
-
return
|
|
327
|
+
return ER4.isInstanceIdEqual(context, a.timerId, b.timerId);
|
|
352
328
|
}
|
|
353
329
|
return {
|
|
354
330
|
[":function"](node) {
|
|
@@ -365,10 +341,10 @@ function create2(context) {
|
|
|
365
341
|
if (fEntry == null) {
|
|
366
342
|
break;
|
|
367
343
|
}
|
|
368
|
-
if (!
|
|
344
|
+
if (!ER4.ComponentPhaseRelevance.has(fEntry.kind)) {
|
|
369
345
|
break;
|
|
370
346
|
}
|
|
371
|
-
const intervalIdNode =
|
|
347
|
+
const intervalIdNode = VAR.getVariableDeclaratorId(node);
|
|
372
348
|
if (intervalIdNode == null) {
|
|
373
349
|
context.report({
|
|
374
350
|
messageId: "expectedIntervalId",
|
|
@@ -390,7 +366,7 @@ function create2(context) {
|
|
|
390
366
|
if (fEntry == null) {
|
|
391
367
|
break;
|
|
392
368
|
}
|
|
393
|
-
if (!
|
|
369
|
+
if (!ER4.ComponentPhaseRelevance.has(fEntry.kind)) {
|
|
394
370
|
break;
|
|
395
371
|
}
|
|
396
372
|
const [intervalIdNode] = node.arguments;
|
|
@@ -442,16 +418,16 @@ function create2(context) {
|
|
|
442
418
|
var RULE_NAME3 = "no-leaked-resize-observer";
|
|
443
419
|
var RULE_FEATURES3 = [];
|
|
444
420
|
function isNewResizeObserver(node) {
|
|
445
|
-
return node?.type ===
|
|
421
|
+
return node?.type === AST_NODE_TYPES.NewExpression && node.callee.type === AST_NODE_TYPES.Identifier && node.callee.name === "ResizeObserver";
|
|
446
422
|
}
|
|
447
423
|
function isFromObserver(context, node) {
|
|
448
424
|
switch (true) {
|
|
449
|
-
case node.type ===
|
|
425
|
+
case node.type === AST_NODE_TYPES.Identifier: {
|
|
450
426
|
const initialScope = context.sourceCode.getScope(node);
|
|
451
|
-
const object =
|
|
427
|
+
const object = VAR.getVariableInitNode(VAR.findVariable(node, initialScope), 0);
|
|
452
428
|
return isNewResizeObserver(object);
|
|
453
429
|
}
|
|
454
|
-
case node.type ===
|
|
430
|
+
case node.type === AST_NODE_TYPES.MemberExpression:
|
|
455
431
|
return isFromObserver(context, node.object);
|
|
456
432
|
default:
|
|
457
433
|
return false;
|
|
@@ -459,9 +435,9 @@ function isFromObserver(context, node) {
|
|
|
459
435
|
}
|
|
460
436
|
function getCallKind3(context, node) {
|
|
461
437
|
switch (true) {
|
|
462
|
-
case (node.callee.type ===
|
|
438
|
+
case (node.callee.type === AST_NODE_TYPES.Identifier && isMatching(P.union("observe", "unobserve", "disconnect"))(node.callee.name) && isFromObserver(context, node.callee)):
|
|
463
439
|
return node.callee.name;
|
|
464
|
-
case (node.callee.type ===
|
|
440
|
+
case (node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier && isMatching(P.union("observe", "unobserve", "disconnect"))(node.callee.property.name) && isFromObserver(context, node.callee)):
|
|
465
441
|
return node.callee.property.name;
|
|
466
442
|
default:
|
|
467
443
|
return "other";
|
|
@@ -506,15 +482,15 @@ function create3(context) {
|
|
|
506
482
|
fEntries.pop();
|
|
507
483
|
},
|
|
508
484
|
["CallExpression"](node) {
|
|
509
|
-
if (node.callee.type !==
|
|
485
|
+
if (node.callee.type !== AST_NODE_TYPES.MemberExpression) {
|
|
510
486
|
return;
|
|
511
487
|
}
|
|
512
488
|
const fKind = fEntries.findLast((x) => x.kind !== "other")?.kind;
|
|
513
|
-
if (fKind == null || !
|
|
489
|
+
if (fKind == null || !ER4.ComponentPhaseRelevance.has(fKind)) {
|
|
514
490
|
return;
|
|
515
491
|
}
|
|
516
492
|
const { object } = node.callee;
|
|
517
|
-
|
|
493
|
+
match(getCallKind3(context, node)).with("disconnect", () => {
|
|
518
494
|
dEntries.push({
|
|
519
495
|
kind: "disconnect",
|
|
520
496
|
node,
|
|
@@ -556,13 +532,13 @@ function create3(context) {
|
|
|
556
532
|
["NewExpression"](node) {
|
|
557
533
|
const fEntry = fEntries.findLast((x) => x.kind !== "other");
|
|
558
534
|
if (fEntry == null) return;
|
|
559
|
-
if (!
|
|
535
|
+
if (!ER4.ComponentPhaseRelevance.has(fEntry.kind)) {
|
|
560
536
|
return;
|
|
561
537
|
}
|
|
562
538
|
if (!isNewResizeObserver(node)) {
|
|
563
539
|
return;
|
|
564
540
|
}
|
|
565
|
-
const id =
|
|
541
|
+
const id = ER4.getInstanceId(node);
|
|
566
542
|
if (id == null) {
|
|
567
543
|
context.report({
|
|
568
544
|
messageId: "unexpectedFloatingInstance",
|
|
@@ -579,20 +555,20 @@ function create3(context) {
|
|
|
579
555
|
},
|
|
580
556
|
["Program:exit"]() {
|
|
581
557
|
for (const { id, node, phaseNode } of observers) {
|
|
582
|
-
if (dEntries.some((e) =>
|
|
558
|
+
if (dEntries.some((e) => ER4.isInstanceIdEqual(context, e.observer, id))) {
|
|
583
559
|
continue;
|
|
584
560
|
}
|
|
585
|
-
const oentries = oEntries.filter((e) =>
|
|
586
|
-
const uentries = uEntries.filter((e) =>
|
|
587
|
-
const isDynamic = (node2) => node2?.type ===
|
|
561
|
+
const oentries = oEntries.filter((e) => ER4.isInstanceIdEqual(context, e.observer, id));
|
|
562
|
+
const uentries = uEntries.filter((e) => ER4.isInstanceIdEqual(context, e.observer, id));
|
|
563
|
+
const isDynamic = (node2) => node2?.type === AST_NODE_TYPES.CallExpression || AST.isConditional(node2);
|
|
588
564
|
const isPhaseNode = (node2) => node2 === phaseNode;
|
|
589
|
-
const hasDynamicallyAdded = oentries.some((e) => !isPhaseNode(
|
|
565
|
+
const hasDynamicallyAdded = oentries.some((e) => !isPhaseNode(AST.findParentNode(e.node, or(isDynamic, isPhaseNode))));
|
|
590
566
|
if (hasDynamicallyAdded) {
|
|
591
567
|
context.report({ messageId: "expectedDisconnectInControlFlow", node });
|
|
592
568
|
continue;
|
|
593
569
|
}
|
|
594
570
|
for (const oEntry of oentries) {
|
|
595
|
-
if (uentries.some((uEntry) =>
|
|
571
|
+
if (uentries.some((uEntry) => ER4.isInstanceIdEqual(context, uEntry.element, oEntry.element))) {
|
|
596
572
|
continue;
|
|
597
573
|
}
|
|
598
574
|
context.report({ messageId: "expectedDisconnectOrUnobserveInCleanup", node: oEntry.node });
|
|
@@ -605,9 +581,9 @@ var RULE_NAME4 = "no-leaked-timeout";
|
|
|
605
581
|
var RULE_FEATURES4 = [];
|
|
606
582
|
function getCallKind4(node) {
|
|
607
583
|
switch (true) {
|
|
608
|
-
case (node.callee.type ===
|
|
584
|
+
case (node.callee.type === AST_NODE_TYPES.Identifier && isMatching(P.union("setTimeout", "clearTimeout"))(node.callee.name)):
|
|
609
585
|
return node.callee.name;
|
|
610
|
-
case (node.callee.type ===
|
|
586
|
+
case (node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier && isMatching(P.union("setTimeout", "clearTimeout"))(node.callee.property.name)):
|
|
611
587
|
return node.callee.property.name;
|
|
612
588
|
default:
|
|
613
589
|
return "other";
|
|
@@ -639,7 +615,7 @@ function create4(context) {
|
|
|
639
615
|
const sEntries = [];
|
|
640
616
|
const rEntries = [];
|
|
641
617
|
function isInverseEntry(a, b) {
|
|
642
|
-
return
|
|
618
|
+
return ER4.isInstanceIdEqual(context, a.timerId, b.timerId);
|
|
643
619
|
}
|
|
644
620
|
return {
|
|
645
621
|
[":function"](node) {
|
|
@@ -651,12 +627,12 @@ function create4(context) {
|
|
|
651
627
|
},
|
|
652
628
|
["CallExpression"](node) {
|
|
653
629
|
const fEntry = fEntries.findLast((f) => f.kind !== "other");
|
|
654
|
-
if (!
|
|
630
|
+
if (!ER4.ComponentPhaseRelevance.has(fEntry?.kind)) {
|
|
655
631
|
return;
|
|
656
632
|
}
|
|
657
633
|
switch (getCallKind4(node)) {
|
|
658
634
|
case "setTimeout": {
|
|
659
|
-
const timeoutIdNode =
|
|
635
|
+
const timeoutIdNode = VAR.getVariableDeclaratorId(node);
|
|
660
636
|
if (timeoutIdNode == null) {
|
|
661
637
|
context.report({
|
|
662
638
|
messageId: "expectedTimeoutId",
|
|
@@ -758,4 +734,4 @@ var index_default = {
|
|
|
758
734
|
}
|
|
759
735
|
};
|
|
760
736
|
|
|
761
|
-
|
|
737
|
+
export { index_default as default };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-react-web-api",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.2",
|
|
4
4
|
"description": "ESLint React's ESLint plugin for interacting with Web APIs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -22,22 +22,14 @@
|
|
|
22
22
|
"license": "MIT",
|
|
23
23
|
"author": "Rel1cx<rel1cx@proton.me>",
|
|
24
24
|
"sideEffects": false,
|
|
25
|
+
"type": "module",
|
|
25
26
|
"exports": {
|
|
26
27
|
".": {
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
"default": "./dist/index.mjs"
|
|
30
|
-
},
|
|
31
|
-
"require": {
|
|
32
|
-
"types": "./dist/index.d.ts",
|
|
33
|
-
"default": "./dist/index.js"
|
|
34
|
-
}
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js"
|
|
35
30
|
},
|
|
36
31
|
"./package.json": "./package.json"
|
|
37
32
|
},
|
|
38
|
-
"main": "dist/index.js",
|
|
39
|
-
"module": "dist/index.mjs",
|
|
40
|
-
"types": "dist/index.d.ts",
|
|
41
33
|
"files": [
|
|
42
34
|
"dist",
|
|
43
35
|
"./package.json"
|
|
@@ -48,12 +40,12 @@
|
|
|
48
40
|
"@typescript-eslint/utils": "^8.30.1",
|
|
49
41
|
"string-ts": "^2.2.1",
|
|
50
42
|
"ts-pattern": "^5.7.0",
|
|
51
|
-
"@eslint-react/ast": "2.0.0-next.
|
|
52
|
-
"@eslint-react/eff": "2.0.0-next.
|
|
53
|
-
"@eslint-react/kit": "2.0.0-next.
|
|
54
|
-
"@eslint-react/
|
|
55
|
-
"@eslint-react/var": "2.0.0-next.
|
|
56
|
-
"@eslint-react/
|
|
43
|
+
"@eslint-react/ast": "2.0.0-next.2",
|
|
44
|
+
"@eslint-react/eff": "2.0.0-next.2",
|
|
45
|
+
"@eslint-react/kit": "2.0.0-next.2",
|
|
46
|
+
"@eslint-react/shared": "2.0.0-next.2",
|
|
47
|
+
"@eslint-react/var": "2.0.0-next.2",
|
|
48
|
+
"@eslint-react/core": "2.0.0-next.2"
|
|
57
49
|
},
|
|
58
50
|
"devDependencies": {
|
|
59
51
|
"@types/react": "^19.1.2",
|
package/dist/index.d.mts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import * as _typescript_eslint_utils_ts_eslint from '@typescript-eslint/utils/ts-eslint';
|
|
2
|
-
import { RulePreset } from '@eslint-react/kit';
|
|
3
|
-
|
|
4
|
-
type MessageID$3 = "expectedClearTimeoutInCleanup" | "expectedClearTimeoutInUnmount" | "expectedTimeoutId";
|
|
5
|
-
|
|
6
|
-
type MessageID$2 = "expectedDisconnectInControlFlow" | "expectedDisconnectOrUnobserveInCleanup" | "unexpectedFloatingInstance";
|
|
7
|
-
|
|
8
|
-
type MessageID$1 = "expectedClearIntervalInCleanup" | "expectedClearIntervalInUnmount" | "expectedIntervalId";
|
|
9
|
-
|
|
10
|
-
type MessageID = "expectedRemoveEventListenerInCleanup" | "expectedRemoveEventListenerInUnmount" | "unexpectedInlineFunction";
|
|
11
|
-
|
|
12
|
-
declare const _default: {
|
|
13
|
-
configs: {
|
|
14
|
-
recommended: {
|
|
15
|
-
plugins: {
|
|
16
|
-
"react-web-api": {
|
|
17
|
-
readonly meta: {
|
|
18
|
-
readonly name: string;
|
|
19
|
-
readonly version: string;
|
|
20
|
-
};
|
|
21
|
-
readonly rules: {
|
|
22
|
-
readonly "no-leaked-event-listener": _typescript_eslint_utils_ts_eslint.RuleModule<MessageID, [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
23
|
-
readonly "no-leaked-interval": _typescript_eslint_utils_ts_eslint.RuleModule<MessageID$1, [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
24
|
-
readonly "no-leaked-resize-observer": _typescript_eslint_utils_ts_eslint.RuleModule<MessageID$2, [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
25
|
-
readonly "no-leaked-timeout": _typescript_eslint_utils_ts_eslint.RuleModule<MessageID$3, [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
};
|
|
29
|
-
name: string;
|
|
30
|
-
rules: RulePreset;
|
|
31
|
-
};
|
|
32
|
-
"recommended-legacy": {
|
|
33
|
-
plugins: string[];
|
|
34
|
-
rules: RulePreset;
|
|
35
|
-
};
|
|
36
|
-
};
|
|
37
|
-
meta: {
|
|
38
|
-
readonly name: string;
|
|
39
|
-
readonly version: string;
|
|
40
|
-
};
|
|
41
|
-
rules: {
|
|
42
|
-
readonly "no-leaked-event-listener": _typescript_eslint_utils_ts_eslint.RuleModule<MessageID, [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
43
|
-
readonly "no-leaked-interval": _typescript_eslint_utils_ts_eslint.RuleModule<MessageID$1, [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
44
|
-
readonly "no-leaked-resize-observer": _typescript_eslint_utils_ts_eslint.RuleModule<MessageID$2, [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
45
|
-
readonly "no-leaked-timeout": _typescript_eslint_utils_ts_eslint.RuleModule<MessageID$3, [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
|
|
46
|
-
};
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export { _default as default };
|
package/dist/index.mjs
DELETED
|
@@ -1,737 +0,0 @@
|
|
|
1
|
-
import { getDocsUrl, DEFAULT_ESLINT_REACT_SETTINGS } from '@eslint-react/shared';
|
|
2
|
-
import * as AST from '@eslint-react/ast';
|
|
3
|
-
import * as ER4 from '@eslint-react/core';
|
|
4
|
-
import { _, or } from '@eslint-react/eff';
|
|
5
|
-
import * as VAR from '@eslint-react/var';
|
|
6
|
-
import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
|
|
7
|
-
import { match, isMatching, P } from 'ts-pattern';
|
|
8
|
-
import { AST_NODE_TYPES as AST_NODE_TYPES$1 } from '@typescript-eslint/types';
|
|
9
|
-
|
|
10
|
-
var __defProp = Object.defineProperty;
|
|
11
|
-
var __export = (target, all) => {
|
|
12
|
-
for (var name3 in all)
|
|
13
|
-
__defProp(target, name3, { get: all[name3], enumerable: true });
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
// src/configs/recommended.ts
|
|
17
|
-
var recommended_exports = {};
|
|
18
|
-
__export(recommended_exports, {
|
|
19
|
-
name: () => name,
|
|
20
|
-
rules: () => rules,
|
|
21
|
-
settings: () => settings
|
|
22
|
-
});
|
|
23
|
-
var name = "react-web-api/recommended";
|
|
24
|
-
var rules = {
|
|
25
|
-
"react-web-api/no-leaked-event-listener": "warn",
|
|
26
|
-
"react-web-api/no-leaked-interval": "warn",
|
|
27
|
-
"react-web-api/no-leaked-resize-observer": "warn",
|
|
28
|
-
"react-web-api/no-leaked-timeout": "warn"
|
|
29
|
-
};
|
|
30
|
-
var settings = {
|
|
31
|
-
"react-x": DEFAULT_ESLINT_REACT_SETTINGS
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// package.json
|
|
35
|
-
var name2 = "eslint-plugin-react-web-api";
|
|
36
|
-
var version = "2.0.0-next.0";
|
|
37
|
-
var createRule = ESLintUtils.RuleCreator(getDocsUrl("web-api"));
|
|
38
|
-
function getPhaseKindOfFunction(node) {
|
|
39
|
-
return match(node).when(isFunctionOfUseEffectSetup, () => "setup").when(isFunctionOfUseEffectCleanup, () => "cleanup").when(isFunctionOfComponentDidMount, () => "mount").when(isFunctionOfComponentWillUnmount, () => "unmount").otherwise(() => null);
|
|
40
|
-
}
|
|
41
|
-
function isFunctionOfUseEffectSetup(node) {
|
|
42
|
-
if (node == null) return false;
|
|
43
|
-
return node.parent?.type === AST_NODE_TYPES$1.CallExpression && node.parent.callee !== node && node.parent.callee.type === AST_NODE_TYPES$1.Identifier && node.parent.arguments.at(0) === node && ER4.isUseEffectCallLoose(node.parent);
|
|
44
|
-
}
|
|
45
|
-
function isFunctionOfUseEffectCleanup(node) {
|
|
46
|
-
if (node == null) return false;
|
|
47
|
-
const pReturn = AST.findParentNode(node, AST.is(AST_NODE_TYPES$1.ReturnStatement));
|
|
48
|
-
const pFunction = AST.findParentNode(node, AST.isFunction);
|
|
49
|
-
const pFunctionOfReturn = AST.findParentNode(pReturn, AST.isFunction);
|
|
50
|
-
if (pFunction !== pFunctionOfReturn) return false;
|
|
51
|
-
return isFunctionOfUseEffectSetup(pFunction);
|
|
52
|
-
}
|
|
53
|
-
function isFunctionOfComponentDidMount(node) {
|
|
54
|
-
return AST.isFunction(node) && ER4.isComponentDidMount(node.parent) && node.parent.value === node;
|
|
55
|
-
}
|
|
56
|
-
function isFunctionOfComponentWillUnmount(node) {
|
|
57
|
-
return AST.isFunction(node) && ER4.isComponentWillUnmount(node.parent) && node.parent.value === node;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// src/rules/no-leaked-event-listener.ts
|
|
61
|
-
var RULE_NAME = "no-leaked-event-listener";
|
|
62
|
-
var RULE_FEATURES = [];
|
|
63
|
-
var defaultOptions = {
|
|
64
|
-
capture: false,
|
|
65
|
-
// once: false,
|
|
66
|
-
signal: _
|
|
67
|
-
};
|
|
68
|
-
function getCallKind(node) {
|
|
69
|
-
switch (true) {
|
|
70
|
-
case (node.callee.type === AST_NODE_TYPES.Identifier && isMatching(P.union("addEventListener", "removeEventListener", "abort"))(node.callee.name)):
|
|
71
|
-
return node.callee.name;
|
|
72
|
-
case (node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier && isMatching(P.union("addEventListener", "removeEventListener", "abort"))(node.callee.property.name)):
|
|
73
|
-
return node.callee.property.name;
|
|
74
|
-
default:
|
|
75
|
-
return "other";
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
function getFunctionKind(node) {
|
|
79
|
-
return getPhaseKindOfFunction(node) ?? "other";
|
|
80
|
-
}
|
|
81
|
-
function getSignalValueExpression(node, initialScope) {
|
|
82
|
-
if (node == null) return _;
|
|
83
|
-
switch (node.type) {
|
|
84
|
-
case AST_NODE_TYPES.Identifier: {
|
|
85
|
-
return getSignalValueExpression(VAR.getVariableInitNode(VAR.findVariable(node, initialScope), 0), initialScope);
|
|
86
|
-
}
|
|
87
|
-
case AST_NODE_TYPES.MemberExpression:
|
|
88
|
-
return node;
|
|
89
|
-
default:
|
|
90
|
-
return _;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
function getOptions(node, initialScope) {
|
|
94
|
-
function findProp(properties, propName) {
|
|
95
|
-
return VAR.findPropertyInProperties(propName, properties, initialScope);
|
|
96
|
-
}
|
|
97
|
-
function getPropValue(prop, filter = (a) => true) {
|
|
98
|
-
if (prop?.type !== AST_NODE_TYPES.Property) return _;
|
|
99
|
-
const { value } = prop;
|
|
100
|
-
let v = value;
|
|
101
|
-
switch (value.type) {
|
|
102
|
-
case AST_NODE_TYPES.Literal: {
|
|
103
|
-
v = value.value;
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
106
|
-
default: {
|
|
107
|
-
v = VAR.toStaticValue({ kind: "lazy", node: value, initialScope }).value;
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return filter(v) ? v : _;
|
|
112
|
-
}
|
|
113
|
-
function getOpts(node2) {
|
|
114
|
-
switch (node2.type) {
|
|
115
|
-
case AST_NODE_TYPES.Identifier: {
|
|
116
|
-
const variable = VAR.findVariable(node2, initialScope);
|
|
117
|
-
const variableNode = VAR.getVariableInitNode(variable, 0);
|
|
118
|
-
if (variableNode?.type === AST_NODE_TYPES.ObjectExpression) {
|
|
119
|
-
return getOpts(variableNode);
|
|
120
|
-
}
|
|
121
|
-
return defaultOptions;
|
|
122
|
-
}
|
|
123
|
-
case AST_NODE_TYPES.Literal: {
|
|
124
|
-
return { ...defaultOptions, capture: Boolean(node2.value) };
|
|
125
|
-
}
|
|
126
|
-
case AST_NODE_TYPES.ObjectExpression: {
|
|
127
|
-
const pCapture = findProp(node2.properties, "capture");
|
|
128
|
-
const vCapture = !!getPropValue(pCapture);
|
|
129
|
-
const pSignal = findProp(node2.properties, "signal");
|
|
130
|
-
const vSignal = pSignal?.type === AST_NODE_TYPES.Property ? getSignalValueExpression(pSignal.value, initialScope) : _;
|
|
131
|
-
return { capture: vCapture, signal: vSignal };
|
|
132
|
-
}
|
|
133
|
-
default: {
|
|
134
|
-
return defaultOptions;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return getOpts(node);
|
|
139
|
-
}
|
|
140
|
-
var no_leaked_event_listener_default = createRule({
|
|
141
|
-
meta: {
|
|
142
|
-
type: "problem",
|
|
143
|
-
docs: {
|
|
144
|
-
description: "Prevents leaked `addEventListener` in a component or custom Hook.",
|
|
145
|
-
[Symbol.for("rule_features")]: RULE_FEATURES
|
|
146
|
-
},
|
|
147
|
-
messages: {
|
|
148
|
-
expectedRemoveEventListenerInCleanup: "An 'addEventListener' in '{{effectMethodKind}}' should have a corresponding 'removeEventListener' in its cleanup function.",
|
|
149
|
-
expectedRemoveEventListenerInUnmount: "An 'addEventListener' in 'componentDidMount' should have a corresponding 'removeEventListener' in 'componentWillUnmount' method.",
|
|
150
|
-
unexpectedInlineFunction: "A/an '{{eventMethodKind}}' should not have an inline listener function."
|
|
151
|
-
},
|
|
152
|
-
schema: []
|
|
153
|
-
},
|
|
154
|
-
name: RULE_NAME,
|
|
155
|
-
create,
|
|
156
|
-
defaultOptions: []
|
|
157
|
-
});
|
|
158
|
-
function create(context) {
|
|
159
|
-
if (!context.sourceCode.text.includes("addEventListener")) {
|
|
160
|
-
return {};
|
|
161
|
-
}
|
|
162
|
-
if (!/use\w*Effect|componentDidMount|componentWillUnmount/u.test(context.sourceCode.text)) {
|
|
163
|
-
return {};
|
|
164
|
-
}
|
|
165
|
-
const fEntries = [];
|
|
166
|
-
const aEntries = [];
|
|
167
|
-
const rEntries = [];
|
|
168
|
-
const abortedSignals = [];
|
|
169
|
-
function isSameObject(a, b) {
|
|
170
|
-
switch (true) {
|
|
171
|
-
case (a.type === AST_NODE_TYPES.MemberExpression && b.type === AST_NODE_TYPES.MemberExpression):
|
|
172
|
-
return AST.isNodeEqual(a.object, b.object);
|
|
173
|
-
// TODO: Maybe there other cases to consider here.
|
|
174
|
-
default:
|
|
175
|
-
return false;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
function isInverseEntry(aEntry, rEntry) {
|
|
179
|
-
const { type: aType, callee: aCallee, capture: aCapture, listener: aListener, phase: aPhase } = aEntry;
|
|
180
|
-
const { type: rType, callee: rCallee, capture: rCapture, listener: rListener, phase: rPhase } = rEntry;
|
|
181
|
-
if (!ER4.isInversePhase(aPhase, rPhase)) {
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
return isSameObject(aCallee, rCallee) && AST.isNodeEqual(aListener, rListener) && VAR.isNodeValueEqual(aType, rType, [
|
|
185
|
-
context.sourceCode.getScope(aType),
|
|
186
|
-
context.sourceCode.getScope(rType)
|
|
187
|
-
]) && aCapture === rCapture;
|
|
188
|
-
}
|
|
189
|
-
function checkInlineFunction(node, callKind, options) {
|
|
190
|
-
const listener = node.arguments.at(1);
|
|
191
|
-
if (!AST.isFunction(listener)) {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
if (options.signal != null) {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
context.report({
|
|
198
|
-
messageId: "unexpectedInlineFunction",
|
|
199
|
-
node: listener,
|
|
200
|
-
data: { eventMethodKind: callKind }
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
return {
|
|
204
|
-
[":function"](node) {
|
|
205
|
-
const kind = getFunctionKind(node);
|
|
206
|
-
fEntries.push({ kind, node });
|
|
207
|
-
},
|
|
208
|
-
[":function:exit"]() {
|
|
209
|
-
fEntries.pop();
|
|
210
|
-
},
|
|
211
|
-
["CallExpression"](node) {
|
|
212
|
-
const fKind = fEntries.findLast((x) => x.kind !== "other")?.kind;
|
|
213
|
-
if (fKind == null) {
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
if (!ER4.ComponentPhaseRelevance.has(fKind)) {
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
match(getCallKind(node)).with("addEventListener", (callKind) => {
|
|
220
|
-
const [type, listener, options] = node.arguments;
|
|
221
|
-
if (type == null || listener == null) {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
const opts = options == null ? defaultOptions : getOptions(options, context.sourceCode.getScope(options));
|
|
225
|
-
const { callee } = node;
|
|
226
|
-
checkInlineFunction(node, callKind, opts);
|
|
227
|
-
aEntries.push({
|
|
228
|
-
...opts,
|
|
229
|
-
kind: "addEventListener",
|
|
230
|
-
type,
|
|
231
|
-
node,
|
|
232
|
-
callee,
|
|
233
|
-
listener,
|
|
234
|
-
phase: fKind
|
|
235
|
-
});
|
|
236
|
-
}).with("removeEventListener", (callKind) => {
|
|
237
|
-
const [type, listener, options] = node.arguments;
|
|
238
|
-
if (type == null || listener == null) {
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
const opts = options == null ? defaultOptions : getOptions(options, context.sourceCode.getScope(options));
|
|
242
|
-
const { callee } = node;
|
|
243
|
-
checkInlineFunction(node, callKind, opts);
|
|
244
|
-
rEntries.push({
|
|
245
|
-
...opts,
|
|
246
|
-
kind: "removeEventListener",
|
|
247
|
-
type,
|
|
248
|
-
node,
|
|
249
|
-
callee,
|
|
250
|
-
listener,
|
|
251
|
-
phase: fKind
|
|
252
|
-
});
|
|
253
|
-
}).with("abort", () => {
|
|
254
|
-
abortedSignals.push(node.callee);
|
|
255
|
-
}).otherwise(() => null);
|
|
256
|
-
},
|
|
257
|
-
["Program:exit"]() {
|
|
258
|
-
for (const aEntry of aEntries) {
|
|
259
|
-
const signal = aEntry.signal;
|
|
260
|
-
if (signal != null && abortedSignals.some((a) => isSameObject(a, signal))) {
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
if (rEntries.some((rEntry) => isInverseEntry(aEntry, rEntry))) {
|
|
264
|
-
continue;
|
|
265
|
-
}
|
|
266
|
-
switch (aEntry.phase) {
|
|
267
|
-
case "setup":
|
|
268
|
-
case "cleanup":
|
|
269
|
-
context.report({
|
|
270
|
-
messageId: "expectedRemoveEventListenerInCleanup",
|
|
271
|
-
node: aEntry.node,
|
|
272
|
-
data: {
|
|
273
|
-
effectMethodKind: "useEffect"
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
continue;
|
|
277
|
-
case "mount":
|
|
278
|
-
case "unmount":
|
|
279
|
-
context.report({
|
|
280
|
-
messageId: "expectedRemoveEventListenerInUnmount",
|
|
281
|
-
node: aEntry.node
|
|
282
|
-
});
|
|
283
|
-
continue;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
var RULE_NAME2 = "no-leaked-interval";
|
|
290
|
-
var RULE_FEATURES2 = [];
|
|
291
|
-
function getCallKind2(node) {
|
|
292
|
-
switch (true) {
|
|
293
|
-
case (node.callee.type === AST_NODE_TYPES.Identifier && isMatching(P.union("setInterval", "clearInterval"))(node.callee.name)):
|
|
294
|
-
return node.callee.name;
|
|
295
|
-
case (node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier && isMatching(P.union("setInterval", "clearInterval"))(node.callee.property.name)):
|
|
296
|
-
return node.callee.property.name;
|
|
297
|
-
default:
|
|
298
|
-
return "other";
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
var no_leaked_interval_default = createRule({
|
|
302
|
-
meta: {
|
|
303
|
-
type: "problem",
|
|
304
|
-
docs: {
|
|
305
|
-
description: "Prevents leaked `setInterval` in a component or custom Hook.",
|
|
306
|
-
[Symbol.for("rule_features")]: RULE_FEATURES2
|
|
307
|
-
},
|
|
308
|
-
messages: {
|
|
309
|
-
expectedClearIntervalInCleanup: "A 'setInterval' created in '{{ kind }}' must be cleared with 'clearInterval' in the cleanup function.",
|
|
310
|
-
expectedClearIntervalInUnmount: "A 'setInterval' created in '{{ kind }}' must be cleared with 'clearInterval' in the 'componentWillUnmount' method.",
|
|
311
|
-
expectedIntervalId: "A 'setInterval' must be assigned to a variable for proper cleanup."
|
|
312
|
-
},
|
|
313
|
-
schema: []
|
|
314
|
-
},
|
|
315
|
-
name: RULE_NAME2,
|
|
316
|
-
create: create2,
|
|
317
|
-
defaultOptions: []
|
|
318
|
-
});
|
|
319
|
-
function create2(context) {
|
|
320
|
-
if (!context.sourceCode.text.includes("setInterval")) {
|
|
321
|
-
return {};
|
|
322
|
-
}
|
|
323
|
-
const fEntries = [];
|
|
324
|
-
const sEntries = [];
|
|
325
|
-
const cEntries = [];
|
|
326
|
-
function isInverseEntry(a, b) {
|
|
327
|
-
return ER4.isInstanceIdEqual(context, a.timerId, b.timerId);
|
|
328
|
-
}
|
|
329
|
-
return {
|
|
330
|
-
[":function"](node) {
|
|
331
|
-
const kind = getPhaseKindOfFunction(node) ?? "other";
|
|
332
|
-
fEntries.push({ kind, node });
|
|
333
|
-
},
|
|
334
|
-
[":function:exit"]() {
|
|
335
|
-
fEntries.pop();
|
|
336
|
-
},
|
|
337
|
-
["CallExpression"](node) {
|
|
338
|
-
switch (getCallKind2(node)) {
|
|
339
|
-
case "setInterval": {
|
|
340
|
-
const fEntry = fEntries.findLast((x) => x.kind !== "other");
|
|
341
|
-
if (fEntry == null) {
|
|
342
|
-
break;
|
|
343
|
-
}
|
|
344
|
-
if (!ER4.ComponentPhaseRelevance.has(fEntry.kind)) {
|
|
345
|
-
break;
|
|
346
|
-
}
|
|
347
|
-
const intervalIdNode = VAR.getVariableDeclaratorId(node);
|
|
348
|
-
if (intervalIdNode == null) {
|
|
349
|
-
context.report({
|
|
350
|
-
messageId: "expectedIntervalId",
|
|
351
|
-
node
|
|
352
|
-
});
|
|
353
|
-
break;
|
|
354
|
-
}
|
|
355
|
-
sEntries.push({
|
|
356
|
-
kind: "interval",
|
|
357
|
-
node,
|
|
358
|
-
callee: node.callee,
|
|
359
|
-
phase: fEntry.kind,
|
|
360
|
-
timerId: intervalIdNode
|
|
361
|
-
});
|
|
362
|
-
break;
|
|
363
|
-
}
|
|
364
|
-
case "clearInterval": {
|
|
365
|
-
const fEntry = fEntries.findLast((x) => x.kind !== "other");
|
|
366
|
-
if (fEntry == null) {
|
|
367
|
-
break;
|
|
368
|
-
}
|
|
369
|
-
if (!ER4.ComponentPhaseRelevance.has(fEntry.kind)) {
|
|
370
|
-
break;
|
|
371
|
-
}
|
|
372
|
-
const [intervalIdNode] = node.arguments;
|
|
373
|
-
if (intervalIdNode == null) {
|
|
374
|
-
break;
|
|
375
|
-
}
|
|
376
|
-
cEntries.push({
|
|
377
|
-
kind: "interval",
|
|
378
|
-
node,
|
|
379
|
-
callee: node.callee,
|
|
380
|
-
phase: fEntry.kind,
|
|
381
|
-
timerId: intervalIdNode
|
|
382
|
-
});
|
|
383
|
-
break;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
},
|
|
387
|
-
["Program:exit"]() {
|
|
388
|
-
for (const sEntry of sEntries) {
|
|
389
|
-
if (cEntries.some((cEntry) => isInverseEntry(sEntry, cEntry))) {
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
switch (sEntry.phase) {
|
|
393
|
-
case "setup":
|
|
394
|
-
case "cleanup":
|
|
395
|
-
context.report({
|
|
396
|
-
messageId: "expectedClearIntervalInCleanup",
|
|
397
|
-
node: sEntry.node,
|
|
398
|
-
data: {
|
|
399
|
-
kind: "useEffect"
|
|
400
|
-
}
|
|
401
|
-
});
|
|
402
|
-
continue;
|
|
403
|
-
case "mount":
|
|
404
|
-
case "unmount":
|
|
405
|
-
context.report({
|
|
406
|
-
messageId: "expectedClearIntervalInUnmount",
|
|
407
|
-
node: sEntry.node,
|
|
408
|
-
data: {
|
|
409
|
-
kind: "componentDidMount"
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
continue;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
var RULE_NAME3 = "no-leaked-resize-observer";
|
|
419
|
-
var RULE_FEATURES3 = [];
|
|
420
|
-
function isNewResizeObserver(node) {
|
|
421
|
-
return node?.type === AST_NODE_TYPES.NewExpression && node.callee.type === AST_NODE_TYPES.Identifier && node.callee.name === "ResizeObserver";
|
|
422
|
-
}
|
|
423
|
-
function isFromObserver(context, node) {
|
|
424
|
-
switch (true) {
|
|
425
|
-
case node.type === AST_NODE_TYPES.Identifier: {
|
|
426
|
-
const initialScope = context.sourceCode.getScope(node);
|
|
427
|
-
const object = VAR.getVariableInitNode(VAR.findVariable(node, initialScope), 0);
|
|
428
|
-
return isNewResizeObserver(object);
|
|
429
|
-
}
|
|
430
|
-
case node.type === AST_NODE_TYPES.MemberExpression:
|
|
431
|
-
return isFromObserver(context, node.object);
|
|
432
|
-
default:
|
|
433
|
-
return false;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
function getCallKind3(context, node) {
|
|
437
|
-
switch (true) {
|
|
438
|
-
case (node.callee.type === AST_NODE_TYPES.Identifier && isMatching(P.union("observe", "unobserve", "disconnect"))(node.callee.name) && isFromObserver(context, node.callee)):
|
|
439
|
-
return node.callee.name;
|
|
440
|
-
case (node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier && isMatching(P.union("observe", "unobserve", "disconnect"))(node.callee.property.name) && isFromObserver(context, node.callee)):
|
|
441
|
-
return node.callee.property.name;
|
|
442
|
-
default:
|
|
443
|
-
return "other";
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
function getFunctionKind2(node) {
|
|
447
|
-
return getPhaseKindOfFunction(node) ?? "other";
|
|
448
|
-
}
|
|
449
|
-
var no_leaked_resize_observer_default = createRule({
|
|
450
|
-
meta: {
|
|
451
|
-
type: "problem",
|
|
452
|
-
docs: {
|
|
453
|
-
description: "Prevents leaked `ResizeObserver` in a component or custom Hook.",
|
|
454
|
-
[Symbol.for("rule_features")]: RULE_FEATURES3
|
|
455
|
-
},
|
|
456
|
-
messages: {
|
|
457
|
-
expectedDisconnectInControlFlow: "Dynamically added 'ResizeObserver.observe' should be cleared all at once using 'ResizeObserver.disconnect' in the cleanup function.",
|
|
458
|
-
expectedDisconnectOrUnobserveInCleanup: "A 'ResizeObserver' instance created in 'useEffect' must be disconnected in the cleanup function.",
|
|
459
|
-
unexpectedFloatingInstance: "A 'ResizeObserver' instance created in component or custom Hook must be assigned to a variable for proper cleanup."
|
|
460
|
-
},
|
|
461
|
-
schema: []
|
|
462
|
-
},
|
|
463
|
-
name: RULE_NAME3,
|
|
464
|
-
create: create3,
|
|
465
|
-
defaultOptions: []
|
|
466
|
-
});
|
|
467
|
-
function create3(context) {
|
|
468
|
-
if (!context.sourceCode.text.includes("ResizeObserver")) {
|
|
469
|
-
return {};
|
|
470
|
-
}
|
|
471
|
-
const fEntries = [];
|
|
472
|
-
const observers = [];
|
|
473
|
-
const oEntries = [];
|
|
474
|
-
const uEntries = [];
|
|
475
|
-
const dEntries = [];
|
|
476
|
-
return {
|
|
477
|
-
[":function"](node) {
|
|
478
|
-
const kind = getFunctionKind2(node);
|
|
479
|
-
fEntries.push({ kind, node });
|
|
480
|
-
},
|
|
481
|
-
[":function:exit"]() {
|
|
482
|
-
fEntries.pop();
|
|
483
|
-
},
|
|
484
|
-
["CallExpression"](node) {
|
|
485
|
-
if (node.callee.type !== AST_NODE_TYPES.MemberExpression) {
|
|
486
|
-
return;
|
|
487
|
-
}
|
|
488
|
-
const fKind = fEntries.findLast((x) => x.kind !== "other")?.kind;
|
|
489
|
-
if (fKind == null || !ER4.ComponentPhaseRelevance.has(fKind)) {
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
const { object } = node.callee;
|
|
493
|
-
match(getCallKind3(context, node)).with("disconnect", () => {
|
|
494
|
-
dEntries.push({
|
|
495
|
-
kind: "disconnect",
|
|
496
|
-
node,
|
|
497
|
-
callee: node.callee,
|
|
498
|
-
observer: object,
|
|
499
|
-
observerKind: "ResizeObserver",
|
|
500
|
-
phase: fKind
|
|
501
|
-
});
|
|
502
|
-
}).with("observe", () => {
|
|
503
|
-
const [element] = node.arguments;
|
|
504
|
-
if (element == null) {
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
oEntries.push({
|
|
508
|
-
kind: "observe",
|
|
509
|
-
node,
|
|
510
|
-
callee: node.callee,
|
|
511
|
-
element,
|
|
512
|
-
observer: object,
|
|
513
|
-
observerKind: "ResizeObserver",
|
|
514
|
-
phase: fKind
|
|
515
|
-
});
|
|
516
|
-
}).with("unobserve", () => {
|
|
517
|
-
const [element] = node.arguments;
|
|
518
|
-
if (element == null) {
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
uEntries.push({
|
|
522
|
-
kind: "unobserve",
|
|
523
|
-
node,
|
|
524
|
-
callee: node.callee,
|
|
525
|
-
element,
|
|
526
|
-
observer: object,
|
|
527
|
-
observerKind: "ResizeObserver",
|
|
528
|
-
phase: fKind
|
|
529
|
-
});
|
|
530
|
-
}).otherwise(() => null);
|
|
531
|
-
},
|
|
532
|
-
["NewExpression"](node) {
|
|
533
|
-
const fEntry = fEntries.findLast((x) => x.kind !== "other");
|
|
534
|
-
if (fEntry == null) return;
|
|
535
|
-
if (!ER4.ComponentPhaseRelevance.has(fEntry.kind)) {
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
if (!isNewResizeObserver(node)) {
|
|
539
|
-
return;
|
|
540
|
-
}
|
|
541
|
-
const id = ER4.getInstanceId(node);
|
|
542
|
-
if (id == null) {
|
|
543
|
-
context.report({
|
|
544
|
-
messageId: "unexpectedFloatingInstance",
|
|
545
|
-
node
|
|
546
|
-
});
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
observers.push({
|
|
550
|
-
id,
|
|
551
|
-
node,
|
|
552
|
-
phase: fEntry.kind,
|
|
553
|
-
phaseNode: fEntry.node
|
|
554
|
-
});
|
|
555
|
-
},
|
|
556
|
-
["Program:exit"]() {
|
|
557
|
-
for (const { id, node, phaseNode } of observers) {
|
|
558
|
-
if (dEntries.some((e) => ER4.isInstanceIdEqual(context, e.observer, id))) {
|
|
559
|
-
continue;
|
|
560
|
-
}
|
|
561
|
-
const oentries = oEntries.filter((e) => ER4.isInstanceIdEqual(context, e.observer, id));
|
|
562
|
-
const uentries = uEntries.filter((e) => ER4.isInstanceIdEqual(context, e.observer, id));
|
|
563
|
-
const isDynamic = (node2) => node2?.type === AST_NODE_TYPES.CallExpression || AST.isConditional(node2);
|
|
564
|
-
const isPhaseNode = (node2) => node2 === phaseNode;
|
|
565
|
-
const hasDynamicallyAdded = oentries.some((e) => !isPhaseNode(AST.findParentNode(e.node, or(isDynamic, isPhaseNode))));
|
|
566
|
-
if (hasDynamicallyAdded) {
|
|
567
|
-
context.report({ messageId: "expectedDisconnectInControlFlow", node });
|
|
568
|
-
continue;
|
|
569
|
-
}
|
|
570
|
-
for (const oEntry of oentries) {
|
|
571
|
-
if (uentries.some((uEntry) => ER4.isInstanceIdEqual(context, uEntry.element, oEntry.element))) {
|
|
572
|
-
continue;
|
|
573
|
-
}
|
|
574
|
-
context.report({ messageId: "expectedDisconnectOrUnobserveInCleanup", node: oEntry.node });
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
var RULE_NAME4 = "no-leaked-timeout";
|
|
581
|
-
var RULE_FEATURES4 = [];
|
|
582
|
-
function getCallKind4(node) {
|
|
583
|
-
switch (true) {
|
|
584
|
-
case (node.callee.type === AST_NODE_TYPES.Identifier && isMatching(P.union("setTimeout", "clearTimeout"))(node.callee.name)):
|
|
585
|
-
return node.callee.name;
|
|
586
|
-
case (node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier && isMatching(P.union("setTimeout", "clearTimeout"))(node.callee.property.name)):
|
|
587
|
-
return node.callee.property.name;
|
|
588
|
-
default:
|
|
589
|
-
return "other";
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
var no_leaked_timeout_default = createRule({
|
|
593
|
-
meta: {
|
|
594
|
-
type: "problem",
|
|
595
|
-
docs: {
|
|
596
|
-
description: "Prevents leaked `setTimeout` in a component or custom Hook.",
|
|
597
|
-
[Symbol.for("rule_features")]: RULE_FEATURES4
|
|
598
|
-
},
|
|
599
|
-
messages: {
|
|
600
|
-
expectedClearTimeoutInCleanup: "A 'setTimeout' created in '{{ kind }}' must be cleared with 'clearTimeout' in the cleanup function.",
|
|
601
|
-
expectedClearTimeoutInUnmount: "A 'setTimeout' created in '{{ kind }}' must be cleared with 'clearTimeout' in the 'componentWillUnmount' method.",
|
|
602
|
-
expectedTimeoutId: "A 'setTimeout' must be assigned to a variable for proper cleanup."
|
|
603
|
-
},
|
|
604
|
-
schema: []
|
|
605
|
-
},
|
|
606
|
-
name: RULE_NAME4,
|
|
607
|
-
create: create4,
|
|
608
|
-
defaultOptions: []
|
|
609
|
-
});
|
|
610
|
-
function create4(context) {
|
|
611
|
-
if (!context.sourceCode.text.includes("setTimeout")) {
|
|
612
|
-
return {};
|
|
613
|
-
}
|
|
614
|
-
const fEntries = [];
|
|
615
|
-
const sEntries = [];
|
|
616
|
-
const rEntries = [];
|
|
617
|
-
function isInverseEntry(a, b) {
|
|
618
|
-
return ER4.isInstanceIdEqual(context, a.timerId, b.timerId);
|
|
619
|
-
}
|
|
620
|
-
return {
|
|
621
|
-
[":function"](node) {
|
|
622
|
-
const kind = getPhaseKindOfFunction(node) ?? "other";
|
|
623
|
-
fEntries.push({ kind, node });
|
|
624
|
-
},
|
|
625
|
-
[":function:exit"]() {
|
|
626
|
-
fEntries.pop();
|
|
627
|
-
},
|
|
628
|
-
["CallExpression"](node) {
|
|
629
|
-
const fEntry = fEntries.findLast((f) => f.kind !== "other");
|
|
630
|
-
if (!ER4.ComponentPhaseRelevance.has(fEntry?.kind)) {
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
|
-
switch (getCallKind4(node)) {
|
|
634
|
-
case "setTimeout": {
|
|
635
|
-
const timeoutIdNode = VAR.getVariableDeclaratorId(node);
|
|
636
|
-
if (timeoutIdNode == null) {
|
|
637
|
-
context.report({
|
|
638
|
-
messageId: "expectedTimeoutId",
|
|
639
|
-
node
|
|
640
|
-
});
|
|
641
|
-
break;
|
|
642
|
-
}
|
|
643
|
-
sEntries.push({
|
|
644
|
-
kind: "timeout",
|
|
645
|
-
node,
|
|
646
|
-
callee: node.callee,
|
|
647
|
-
phase: fEntry.kind,
|
|
648
|
-
timerId: timeoutIdNode
|
|
649
|
-
});
|
|
650
|
-
break;
|
|
651
|
-
}
|
|
652
|
-
case "clearTimeout": {
|
|
653
|
-
const [timeoutIdNode] = node.arguments;
|
|
654
|
-
if (timeoutIdNode == null) {
|
|
655
|
-
break;
|
|
656
|
-
}
|
|
657
|
-
rEntries.push({
|
|
658
|
-
kind: "timeout",
|
|
659
|
-
node,
|
|
660
|
-
callee: node.callee,
|
|
661
|
-
phase: fEntry.kind,
|
|
662
|
-
timerId: timeoutIdNode
|
|
663
|
-
});
|
|
664
|
-
break;
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
},
|
|
668
|
-
["Program:exit"]() {
|
|
669
|
-
for (const sEntry of sEntries) {
|
|
670
|
-
if (rEntries.some((rEntry) => isInverseEntry(sEntry, rEntry))) {
|
|
671
|
-
continue;
|
|
672
|
-
}
|
|
673
|
-
switch (sEntry.phase) {
|
|
674
|
-
case "setup":
|
|
675
|
-
case "cleanup":
|
|
676
|
-
context.report({
|
|
677
|
-
messageId: "expectedClearTimeoutInCleanup",
|
|
678
|
-
node: sEntry.node,
|
|
679
|
-
data: {
|
|
680
|
-
kind: "useEffect"
|
|
681
|
-
}
|
|
682
|
-
});
|
|
683
|
-
continue;
|
|
684
|
-
case "mount":
|
|
685
|
-
case "unmount":
|
|
686
|
-
context.report({
|
|
687
|
-
messageId: "expectedClearTimeoutInUnmount",
|
|
688
|
-
node: sEntry.node,
|
|
689
|
-
data: {
|
|
690
|
-
kind: "componentDidMount"
|
|
691
|
-
}
|
|
692
|
-
});
|
|
693
|
-
continue;
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
};
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// src/plugin.ts
|
|
701
|
-
var plugin = {
|
|
702
|
-
meta: {
|
|
703
|
-
name: name2,
|
|
704
|
-
version
|
|
705
|
-
},
|
|
706
|
-
rules: {
|
|
707
|
-
"no-leaked-event-listener": no_leaked_event_listener_default,
|
|
708
|
-
"no-leaked-interval": no_leaked_interval_default,
|
|
709
|
-
"no-leaked-resize-observer": no_leaked_resize_observer_default,
|
|
710
|
-
"no-leaked-timeout": no_leaked_timeout_default
|
|
711
|
-
}
|
|
712
|
-
};
|
|
713
|
-
|
|
714
|
-
// src/index.ts
|
|
715
|
-
function makeConfig(config) {
|
|
716
|
-
return {
|
|
717
|
-
...config,
|
|
718
|
-
plugins: {
|
|
719
|
-
"react-web-api": plugin
|
|
720
|
-
}
|
|
721
|
-
};
|
|
722
|
-
}
|
|
723
|
-
function makeLegacyConfig({ rules: rules2 }) {
|
|
724
|
-
return {
|
|
725
|
-
plugins: ["react-web-api"],
|
|
726
|
-
rules: rules2
|
|
727
|
-
};
|
|
728
|
-
}
|
|
729
|
-
var index_default = {
|
|
730
|
-
...plugin,
|
|
731
|
-
configs: {
|
|
732
|
-
["recommended"]: makeConfig(recommended_exports),
|
|
733
|
-
["recommended-legacy"]: makeLegacyConfig(recommended_exports)
|
|
734
|
-
}
|
|
735
|
-
};
|
|
736
|
-
|
|
737
|
-
export { index_default as default };
|