eslint-plugin-react-web-api 5.20.3 → 5.20.4
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 +157 -250
- package/package.json +6 -7
package/dist/index.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { DEFAULT_ESLINT_REACT_SETTINGS } from "@eslint-react/shared";
|
|
2
|
-
import * as core from "@eslint-react/core";
|
|
3
|
-
import birecord from "birecord";
|
|
4
|
-
import { P, isMatching, match } from "ts-pattern";
|
|
5
2
|
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
6
3
|
import { Check, Compare, Extract, Traverse } from "@eslint-react/ast";
|
|
4
|
+
import { isUseEffectCleanupCallback, isUseEffectSetupCallback } from "@eslint-react/core";
|
|
7
5
|
import "@eslint-react/eslint";
|
|
8
6
|
import { isAssignmentTargetEqual, isInitializedFromReactNative, isValueEqual, resolve, resolveEnclosingAssignmentTarget } from "@eslint-react/var";
|
|
9
7
|
import { AST_NODE_TYPES } from "@typescript-eslint/types";
|
|
8
|
+
import { P, isMatching, match } from "ts-pattern";
|
|
10
9
|
import { getStaticValue } from "@typescript-eslint/utils/ast-utils";
|
|
11
10
|
|
|
12
11
|
//#region \0rolldown/runtime.js
|
|
@@ -28,14 +27,7 @@ var __exportAll = (all, no_symbols) => {
|
|
|
28
27
|
//#endregion
|
|
29
28
|
//#region package.json
|
|
30
29
|
var name$1 = "eslint-plugin-react-web-api";
|
|
31
|
-
var version = "5.20.
|
|
32
|
-
|
|
33
|
-
//#endregion
|
|
34
|
-
//#region src/types/component-phase.ts
|
|
35
|
-
const ComponentPhaseRelevance = birecord({ setup: "cleanup" });
|
|
36
|
-
function getPhaseKindOfFunction(node) {
|
|
37
|
-
return match(node).when(core.isUseEffectSetupCallback, () => "setup").when(core.isUseEffectCleanupCallback, () => "cleanup").otherwise(() => null);
|
|
38
|
-
}
|
|
30
|
+
var version = "5.20.4";
|
|
39
31
|
|
|
40
32
|
//#endregion
|
|
41
33
|
//#region src/utils/create-rule.ts
|
|
@@ -101,11 +93,13 @@ function getOptions(context, node) {
|
|
|
101
93
|
const RULE_NAME$5 = "no-leaked-event-listener";
|
|
102
94
|
function getCallKind$5(node) {
|
|
103
95
|
const name = Extract.getCalleeName(node);
|
|
104
|
-
if (name != null && isMatching(P.union("addEventListener", "removeEventListener"
|
|
96
|
+
if (name != null && isMatching(P.union("addEventListener", "removeEventListener"))(name)) return name;
|
|
105
97
|
return "other";
|
|
106
98
|
}
|
|
107
|
-
function getFunctionKind$
|
|
108
|
-
|
|
99
|
+
function getFunctionKind$5(node) {
|
|
100
|
+
if (isUseEffectSetupCallback(node)) return "setup";
|
|
101
|
+
if (isUseEffectCleanupCallback(node)) return "cleanup";
|
|
102
|
+
return "other";
|
|
109
103
|
}
|
|
110
104
|
var no_leaked_event_listener_default = createRule({
|
|
111
105
|
meta: {
|
|
@@ -127,7 +121,6 @@ function create$5(context) {
|
|
|
127
121
|
const fEntries = [];
|
|
128
122
|
const aEntries = [];
|
|
129
123
|
const rEntries = [];
|
|
130
|
-
const abortedSignals = [];
|
|
131
124
|
function isSameObject(a, b) {
|
|
132
125
|
switch (true) {
|
|
133
126
|
case a.type === AST_NODE_TYPES.MemberExpression && b.type === AST_NODE_TYPES.MemberExpression: return Compare.isEqual(a.object, b.object);
|
|
@@ -137,10 +130,10 @@ function create$5(context) {
|
|
|
137
130
|
function isInverseEntry(aEntry, rEntry) {
|
|
138
131
|
const { type: aType, callee: aCallee, capture: aCapture, listener: aListener, phase: aPhase } = aEntry;
|
|
139
132
|
const { type: rType, callee: rCallee, capture: rCapture, listener: rListener, phase: rPhase } = rEntry;
|
|
140
|
-
if (
|
|
133
|
+
if (aPhase !== "setup" || rPhase !== "cleanup") return false;
|
|
141
134
|
return isSameObject(aCallee, rCallee) && Compare.isEqual(aListener, rListener) && isValueEqual(context, aType, rType) && aCapture === rCapture;
|
|
142
135
|
}
|
|
143
|
-
function
|
|
136
|
+
function visitInlineFunction(node, callKind, options) {
|
|
144
137
|
const listener = node.arguments.at(1);
|
|
145
138
|
if (!Check.isFunction(listener)) return;
|
|
146
139
|
if (options.signal != null) return;
|
|
@@ -152,26 +145,21 @@ function create$5(context) {
|
|
|
152
145
|
}
|
|
153
146
|
return {
|
|
154
147
|
[":function"](node) {
|
|
155
|
-
|
|
156
|
-
fEntries.push({
|
|
157
|
-
kind,
|
|
158
|
-
node
|
|
159
|
-
});
|
|
148
|
+
fEntries.push(getFunctionKind$5(node));
|
|
160
149
|
},
|
|
161
150
|
[":function:exit"]() {
|
|
162
151
|
fEntries.pop();
|
|
163
152
|
},
|
|
164
153
|
["CallExpression"](node) {
|
|
165
|
-
const fKind = fEntries.findLast((
|
|
154
|
+
const fKind = fEntries.findLast((kind) => kind !== "other");
|
|
166
155
|
if (fKind == null) return;
|
|
167
|
-
if (!ComponentPhaseRelevance.has(fKind)) return;
|
|
168
156
|
const callee = Extract.unwrap(node.callee);
|
|
169
157
|
match(getCallKind$5(node)).with("addEventListener", (callKind) => {
|
|
170
158
|
if (callee.type === AST_NODE_TYPES.MemberExpression && Check.isIdentifier(callee.object) && isInitializedFromReactNative(callee.object.name, context.sourceCode.getScope(node))) return;
|
|
171
159
|
const [type, listener, options] = node.arguments;
|
|
172
160
|
if (type == null || listener == null) return;
|
|
173
161
|
const opts = options == null ? defaultOptions : getOptions(context, options);
|
|
174
|
-
|
|
162
|
+
visitInlineFunction(node, callKind, opts);
|
|
175
163
|
aEntries.push({
|
|
176
164
|
...opts,
|
|
177
165
|
type,
|
|
@@ -185,34 +173,27 @@ function create$5(context) {
|
|
|
185
173
|
const [type, listener, options] = node.arguments;
|
|
186
174
|
if (type == null || listener == null) return;
|
|
187
175
|
const opts = options == null ? defaultOptions : getOptions(context, options);
|
|
188
|
-
|
|
176
|
+
visitInlineFunction(node, callKind, opts);
|
|
189
177
|
rEntries.push({
|
|
190
|
-
...opts,
|
|
191
178
|
type,
|
|
192
179
|
callee,
|
|
180
|
+
capture: opts.capture,
|
|
193
181
|
listener,
|
|
194
182
|
method: "removeEventListener",
|
|
195
183
|
node,
|
|
196
184
|
phase: fKind
|
|
197
185
|
});
|
|
198
|
-
}).with("abort", () => {
|
|
199
|
-
abortedSignals.push(node.callee);
|
|
200
186
|
}).otherwise(() => null);
|
|
201
187
|
},
|
|
202
188
|
["Program:exit"]() {
|
|
203
189
|
for (const aEntry of aEntries) {
|
|
204
190
|
if (aEntry.signal != null) continue;
|
|
205
191
|
if (rEntries.some((rEntry) => isInverseEntry(aEntry, rEntry))) continue;
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
messageId: "expectedRemoveEventListenerInCleanup",
|
|
212
|
-
node: aEntry.node
|
|
213
|
-
});
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
192
|
+
context.report({
|
|
193
|
+
data: { effectMethodKind: "useEffect" },
|
|
194
|
+
messageId: "expectedRemoveEventListenerInCleanup",
|
|
195
|
+
node: aEntry.node
|
|
196
|
+
});
|
|
216
197
|
}
|
|
217
198
|
}
|
|
218
199
|
};
|
|
@@ -220,16 +201,6 @@ function create$5(context) {
|
|
|
220
201
|
|
|
221
202
|
//#endregion
|
|
222
203
|
//#region src/rules/no-leaked-fetch/lib.ts
|
|
223
|
-
function findProperty(node, name) {
|
|
224
|
-
for (const property of node) {
|
|
225
|
-
if (property.type === AST_NODE_TYPES.Property && !property.computed && Check.isIdentifier(property.key, name)) return property;
|
|
226
|
-
if (property.type === AST_NODE_TYPES.SpreadElement && property.argument.type === AST_NODE_TYPES.ObjectExpression) {
|
|
227
|
-
const found = findProperty(property.argument.properties, name);
|
|
228
|
-
if (found != null) return found;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
return null;
|
|
232
|
-
}
|
|
233
204
|
function resolveToObjectExpression(context, node) {
|
|
234
205
|
node = Extract.unwrap(node);
|
|
235
206
|
switch (node.type) {
|
|
@@ -248,11 +219,14 @@ function resolveToObjectExpression(context, node) {
|
|
|
248
219
|
//#region src/rules/no-leaked-fetch/no-leaked-fetch.ts
|
|
249
220
|
const RULE_NAME$4 = "no-leaked-fetch";
|
|
250
221
|
function getCallKind$4(node) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
222
|
+
const name = Extract.getCalleeName(node);
|
|
223
|
+
if (name != null && isMatching(P.union("fetch", "abort"))(name)) return name;
|
|
224
|
+
return "other";
|
|
225
|
+
}
|
|
226
|
+
function getFunctionKind$4(node) {
|
|
227
|
+
if (isUseEffectSetupCallback(node)) return "setup";
|
|
228
|
+
if (isUseEffectCleanupCallback(node)) return "cleanup";
|
|
229
|
+
return "other";
|
|
256
230
|
}
|
|
257
231
|
function getControllerFromSignal(context, node) {
|
|
258
232
|
node = Extract.unwrap(node);
|
|
@@ -294,7 +268,7 @@ function getFetchController(context, node) {
|
|
|
294
268
|
controller: null,
|
|
295
269
|
isParamSignal: false
|
|
296
270
|
};
|
|
297
|
-
const signalProp = findProperty(options.properties, "signal");
|
|
271
|
+
const signalProp = Extract.findProperty(options.properties, "signal");
|
|
298
272
|
if (signalProp?.type !== AST_NODE_TYPES.Property) return {
|
|
299
273
|
controller: null,
|
|
300
274
|
isParamSignal: false
|
|
@@ -328,42 +302,31 @@ function create$4(context) {
|
|
|
328
302
|
const abortEntries = [];
|
|
329
303
|
return {
|
|
330
304
|
[":function"](node) {
|
|
331
|
-
|
|
332
|
-
fEntries.push({
|
|
333
|
-
kind,
|
|
334
|
-
node
|
|
335
|
-
});
|
|
305
|
+
fEntries.push(getFunctionKind$4(node));
|
|
336
306
|
},
|
|
337
307
|
[":function:exit"]() {
|
|
338
308
|
fEntries.pop();
|
|
339
309
|
},
|
|
340
310
|
["CallExpression"](node) {
|
|
341
|
-
const
|
|
342
|
-
if (
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
controller,
|
|
361
|
-
node,
|
|
362
|
-
phase: fEntry.kind
|
|
363
|
-
});
|
|
364
|
-
break;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
311
|
+
const fKind = fEntries.at(-1);
|
|
312
|
+
if (fKind == null || fKind === "other") return;
|
|
313
|
+
match(getCallKind$4(node)).with("fetch", () => {
|
|
314
|
+
if (fKind !== "setup") return;
|
|
315
|
+
const { controller, isParamSignal } = getFetchController(context, node);
|
|
316
|
+
fetchEntries.push({
|
|
317
|
+
controller,
|
|
318
|
+
isParamSignal,
|
|
319
|
+
node
|
|
320
|
+
});
|
|
321
|
+
}).with("abort", () => {
|
|
322
|
+
if (fKind !== "cleanup") return;
|
|
323
|
+
const controller = getAbortController(node);
|
|
324
|
+
if (controller == null) return;
|
|
325
|
+
abortEntries.push({
|
|
326
|
+
controller,
|
|
327
|
+
node
|
|
328
|
+
});
|
|
329
|
+
}).otherwise(() => null);
|
|
367
330
|
},
|
|
368
331
|
["Program:exit"]() {
|
|
369
332
|
for (const fEntry of fetchEntries) {
|
|
@@ -864,18 +827,18 @@ const takeWhile = dual(2, (xs, pred) => {
|
|
|
864
827
|
|
|
865
828
|
//#endregion
|
|
866
829
|
//#region src/rules/no-leaked-intersection-observer/lib.ts
|
|
867
|
-
function
|
|
830
|
+
function isNewObserver$1(node, name) {
|
|
868
831
|
if (node?.type !== AST_NODE_TYPES.NewExpression) return false;
|
|
869
832
|
const callee = Extract.unwrap(node.callee);
|
|
870
|
-
return Check.isIdentifier(callee,
|
|
833
|
+
return Check.isIdentifier(callee, name);
|
|
871
834
|
}
|
|
872
|
-
function isFromObserver$1(context, node) {
|
|
835
|
+
function isFromObserver$1(context, node, name) {
|
|
873
836
|
switch (true) {
|
|
874
837
|
case Check.isIdentifier(node): {
|
|
875
838
|
const initNode = resolve(context, node);
|
|
876
|
-
return
|
|
839
|
+
return isNewObserver$1(initNode == null ? null : Extract.unwrap(initNode), name);
|
|
877
840
|
}
|
|
878
|
-
case node.type === AST_NODE_TYPES.MemberExpression: return isFromObserver$1(context, node.object);
|
|
841
|
+
case node.type === AST_NODE_TYPES.MemberExpression: return isFromObserver$1(context, node.object, name);
|
|
879
842
|
default: return false;
|
|
880
843
|
}
|
|
881
844
|
}
|
|
@@ -885,13 +848,15 @@ function isFromObserver$1(context, node) {
|
|
|
885
848
|
const RULE_NAME$3 = "no-leaked-intersection-observer";
|
|
886
849
|
function getCallKind$3(context, node) {
|
|
887
850
|
const callee = Extract.unwrap(node.callee);
|
|
888
|
-
if (callee.type !== AST_NODE_TYPES.
|
|
851
|
+
if (callee.type !== AST_NODE_TYPES.MemberExpression) return "other";
|
|
889
852
|
const name = Extract.getCalleeName(node);
|
|
890
|
-
if (name != null && isMatching(P.union("observe", "unobserve", "disconnect"))(name) && isFromObserver$1(context, callee)) return name;
|
|
853
|
+
if (name != null && isMatching(P.union("observe", "unobserve", "disconnect"))(name) && isFromObserver$1(context, callee, "IntersectionObserver")) return name;
|
|
891
854
|
return "other";
|
|
892
855
|
}
|
|
893
|
-
function getFunctionKind$
|
|
894
|
-
|
|
856
|
+
function getFunctionKind$3(node) {
|
|
857
|
+
if (isUseEffectSetupCallback(node)) return "setup";
|
|
858
|
+
if (isUseEffectCleanupCallback(node)) return "cleanup";
|
|
859
|
+
return "other";
|
|
895
860
|
}
|
|
896
861
|
var no_leaked_intersection_observer_default = createRule({
|
|
897
862
|
meta: {
|
|
@@ -917,7 +882,7 @@ function create$3(context) {
|
|
|
917
882
|
const dEntries = [];
|
|
918
883
|
return {
|
|
919
884
|
[":function"](node) {
|
|
920
|
-
const kind = getFunctionKind$
|
|
885
|
+
const kind = getFunctionKind$3(node);
|
|
921
886
|
fEntries.push({
|
|
922
887
|
kind,
|
|
923
888
|
node
|
|
@@ -927,51 +892,40 @@ function create$3(context) {
|
|
|
927
892
|
fEntries.pop();
|
|
928
893
|
},
|
|
929
894
|
["CallExpression"](node) {
|
|
895
|
+
if (fEntries.findLast((x) => x.kind !== "other")?.kind == null) return;
|
|
930
896
|
const callee = Extract.unwrap(node.callee);
|
|
931
897
|
if (callee.type !== AST_NODE_TYPES.MemberExpression) return;
|
|
932
|
-
const fKind = fEntries.findLast((x) => x.kind !== "other")?.kind;
|
|
933
|
-
if (fKind == null || !ComponentPhaseRelevance.has(fKind)) return;
|
|
934
898
|
const { object } = callee;
|
|
935
899
|
match(getCallKind$3(context, node)).with("disconnect", () => {
|
|
936
900
|
dEntries.push({
|
|
937
|
-
kind: "IntersectionObserver",
|
|
938
|
-
callee,
|
|
939
901
|
method: "disconnect",
|
|
940
902
|
node,
|
|
941
|
-
observer: object
|
|
942
|
-
phase: fKind
|
|
903
|
+
observer: object
|
|
943
904
|
});
|
|
944
905
|
}).with("observe", () => {
|
|
945
906
|
const [element] = node.arguments;
|
|
946
907
|
if (element == null) return;
|
|
947
908
|
oEntries.push({
|
|
948
|
-
kind: "IntersectionObserver",
|
|
949
|
-
callee,
|
|
950
909
|
element,
|
|
951
910
|
method: "observe",
|
|
952
911
|
node,
|
|
953
|
-
observer: object
|
|
954
|
-
phase: fKind
|
|
912
|
+
observer: object
|
|
955
913
|
});
|
|
956
914
|
}).with("unobserve", () => {
|
|
957
915
|
const [element] = node.arguments;
|
|
958
916
|
if (element == null) return;
|
|
959
917
|
uEntries.push({
|
|
960
|
-
kind: "IntersectionObserver",
|
|
961
|
-
callee,
|
|
962
918
|
element,
|
|
963
919
|
method: "unobserve",
|
|
964
920
|
node,
|
|
965
|
-
observer: object
|
|
966
|
-
phase: fKind
|
|
921
|
+
observer: object
|
|
967
922
|
});
|
|
968
923
|
}).otherwise(() => null);
|
|
969
924
|
},
|
|
970
925
|
["NewExpression"](node) {
|
|
971
926
|
const fEntry = fEntries.findLast((x) => x.kind !== "other");
|
|
972
927
|
if (fEntry == null) return;
|
|
973
|
-
if (!
|
|
974
|
-
if (!isNewIntersectionObserver(node)) return;
|
|
928
|
+
if (!isNewObserver$1(node, "IntersectionObserver")) return;
|
|
975
929
|
const id = resolveEnclosingAssignmentTarget(node);
|
|
976
930
|
if (id == null) {
|
|
977
931
|
context.report({
|
|
@@ -983,7 +937,6 @@ function create$3(context) {
|
|
|
983
937
|
observers.push({
|
|
984
938
|
id,
|
|
985
939
|
node,
|
|
986
|
-
phase: fEntry.kind,
|
|
987
940
|
phaseNode: fEntry.node
|
|
988
941
|
});
|
|
989
942
|
},
|
|
@@ -991,19 +944,19 @@ function create$3(context) {
|
|
|
991
944
|
for (const { id, node, phaseNode } of observers) {
|
|
992
945
|
const isInsideObserverCallback = (e) => Traverse.findParent(e.node, (n) => n === node) != null;
|
|
993
946
|
if (dEntries.some((e) => !isInsideObserverCallback(e) && isAssignmentTargetEqual(context, e.observer, id))) continue;
|
|
994
|
-
const
|
|
995
|
-
const
|
|
947
|
+
const matchedOEntries = oEntries.filter((e) => isAssignmentTargetEqual(context, e.observer, id));
|
|
948
|
+
const matchedUEntries = uEntries.filter((e) => isAssignmentTargetEqual(context, e.observer, id));
|
|
996
949
|
const isDynamic = (node) => node?.type === AST_NODE_TYPES.CallExpression || Check.isConditional(node);
|
|
997
950
|
const isPhaseNode = (node) => node === phaseNode;
|
|
998
|
-
if (
|
|
951
|
+
if (matchedOEntries.some((e) => !isPhaseNode(Traverse.findParent(e.node, or(isDynamic, isPhaseNode))))) {
|
|
999
952
|
context.report({
|
|
1000
953
|
messageId: "expectedDisconnectInControlFlow",
|
|
1001
954
|
node
|
|
1002
955
|
});
|
|
1003
956
|
continue;
|
|
1004
957
|
}
|
|
1005
|
-
for (const oEntry of
|
|
1006
|
-
if (
|
|
958
|
+
for (const oEntry of matchedOEntries) {
|
|
959
|
+
if (matchedUEntries.some((uEntry) => isAssignmentTargetEqual(context, uEntry.element, oEntry.element))) continue;
|
|
1007
960
|
context.report({
|
|
1008
961
|
messageId: "expectedDisconnectOrUnobserveInCleanup",
|
|
1009
962
|
node: oEntry.node
|
|
@@ -1022,6 +975,11 @@ function getCallKind$2(node) {
|
|
|
1022
975
|
if (name != null && isMatching(P.union("setInterval", "clearInterval"))(name)) return name;
|
|
1023
976
|
return "other";
|
|
1024
977
|
}
|
|
978
|
+
function getFunctionKind$2(node) {
|
|
979
|
+
if (isUseEffectSetupCallback(node)) return "setup";
|
|
980
|
+
if (isUseEffectCleanupCallback(node)) return "cleanup";
|
|
981
|
+
return "other";
|
|
982
|
+
}
|
|
1025
983
|
var no_leaked_interval_default = createRule({
|
|
1026
984
|
meta: {
|
|
1027
985
|
type: "problem",
|
|
@@ -1046,68 +1004,43 @@ function create$2(context) {
|
|
|
1046
1004
|
}
|
|
1047
1005
|
return {
|
|
1048
1006
|
[":function"](node) {
|
|
1049
|
-
|
|
1050
|
-
fEntries.push({
|
|
1051
|
-
kind,
|
|
1052
|
-
node
|
|
1053
|
-
});
|
|
1007
|
+
fEntries.push(getFunctionKind$2(node));
|
|
1054
1008
|
},
|
|
1055
1009
|
[":function:exit"]() {
|
|
1056
1010
|
fEntries.pop();
|
|
1057
1011
|
},
|
|
1058
1012
|
["CallExpression"](node) {
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
context.report({
|
|
1067
|
-
messageId: "expectedIntervalId",
|
|
1068
|
-
node
|
|
1069
|
-
});
|
|
1070
|
-
break;
|
|
1071
|
-
}
|
|
1072
|
-
sEntries.push({
|
|
1073
|
-
kind: "interval",
|
|
1074
|
-
callee: node.callee,
|
|
1075
|
-
node,
|
|
1076
|
-
phase: fEntry.kind,
|
|
1077
|
-
timerId: intervalIdNode
|
|
1078
|
-
});
|
|
1079
|
-
break;
|
|
1080
|
-
}
|
|
1081
|
-
case "clearInterval": {
|
|
1082
|
-
const fEntry = fEntries.findLast((x) => x.kind !== "other");
|
|
1083
|
-
if (fEntry == null) break;
|
|
1084
|
-
if (!ComponentPhaseRelevance.has(fEntry.kind)) break;
|
|
1085
|
-
const [intervalIdNode] = node.arguments;
|
|
1086
|
-
if (intervalIdNode == null) break;
|
|
1087
|
-
cEntries.push({
|
|
1088
|
-
kind: "interval",
|
|
1089
|
-
callee: node.callee,
|
|
1090
|
-
node,
|
|
1091
|
-
phase: fEntry.kind,
|
|
1092
|
-
timerId: intervalIdNode
|
|
1013
|
+
if (fEntries.findLast((kind) => kind !== "other") == null) return;
|
|
1014
|
+
match(getCallKind$2(node)).with("setInterval", () => {
|
|
1015
|
+
const intervalIdNode = resolveEnclosingAssignmentTarget(node);
|
|
1016
|
+
if (intervalIdNode == null) {
|
|
1017
|
+
context.report({
|
|
1018
|
+
messageId: "expectedIntervalId",
|
|
1019
|
+
node
|
|
1093
1020
|
});
|
|
1094
|
-
|
|
1021
|
+
return;
|
|
1095
1022
|
}
|
|
1096
|
-
|
|
1023
|
+
sEntries.push({
|
|
1024
|
+
node,
|
|
1025
|
+
timerId: intervalIdNode
|
|
1026
|
+
});
|
|
1027
|
+
}).with("clearInterval", () => {
|
|
1028
|
+
const [intervalIdNode] = node.arguments;
|
|
1029
|
+
if (intervalIdNode == null) return;
|
|
1030
|
+
cEntries.push({
|
|
1031
|
+
node,
|
|
1032
|
+
timerId: intervalIdNode
|
|
1033
|
+
});
|
|
1034
|
+
}).otherwise(() => null);
|
|
1097
1035
|
},
|
|
1098
1036
|
["Program:exit"]() {
|
|
1099
1037
|
for (const sEntry of sEntries) {
|
|
1100
1038
|
if (cEntries.some((cEntry) => isInverseEntry(sEntry, cEntry))) continue;
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
messageId: "expectedClearIntervalInCleanup",
|
|
1107
|
-
node: sEntry.node
|
|
1108
|
-
});
|
|
1109
|
-
continue;
|
|
1110
|
-
}
|
|
1039
|
+
context.report({
|
|
1040
|
+
data: { kind: "useEffect" },
|
|
1041
|
+
messageId: "expectedClearIntervalInCleanup",
|
|
1042
|
+
node: sEntry.node
|
|
1043
|
+
});
|
|
1111
1044
|
}
|
|
1112
1045
|
}
|
|
1113
1046
|
};
|
|
@@ -1115,18 +1048,18 @@ function create$2(context) {
|
|
|
1115
1048
|
|
|
1116
1049
|
//#endregion
|
|
1117
1050
|
//#region src/rules/no-leaked-resize-observer/lib.ts
|
|
1118
|
-
function
|
|
1051
|
+
function isNewObserver(node, name) {
|
|
1119
1052
|
if (node?.type !== AST_NODE_TYPES.NewExpression) return false;
|
|
1120
1053
|
const callee = Extract.unwrap(node.callee);
|
|
1121
|
-
return Check.isIdentifier(callee,
|
|
1054
|
+
return Check.isIdentifier(callee, name);
|
|
1122
1055
|
}
|
|
1123
|
-
function isFromObserver(context, node) {
|
|
1056
|
+
function isFromObserver(context, node, name) {
|
|
1124
1057
|
switch (true) {
|
|
1125
1058
|
case Check.isIdentifier(node): {
|
|
1126
1059
|
const initNode = resolve(context, node);
|
|
1127
|
-
return
|
|
1060
|
+
return isNewObserver(initNode == null ? null : Extract.unwrap(initNode), name);
|
|
1128
1061
|
}
|
|
1129
|
-
case node.type === AST_NODE_TYPES.MemberExpression: return isFromObserver(context, node.object);
|
|
1062
|
+
case node.type === AST_NODE_TYPES.MemberExpression: return isFromObserver(context, node.object, name);
|
|
1130
1063
|
default: return false;
|
|
1131
1064
|
}
|
|
1132
1065
|
}
|
|
@@ -1136,13 +1069,15 @@ function isFromObserver(context, node) {
|
|
|
1136
1069
|
const RULE_NAME$1 = "no-leaked-resize-observer";
|
|
1137
1070
|
function getCallKind$1(context, node) {
|
|
1138
1071
|
const callee = Extract.unwrap(node.callee);
|
|
1139
|
-
if (callee.type !== AST_NODE_TYPES.
|
|
1072
|
+
if (callee.type !== AST_NODE_TYPES.MemberExpression) return "other";
|
|
1140
1073
|
const name = Extract.getCalleeName(node);
|
|
1141
|
-
if (name != null && isMatching(P.union("observe", "unobserve", "disconnect"))(name) && isFromObserver(context, callee)) return name;
|
|
1074
|
+
if (name != null && isMatching(P.union("observe", "unobserve", "disconnect"))(name) && isFromObserver(context, callee, "ResizeObserver")) return name;
|
|
1142
1075
|
return "other";
|
|
1143
1076
|
}
|
|
1144
|
-
function getFunctionKind(node) {
|
|
1145
|
-
|
|
1077
|
+
function getFunctionKind$1(node) {
|
|
1078
|
+
if (isUseEffectSetupCallback(node)) return "setup";
|
|
1079
|
+
if (isUseEffectCleanupCallback(node)) return "cleanup";
|
|
1080
|
+
return "other";
|
|
1146
1081
|
}
|
|
1147
1082
|
var no_leaked_resize_observer_default = createRule({
|
|
1148
1083
|
meta: {
|
|
@@ -1168,7 +1103,7 @@ function create$1(context) {
|
|
|
1168
1103
|
const dEntries = [];
|
|
1169
1104
|
return {
|
|
1170
1105
|
[":function"](node) {
|
|
1171
|
-
const kind = getFunctionKind(node);
|
|
1106
|
+
const kind = getFunctionKind$1(node);
|
|
1172
1107
|
fEntries.push({
|
|
1173
1108
|
kind,
|
|
1174
1109
|
node
|
|
@@ -1178,51 +1113,40 @@ function create$1(context) {
|
|
|
1178
1113
|
fEntries.pop();
|
|
1179
1114
|
},
|
|
1180
1115
|
["CallExpression"](node) {
|
|
1116
|
+
if (fEntries.findLast((x) => x.kind !== "other")?.kind == null) return;
|
|
1181
1117
|
const callee = Extract.unwrap(node.callee);
|
|
1182
1118
|
if (callee.type !== AST_NODE_TYPES.MemberExpression) return;
|
|
1183
|
-
const fKind = fEntries.findLast((x) => x.kind !== "other")?.kind;
|
|
1184
|
-
if (fKind == null || !ComponentPhaseRelevance.has(fKind)) return;
|
|
1185
1119
|
const { object } = callee;
|
|
1186
1120
|
match(getCallKind$1(context, node)).with("disconnect", () => {
|
|
1187
1121
|
dEntries.push({
|
|
1188
|
-
kind: "ResizeObserver",
|
|
1189
|
-
callee,
|
|
1190
1122
|
method: "disconnect",
|
|
1191
1123
|
node,
|
|
1192
|
-
observer: object
|
|
1193
|
-
phase: fKind
|
|
1124
|
+
observer: object
|
|
1194
1125
|
});
|
|
1195
1126
|
}).with("observe", () => {
|
|
1196
1127
|
const [element] = node.arguments;
|
|
1197
1128
|
if (element == null) return;
|
|
1198
1129
|
oEntries.push({
|
|
1199
|
-
kind: "ResizeObserver",
|
|
1200
|
-
callee,
|
|
1201
1130
|
element,
|
|
1202
1131
|
method: "observe",
|
|
1203
1132
|
node,
|
|
1204
|
-
observer: object
|
|
1205
|
-
phase: fKind
|
|
1133
|
+
observer: object
|
|
1206
1134
|
});
|
|
1207
1135
|
}).with("unobserve", () => {
|
|
1208
1136
|
const [element] = node.arguments;
|
|
1209
1137
|
if (element == null) return;
|
|
1210
1138
|
uEntries.push({
|
|
1211
|
-
kind: "ResizeObserver",
|
|
1212
|
-
callee,
|
|
1213
1139
|
element,
|
|
1214
1140
|
method: "unobserve",
|
|
1215
1141
|
node,
|
|
1216
|
-
observer: object
|
|
1217
|
-
phase: fKind
|
|
1142
|
+
observer: object
|
|
1218
1143
|
});
|
|
1219
1144
|
}).otherwise(() => null);
|
|
1220
1145
|
},
|
|
1221
1146
|
["NewExpression"](node) {
|
|
1222
1147
|
const fEntry = fEntries.findLast((x) => x.kind !== "other");
|
|
1223
1148
|
if (fEntry == null) return;
|
|
1224
|
-
if (!
|
|
1225
|
-
if (!isNewResizeObserver(node)) return;
|
|
1149
|
+
if (!isNewObserver(node, "ResizeObserver")) return;
|
|
1226
1150
|
const id = resolveEnclosingAssignmentTarget(node);
|
|
1227
1151
|
if (id == null) {
|
|
1228
1152
|
context.report({
|
|
@@ -1234,7 +1158,6 @@ function create$1(context) {
|
|
|
1234
1158
|
observers.push({
|
|
1235
1159
|
id,
|
|
1236
1160
|
node,
|
|
1237
|
-
phase: fEntry.kind,
|
|
1238
1161
|
phaseNode: fEntry.node
|
|
1239
1162
|
});
|
|
1240
1163
|
},
|
|
@@ -1242,19 +1165,19 @@ function create$1(context) {
|
|
|
1242
1165
|
for (const { id, node, phaseNode } of observers) {
|
|
1243
1166
|
const isInsideObserverCallback = (e) => Traverse.findParent(e.node, (n) => n === node) != null;
|
|
1244
1167
|
if (dEntries.some((e) => !isInsideObserverCallback(e) && isAssignmentTargetEqual(context, e.observer, id))) continue;
|
|
1245
|
-
const
|
|
1246
|
-
const
|
|
1168
|
+
const matchedOEntries = oEntries.filter((e) => isAssignmentTargetEqual(context, e.observer, id));
|
|
1169
|
+
const matchedUEntries = uEntries.filter((e) => isAssignmentTargetEqual(context, e.observer, id));
|
|
1247
1170
|
const isDynamic = (node) => node?.type === AST_NODE_TYPES.CallExpression || Check.isConditional(node);
|
|
1248
1171
|
const isPhaseNode = (node) => node === phaseNode;
|
|
1249
|
-
if (
|
|
1172
|
+
if (matchedOEntries.some((e) => !isPhaseNode(Traverse.findParent(e.node, or(isDynamic, isPhaseNode))))) {
|
|
1250
1173
|
context.report({
|
|
1251
1174
|
messageId: "expectedDisconnectInControlFlow",
|
|
1252
1175
|
node
|
|
1253
1176
|
});
|
|
1254
1177
|
continue;
|
|
1255
1178
|
}
|
|
1256
|
-
for (const oEntry of
|
|
1257
|
-
if (
|
|
1179
|
+
for (const oEntry of matchedOEntries) {
|
|
1180
|
+
if (matchedUEntries.some((uEntry) => isAssignmentTargetEqual(context, uEntry.element, oEntry.element))) continue;
|
|
1258
1181
|
context.report({
|
|
1259
1182
|
messageId: "expectedDisconnectOrUnobserveInCleanup",
|
|
1260
1183
|
node: oEntry.node
|
|
@@ -1273,6 +1196,11 @@ function getCallKind(node) {
|
|
|
1273
1196
|
if (name != null && isMatching(P.union("setTimeout", "clearTimeout"))(name)) return name;
|
|
1274
1197
|
return "other";
|
|
1275
1198
|
}
|
|
1199
|
+
function getFunctionKind(node) {
|
|
1200
|
+
if (isUseEffectSetupCallback(node)) return "setup";
|
|
1201
|
+
if (isUseEffectCleanupCallback(node)) return "cleanup";
|
|
1202
|
+
return "other";
|
|
1203
|
+
}
|
|
1276
1204
|
var no_leaked_timeout_default = createRule({
|
|
1277
1205
|
meta: {
|
|
1278
1206
|
type: "problem",
|
|
@@ -1291,70 +1219,49 @@ function create(context) {
|
|
|
1291
1219
|
if (!context.sourceCode.text.includes("setTimeout")) return {};
|
|
1292
1220
|
const fEntries = [];
|
|
1293
1221
|
const sEntries = [];
|
|
1294
|
-
const
|
|
1222
|
+
const cEntries = [];
|
|
1295
1223
|
function isInverseEntry(a, b) {
|
|
1296
1224
|
return isAssignmentTargetEqual(context, a.timerId, b.timerId);
|
|
1297
1225
|
}
|
|
1298
1226
|
return {
|
|
1299
1227
|
[":function"](node) {
|
|
1300
|
-
|
|
1301
|
-
fEntries.push({
|
|
1302
|
-
kind,
|
|
1303
|
-
node
|
|
1304
|
-
});
|
|
1228
|
+
fEntries.push(getFunctionKind(node));
|
|
1305
1229
|
},
|
|
1306
1230
|
[":function:exit"]() {
|
|
1307
1231
|
fEntries.pop();
|
|
1308
1232
|
},
|
|
1309
1233
|
["CallExpression"](node) {
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
messageId: "expectedTimeoutId",
|
|
1318
|
-
node
|
|
1319
|
-
});
|
|
1320
|
-
break;
|
|
1321
|
-
}
|
|
1322
|
-
sEntries.push({
|
|
1323
|
-
kind: "timeout",
|
|
1324
|
-
callee: node.callee,
|
|
1325
|
-
node,
|
|
1326
|
-
phase: fEntry.kind,
|
|
1327
|
-
timerId: timeoutIdNode
|
|
1328
|
-
});
|
|
1329
|
-
break;
|
|
1330
|
-
}
|
|
1331
|
-
case "clearTimeout": {
|
|
1332
|
-
const [timeoutIdNode] = node.arguments;
|
|
1333
|
-
if (timeoutIdNode == null) break;
|
|
1334
|
-
rEntries.push({
|
|
1335
|
-
kind: "timeout",
|
|
1336
|
-
callee: node.callee,
|
|
1337
|
-
node,
|
|
1338
|
-
phase: fEntry.kind,
|
|
1339
|
-
timerId: timeoutIdNode
|
|
1234
|
+
if (fEntries.findLast((kind) => kind !== "other") == null) return;
|
|
1235
|
+
match(getCallKind(node)).with("setTimeout", () => {
|
|
1236
|
+
const timeoutIdNode = resolveEnclosingAssignmentTarget(node);
|
|
1237
|
+
if (timeoutIdNode == null) {
|
|
1238
|
+
context.report({
|
|
1239
|
+
messageId: "expectedTimeoutId",
|
|
1240
|
+
node
|
|
1340
1241
|
});
|
|
1341
|
-
|
|
1242
|
+
return;
|
|
1342
1243
|
}
|
|
1343
|
-
|
|
1244
|
+
sEntries.push({
|
|
1245
|
+
node,
|
|
1246
|
+
timerId: timeoutIdNode
|
|
1247
|
+
});
|
|
1248
|
+
}).with("clearTimeout", () => {
|
|
1249
|
+
const [timeoutIdNode] = node.arguments;
|
|
1250
|
+
if (timeoutIdNode == null) return;
|
|
1251
|
+
cEntries.push({
|
|
1252
|
+
node,
|
|
1253
|
+
timerId: timeoutIdNode
|
|
1254
|
+
});
|
|
1255
|
+
}).otherwise(() => null);
|
|
1344
1256
|
},
|
|
1345
1257
|
["Program:exit"]() {
|
|
1346
1258
|
for (const sEntry of sEntries) {
|
|
1347
|
-
if (
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
messageId: "expectedClearTimeoutInCleanup",
|
|
1354
|
-
node: sEntry.node
|
|
1355
|
-
});
|
|
1356
|
-
continue;
|
|
1357
|
-
}
|
|
1259
|
+
if (cEntries.some((cEntry) => isInverseEntry(sEntry, cEntry))) continue;
|
|
1260
|
+
context.report({
|
|
1261
|
+
data: { kind: "useEffect" },
|
|
1262
|
+
messageId: "expectedClearTimeoutInCleanup",
|
|
1263
|
+
node: sEntry.node
|
|
1264
|
+
});
|
|
1358
1265
|
}
|
|
1359
1266
|
}
|
|
1360
1267
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-react-web-api",
|
|
3
|
-
"version": "5.20.
|
|
3
|
+
"version": "5.20.4",
|
|
4
4
|
"description": "ESLint React's ESLint plugin for interacting with Web APIs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -37,14 +37,13 @@
|
|
|
37
37
|
"dist"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@eslint-react/ast": "5.20.
|
|
41
|
-
"@eslint-react/core": "5.20.
|
|
42
|
-
"@eslint-react/eslint": "5.20.
|
|
43
|
-
"@eslint-react/shared": "5.20.
|
|
44
|
-
"@eslint-react/var": "5.20.
|
|
40
|
+
"@eslint-react/ast": "5.20.4",
|
|
41
|
+
"@eslint-react/core": "5.20.4",
|
|
42
|
+
"@eslint-react/eslint": "5.20.4",
|
|
43
|
+
"@eslint-react/shared": "5.20.4",
|
|
44
|
+
"@eslint-react/var": "5.20.4",
|
|
45
45
|
"@typescript-eslint/types": "^8.70.0",
|
|
46
46
|
"@typescript-eslint/utils": "^8.70.0",
|
|
47
|
-
"birecord": "^0.1.2",
|
|
48
47
|
"ts-pattern": "^5.9.0"
|
|
49
48
|
},
|
|
50
49
|
"devDependencies": {
|