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 CHANGED
@@ -1,35 +1,11 @@
1
- 'use strict';
2
-
3
- var shared = require('@eslint-react/shared');
4
- var AST = require('@eslint-react/ast');
5
- var ER4 = require('@eslint-react/core');
6
- var eff = require('@eslint-react/eff');
7
- var VAR = require('@eslint-react/var');
8
- var utils = require('@typescript-eslint/utils');
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": shared.DEFAULT_ESLINT_REACT_SETTINGS
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.0";
61
- var createRule = utils.ESLintUtils.RuleCreator(shared.getDocsUrl("web-api"));
36
+ var version = "2.0.0-next.2";
37
+ var createRule = ESLintUtils.RuleCreator(getDocsUrl("web-api"));
62
38
  function getPhaseKindOfFunction(node) {
63
- return tsPattern.match(node).when(isFunctionOfUseEffectSetup, () => "setup").when(isFunctionOfUseEffectCleanup, () => "cleanup").when(isFunctionOfComponentDidMount, () => "mount").when(isFunctionOfComponentWillUnmount, () => "unmount").otherwise(() => null);
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 === types.AST_NODE_TYPES.CallExpression && node.parent.callee !== node && node.parent.callee.type === types.AST_NODE_TYPES.Identifier && node.parent.arguments.at(0) === node && ER4__namespace.isUseEffectCallLoose(node.parent);
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 = AST__namespace.findParentNode(node, AST__namespace.is(types.AST_NODE_TYPES.ReturnStatement));
72
- const pFunction = AST__namespace.findParentNode(node, AST__namespace.isFunction);
73
- const pFunctionOfReturn = AST__namespace.findParentNode(pReturn, AST__namespace.isFunction);
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 AST__namespace.isFunction(node) && ER4__namespace.isComponentDidMount(node.parent) && node.parent.value === node;
54
+ return AST.isFunction(node) && ER4.isComponentDidMount(node.parent) && node.parent.value === node;
79
55
  }
80
56
  function isFunctionOfComponentWillUnmount(node) {
81
- return AST__namespace.isFunction(node) && ER4__namespace.isComponentWillUnmount(node.parent) && node.parent.value === node;
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: eff._
66
+ signal: _
91
67
  };
92
68
  function getCallKind(node) {
93
69
  switch (true) {
94
- case (node.callee.type === utils.AST_NODE_TYPES.Identifier && tsPattern.isMatching(tsPattern.P.union("addEventListener", "removeEventListener", "abort"))(node.callee.name)):
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 === utils.AST_NODE_TYPES.MemberExpression && node.callee.property.type === utils.AST_NODE_TYPES.Identifier && tsPattern.isMatching(tsPattern.P.union("addEventListener", "removeEventListener", "abort"))(node.callee.property.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)):
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 eff._;
82
+ if (node == null) return _;
107
83
  switch (node.type) {
108
- case utils.AST_NODE_TYPES.Identifier: {
109
- return getSignalValueExpression(VAR__namespace.getVariableInitNode(VAR__namespace.findVariable(node, initialScope), 0), initialScope);
84
+ case AST_NODE_TYPES.Identifier: {
85
+ return getSignalValueExpression(VAR.getVariableInitNode(VAR.findVariable(node, initialScope), 0), initialScope);
110
86
  }
111
- case utils.AST_NODE_TYPES.MemberExpression:
87
+ case AST_NODE_TYPES.MemberExpression:
112
88
  return node;
113
89
  default:
114
- return eff._;
90
+ return _;
115
91
  }
116
92
  }
117
93
  function getOptions(node, initialScope) {
118
94
  function findProp(properties, propName) {
119
- return VAR__namespace.findPropertyInProperties(propName, properties, initialScope);
95
+ return VAR.findPropertyInProperties(propName, properties, initialScope);
120
96
  }
121
97
  function getPropValue(prop, filter = (a) => true) {
122
- if (prop?.type !== utils.AST_NODE_TYPES.Property) return eff._;
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 utils.AST_NODE_TYPES.Literal: {
102
+ case AST_NODE_TYPES.Literal: {
127
103
  v = value.value;
128
104
  break;
129
105
  }
130
106
  default: {
131
- v = VAR__namespace.toStaticValue({ kind: "lazy", node: value, initialScope }).value;
107
+ v = VAR.toStaticValue({ kind: "lazy", node: value, initialScope }).value;
132
108
  break;
133
109
  }
134
110
  }
135
- return filter(v) ? v : eff._;
111
+ return filter(v) ? v : _;
136
112
  }
137
113
  function getOpts(node2) {
138
114
  switch (node2.type) {
139
- case utils.AST_NODE_TYPES.Identifier: {
140
- const variable = VAR__namespace.findVariable(node2, initialScope);
141
- const variableNode = VAR__namespace.getVariableInitNode(variable, 0);
142
- if (variableNode?.type === utils.AST_NODE_TYPES.ObjectExpression) {
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 utils.AST_NODE_TYPES.Literal: {
123
+ case AST_NODE_TYPES.Literal: {
148
124
  return { ...defaultOptions, capture: Boolean(node2.value) };
149
125
  }
150
- case utils.AST_NODE_TYPES.ObjectExpression: {
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 === utils.AST_NODE_TYPES.Property ? getSignalValueExpression(pSignal.value, initialScope) : eff._;
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 === utils.AST_NODE_TYPES.MemberExpression && b.type === utils.AST_NODE_TYPES.MemberExpression):
196
- return AST__namespace.isNodeEqual(a.object, b.object);
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 (!ER4__namespace.isInversePhase(aPhase, rPhase)) {
181
+ if (!ER4.isInversePhase(aPhase, rPhase)) {
206
182
  return false;
207
183
  }
208
- return isSameObject(aCallee, rCallee) && AST__namespace.isNodeEqual(aListener, rListener) && VAR__namespace.isNodeValueEqual(aType, rType, [
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 (!AST__namespace.isFunction(listener)) {
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 (!ER4__namespace.ComponentPhaseRelevance.has(fKind)) {
216
+ if (!ER4.ComponentPhaseRelevance.has(fKind)) {
241
217
  return;
242
218
  }
243
- tsPattern.match(getCallKind(node)).with("addEventListener", (callKind) => {
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 === utils.AST_NODE_TYPES.Identifier && tsPattern.isMatching(tsPattern.P.union("setInterval", "clearInterval"))(node.callee.name)):
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 === utils.AST_NODE_TYPES.MemberExpression && node.callee.property.type === utils.AST_NODE_TYPES.Identifier && tsPattern.isMatching(tsPattern.P.union("setInterval", "clearInterval"))(node.callee.property.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)):
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 ER4__namespace.isInstanceIdEqual(context, a.timerId, b.timerId);
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 (!ER4__namespace.ComponentPhaseRelevance.has(fEntry.kind)) {
344
+ if (!ER4.ComponentPhaseRelevance.has(fEntry.kind)) {
369
345
  break;
370
346
  }
371
- const intervalIdNode = VAR__namespace.getVariableDeclaratorId(node);
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 (!ER4__namespace.ComponentPhaseRelevance.has(fEntry.kind)) {
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 === utils.AST_NODE_TYPES.NewExpression && node.callee.type === utils.AST_NODE_TYPES.Identifier && node.callee.name === "ResizeObserver";
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 === utils.AST_NODE_TYPES.Identifier: {
425
+ case node.type === AST_NODE_TYPES.Identifier: {
450
426
  const initialScope = context.sourceCode.getScope(node);
451
- const object = VAR__namespace.getVariableInitNode(VAR__namespace.findVariable(node, initialScope), 0);
427
+ const object = VAR.getVariableInitNode(VAR.findVariable(node, initialScope), 0);
452
428
  return isNewResizeObserver(object);
453
429
  }
454
- case node.type === utils.AST_NODE_TYPES.MemberExpression:
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 === utils.AST_NODE_TYPES.Identifier && tsPattern.isMatching(tsPattern.P.union("observe", "unobserve", "disconnect"))(node.callee.name) && isFromObserver(context, node.callee)):
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 === utils.AST_NODE_TYPES.MemberExpression && node.callee.property.type === utils.AST_NODE_TYPES.Identifier && tsPattern.isMatching(tsPattern.P.union("observe", "unobserve", "disconnect"))(node.callee.property.name) && isFromObserver(context, node.callee)):
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 !== utils.AST_NODE_TYPES.MemberExpression) {
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 || !ER4__namespace.ComponentPhaseRelevance.has(fKind)) {
489
+ if (fKind == null || !ER4.ComponentPhaseRelevance.has(fKind)) {
514
490
  return;
515
491
  }
516
492
  const { object } = node.callee;
517
- tsPattern.match(getCallKind3(context, node)).with("disconnect", () => {
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 (!ER4__namespace.ComponentPhaseRelevance.has(fEntry.kind)) {
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 = ER4__namespace.getInstanceId(node);
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) => ER4__namespace.isInstanceIdEqual(context, e.observer, id))) {
558
+ if (dEntries.some((e) => ER4.isInstanceIdEqual(context, e.observer, id))) {
583
559
  continue;
584
560
  }
585
- const oentries = oEntries.filter((e) => ER4__namespace.isInstanceIdEqual(context, e.observer, id));
586
- const uentries = uEntries.filter((e) => ER4__namespace.isInstanceIdEqual(context, e.observer, id));
587
- const isDynamic = (node2) => node2?.type === utils.AST_NODE_TYPES.CallExpression || AST__namespace.isConditional(node2);
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(AST__namespace.findParentNode(e.node, eff.or(isDynamic, 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) => ER4__namespace.isInstanceIdEqual(context, uEntry.element, oEntry.element))) {
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 === utils.AST_NODE_TYPES.Identifier && tsPattern.isMatching(tsPattern.P.union("setTimeout", "clearTimeout"))(node.callee.name)):
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 === utils.AST_NODE_TYPES.MemberExpression && node.callee.property.type === utils.AST_NODE_TYPES.Identifier && tsPattern.isMatching(tsPattern.P.union("setTimeout", "clearTimeout"))(node.callee.property.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)):
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 ER4__namespace.isInstanceIdEqual(context, a.timerId, b.timerId);
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 (!ER4__namespace.ComponentPhaseRelevance.has(fEntry?.kind)) {
630
+ if (!ER4.ComponentPhaseRelevance.has(fEntry?.kind)) {
655
631
  return;
656
632
  }
657
633
  switch (getCallKind4(node)) {
658
634
  case "setTimeout": {
659
- const timeoutIdNode = VAR__namespace.getVariableDeclaratorId(node);
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
- module.exports = index_default;
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.0",
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
- "import": {
28
- "types": "./dist/index.d.mts",
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.0",
52
- "@eslint-react/eff": "2.0.0-next.0",
53
- "@eslint-react/kit": "2.0.0-next.0",
54
- "@eslint-react/core": "2.0.0-next.0",
55
- "@eslint-react/var": "2.0.0-next.0",
56
- "@eslint-react/shared": "2.0.0-next.0"
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 };