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