eslint-plugin-react-web-api 5.20.3 → 5.20.5

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.
Files changed (2) hide show
  1. package/dist/index.js +155 -250
  2. 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.3";
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.5";
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", "abort"))(name)) return name;
96
+ if (name != null && isMatching(P.union("addEventListener", "removeEventListener"))(name)) return name;
105
97
  return "other";
106
98
  }
107
- function getFunctionKind$2(node) {
108
- return getPhaseKindOfFunction(node) ?? "other";
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 (ComponentPhaseRelevance.get(aPhase) !== rPhase) return false;
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 checkInlineFunction(node, callKind, options) {
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
- const kind = getFunctionKind$2(node);
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((x) => x.kind !== "other")?.kind;
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
- checkInlineFunction(node, callKind, opts);
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
- checkInlineFunction(node, callKind, opts);
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
- switch (aEntry.phase) {
207
- case "setup":
208
- case "cleanup":
209
- context.report({
210
- data: { effectMethodKind: "useEffect" },
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
- switch (Extract.getCalleeName(node)) {
252
- case "fetch": return "fetch";
253
- case "abort": return "abort";
254
- default: return "other";
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,29 @@ function create$4(context) {
328
302
  const abortEntries = [];
329
303
  return {
330
304
  [":function"](node) {
331
- const kind = getPhaseKindOfFunction(node) ?? "other";
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 fEntry = fEntries.at(-1);
342
- if (fEntry == null || !ComponentPhaseRelevance.has(fEntry.kind)) return;
343
- switch (getCallKind$4(node)) {
344
- case "fetch": {
345
- if (fEntry.kind !== "setup") return;
346
- const { controller, isParamSignal } = getFetchController(context, node);
347
- fetchEntries.push({
348
- controller,
349
- isParamSignal,
350
- node,
351
- phase: fEntry.kind
352
- });
353
- break;
354
- }
355
- case "abort": {
356
- if (fEntry.kind !== "cleanup") return;
357
- const controller = getAbortController(node);
358
- if (controller == null) break;
359
- abortEntries.push({
360
- controller,
361
- node,
362
- phase: fEntry.kind
363
- });
364
- break;
365
- }
366
- }
311
+ match(getCallKind$4(node)).with("fetch", () => {
312
+ if (fEntries.at(-1) !== "setup") return;
313
+ const { controller, isParamSignal } = getFetchController(context, node);
314
+ fetchEntries.push({
315
+ controller,
316
+ isParamSignal,
317
+ node
318
+ });
319
+ }).with("abort", () => {
320
+ if (fEntries.findLast((kind) => kind !== "other") !== "cleanup") return;
321
+ const controller = getAbortController(node);
322
+ if (controller == null) return;
323
+ abortEntries.push({
324
+ controller,
325
+ node
326
+ });
327
+ }).otherwise(() => null);
367
328
  },
368
329
  ["Program:exit"]() {
369
330
  for (const fEntry of fetchEntries) {
@@ -864,18 +825,18 @@ const takeWhile = dual(2, (xs, pred) => {
864
825
 
865
826
  //#endregion
866
827
  //#region src/rules/no-leaked-intersection-observer/lib.ts
867
- function isNewIntersectionObserver(node) {
828
+ function isNewObserver$1(node, name) {
868
829
  if (node?.type !== AST_NODE_TYPES.NewExpression) return false;
869
830
  const callee = Extract.unwrap(node.callee);
870
- return Check.isIdentifier(callee, "IntersectionObserver");
831
+ return Check.isIdentifier(callee, name);
871
832
  }
872
- function isFromObserver$1(context, node) {
833
+ function isFromObserver$1(context, node, name) {
873
834
  switch (true) {
874
835
  case Check.isIdentifier(node): {
875
836
  const initNode = resolve(context, node);
876
- return isNewIntersectionObserver(initNode == null ? null : Extract.unwrap(initNode));
837
+ return isNewObserver$1(initNode == null ? null : Extract.unwrap(initNode), name);
877
838
  }
878
- case node.type === AST_NODE_TYPES.MemberExpression: return isFromObserver$1(context, node.object);
839
+ case node.type === AST_NODE_TYPES.MemberExpression: return isFromObserver$1(context, node.object, name);
879
840
  default: return false;
880
841
  }
881
842
  }
@@ -885,13 +846,15 @@ function isFromObserver$1(context, node) {
885
846
  const RULE_NAME$3 = "no-leaked-intersection-observer";
886
847
  function getCallKind$3(context, node) {
887
848
  const callee = Extract.unwrap(node.callee);
888
- if (callee.type !== AST_NODE_TYPES.Identifier && callee.type !== AST_NODE_TYPES.MemberExpression) return "other";
849
+ if (callee.type !== AST_NODE_TYPES.MemberExpression) return "other";
889
850
  const name = Extract.getCalleeName(node);
890
- if (name != null && isMatching(P.union("observe", "unobserve", "disconnect"))(name) && isFromObserver$1(context, callee)) return name;
851
+ if (name != null && isMatching(P.union("observe", "unobserve", "disconnect"))(name) && isFromObserver$1(context, callee, "IntersectionObserver")) return name;
891
852
  return "other";
892
853
  }
893
- function getFunctionKind$1(node) {
894
- return getPhaseKindOfFunction(node) ?? "other";
854
+ function getFunctionKind$3(node) {
855
+ if (isUseEffectSetupCallback(node)) return "setup";
856
+ if (isUseEffectCleanupCallback(node)) return "cleanup";
857
+ return "other";
895
858
  }
896
859
  var no_leaked_intersection_observer_default = createRule({
897
860
  meta: {
@@ -917,7 +880,7 @@ function create$3(context) {
917
880
  const dEntries = [];
918
881
  return {
919
882
  [":function"](node) {
920
- const kind = getFunctionKind$1(node);
883
+ const kind = getFunctionKind$3(node);
921
884
  fEntries.push({
922
885
  kind,
923
886
  node
@@ -927,51 +890,40 @@ function create$3(context) {
927
890
  fEntries.pop();
928
891
  },
929
892
  ["CallExpression"](node) {
893
+ if (fEntries.findLast((x) => x.kind !== "other")?.kind == null) return;
930
894
  const callee = Extract.unwrap(node.callee);
931
895
  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
896
  const { object } = callee;
935
897
  match(getCallKind$3(context, node)).with("disconnect", () => {
936
898
  dEntries.push({
937
- kind: "IntersectionObserver",
938
- callee,
939
899
  method: "disconnect",
940
900
  node,
941
- observer: object,
942
- phase: fKind
901
+ observer: object
943
902
  });
944
903
  }).with("observe", () => {
945
904
  const [element] = node.arguments;
946
905
  if (element == null) return;
947
906
  oEntries.push({
948
- kind: "IntersectionObserver",
949
- callee,
950
907
  element,
951
908
  method: "observe",
952
909
  node,
953
- observer: object,
954
- phase: fKind
910
+ observer: object
955
911
  });
956
912
  }).with("unobserve", () => {
957
913
  const [element] = node.arguments;
958
914
  if (element == null) return;
959
915
  uEntries.push({
960
- kind: "IntersectionObserver",
961
- callee,
962
916
  element,
963
917
  method: "unobserve",
964
918
  node,
965
- observer: object,
966
- phase: fKind
919
+ observer: object
967
920
  });
968
921
  }).otherwise(() => null);
969
922
  },
970
923
  ["NewExpression"](node) {
971
924
  const fEntry = fEntries.findLast((x) => x.kind !== "other");
972
925
  if (fEntry == null) return;
973
- if (!ComponentPhaseRelevance.has(fEntry.kind)) return;
974
- if (!isNewIntersectionObserver(node)) return;
926
+ if (!isNewObserver$1(node, "IntersectionObserver")) return;
975
927
  const id = resolveEnclosingAssignmentTarget(node);
976
928
  if (id == null) {
977
929
  context.report({
@@ -983,7 +935,6 @@ function create$3(context) {
983
935
  observers.push({
984
936
  id,
985
937
  node,
986
- phase: fEntry.kind,
987
938
  phaseNode: fEntry.node
988
939
  });
989
940
  },
@@ -991,19 +942,19 @@ function create$3(context) {
991
942
  for (const { id, node, phaseNode } of observers) {
992
943
  const isInsideObserverCallback = (e) => Traverse.findParent(e.node, (n) => n === node) != null;
993
944
  if (dEntries.some((e) => !isInsideObserverCallback(e) && isAssignmentTargetEqual(context, e.observer, id))) continue;
994
- const oentries = oEntries.filter((e) => isAssignmentTargetEqual(context, e.observer, id));
995
- const uentries = uEntries.filter((e) => isAssignmentTargetEqual(context, e.observer, id));
945
+ const matchedOEntries = oEntries.filter((e) => isAssignmentTargetEqual(context, e.observer, id));
946
+ const matchedUEntries = uEntries.filter((e) => isAssignmentTargetEqual(context, e.observer, id));
996
947
  const isDynamic = (node) => node?.type === AST_NODE_TYPES.CallExpression || Check.isConditional(node);
997
948
  const isPhaseNode = (node) => node === phaseNode;
998
- if (oentries.some((e) => !isPhaseNode(Traverse.findParent(e.node, or(isDynamic, isPhaseNode))))) {
949
+ if (matchedOEntries.some((e) => !isPhaseNode(Traverse.findParent(e.node, or(isDynamic, isPhaseNode))))) {
999
950
  context.report({
1000
951
  messageId: "expectedDisconnectInControlFlow",
1001
952
  node
1002
953
  });
1003
954
  continue;
1004
955
  }
1005
- for (const oEntry of oentries) {
1006
- if (uentries.some((uEntry) => isAssignmentTargetEqual(context, uEntry.element, oEntry.element))) continue;
956
+ for (const oEntry of matchedOEntries) {
957
+ if (matchedUEntries.some((uEntry) => isAssignmentTargetEqual(context, uEntry.element, oEntry.element))) continue;
1007
958
  context.report({
1008
959
  messageId: "expectedDisconnectOrUnobserveInCleanup",
1009
960
  node: oEntry.node
@@ -1022,6 +973,11 @@ function getCallKind$2(node) {
1022
973
  if (name != null && isMatching(P.union("setInterval", "clearInterval"))(name)) return name;
1023
974
  return "other";
1024
975
  }
976
+ function getFunctionKind$2(node) {
977
+ if (isUseEffectSetupCallback(node)) return "setup";
978
+ if (isUseEffectCleanupCallback(node)) return "cleanup";
979
+ return "other";
980
+ }
1025
981
  var no_leaked_interval_default = createRule({
1026
982
  meta: {
1027
983
  type: "problem",
@@ -1046,68 +1002,43 @@ function create$2(context) {
1046
1002
  }
1047
1003
  return {
1048
1004
  [":function"](node) {
1049
- const kind = getPhaseKindOfFunction(node) ?? "other";
1050
- fEntries.push({
1051
- kind,
1052
- node
1053
- });
1005
+ fEntries.push(getFunctionKind$2(node));
1054
1006
  },
1055
1007
  [":function:exit"]() {
1056
1008
  fEntries.pop();
1057
1009
  },
1058
1010
  ["CallExpression"](node) {
1059
- switch (getCallKind$2(node)) {
1060
- case "setInterval": {
1061
- const fEntry = fEntries.findLast((x) => x.kind !== "other");
1062
- if (fEntry == null) break;
1063
- if (!ComponentPhaseRelevance.has(fEntry.kind)) break;
1064
- const intervalIdNode = resolveEnclosingAssignmentTarget(node);
1065
- if (intervalIdNode == null) {
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
1011
+ if (fEntries.findLast((kind) => kind !== "other") == null) return;
1012
+ match(getCallKind$2(node)).with("setInterval", () => {
1013
+ const intervalIdNode = resolveEnclosingAssignmentTarget(node);
1014
+ if (intervalIdNode == null) {
1015
+ context.report({
1016
+ messageId: "expectedIntervalId",
1017
+ node
1093
1018
  });
1094
- break;
1019
+ return;
1095
1020
  }
1096
- }
1021
+ sEntries.push({
1022
+ node,
1023
+ timerId: intervalIdNode
1024
+ });
1025
+ }).with("clearInterval", () => {
1026
+ const [intervalIdNode] = node.arguments;
1027
+ if (intervalIdNode == null) return;
1028
+ cEntries.push({
1029
+ node,
1030
+ timerId: intervalIdNode
1031
+ });
1032
+ }).otherwise(() => null);
1097
1033
  },
1098
1034
  ["Program:exit"]() {
1099
1035
  for (const sEntry of sEntries) {
1100
1036
  if (cEntries.some((cEntry) => isInverseEntry(sEntry, cEntry))) continue;
1101
- switch (sEntry.phase) {
1102
- case "setup":
1103
- case "cleanup":
1104
- context.report({
1105
- data: { kind: "useEffect" },
1106
- messageId: "expectedClearIntervalInCleanup",
1107
- node: sEntry.node
1108
- });
1109
- continue;
1110
- }
1037
+ context.report({
1038
+ data: { kind: "useEffect" },
1039
+ messageId: "expectedClearIntervalInCleanup",
1040
+ node: sEntry.node
1041
+ });
1111
1042
  }
1112
1043
  }
1113
1044
  };
@@ -1115,18 +1046,18 @@ function create$2(context) {
1115
1046
 
1116
1047
  //#endregion
1117
1048
  //#region src/rules/no-leaked-resize-observer/lib.ts
1118
- function isNewResizeObserver(node) {
1049
+ function isNewObserver(node, name) {
1119
1050
  if (node?.type !== AST_NODE_TYPES.NewExpression) return false;
1120
1051
  const callee = Extract.unwrap(node.callee);
1121
- return Check.isIdentifier(callee, "ResizeObserver");
1052
+ return Check.isIdentifier(callee, name);
1122
1053
  }
1123
- function isFromObserver(context, node) {
1054
+ function isFromObserver(context, node, name) {
1124
1055
  switch (true) {
1125
1056
  case Check.isIdentifier(node): {
1126
1057
  const initNode = resolve(context, node);
1127
- return isNewResizeObserver(initNode == null ? null : Extract.unwrap(initNode));
1058
+ return isNewObserver(initNode == null ? null : Extract.unwrap(initNode), name);
1128
1059
  }
1129
- case node.type === AST_NODE_TYPES.MemberExpression: return isFromObserver(context, node.object);
1060
+ case node.type === AST_NODE_TYPES.MemberExpression: return isFromObserver(context, node.object, name);
1130
1061
  default: return false;
1131
1062
  }
1132
1063
  }
@@ -1136,13 +1067,15 @@ function isFromObserver(context, node) {
1136
1067
  const RULE_NAME$1 = "no-leaked-resize-observer";
1137
1068
  function getCallKind$1(context, node) {
1138
1069
  const callee = Extract.unwrap(node.callee);
1139
- if (callee.type !== AST_NODE_TYPES.Identifier && callee.type !== AST_NODE_TYPES.MemberExpression) return "other";
1070
+ if (callee.type !== AST_NODE_TYPES.MemberExpression) return "other";
1140
1071
  const name = Extract.getCalleeName(node);
1141
- if (name != null && isMatching(P.union("observe", "unobserve", "disconnect"))(name) && isFromObserver(context, callee)) return name;
1072
+ if (name != null && isMatching(P.union("observe", "unobserve", "disconnect"))(name) && isFromObserver(context, callee, "ResizeObserver")) return name;
1142
1073
  return "other";
1143
1074
  }
1144
- function getFunctionKind(node) {
1145
- return getPhaseKindOfFunction(node) ?? "other";
1075
+ function getFunctionKind$1(node) {
1076
+ if (isUseEffectSetupCallback(node)) return "setup";
1077
+ if (isUseEffectCleanupCallback(node)) return "cleanup";
1078
+ return "other";
1146
1079
  }
1147
1080
  var no_leaked_resize_observer_default = createRule({
1148
1081
  meta: {
@@ -1168,7 +1101,7 @@ function create$1(context) {
1168
1101
  const dEntries = [];
1169
1102
  return {
1170
1103
  [":function"](node) {
1171
- const kind = getFunctionKind(node);
1104
+ const kind = getFunctionKind$1(node);
1172
1105
  fEntries.push({
1173
1106
  kind,
1174
1107
  node
@@ -1178,51 +1111,40 @@ function create$1(context) {
1178
1111
  fEntries.pop();
1179
1112
  },
1180
1113
  ["CallExpression"](node) {
1114
+ if (fEntries.findLast((x) => x.kind !== "other")?.kind == null) return;
1181
1115
  const callee = Extract.unwrap(node.callee);
1182
1116
  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
1117
  const { object } = callee;
1186
1118
  match(getCallKind$1(context, node)).with("disconnect", () => {
1187
1119
  dEntries.push({
1188
- kind: "ResizeObserver",
1189
- callee,
1190
1120
  method: "disconnect",
1191
1121
  node,
1192
- observer: object,
1193
- phase: fKind
1122
+ observer: object
1194
1123
  });
1195
1124
  }).with("observe", () => {
1196
1125
  const [element] = node.arguments;
1197
1126
  if (element == null) return;
1198
1127
  oEntries.push({
1199
- kind: "ResizeObserver",
1200
- callee,
1201
1128
  element,
1202
1129
  method: "observe",
1203
1130
  node,
1204
- observer: object,
1205
- phase: fKind
1131
+ observer: object
1206
1132
  });
1207
1133
  }).with("unobserve", () => {
1208
1134
  const [element] = node.arguments;
1209
1135
  if (element == null) return;
1210
1136
  uEntries.push({
1211
- kind: "ResizeObserver",
1212
- callee,
1213
1137
  element,
1214
1138
  method: "unobserve",
1215
1139
  node,
1216
- observer: object,
1217
- phase: fKind
1140
+ observer: object
1218
1141
  });
1219
1142
  }).otherwise(() => null);
1220
1143
  },
1221
1144
  ["NewExpression"](node) {
1222
1145
  const fEntry = fEntries.findLast((x) => x.kind !== "other");
1223
1146
  if (fEntry == null) return;
1224
- if (!ComponentPhaseRelevance.has(fEntry.kind)) return;
1225
- if (!isNewResizeObserver(node)) return;
1147
+ if (!isNewObserver(node, "ResizeObserver")) return;
1226
1148
  const id = resolveEnclosingAssignmentTarget(node);
1227
1149
  if (id == null) {
1228
1150
  context.report({
@@ -1234,7 +1156,6 @@ function create$1(context) {
1234
1156
  observers.push({
1235
1157
  id,
1236
1158
  node,
1237
- phase: fEntry.kind,
1238
1159
  phaseNode: fEntry.node
1239
1160
  });
1240
1161
  },
@@ -1242,19 +1163,19 @@ function create$1(context) {
1242
1163
  for (const { id, node, phaseNode } of observers) {
1243
1164
  const isInsideObserverCallback = (e) => Traverse.findParent(e.node, (n) => n === node) != null;
1244
1165
  if (dEntries.some((e) => !isInsideObserverCallback(e) && isAssignmentTargetEqual(context, e.observer, id))) continue;
1245
- const oentries = oEntries.filter((e) => isAssignmentTargetEqual(context, e.observer, id));
1246
- const uentries = uEntries.filter((e) => isAssignmentTargetEqual(context, e.observer, id));
1166
+ const matchedOEntries = oEntries.filter((e) => isAssignmentTargetEqual(context, e.observer, id));
1167
+ const matchedUEntries = uEntries.filter((e) => isAssignmentTargetEqual(context, e.observer, id));
1247
1168
  const isDynamic = (node) => node?.type === AST_NODE_TYPES.CallExpression || Check.isConditional(node);
1248
1169
  const isPhaseNode = (node) => node === phaseNode;
1249
- if (oentries.some((e) => !isPhaseNode(Traverse.findParent(e.node, or(isDynamic, isPhaseNode))))) {
1170
+ if (matchedOEntries.some((e) => !isPhaseNode(Traverse.findParent(e.node, or(isDynamic, isPhaseNode))))) {
1250
1171
  context.report({
1251
1172
  messageId: "expectedDisconnectInControlFlow",
1252
1173
  node
1253
1174
  });
1254
1175
  continue;
1255
1176
  }
1256
- for (const oEntry of oentries) {
1257
- if (uentries.some((uEntry) => isAssignmentTargetEqual(context, uEntry.element, oEntry.element))) continue;
1177
+ for (const oEntry of matchedOEntries) {
1178
+ if (matchedUEntries.some((uEntry) => isAssignmentTargetEqual(context, uEntry.element, oEntry.element))) continue;
1258
1179
  context.report({
1259
1180
  messageId: "expectedDisconnectOrUnobserveInCleanup",
1260
1181
  node: oEntry.node
@@ -1273,6 +1194,11 @@ function getCallKind(node) {
1273
1194
  if (name != null && isMatching(P.union("setTimeout", "clearTimeout"))(name)) return name;
1274
1195
  return "other";
1275
1196
  }
1197
+ function getFunctionKind(node) {
1198
+ if (isUseEffectSetupCallback(node)) return "setup";
1199
+ if (isUseEffectCleanupCallback(node)) return "cleanup";
1200
+ return "other";
1201
+ }
1276
1202
  var no_leaked_timeout_default = createRule({
1277
1203
  meta: {
1278
1204
  type: "problem",
@@ -1291,70 +1217,49 @@ function create(context) {
1291
1217
  if (!context.sourceCode.text.includes("setTimeout")) return {};
1292
1218
  const fEntries = [];
1293
1219
  const sEntries = [];
1294
- const rEntries = [];
1220
+ const cEntries = [];
1295
1221
  function isInverseEntry(a, b) {
1296
1222
  return isAssignmentTargetEqual(context, a.timerId, b.timerId);
1297
1223
  }
1298
1224
  return {
1299
1225
  [":function"](node) {
1300
- const kind = getPhaseKindOfFunction(node) ?? "other";
1301
- fEntries.push({
1302
- kind,
1303
- node
1304
- });
1226
+ fEntries.push(getFunctionKind(node));
1305
1227
  },
1306
1228
  [":function:exit"]() {
1307
1229
  fEntries.pop();
1308
1230
  },
1309
1231
  ["CallExpression"](node) {
1310
- const fEntry = fEntries.findLast((f) => f.kind !== "other");
1311
- if (!ComponentPhaseRelevance.has(fEntry?.kind)) return;
1312
- switch (getCallKind(node)) {
1313
- case "setTimeout": {
1314
- const timeoutIdNode = resolveEnclosingAssignmentTarget(node);
1315
- if (timeoutIdNode == null) {
1316
- context.report({
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
1232
+ if (fEntries.findLast((kind) => kind !== "other") == null) return;
1233
+ match(getCallKind(node)).with("setTimeout", () => {
1234
+ const timeoutIdNode = resolveEnclosingAssignmentTarget(node);
1235
+ if (timeoutIdNode == null) {
1236
+ context.report({
1237
+ messageId: "expectedTimeoutId",
1238
+ node
1340
1239
  });
1341
- break;
1240
+ return;
1342
1241
  }
1343
- }
1242
+ sEntries.push({
1243
+ node,
1244
+ timerId: timeoutIdNode
1245
+ });
1246
+ }).with("clearTimeout", () => {
1247
+ const [timeoutIdNode] = node.arguments;
1248
+ if (timeoutIdNode == null) return;
1249
+ cEntries.push({
1250
+ node,
1251
+ timerId: timeoutIdNode
1252
+ });
1253
+ }).otherwise(() => null);
1344
1254
  },
1345
1255
  ["Program:exit"]() {
1346
1256
  for (const sEntry of sEntries) {
1347
- if (rEntries.some((rEntry) => isInverseEntry(sEntry, rEntry))) continue;
1348
- switch (sEntry.phase) {
1349
- case "setup":
1350
- case "cleanup":
1351
- context.report({
1352
- data: { kind: "useEffect" },
1353
- messageId: "expectedClearTimeoutInCleanup",
1354
- node: sEntry.node
1355
- });
1356
- continue;
1357
- }
1257
+ if (cEntries.some((cEntry) => isInverseEntry(sEntry, cEntry))) continue;
1258
+ context.report({
1259
+ data: { kind: "useEffect" },
1260
+ messageId: "expectedClearTimeoutInCleanup",
1261
+ node: sEntry.node
1262
+ });
1358
1263
  }
1359
1264
  }
1360
1265
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-web-api",
3
- "version": "5.20.3",
3
+ "version": "5.20.5",
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.3",
41
- "@eslint-react/core": "5.20.3",
42
- "@eslint-react/eslint": "5.20.3",
43
- "@eslint-react/shared": "5.20.3",
44
- "@eslint-react/var": "5.20.3",
40
+ "@eslint-react/ast": "5.20.5",
41
+ "@eslint-react/core": "5.20.5",
42
+ "@eslint-react/eslint": "5.20.5",
43
+ "@eslint-react/shared": "5.20.5",
44
+ "@eslint-react/var": "5.20.5",
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": {