monocart-coverage-reports 2.13.2 → 3.1.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 (69) hide show
  1. package/README.md +314 -116
  2. package/README.zh-Hans.md +315 -117
  3. package/lib/assets.js +6 -6
  4. package/lib/cli.js +191 -95
  5. package/lib/client/cdp-client.js +74 -43
  6. package/lib/client/coverage-client.js +24 -8
  7. package/lib/client/ws-session.js +115 -37
  8. package/lib/converter/ast-visitor.js +5 -7
  9. package/lib/converter/ast.js +29 -8
  10. package/lib/converter/collect-source-maps.js +42 -21
  11. package/lib/converter/converter.js +124 -129
  12. package/lib/converter/css-coverage.js +76 -0
  13. package/lib/converter/fake-source-coverage.js +64 -0
  14. package/lib/converter/find-original-range.js +214 -6
  15. package/lib/converter/flatten-source-maps.js +2 -4
  16. package/lib/converter/ignore.js +2 -4
  17. package/lib/converter/info-branch.js +5 -3
  18. package/lib/converter/info-function.js +5 -3
  19. package/lib/converter/info-statement.js +5 -3
  20. package/lib/converter/range-index.js +78 -0
  21. package/lib/converter/source-range-merge.js +103 -0
  22. package/lib/converter/untested.js +30 -60
  23. package/lib/default/options.js +13 -1
  24. package/lib/generate.js +44 -26
  25. package/lib/index.d.ts +52 -4
  26. package/lib/index.js +35 -41
  27. package/lib/istanbul/istanbul-summary.js +2 -2
  28. package/lib/istanbul/istanbul.js +35 -13
  29. package/lib/packages/monocart-coverage-assets.js +4 -3
  30. package/lib/packages/monocart-coverage-converter.js +27 -0
  31. package/lib/packages/monocart-coverage-vendor.js +2 -41
  32. package/lib/packages/monocart-coverage-vendor.js.LICENSE +3 -221
  33. package/lib/platform/concurrency.js +47 -43
  34. package/lib/platform/share.js +8 -1
  35. package/lib/register/hooks.js +19 -13
  36. package/lib/register/register.js +5 -3
  37. package/lib/reports/codacy.js +3 -5
  38. package/lib/reports/codecov.js +3 -5
  39. package/lib/reports/console-details.js +5 -5
  40. package/lib/reports/console-summary.js +4 -4
  41. package/lib/reports/custom.js +8 -24
  42. package/lib/reports/markdown-details.js +4 -6
  43. package/lib/reports/markdown-summary.js +4 -6
  44. package/lib/reports/raw.js +10 -12
  45. package/lib/reports/v8-json.js +3 -5
  46. package/lib/reports/v8.js +3 -5
  47. package/lib/utils/dedupe.js +1 -1
  48. package/lib/utils/gc.js +30 -14
  49. package/lib/utils/markdown.js +2 -2
  50. package/lib/utils/merge/merge.js +2 -4
  51. package/lib/utils/merge/range-tree.js +1 -1
  52. package/lib/utils/request.js +85 -73
  53. package/lib/utils/snapshot.js +4 -4
  54. package/lib/utils/source-path.js +26 -11
  55. package/lib/utils/util.js +67 -42
  56. package/lib/v8/v8-summary.js +2 -4
  57. package/lib/v8/v8.js +11 -11
  58. package/migrate-to-3.md +175 -0
  59. package/package.json +54 -42
  60. package/skills/monocart-coverage-reports/SKILL.md +106 -0
  61. package/skills/monocart-coverage-reports/references/best-practices.md +218 -0
  62. package/skills/monocart-coverage-reports/references/cli.md +108 -0
  63. package/skills/monocart-coverage-reports/references/collection.md +154 -0
  64. package/skills/monocart-coverage-reports/references/filtering-and-sourcemaps.md +170 -0
  65. package/skills/monocart-coverage-reports/references/reports-and-ci.md +219 -0
  66. package/skills/monocart-coverage-reports/references/repository-map.md +59 -0
  67. package/lib/index.mjs +0 -9
  68. package/lib/register/hooks.mjs +0 -3
  69. package/lib/register/register.mjs +0 -3
@@ -4,27 +4,31 @@
4
4
  * @author cenfun@gmail.com
5
5
  */
6
6
 
7
- const EC = require('eight-colors');
7
+ import EC from 'eight-colors';
8
8
 
9
- const { Locator } = require('monocart-locator');
10
- const Util = require('../utils/util.js');
9
+ import { Locator } from 'monocart-locator';
10
+ import { Util } from '../utils/util.js';
11
11
 
12
- const findOriginalRange = require('./find-original-range.js');
13
- const { getJsAstInfo, getCssAstInfo } = require('./ast.js');
14
- const { getIgnoredRanges } = require('./ignore.js');
12
+ import { findOriginalRange } from './find-original-range.js';
13
+ import { isUncoveredRange } from './source-range-merge.js';
14
+ import { mergeCssBytes } from './css-coverage.js';
15
+ import { handleUncoveredInCovered } from './fake-source-coverage.js';
16
+ import { getJsAstInfo, getCssAstInfo } from './ast.js';
17
+ import { getIgnoredRanges } from './ignore.js';
15
18
 
16
- const { getUntestedList } = require('./untested.js');
17
-
18
- const {
19
+ import { getUntestedList } from './untested.js';
20
+ import {
19
21
  sortRanges, dedupeCountRanges, mergeRangesWith
20
- } = require('../utils/dedupe.js');
21
- const { getSourceType, initSourceMapSourcesPath } = require('../utils/source-path.js');
22
+ } from '../utils/dedupe.js';
23
+ import {
24
+ getSourceType, initSourceMapSourcesPath, cssTypes
25
+ } from '../utils/source-path.js';
22
26
 
23
- const { decode } = require('../packages/monocart-coverage-vendor.js');
27
+ import { decode } from '@jridgewell/sourcemap-codec';
24
28
 
25
- const InfoBranch = require('./info-branch.js');
26
- const InfoFunction = require('./info-function.js');
27
- const InfoStatement = require('./info-statement.js');
29
+ import { InfoBranch } from './info-branch.js';
30
+ import { InfoFunction } from './info-function.js';
31
+ import { InfoStatement } from './info-statement.js';
28
32
 
29
33
  // ========================================================================================================
30
34
 
@@ -212,7 +216,7 @@ const calculateV8Summary = (list) => {
212
216
  * * `f` - hit count for functions
213
217
  * * `b` - hit count for branches
214
218
  */
215
- const collectFileCoverage = (v8Data, state, options) => {
219
+ const collectFileCoverage = (v8Data, state, options, needsIstanbul) => {
216
220
 
217
221
  const {
218
222
  bytes,
@@ -220,7 +224,6 @@ const collectFileCoverage = (v8Data, state, options) => {
220
224
  branches,
221
225
  statements,
222
226
 
223
- sourcePath,
224
227
  locator
225
228
  } = state;
226
229
 
@@ -313,7 +316,19 @@ const collectFileCoverage = (v8Data, state, options) => {
313
316
  v8Data.summary = summary;
314
317
 
315
318
  // ==========================================
316
- // istanbul
319
+ // Istanbul location maps are not needed by V8-only reports. Keep the
320
+ // V8 ranges, counts and summaries above identical in either mode.
321
+ if (!needsIstanbul) {
322
+ return;
323
+ }
324
+
325
+ return collectIstanbulCoverage(state);
326
+ };
327
+
328
+ const collectIstanbulCoverage = (state) => {
329
+ const {
330
+ sourcePath, locator, statements, functions, branches
331
+ } = state;
317
332
  const istanbulData = {
318
333
  path: sourcePath,
319
334
 
@@ -818,16 +833,71 @@ const handleOriginalEmptyBytesCoverage = (state, originalStateMap) => {
818
833
  });
819
834
  };
820
835
 
821
-
822
836
  const handleAllOriginalBytesCoverage = (state, originalStateMap) => {
823
837
 
824
- const { js, coverageList } = state;
838
+ const {
839
+ js, coverageList, type
840
+ } = state;
841
+
842
+ if (cssTypes.has(type)) {
843
+ // An intentional exclusion is not a broken source map, even when all
844
+ // sources were excluded and there is nothing left to map.
845
+ if (state.filteredSourceIndexes.size > 0 && state.filteredSourceIndexes.size === state.sourceMap.sources.length) {
846
+ return;
847
+ }
848
+ const positionMap = new Map();
849
+ const couldMapAllRanges = coverageList.every((range) => {
850
+ const {
851
+ start, end, originalState, error, filtered
852
+ } = findOriginalRange(range.start, range.end, state, originalStateMap, {
853
+ fixOriginalRange: true
854
+ });
855
+
856
+ if (filtered) {
857
+ return true;
858
+ }
859
+ if (error) {
860
+ return false;
861
+ }
862
+
863
+ // Offsets are local to each source, not unique across the bundle.
864
+ let positions = positionMap.get(originalState);
865
+ if (!positions) {
866
+ positions = new Set();
867
+ positionMap.set(originalState, positions);
868
+ }
869
+ const positionKey = `${start}-${end}`;
870
+ if (positions.has(positionKey)) {
871
+ return false;
872
+ }
873
+ positions.add(positionKey);
874
+ originalState.bytes.push({
875
+ start, end, count: range.count
876
+ });
877
+ return true;
878
+ });
879
+
880
+ // make sure every range mapped correctly,
881
+ // otherwise discard changes and fall back to dist file to show at least some meaningful coverage.
882
+ // Sass `@include` is a common case: one generated rule maps to both the
883
+ // call site and the mixin body, which cannot be one contiguous original
884
+ // range. See "Sass mixins and @include" in README.
885
+ if (!couldMapAllRanges) {
886
+ Util.logDebug(`Could not map all ranges, fallback to dist file for ${EC.yellow(state.sourcePath)}`);
887
+ state.v8Data.dedupe = false;
888
+ // The fallback is now a real report file, not a debug-only copy.
889
+ delete state.v8Data.debug;
890
+ state.originalList = [];
891
+ originalStateMap.clear();
892
+ generateCoverageForDist(state);
893
+ }
825
894
 
826
- // only for js, no sourcemap for css for now
827
- if (!js) {
828
895
  return;
829
896
  }
830
897
 
898
+ if (!js) {
899
+ return;
900
+ }
831
901
  // v8 coverage
832
902
  coverageList.forEach((block) => {
833
903
  block.ranges.forEach((range) => {
@@ -1033,7 +1103,6 @@ const getOriginalDecodedMappings = (originalDecodedMap, sourceIndex, locator) =>
1033
1103
  };
1034
1104
 
1035
1105
  // ========================================================================================================
1036
-
1037
1106
  const initOriginalList = (state, originalDecodedMap, options) => {
1038
1107
 
1039
1108
  // source filter
@@ -1041,6 +1110,9 @@ const initOriginalList = (state, originalDecodedMap, options) => {
1041
1110
 
1042
1111
  // create original content mappings
1043
1112
  const originalStateMap = new Map();
1113
+ if (!state.js) {
1114
+ state.filteredSourceIndexes = new Set();
1115
+ }
1044
1116
 
1045
1117
  const { sources, sourcesContent } = state.sourceMap;
1046
1118
 
@@ -1052,7 +1124,7 @@ const initOriginalList = (state, originalDecodedMap, options) => {
1052
1124
  // filter
1053
1125
  // do not change for sourceIndex
1054
1126
  if (!sourceFilter(sourcePath)) {
1055
- // console.log('-', sourcePath);
1127
+ state.filteredSourceIndexes?.add(sourceIndex);
1056
1128
  return;
1057
1129
  }
1058
1130
  // console.log(sourcePath);
@@ -1069,14 +1141,12 @@ const initOriginalList = (state, originalDecodedMap, options) => {
1069
1141
 
1070
1142
  const decodedMappings = getOriginalDecodedMappings(originalDecodedMap, sourceIndex, locator);
1071
1143
 
1072
- // unpacked file always is js
1073
-
1074
1144
  const type = getSourceType(sourcePath);
1075
1145
 
1076
1146
  const originalState = {
1077
1147
  original: true,
1078
- // original file is js
1079
- js: true,
1148
+ // Only known style types are CSS; preserve JS coverage for all other sources.
1149
+ js: !cssTypes.has(type),
1080
1150
  type,
1081
1151
  source: sourceContent,
1082
1152
  sourcePath,
@@ -1136,6 +1206,12 @@ const collectOriginalList = (state, originalStateMap) => {
1136
1206
  source
1137
1207
  };
1138
1208
 
1209
+ // Sourcemap gaps must not count as covered bytes for untested files.
1210
+ // Keep the marker so real coverage can replace this entry during merge.
1211
+ if (state.v8Data.empty) {
1212
+ sourceItem.empty = true;
1213
+ }
1214
+
1139
1215
  // save v8 data and add to originalList
1140
1216
  originalState.v8Data = sourceItem;
1141
1217
  state.originalList.push(originalState);
@@ -1157,56 +1233,6 @@ const generateCoverageForDist = (state) => {
1157
1233
 
1158
1234
  };
1159
1235
 
1160
- const handleUncoveredInCovered = (state, originalStateMap) => {
1161
- if (!state.fake) {
1162
- return;
1163
- }
1164
-
1165
- // fake statements/branches/functions
1166
- originalStateMap.forEach((originalState) => {
1167
- const { bytes } = originalState;
1168
- const list = [];
1169
- originalState.bytes = bytes.filter((item) => {
1170
- if (item.count === 0) {
1171
- const range = bytes.find((it) => {
1172
- if (it.count === 0) {
1173
- return false;
1174
- }
1175
- if (it.start > item.end) {
1176
- return false;
1177
- }
1178
- if (it.end < item.start) {
1179
- return false;
1180
- }
1181
- return true;
1182
- });
1183
- if (range) {
1184
- list.push(item);
1185
- return false;
1186
- }
1187
- }
1188
- return true;
1189
- });
1190
-
1191
- if (!list.length) {
1192
- return;
1193
- }
1194
-
1195
- list.forEach((item) => {
1196
- ['statements', 'branches', 'functions'].forEach((key) => {
1197
- originalState[key] = originalState[key].filter((it) => {
1198
- if (it.start === item.start && it.end === item.end) {
1199
- return false;
1200
- }
1201
- return true;
1202
- });
1203
- });
1204
- });
1205
-
1206
- });
1207
-
1208
- };
1209
-
1210
1236
  const unpackSourceMap = (state, options) => {
1211
1237
 
1212
1238
  const { sourceMap, sourcePath } = state;
@@ -1525,44 +1551,6 @@ const convertCoverages = (list, options, untested) => {
1525
1551
 
1526
1552
  // ========================================================================================================
1527
1553
 
1528
- const isUncoveredRange = (range, key, uncoveredGroups) => {
1529
-
1530
- let uncovered = true;
1531
- uncoveredGroups.forEach((group) => {
1532
- const { groupMap, state } = group;
1533
-
1534
- // ========================================================
1535
- // self key
1536
- if (groupMap.has(key)) {
1537
- return;
1538
- }
1539
-
1540
- // ========================================================
1541
- // in all group
1542
- for (const item of groupMap.values()) {
1543
- if (range.start >= item.start && range.end <= item.end) {
1544
- return;
1545
- }
1546
- }
1547
-
1548
- // ========================================================
1549
- // no sourcemap mappings in range
1550
- // check original range in decodedMappings
1551
- const { decodedMappings } = state;
1552
- const item = decodedMappings.find((it) => it.originalOffset >= range.start && it.originalOffset <= range.end);
1553
- if (!item) {
1554
- return;
1555
- }
1556
-
1557
- // ========================================================
1558
- uncovered = false;
1559
-
1560
- });
1561
-
1562
- return uncovered;
1563
-
1564
- };
1565
-
1566
1554
  const mergeV8Data = (state, stateList) => {
1567
1555
  // console.log(stateList);
1568
1556
 
@@ -1747,7 +1735,7 @@ const addUntestedFiles = async (stateList, options) => {
1747
1735
 
1748
1736
  };
1749
1737
 
1750
- const generateV8DataList = (stateList, options) => {
1738
+ const generateV8DataList = (stateList, options, needsIstanbul) => {
1751
1739
 
1752
1740
  const stateMap = new Map();
1753
1741
 
@@ -1770,8 +1758,14 @@ const generateV8DataList = (stateList, options) => {
1770
1758
  // exists item
1771
1759
  const prevState = stateMap.get(id);
1772
1760
  if (prevState) {
1773
- // ignore empty item, just override it
1774
- if (!prevState.v8Data.empty) {
1761
+ // Empty entries and debug copies must not replace real original
1762
+ // coverage, nor contribute a duplicate execution to its merge.
1763
+ if (!prevState.v8Data.empty && !prevState.v8Data.debug) {
1764
+ // An untested build may contain different mapped AST nodes
1765
+ // (for example ES5 helpers). It must not alter real metrics.
1766
+ if (v8Data.empty) {
1767
+ return;
1768
+ }
1775
1769
  if (mergeMap.has(id)) {
1776
1770
  mergeMap.get(id).push(originalState);
1777
1771
  } else {
@@ -1790,7 +1784,7 @@ const generateV8DataList = (stateList, options) => {
1790
1784
  if (state.js) {
1791
1785
  mergeV8Data(state, mergeMap.get(id));
1792
1786
  } else {
1793
- // should no css here, css can not be in sources
1787
+ state.bytes = mergeCssBytes(mergeMap.get(id).map((it) => it.bytes));
1794
1788
  }
1795
1789
  }
1796
1790
 
@@ -1801,11 +1795,13 @@ const generateV8DataList = (stateList, options) => {
1801
1795
  const coverageData = {};
1802
1796
  stateMap.forEach((state) => {
1803
1797
  const { v8Data } = state;
1804
- const istanbulData = collectFileCoverage(v8Data, state, options);
1805
- const { sourcePath, source } = v8Data;
1798
+ const istanbulData = collectFileCoverage(v8Data, state, options, needsIstanbul);
1806
1799
  v8DataList.push(v8Data);
1807
- fileSources[sourcePath] = source;
1808
- coverageData[sourcePath] = istanbulData;
1800
+ if (needsIstanbul) {
1801
+ const { sourcePath, source } = v8Data;
1802
+ fileSources[sourcePath] = source;
1803
+ coverageData[sourcePath] = istanbulData;
1804
+ }
1809
1805
  });
1810
1806
 
1811
1807
  // sort v8DataList (files)
@@ -1823,7 +1819,8 @@ const generateV8DataList = (stateList, options) => {
1823
1819
  };
1824
1820
  };
1825
1821
 
1826
- const convertV8List = async (v8list, options) => {
1822
+ // Direct users of the converter retain the full result by default.
1823
+ const convertV8List = async (v8list, options, needsIstanbul = true) => {
1827
1824
 
1828
1825
  // for tested files
1829
1826
  const stateList = convertCoverages(v8list, options);
@@ -1832,13 +1829,11 @@ const convertV8List = async (v8list, options) => {
1832
1829
  await addUntestedFiles(stateList, options);
1833
1830
 
1834
1831
  const time_start_convert = Date.now();
1835
- const dataList = generateV8DataList(stateList, options);
1832
+ const dataList = generateV8DataList(stateList, options, needsIstanbul);
1836
1833
  const dataFiles = EC.cyan(`${dataList.v8DataList.length} files`);
1837
1834
  Util.logTime(`${EC.magenta('│ ')}${EC.cyan('├')} [convert] converted data list (${dataFiles})`, time_start_convert);
1838
1835
 
1839
1836
  return dataList;
1840
1837
  };
1841
1838
 
1842
- module.exports = {
1843
- convertV8List
1844
- };
1839
+ export { convertV8List };
@@ -0,0 +1,76 @@
1
+ const getCssRangePoints = (groups) => {
2
+ const points = [];
3
+ groups.forEach((ranges) => {
4
+ ranges.forEach(({
5
+ start, end, count
6
+ }) => {
7
+ if (start >= end) {
8
+ return;
9
+ }
10
+ const covered = count > 0 ? 1 : 0;
11
+ const uncovered = count > 0 ? 0 : 1;
12
+ points.push({
13
+ offset: start, covered, uncovered
14
+ }, {
15
+ offset: end, covered: -covered, uncovered: -uncovered
16
+ });
17
+ });
18
+ });
19
+ return points.sort((a, b) => a.offset - b.offset);
20
+ };
21
+
22
+ const appendCssRange = (merged, start, end, count) => {
23
+ const last = merged[merged.length - 1];
24
+ if (last && last.end === start && last.count === count) {
25
+ last.end = end;
26
+ } else {
27
+ merged.push({
28
+ start, end, count
29
+ });
30
+ }
31
+ };
32
+
33
+ const sweepCssBytes = (groups, uncoveredWins) => {
34
+ const points = getCssRangePoints(groups);
35
+ const merged = [];
36
+ let start = 0;
37
+ let covered = 0;
38
+ let uncovered = 0;
39
+ for (const point of points) {
40
+ const end = point.offset;
41
+ if (start < end && (covered > 0 || uncovered > 0)) {
42
+ const count = uncoveredWins ? Number(uncovered === 0) : Number(covered > 0);
43
+ appendCssRange(merged, start, end, count);
44
+ }
45
+ covered += point.covered;
46
+ uncovered += point.uncovered;
47
+ start = end;
48
+ }
49
+ return merged;
50
+ };
51
+
52
+ const isSortedDisjoint = (ranges) => {
53
+ let end = -Infinity;
54
+ for (const range of ranges) {
55
+ if (!(range.start >= end && range.start < range.end)) {
56
+ return false;
57
+ }
58
+ end = range.end;
59
+ }
60
+ return true;
61
+ };
62
+
63
+ // Within one bundle an uncovered child overrides its covered wrapper. Only
64
+ // after that interpretation may coverage from separate bundles be unioned.
65
+ // Unmapped gaps stay absent from this union; this function does not turn them
66
+ // into covered ranges. Byte summaries still derive covered bytes from total
67
+ // length minus explicit uncovered ranges, so gaps without an uncovered range
68
+ // are reported as covered. Use the line/code views when gaps matter.
69
+ const mergeCssBytes = (groups) => {
70
+ // Ordered, disjoint ranges already have unambiguous per-bundle coverage.
71
+ // Reuse them without allocating intermediate events and normalized ranges.
72
+ const normalized = groups.map((ranges) => (isSortedDisjoint(ranges) ? ranges : sweepCssBytes([ranges], true)));
73
+ return sweepCssBytes(normalized, false);
74
+ };
75
+
76
+ export { mergeCssBytes };
@@ -0,0 +1,64 @@
1
+ const rangeKey = (range) => `${range.start}_${range.end}`;
2
+
3
+ const handleUncoveredInCovered = (state, originalStateMap) => {
4
+ if (!state.fake) {
5
+ return;
6
+ }
7
+
8
+ originalStateMap.forEach((originalState) => {
9
+ const { bytes } = originalState;
10
+ // The old check treats every non-zero count as covered, not just > 0.
11
+ const covered = bytes.filter((range) => range.count !== 0);
12
+ if (!covered.length || covered.length === bytes.length) {
13
+ originalState.bytes = bytes.slice();
14
+ return;
15
+ }
16
+
17
+ // Sort a separate array so byte order and original range objects stay
18
+ // unchanged. Prefix maxima also handle nested/overlapping coverage.
19
+ covered.sort((a, b) => a.start - b.start);
20
+ const starts = new Float64Array(covered.length);
21
+ const maxEnds = new Float64Array(covered.length);
22
+ let maxEnd = -Infinity;
23
+ for (let i = 0; i < covered.length; i++) {
24
+ starts[i] = covered[i].start;
25
+ maxEnd = Math.max(maxEnd, covered[i].end);
26
+ maxEnds[i] = maxEnd;
27
+ }
28
+
29
+ const removed = new Set();
30
+ originalState.bytes = bytes.filter((range) => {
31
+ if (range.count !== 0) {
32
+ return true;
33
+ }
34
+ let low = 0;
35
+ let high = starts.length;
36
+ while (low < high) {
37
+ const mid = Math.floor((low + high) / 2);
38
+ // Preserve the original inclusive overlap test: touching
39
+ // endpoints count as overlap even though ranges are half-open.
40
+ if (starts[mid] <= range.end) {
41
+ low = mid + 1;
42
+ } else {
43
+ high = mid;
44
+ }
45
+ }
46
+ if (low > 0 && maxEnds[low - 1] >= range.start) {
47
+ removed.add(rangeKey(range));
48
+ return false;
49
+ }
50
+ return true;
51
+ });
52
+
53
+ if (!removed.size) {
54
+ return;
55
+ }
56
+ // Remove exact start/end matches, not every overlapping AST range.
57
+ // Each metadata array is filtered once, regardless of removal count.
58
+ for (const key of ['statements', 'branches', 'functions']) {
59
+ originalState[key] = originalState[key].filter((range) => !removed.has(rangeKey(range)));
60
+ }
61
+ });
62
+ };
63
+
64
+ export { handleUncoveredInCovered };