eslint-plugin-react-web-api 1.48.3 → 2.0.0-next.1

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