eslint-plugin-react-web-api 5.8.18 → 5.9.0

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 (3) hide show
  1. package/README.md +0 -1
  2. package/dist/index.js +283 -123
  3. package/package.json +8 -8
package/README.md CHANGED
@@ -45,7 +45,6 @@ export default defineConfig(
45
45
  | `no-leaked-idle-callback` | Prevents leaked `requestIdleCallback` |
46
46
  | `no-leaked-animation-frame` | Prevents leaked `requestAnimationFrame` |
47
47
  | `no-leaked-event-source` | Prevents leaked `EventSource` |
48
- | `no-leaked-intersection-observer` | Prevents leaked `IntersectionObserver` |
49
48
  | `no-leaked-mutation-observer` | Prevents leaked `MutationObserver` |
50
49
  | `no-leaked-performance-observer` | Prevents leaked `PerformanceObserver` |
51
50
  | `no-leaked-websocket` | Prevents leaked `WebSocket` |
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ var __exportAll = (all, no_symbols) => {
28
28
  //#endregion
29
29
  //#region package.json
30
30
  var name$1 = "eslint-plugin-react-web-api";
31
- var version = "5.8.18";
31
+ var version = "5.9.0";
32
32
 
33
33
  //#endregion
34
34
  //#region src/types/component-phase.ts
@@ -108,8 +108,8 @@ function getOptions(context, node) {
108
108
 
109
109
  //#endregion
110
110
  //#region src/rules/no-leaked-event-listener/no-leaked-event-listener.ts
111
- const RULE_NAME$4 = "no-leaked-event-listener";
112
- function getCallKind$4(node) {
111
+ const RULE_NAME$5 = "no-leaked-event-listener";
112
+ function getCallKind$5(node) {
113
113
  const callee = Extract.unwrap(node.callee);
114
114
  switch (true) {
115
115
  case callee.type === AST_NODE_TYPES.Identifier && isMatching(P.union("addEventListener", "removeEventListener", "abort"))(callee.name): return callee.name;
@@ -117,7 +117,7 @@ function getCallKind$4(node) {
117
117
  default: return "other";
118
118
  }
119
119
  }
120
- function getFunctionKind$1(node) {
120
+ function getFunctionKind$2(node) {
121
121
  return getPhaseKindOfFunction(node) ?? "other";
122
122
  }
123
123
  var no_leaked_event_listener_default = createRule({
@@ -130,11 +130,11 @@ var no_leaked_event_listener_default = createRule({
130
130
  },
131
131
  schema: []
132
132
  },
133
- name: RULE_NAME$4,
134
- create: create$4,
133
+ name: RULE_NAME$5,
134
+ create: create$5,
135
135
  defaultOptions: []
136
136
  });
137
- function create$4(context) {
137
+ function create$5(context) {
138
138
  if (!context.sourceCode.text.includes("addEventListener")) return {};
139
139
  if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
140
140
  const fEntries = [];
@@ -165,7 +165,7 @@ function create$4(context) {
165
165
  }
166
166
  return {
167
167
  [":function"](node) {
168
- const kind = getFunctionKind$1(node);
168
+ const kind = getFunctionKind$2(node);
169
169
  fEntries.push({
170
170
  kind,
171
171
  node
@@ -179,7 +179,7 @@ function create$4(context) {
179
179
  if (fKind == null) return;
180
180
  if (!ComponentPhaseRelevance.has(fKind)) return;
181
181
  const unwrappedCallee = Extract.unwrap(node.callee);
182
- match(getCallKind$4(node)).with("addEventListener", (callKind) => {
182
+ match(getCallKind$5(node)).with("addEventListener", (callKind) => {
183
183
  if (unwrappedCallee.type === AST_NODE_TYPES.MemberExpression && unwrappedCallee.object.type === AST_NODE_TYPES.Identifier && core.isAPIFromReactNative(unwrappedCallee.object.name, context.sourceCode.getScope(node))) return;
184
184
  const [type, listener, options] = node.arguments;
185
185
  if (type == null || listener == null) return;
@@ -261,8 +261,8 @@ function resolveToObjectExpression(context, node) {
261
261
 
262
262
  //#endregion
263
263
  //#region src/rules/no-leaked-fetch/no-leaked-fetch.ts
264
- const RULE_NAME$3 = "no-leaked-fetch";
265
- function getCallKind$3(node) {
264
+ const RULE_NAME$4 = "no-leaked-fetch";
265
+ function getCallKind$4(node) {
266
266
  const callee = Extract.unwrap(node.callee);
267
267
  switch (true) {
268
268
  case callee.type === AST_NODE_TYPES.Identifier && callee.name === "fetch": return "fetch";
@@ -334,11 +334,11 @@ var no_leaked_fetch_default = createRule({
334
334
  },
335
335
  schema: []
336
336
  },
337
- name: RULE_NAME$3,
338
- create: create$3,
337
+ name: RULE_NAME$4,
338
+ create: create$4,
339
339
  defaultOptions: []
340
340
  });
341
- function create$3(context) {
341
+ function create$4(context) {
342
342
  if (!context.sourceCode.text.includes("fetch")) return {};
343
343
  if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
344
344
  const fEntries = [];
@@ -358,7 +358,7 @@ function create$3(context) {
358
358
  ["CallExpression"](node) {
359
359
  const fEntry = fEntries.at(-1);
360
360
  if (fEntry == null || !ComponentPhaseRelevance.has(fEntry.kind)) return;
361
- switch (getCallKind$3(node)) {
361
+ switch (getCallKind$4(node)) {
362
362
  case "fetch": {
363
363
  if (fEntry.kind !== "setup") return;
364
364
  const { controller, isParamSignal } = getFetchController(context, node);
@@ -403,108 +403,6 @@ function create$3(context) {
403
403
  };
404
404
  }
405
405
 
406
- //#endregion
407
- //#region src/rules/no-leaked-interval/no-leaked-interval.ts
408
- const RULE_NAME$2 = "no-leaked-interval";
409
- function getCallKind$2(node) {
410
- const callee = Extract.unwrap(node.callee);
411
- switch (true) {
412
- case callee.type === AST_NODE_TYPES.Identifier && isMatching(P.union("setInterval", "clearInterval"))(callee.name): return callee.name;
413
- case callee.type === AST_NODE_TYPES.MemberExpression && callee.property.type === AST_NODE_TYPES.Identifier && isMatching(P.union("setInterval", "clearInterval"))(callee.property.name): return callee.property.name;
414
- default: return "other";
415
- }
416
- }
417
- var no_leaked_interval_default = createRule({
418
- meta: {
419
- type: "problem",
420
- docs: { description: "Enforces that every 'setInterval' in a component or custom hook has a corresponding 'clearInterval'." },
421
- messages: {
422
- expectedClearIntervalInCleanup: "A 'setInterval' created in '{{ kind }}' must be cleared with 'clearInterval' in the cleanup function.",
423
- expectedIntervalId: "A 'setInterval' must be assigned to a variable for proper cleanup."
424
- },
425
- schema: []
426
- },
427
- name: RULE_NAME$2,
428
- create: create$2,
429
- defaultOptions: []
430
- });
431
- function create$2(context) {
432
- if (!context.sourceCode.text.includes("setInterval")) return {};
433
- const fEntries = [];
434
- const sEntries = [];
435
- const cEntries = [];
436
- function isInverseEntry(a, b) {
437
- return isAssignmentTargetEqual(context, a.timerId, b.timerId);
438
- }
439
- return {
440
- [":function"](node) {
441
- const kind = getPhaseKindOfFunction(node) ?? "other";
442
- fEntries.push({
443
- kind,
444
- node
445
- });
446
- },
447
- [":function:exit"]() {
448
- fEntries.pop();
449
- },
450
- ["CallExpression"](node) {
451
- switch (getCallKind$2(node)) {
452
- case "setInterval": {
453
- const fEntry = fEntries.findLast((x) => x.kind !== "other");
454
- if (fEntry == null) break;
455
- if (!ComponentPhaseRelevance.has(fEntry.kind)) break;
456
- const intervalIdNode = resolveEnclosingAssignmentTarget(node);
457
- if (intervalIdNode == null) {
458
- context.report({
459
- messageId: "expectedIntervalId",
460
- node
461
- });
462
- break;
463
- }
464
- sEntries.push({
465
- kind: "interval",
466
- callee: node.callee,
467
- node,
468
- phase: fEntry.kind,
469
- timerId: intervalIdNode
470
- });
471
- break;
472
- }
473
- case "clearInterval": {
474
- const fEntry = fEntries.findLast((x) => x.kind !== "other");
475
- if (fEntry == null) break;
476
- if (!ComponentPhaseRelevance.has(fEntry.kind)) break;
477
- const [intervalIdNode] = node.arguments;
478
- if (intervalIdNode == null) break;
479
- cEntries.push({
480
- kind: "interval",
481
- callee: node.callee,
482
- node,
483
- phase: fEntry.kind,
484
- timerId: intervalIdNode
485
- });
486
- break;
487
- }
488
- }
489
- },
490
- ["Program:exit"]() {
491
- for (const sEntry of sEntries) {
492
- if (cEntries.some((cEntry) => isInverseEntry(sEntry, cEntry))) continue;
493
- switch (sEntry.phase) {
494
- case "setup":
495
- case "cleanup":
496
- context.report({
497
- data: { kind: "useEffect" },
498
- messageId: "expectedClearIntervalInCleanup",
499
- node: sEntry.node
500
- });
501
- continue;
502
- }
503
- }
504
- }
505
- };
506
- }
507
-
508
406
  //#endregion
509
407
  //#region ../../.pkgs/eff/dist/index.js
510
408
  function or(a, b) {
@@ -627,19 +525,278 @@ const dual = function(arity, body) {
627
525
  const compose = dual(2, (ab, bc) => (a) => bc(ab(a)));
628
526
 
629
527
  //#endregion
630
- //#region src/rules/no-leaked-resize-observer/lib.ts
528
+ //#region src/rules/no-leaked-intersection-observer/lib.ts
631
529
  /**
632
- * Check if a node is a loop statement
530
+ * Check if a node is a conditional expression or control flow statement
633
531
  * @param node The node to check
634
- * @returns True if the node is a loop
532
+ * @returns True if the node is conditional
635
533
  */
636
- const isLoop = isOneOf([
534
+ const isConditional$1 = isOneOf([
637
535
  AST_NODE_TYPES.DoWhileStatement,
638
536
  AST_NODE_TYPES.ForInStatement,
639
537
  AST_NODE_TYPES.ForOfStatement,
640
538
  AST_NODE_TYPES.ForStatement,
641
- AST_NODE_TYPES.WhileStatement
539
+ AST_NODE_TYPES.WhileStatement,
540
+ AST_NODE_TYPES.IfStatement,
541
+ AST_NODE_TYPES.SwitchStatement,
542
+ AST_NODE_TYPES.LogicalExpression,
543
+ AST_NODE_TYPES.ConditionalExpression
642
544
  ]);
545
+ function isNewIntersectionObserver(node) {
546
+ if (node?.type !== AST_NODE_TYPES.NewExpression) return false;
547
+ const callee = Extract.unwrap(node.callee);
548
+ return callee.type === AST_NODE_TYPES.Identifier && callee.name === "IntersectionObserver";
549
+ }
550
+ function isFromObserver$1(context, node) {
551
+ switch (true) {
552
+ case node.type === AST_NODE_TYPES.Identifier: {
553
+ const initNode = resolve(context, node);
554
+ return isNewIntersectionObserver(initNode == null ? null : Extract.unwrap(initNode));
555
+ }
556
+ case node.type === AST_NODE_TYPES.MemberExpression: return isFromObserver$1(context, node.object);
557
+ default: return false;
558
+ }
559
+ }
560
+
561
+ //#endregion
562
+ //#region src/rules/no-leaked-intersection-observer/no-leaked-intersection-observer.ts
563
+ const RULE_NAME$3 = "no-leaked-intersection-observer";
564
+ function getCallKind$3(context, node) {
565
+ const callee = Extract.unwrap(node.callee);
566
+ switch (true) {
567
+ case callee.type === AST_NODE_TYPES.Identifier && isMatching(P.union("observe", "unobserve", "disconnect"))(callee.name) && isFromObserver$1(context, callee): return callee.name;
568
+ case callee.type === AST_NODE_TYPES.MemberExpression && callee.property.type === AST_NODE_TYPES.Identifier && isMatching(P.union("observe", "unobserve", "disconnect"))(callee.property.name) && isFromObserver$1(context, callee): return callee.property.name;
569
+ default: return "other";
570
+ }
571
+ }
572
+ function getFunctionKind$1(node) {
573
+ return getPhaseKindOfFunction(node) ?? "other";
574
+ }
575
+ var no_leaked_intersection_observer_default = createRule({
576
+ meta: {
577
+ type: "problem",
578
+ docs: { description: "Enforces that every 'IntersectionObserver' created in a component or custom hook has a corresponding 'IntersectionObserver.disconnect()'." },
579
+ messages: {
580
+ expectedDisconnectInControlFlow: "Dynamically added 'IntersectionObserver.observe' should be cleared all at once using 'IntersectionObserver.disconnect' in the cleanup function.",
581
+ expectedDisconnectOrUnobserveInCleanup: "An 'IntersectionObserver' instance created in 'useEffect' must be disconnected in the cleanup function.",
582
+ unexpectedFloatingInstance: "An 'IntersectionObserver' instance created in component or custom hook must be assigned to a variable for proper cleanup."
583
+ },
584
+ schema: []
585
+ },
586
+ name: RULE_NAME$3,
587
+ create: create$3,
588
+ defaultOptions: []
589
+ });
590
+ function create$3(context) {
591
+ if (!context.sourceCode.text.includes("IntersectionObserver")) return {};
592
+ const fEntries = [];
593
+ const observers = [];
594
+ const oEntries = [];
595
+ const uEntries = [];
596
+ const dEntries = [];
597
+ return {
598
+ [":function"](node) {
599
+ const kind = getFunctionKind$1(node);
600
+ fEntries.push({
601
+ kind,
602
+ node
603
+ });
604
+ },
605
+ [":function:exit"]() {
606
+ fEntries.pop();
607
+ },
608
+ ["CallExpression"](node) {
609
+ const unwrappedCallee = Extract.unwrap(node.callee);
610
+ if (unwrappedCallee.type !== AST_NODE_TYPES.MemberExpression) return;
611
+ const fKind = fEntries.findLast((x) => x.kind !== "other")?.kind;
612
+ if (fKind == null || !ComponentPhaseRelevance.has(fKind)) return;
613
+ const { object } = unwrappedCallee;
614
+ match(getCallKind$3(context, node)).with("disconnect", () => {
615
+ dEntries.push({
616
+ kind: "IntersectionObserver",
617
+ callee: node.callee,
618
+ method: "disconnect",
619
+ node,
620
+ observer: object,
621
+ phase: fKind
622
+ });
623
+ }).with("observe", () => {
624
+ const [element] = node.arguments;
625
+ if (element == null) return;
626
+ oEntries.push({
627
+ kind: "IntersectionObserver",
628
+ callee: node.callee,
629
+ element,
630
+ method: "observe",
631
+ node,
632
+ observer: object,
633
+ phase: fKind
634
+ });
635
+ }).with("unobserve", () => {
636
+ const [element] = node.arguments;
637
+ if (element == null) return;
638
+ uEntries.push({
639
+ kind: "IntersectionObserver",
640
+ callee: node.callee,
641
+ element,
642
+ method: "unobserve",
643
+ node,
644
+ observer: object,
645
+ phase: fKind
646
+ });
647
+ }).otherwise(() => null);
648
+ },
649
+ ["NewExpression"](node) {
650
+ const fEntry = fEntries.findLast((x) => x.kind !== "other");
651
+ if (fEntry == null) return;
652
+ if (!ComponentPhaseRelevance.has(fEntry.kind)) return;
653
+ if (!isNewIntersectionObserver(node)) return;
654
+ const id = resolveEnclosingAssignmentTarget(node);
655
+ if (id == null) {
656
+ context.report({
657
+ messageId: "unexpectedFloatingInstance",
658
+ node
659
+ });
660
+ return;
661
+ }
662
+ observers.push({
663
+ id,
664
+ node,
665
+ phase: fEntry.kind,
666
+ phaseNode: fEntry.node
667
+ });
668
+ },
669
+ ["Program:exit"]() {
670
+ for (const { id, node, phaseNode } of observers) {
671
+ const isInsideObserverCallback = (e) => Traverse.findParent(e.node, (n) => n === node) != null;
672
+ if (dEntries.some((e) => !isInsideObserverCallback(e) && isAssignmentTargetEqual(context, e.observer, id))) continue;
673
+ const oentries = oEntries.filter((e) => isAssignmentTargetEqual(context, e.observer, id));
674
+ const uentries = uEntries.filter((e) => isAssignmentTargetEqual(context, e.observer, id));
675
+ const isDynamic = (node) => node?.type === AST_NODE_TYPES.CallExpression || isConditional$1(node);
676
+ const isPhaseNode = (node) => node === phaseNode;
677
+ if (oentries.some((e) => !isPhaseNode(Traverse.findParent(e.node, or(isDynamic, isPhaseNode))))) {
678
+ context.report({
679
+ messageId: "expectedDisconnectInControlFlow",
680
+ node
681
+ });
682
+ continue;
683
+ }
684
+ for (const oEntry of oentries) {
685
+ if (uentries.some((uEntry) => isAssignmentTargetEqual(context, uEntry.element, oEntry.element))) continue;
686
+ context.report({
687
+ messageId: "expectedDisconnectOrUnobserveInCleanup",
688
+ node: oEntry.node
689
+ });
690
+ }
691
+ }
692
+ }
693
+ };
694
+ }
695
+
696
+ //#endregion
697
+ //#region src/rules/no-leaked-interval/no-leaked-interval.ts
698
+ const RULE_NAME$2 = "no-leaked-interval";
699
+ function getCallKind$2(node) {
700
+ const callee = Extract.unwrap(node.callee);
701
+ switch (true) {
702
+ case callee.type === AST_NODE_TYPES.Identifier && isMatching(P.union("setInterval", "clearInterval"))(callee.name): return callee.name;
703
+ case callee.type === AST_NODE_TYPES.MemberExpression && callee.property.type === AST_NODE_TYPES.Identifier && isMatching(P.union("setInterval", "clearInterval"))(callee.property.name): return callee.property.name;
704
+ default: return "other";
705
+ }
706
+ }
707
+ var no_leaked_interval_default = createRule({
708
+ meta: {
709
+ type: "problem",
710
+ docs: { description: "Enforces that every 'setInterval' in a component or custom hook has a corresponding 'clearInterval'." },
711
+ messages: {
712
+ expectedClearIntervalInCleanup: "A 'setInterval' created in '{{ kind }}' must be cleared with 'clearInterval' in the cleanup function.",
713
+ expectedIntervalId: "A 'setInterval' must be assigned to a variable for proper cleanup."
714
+ },
715
+ schema: []
716
+ },
717
+ name: RULE_NAME$2,
718
+ create: create$2,
719
+ defaultOptions: []
720
+ });
721
+ function create$2(context) {
722
+ if (!context.sourceCode.text.includes("setInterval")) return {};
723
+ const fEntries = [];
724
+ const sEntries = [];
725
+ const cEntries = [];
726
+ function isInverseEntry(a, b) {
727
+ return isAssignmentTargetEqual(context, a.timerId, b.timerId);
728
+ }
729
+ return {
730
+ [":function"](node) {
731
+ const kind = getPhaseKindOfFunction(node) ?? "other";
732
+ fEntries.push({
733
+ kind,
734
+ node
735
+ });
736
+ },
737
+ [":function:exit"]() {
738
+ fEntries.pop();
739
+ },
740
+ ["CallExpression"](node) {
741
+ switch (getCallKind$2(node)) {
742
+ case "setInterval": {
743
+ const fEntry = fEntries.findLast((x) => x.kind !== "other");
744
+ if (fEntry == null) break;
745
+ if (!ComponentPhaseRelevance.has(fEntry.kind)) break;
746
+ const intervalIdNode = resolveEnclosingAssignmentTarget(node);
747
+ if (intervalIdNode == null) {
748
+ context.report({
749
+ messageId: "expectedIntervalId",
750
+ node
751
+ });
752
+ break;
753
+ }
754
+ sEntries.push({
755
+ kind: "interval",
756
+ callee: node.callee,
757
+ node,
758
+ phase: fEntry.kind,
759
+ timerId: intervalIdNode
760
+ });
761
+ break;
762
+ }
763
+ case "clearInterval": {
764
+ const fEntry = fEntries.findLast((x) => x.kind !== "other");
765
+ if (fEntry == null) break;
766
+ if (!ComponentPhaseRelevance.has(fEntry.kind)) break;
767
+ const [intervalIdNode] = node.arguments;
768
+ if (intervalIdNode == null) break;
769
+ cEntries.push({
770
+ kind: "interval",
771
+ callee: node.callee,
772
+ node,
773
+ phase: fEntry.kind,
774
+ timerId: intervalIdNode
775
+ });
776
+ break;
777
+ }
778
+ }
779
+ },
780
+ ["Program:exit"]() {
781
+ for (const sEntry of sEntries) {
782
+ if (cEntries.some((cEntry) => isInverseEntry(sEntry, cEntry))) continue;
783
+ switch (sEntry.phase) {
784
+ case "setup":
785
+ case "cleanup":
786
+ context.report({
787
+ data: { kind: "useEffect" },
788
+ messageId: "expectedClearIntervalInCleanup",
789
+ node: sEntry.node
790
+ });
791
+ continue;
792
+ }
793
+ }
794
+ }
795
+ };
796
+ }
797
+
798
+ //#endregion
799
+ //#region src/rules/no-leaked-resize-observer/lib.ts
643
800
  /**
644
801
  * Check if a node is a conditional expression or control flow statement
645
802
  * @param node The node to check
@@ -782,7 +939,8 @@ function create$1(context) {
782
939
  },
783
940
  ["Program:exit"]() {
784
941
  for (const { id, node, phaseNode } of observers) {
785
- if (dEntries.some((e) => isAssignmentTargetEqual(context, e.observer, id))) continue;
942
+ const isInsideObserverCallback = (e) => Traverse.findParent(e.node, (n) => n === node) != null;
943
+ if (dEntries.some((e) => !isInsideObserverCallback(e) && isAssignmentTargetEqual(context, e.observer, id))) continue;
786
944
  const oentries = oEntries.filter((e) => isAssignmentTargetEqual(context, e.observer, id));
787
945
  const uentries = uEntries.filter((e) => isAssignmentTargetEqual(context, e.observer, id));
788
946
  const isDynamic = (node) => node?.type === AST_NODE_TYPES.CallExpression || isConditional(node);
@@ -914,6 +1072,7 @@ const plugin = {
914
1072
  rules: {
915
1073
  "no-leaked-event-listener": no_leaked_event_listener_default,
916
1074
  "no-leaked-fetch": no_leaked_fetch_default,
1075
+ "no-leaked-intersection-observer": no_leaked_intersection_observer_default,
917
1076
  "no-leaked-interval": no_leaked_interval_default,
918
1077
  "no-leaked-resize-observer": no_leaked_resize_observer_default,
919
1078
  "no-leaked-timeout": no_leaked_timeout_default
@@ -932,6 +1091,7 @@ const name = "react-web-api/recommended";
932
1091
  const rules = {
933
1092
  "react-web-api/no-leaked-event-listener": "warn",
934
1093
  "react-web-api/no-leaked-fetch": "warn",
1094
+ "react-web-api/no-leaked-intersection-observer": "warn",
935
1095
  "react-web-api/no-leaked-interval": "warn",
936
1096
  "react-web-api/no-leaked-resize-observer": "warn",
937
1097
  "react-web-api/no-leaked-timeout": "warn"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-web-api",
3
- "version": "5.8.18",
3
+ "version": "5.9.0",
4
4
  "description": "ESLint React's ESLint plugin for interacting with Web APIs",
5
5
  "keywords": [
6
6
  "react",
@@ -41,11 +41,11 @@
41
41
  "@typescript-eslint/utils": "^8.61.0",
42
42
  "birecord": "^0.1.1",
43
43
  "ts-pattern": "^5.9.0",
44
- "@eslint-react/ast": "5.8.18",
45
- "@eslint-react/eslint": "5.8.18",
46
- "@eslint-react/shared": "5.8.18",
47
- "@eslint-react/core": "5.8.18",
48
- "@eslint-react/var": "5.8.18"
44
+ "@eslint-react/core": "5.9.0",
45
+ "@eslint-react/ast": "5.9.0",
46
+ "@eslint-react/eslint": "5.9.0",
47
+ "@eslint-react/var": "5.9.0",
48
+ "@eslint-react/shared": "5.9.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/react": "^19.2.17",
@@ -54,8 +54,8 @@
54
54
  "eslint": "^10.4.1",
55
55
  "tsdown": "^0.22.2",
56
56
  "typescript": "6.0.3",
57
- "@local/configs": "0.0.0",
58
- "@local/eff": "0.0.0"
57
+ "@local/eff": "0.0.0",
58
+ "@local/configs": "0.0.0"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "eslint": "*",