code-gauge 10.2.1 → 10.2.2
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/README.md +2 -1
- package/dist/crossFileDuplication.cjs +1 -1
- package/dist/crossFileDuplication.cjs.map +1 -1
- package/dist/crossFileDuplication.js +1 -1
- package/dist/crossFileDuplication.js.map +1 -1
- package/dist/crossFileNearMiss.cjs +1 -1
- package/dist/crossFileNearMiss.cjs.map +1 -1
- package/dist/crossFileNearMiss.d.ts +8 -8
- package/dist/crossFileNearMiss.js +1 -1
- package/dist/crossFileNearMiss.js.map +1 -1
- package/dist/duplicateSelection.cjs +1 -1
- package/dist/duplicateSelection.cjs.map +1 -1
- package/dist/duplicateSelection.d.ts +6 -11
- package/dist/duplicateSelection.js +1 -1
- package/dist/duplicateSelection.js.map +1 -1
- package/dist/duplication.cjs.map +1 -1
- package/dist/duplication.d.ts +3 -3
- package/dist/duplication.js.map +1 -1
- package/dist/types.d.ts +5 -1
- package/native/code-gauge.wasm +0 -0
- package/native/src/duplication.rs +289 -87
- package/package.json +8 -8
|
@@ -381,6 +381,9 @@ struct DuplicateCandidate {
|
|
|
381
381
|
end_index: usize,
|
|
382
382
|
start_line: usize,
|
|
383
383
|
end_line: usize,
|
|
384
|
+
/// Set by select_maximal_duplicates on a copy lying inside a larger group's region: it is
|
|
385
|
+
/// reported with its group, but its span is already counted by that larger group.
|
|
386
|
+
nested_in_larger_group: bool,
|
|
384
387
|
}
|
|
385
388
|
|
|
386
389
|
/// A file's normalized token stream with the block and statement structure clone detection
|
|
@@ -1375,6 +1378,7 @@ fn to_candidate(
|
|
|
1375
1378
|
end_index: last.end_index,
|
|
1376
1379
|
start_line: first.start_line,
|
|
1377
1380
|
end_line: last.end_line,
|
|
1381
|
+
nested_in_larger_group: false,
|
|
1378
1382
|
}
|
|
1379
1383
|
}
|
|
1380
1384
|
|
|
@@ -1474,7 +1478,8 @@ fn combine_hashes(hash: i64, value: i64) -> i64 {
|
|
|
1474
1478
|
(to_int32(hash).wrapping_mul(31)) as i64 + value
|
|
1475
1479
|
}
|
|
1476
1480
|
|
|
1477
|
-
/// Keeps only maximal, non-overlapping duplicates;
|
|
1481
|
+
/// Keeps only maximal, non-overlapping duplicates; a port of selectMaximalGroups in
|
|
1482
|
+
/// duplicateSelection.ts, which states the ranking, shedding, and nested-copy rules.
|
|
1478
1483
|
fn select_maximal_duplicates(
|
|
1479
1484
|
candidates: Vec<DuplicateCandidate>,
|
|
1480
1485
|
) -> IndexMap<std::rc::Rc<str>, Vec<DuplicateCandidate>> {
|
|
@@ -1514,49 +1519,191 @@ fn select_maximal_duplicates(
|
|
|
1514
1519
|
let mut duplicates: Vec<DuplicateCandidate> = groups.into_iter().flatten().collect();
|
|
1515
1520
|
duplicates.sort_by_key(|candidate| std::cmp::Reverse(coverage(candidate)));
|
|
1516
1521
|
|
|
1517
|
-
// Greedy selection can keep a candidate whose group ends up below two survivors; the
|
|
1518
|
-
//
|
|
1522
|
+
// Greedy selection can keep a candidate whose group ends up below two survivors; the failed
|
|
1523
|
+
// groups are removed and the selection reruns: the largest one at a time, then all at once.
|
|
1519
1524
|
let mut rerun = 0;
|
|
1520
1525
|
loop {
|
|
1521
|
-
let mut
|
|
1522
|
-
let
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
{
|
|
1528
|
-
|
|
1526
|
+
let (mut counted, blockers) = select_greedily(&duplicates);
|
|
1527
|
+
let failed = |group: &[DuplicateCandidate]| group.len() < 2;
|
|
1528
|
+
// A failed group that another failed group kept a copy from is not shed before it:
|
|
1529
|
+
// shedding the blocking group can rescue it, while shedding it frees nothing the other
|
|
1530
|
+
// waits for.
|
|
1531
|
+
let blocked_by_failed = |fingerprint: &std::rc::Rc<str>| {
|
|
1532
|
+
blockers.get(fingerprint).is_some_and(|blocking| {
|
|
1533
|
+
blocking.iter().any(|other| {
|
|
1534
|
+
other != fingerprint && counted.get(other).is_some_and(|group| failed(group))
|
|
1535
|
+
})
|
|
1536
|
+
})
|
|
1537
|
+
};
|
|
1538
|
+
let largest_failed = counted
|
|
1539
|
+
.iter()
|
|
1540
|
+
.filter(|(_, group)| failed(group))
|
|
1541
|
+
// The first of the largest, as in selectMaximalGroups.
|
|
1542
|
+
.rev()
|
|
1543
|
+
.max_by_key(|(fingerprint, group)| {
|
|
1544
|
+
(
|
|
1545
|
+
!blocked_by_failed(fingerprint),
|
|
1546
|
+
group.first().map_or(0, |first| first.token_count),
|
|
1547
|
+
)
|
|
1548
|
+
})
|
|
1549
|
+
.map(|(fingerprint, _)| fingerprint.clone());
|
|
1550
|
+
// No failed fingerprint means every counted group kept at least two survivors.
|
|
1551
|
+
let Some(largest_failed) = largest_failed else {
|
|
1552
|
+
return counted;
|
|
1553
|
+
};
|
|
1554
|
+
if rerun >= 2 * MAX_SELECTION_RERUN_COUNT {
|
|
1555
|
+
drop_failed_groups(&mut counted);
|
|
1556
|
+
return counted;
|
|
1557
|
+
}
|
|
1558
|
+
if rerun < MAX_SELECTION_RERUN_COUNT {
|
|
1559
|
+
duplicates.retain(|candidate| candidate.fingerprint != largest_failed);
|
|
1560
|
+
} else {
|
|
1561
|
+
duplicates.retain(|candidate| {
|
|
1562
|
+
counted
|
|
1563
|
+
.get(&candidate.fingerprint)
|
|
1564
|
+
.is_none_or(|group| !failed(group))
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
rerun += 1;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
/// Per fingerprint, the fingerprints of the kept regions that kept a candidate of it from being
|
|
1572
|
+
/// selected.
|
|
1573
|
+
type BlockersByFingerprint = FxHashMap<std::rc::Rc<str>, Vec<std::rc::Rc<str>>>;
|
|
1574
|
+
|
|
1575
|
+
/// One greedy selection in ranking order, in two passes. The first keeps each candidate that
|
|
1576
|
+
/// overlaps no kept region and lets a candidate a kept region contains join its group as a nested
|
|
1577
|
+
/// copy. The second lets a candidate left over take the place of the kept copies it encloses,
|
|
1578
|
+
/// which become nested copies of their groups, unless it partially overlaps a kept region: a
|
|
1579
|
+
/// larger clone must not displace a clone of the first pass it merely overlaps. A candidate
|
|
1580
|
+
/// lying inside a kept region in the end is a nested copy whenever its turn came. Groups left
|
|
1581
|
+
/// without a standalone copy are dropped.
|
|
1582
|
+
fn select_greedily(
|
|
1583
|
+
duplicates: &[DuplicateCandidate],
|
|
1584
|
+
) -> (
|
|
1585
|
+
IndexMap<std::rc::Rc<str>, Vec<DuplicateCandidate>>,
|
|
1586
|
+
BlockersByFingerprint,
|
|
1587
|
+
) {
|
|
1588
|
+
let is_region = |candidate: &DuplicateCandidate, region: (usize, usize)| {
|
|
1589
|
+
(candidate.start_index, candidate.end_index) == region
|
|
1590
|
+
};
|
|
1591
|
+
// Mutually non-overlapping, each with the fingerprint of the candidate occupying it.
|
|
1592
|
+
let mut kept_regions: Vec<((usize, usize), std::rc::Rc<str>)> = Vec::new();
|
|
1593
|
+
let mut counted: IndexMap<std::rc::Rc<str>, Vec<DuplicateCandidate>> = IndexMap::new();
|
|
1594
|
+
let mut nested: Vec<DuplicateCandidate> = Vec::new();
|
|
1595
|
+
let mut left_over: Vec<&DuplicateCandidate> = Vec::new();
|
|
1596
|
+
let mut blockers = BlockersByFingerprint::default();
|
|
1597
|
+
for encloses in [false, true] {
|
|
1598
|
+
let candidates: Vec<&DuplicateCandidate> = if encloses {
|
|
1599
|
+
std::mem::take(&mut left_over)
|
|
1600
|
+
} else {
|
|
1601
|
+
duplicates.iter().collect()
|
|
1602
|
+
};
|
|
1603
|
+
'candidates: for candidate in candidates {
|
|
1604
|
+
let (start, end) = (candidate.start_index, candidate.end_index);
|
|
1605
|
+
let mut enclosed: Vec<usize> = Vec::new();
|
|
1606
|
+
for (index, ((region_start, region_end), blocker)) in kept_regions.iter().enumerate() {
|
|
1607
|
+
let (region_start, region_end) = (*region_start, *region_end);
|
|
1608
|
+
if region_start >= end || start >= region_end {
|
|
1609
|
+
continue;
|
|
1610
|
+
}
|
|
1611
|
+
if region_start <= start && end <= region_end {
|
|
1612
|
+
nested.push(DuplicateCandidate {
|
|
1613
|
+
nested_in_larger_group: true,
|
|
1614
|
+
..candidate.clone()
|
|
1615
|
+
});
|
|
1616
|
+
continue 'candidates;
|
|
1617
|
+
}
|
|
1618
|
+
if encloses && start <= region_start && region_end <= end {
|
|
1619
|
+
enclosed.push(index);
|
|
1620
|
+
} else {
|
|
1621
|
+
left_over.push(candidate);
|
|
1622
|
+
if encloses {
|
|
1623
|
+
blockers
|
|
1624
|
+
.entry(candidate.fingerprint.clone())
|
|
1625
|
+
.or_default()
|
|
1626
|
+
.push(blocker.clone());
|
|
1627
|
+
}
|
|
1628
|
+
continue 'candidates;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
// Descending, so that removing one keeps the indexes of the others.
|
|
1632
|
+
for &index in enclosed.iter().rev() {
|
|
1633
|
+
let (region, fingerprint) = kept_regions.remove(index);
|
|
1634
|
+
if let Some(inner) = counted
|
|
1635
|
+
.get_mut(&fingerprint)
|
|
1636
|
+
.and_then(|group| group.iter_mut().find(|inner| is_region(inner, region)))
|
|
1637
|
+
{
|
|
1638
|
+
inner.nested_in_larger_group = true;
|
|
1639
|
+
}
|
|
1529
1640
|
}
|
|
1530
|
-
kept_regions.push((
|
|
1641
|
+
kept_regions.push(((start, end), candidate.fingerprint.clone()));
|
|
1531
1642
|
counted
|
|
1532
1643
|
.entry(candidate.fingerprint.clone())
|
|
1533
1644
|
.or_default()
|
|
1534
1645
|
.push(candidate.clone());
|
|
1535
1646
|
}
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
.unwrap_or(0);
|
|
1544
|
-
if group.len() < 2 && token_count > failed_token_count {
|
|
1545
|
-
failed_fingerprint = Some(fingerprint.clone());
|
|
1546
|
-
failed_token_count = token_count;
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
// No failed fingerprint means every counted group kept at least two survivors.
|
|
1550
|
-
let Some(failed_fingerprint) = failed_fingerprint else {
|
|
1551
|
-
return counted;
|
|
1647
|
+
}
|
|
1648
|
+
// A candidate still left over overlapped a kept region partially when its turn came. Where
|
|
1649
|
+
// an enclosing candidate took that region since, the candidate lies inside a kept region now
|
|
1650
|
+
// and is a nested copy like one visited later.
|
|
1651
|
+
for candidate in left_over {
|
|
1652
|
+
let lies_in = |&((start, end), _): &((usize, usize), std::rc::Rc<str>)| {
|
|
1653
|
+
start <= candidate.start_index && candidate.end_index <= end
|
|
1552
1654
|
};
|
|
1553
|
-
if
|
|
1554
|
-
|
|
1555
|
-
|
|
1655
|
+
if kept_regions.iter().any(lies_in) {
|
|
1656
|
+
nested.push(DuplicateCandidate {
|
|
1657
|
+
nested_in_larger_group: true,
|
|
1658
|
+
..candidate.clone()
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
for candidate in nested {
|
|
1663
|
+
if let Some(group) = counted.get_mut(&candidate.fingerprint) {
|
|
1664
|
+
group.push(candidate);
|
|
1556
1665
|
}
|
|
1666
|
+
}
|
|
1667
|
+
counted.retain(|_, group| {
|
|
1668
|
+
group
|
|
1669
|
+
.iter()
|
|
1670
|
+
.any(|candidate| !candidate.nested_in_larger_group)
|
|
1671
|
+
});
|
|
1672
|
+
(counted, blockers)
|
|
1673
|
+
}
|
|
1557
1674
|
|
|
1558
|
-
|
|
1559
|
-
|
|
1675
|
+
/// Past the rerun caps: drops the groups below two copies without another selection pass, and
|
|
1676
|
+
/// with them the nested copies left inside no standalone copy, until nothing changes; see
|
|
1677
|
+
/// dropFailedGroups in duplicateSelection.ts.
|
|
1678
|
+
fn drop_failed_groups(counted: &mut IndexMap<std::rc::Rc<str>, Vec<DuplicateCandidate>>) {
|
|
1679
|
+
loop {
|
|
1680
|
+
let group_count = counted.len();
|
|
1681
|
+
counted.retain(|_, group| {
|
|
1682
|
+
group.len() >= 2
|
|
1683
|
+
&& group
|
|
1684
|
+
.iter()
|
|
1685
|
+
.any(|candidate| !candidate.nested_in_larger_group)
|
|
1686
|
+
});
|
|
1687
|
+
let standalone: Vec<(usize, usize)> = counted
|
|
1688
|
+
.values()
|
|
1689
|
+
.flatten()
|
|
1690
|
+
.filter(|candidate| !candidate.nested_in_larger_group)
|
|
1691
|
+
.map(|candidate| (candidate.start_index, candidate.end_index))
|
|
1692
|
+
.collect();
|
|
1693
|
+
let mut changed = counted.len() != group_count;
|
|
1694
|
+
for group in counted.values_mut() {
|
|
1695
|
+
let copy_count = group.len();
|
|
1696
|
+
group.retain(|candidate| {
|
|
1697
|
+
!candidate.nested_in_larger_group
|
|
1698
|
+
|| standalone.iter().any(|&(start, end)| {
|
|
1699
|
+
start <= candidate.start_index && candidate.end_index <= end
|
|
1700
|
+
})
|
|
1701
|
+
});
|
|
1702
|
+
changed |= group.len() != copy_count;
|
|
1703
|
+
}
|
|
1704
|
+
if !changed {
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1560
1707
|
}
|
|
1561
1708
|
}
|
|
1562
1709
|
|
|
@@ -1583,6 +1730,9 @@ struct CountedOccurrence {
|
|
|
1583
1730
|
/// again: a retained group's occurrence that a partial gapped merge also paired into a merged
|
|
1584
1731
|
/// group, and a near-miss anchor, which overlaps the reported occurrence it was matched through.
|
|
1585
1732
|
shared_with_merged_group: bool,
|
|
1733
|
+
/// Set on a copy nested inside a larger group's region (it also sets
|
|
1734
|
+
/// `shared_with_merged_group`); see `nestedInLargerGroup` in duplication.ts.
|
|
1735
|
+
nested_in_larger_group: bool,
|
|
1586
1736
|
/// The token runs of a near-miss copy that its partners match, within its segments;
|
|
1587
1737
|
/// `None` for an exact or gapped copy, whose segments are matched throughout.
|
|
1588
1738
|
matched_runs: Option<Vec<(usize, usize)>>,
|
|
@@ -1615,7 +1765,8 @@ fn to_counted_groups(
|
|
|
1615
1765
|
let mut occurrences: Vec<CountedOccurrence> = group
|
|
1616
1766
|
.iter()
|
|
1617
1767
|
.map(|candidate| CountedOccurrence {
|
|
1618
|
-
shared_with_merged_group:
|
|
1768
|
+
shared_with_merged_group: candidate.nested_in_larger_group,
|
|
1769
|
+
nested_in_larger_group: candidate.nested_in_larger_group,
|
|
1619
1770
|
matched_runs: None,
|
|
1620
1771
|
exact_runs: Vec::new(),
|
|
1621
1772
|
segments: vec![(candidate.start_token_index, candidate.end_token_index)],
|
|
@@ -1636,7 +1787,7 @@ fn to_counted_groups(
|
|
|
1636
1787
|
/// Merges duplicate groups separated by a small token gap into one gapped (Type-3) clone group;
|
|
1637
1788
|
/// see mergeAdjacentGroups in duplication.ts for the pairing, partial-merge (unequal
|
|
1638
1789
|
/// cardinalities: the fully-paired group is subsumed, the other is retained with all its
|
|
1639
|
-
/// occurrences), and fixpoint/termination rules replicated here.
|
|
1790
|
+
/// occurrences), nested-copy, and fixpoint/termination rules replicated here.
|
|
1640
1791
|
fn merge_adjacent_groups(
|
|
1641
1792
|
mut groups: Vec<Vec<CountedOccurrence>>,
|
|
1642
1793
|
max_gap_tokens: usize,
|
|
@@ -1660,27 +1811,29 @@ fn merge_adjacent_groups(
|
|
|
1660
1811
|
let Some(result) = result else {
|
|
1661
1812
|
continue;
|
|
1662
1813
|
};
|
|
1663
|
-
let (
|
|
1664
|
-
(result.
|
|
1814
|
+
let (left, right) = if swapped {
|
|
1815
|
+
(result.second, result.first)
|
|
1665
1816
|
} else {
|
|
1666
|
-
(result.
|
|
1817
|
+
(result.first, result.second)
|
|
1667
1818
|
};
|
|
1668
|
-
// A
|
|
1669
|
-
//
|
|
1670
|
-
//
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
for &occurrence_index in &result.paired_retained_indexes {
|
|
1677
|
-
groups[left_index][occurrence_index].shared_with_merged_group = true;
|
|
1819
|
+
// A group that stays keeps ALL its occurrences, so its paired occurrences now
|
|
1820
|
+
// also live inside the merged group's occurrences: mark them so
|
|
1821
|
+
// duplicate_block_count counts each token span once.
|
|
1822
|
+
for (group_index, side) in [(left_index, &left), (right_index, &right)] {
|
|
1823
|
+
if !side.replaced {
|
|
1824
|
+
for &occurrence_index in &side.paired_indexes {
|
|
1825
|
+
groups[group_index][occurrence_index].shared_with_merged_group = true;
|
|
1826
|
+
}
|
|
1678
1827
|
}
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
groups[
|
|
1828
|
+
}
|
|
1829
|
+
match (left.replaced, right.replaced) {
|
|
1830
|
+
(true, true) => {
|
|
1831
|
+
groups[left_index] = result.merged;
|
|
1832
|
+
groups.remove(right_index);
|
|
1683
1833
|
}
|
|
1834
|
+
(false, true) => groups[right_index] = result.merged,
|
|
1835
|
+
(true, false) => groups[left_index] = result.merged,
|
|
1836
|
+
(false, false) => groups.push(result.merged),
|
|
1684
1837
|
}
|
|
1685
1838
|
groups.sort_by_key(|group| group_sort_key(group));
|
|
1686
1839
|
restart = true;
|
|
@@ -1737,16 +1890,21 @@ fn group_sort_key(group: &[CountedOccurrence]) -> (usize, usize) {
|
|
|
1737
1890
|
|
|
1738
1891
|
struct MergeResult {
|
|
1739
1892
|
merged: Vec<CountedOccurrence>,
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1893
|
+
first: MergedSide,
|
|
1894
|
+
second: MergedSide,
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
struct MergedSide {
|
|
1898
|
+
/// Whether the merged group takes the input group's place: its standalone occurrences were
|
|
1899
|
+
/// all paired and it holds no nested copies that only it can report.
|
|
1900
|
+
replaced: bool,
|
|
1901
|
+
/// Indexes into the input group of the occurrences that were paired.
|
|
1902
|
+
paired_indexes: Vec<usize>,
|
|
1745
1903
|
}
|
|
1746
1904
|
|
|
1747
1905
|
/// Pairs `second` occurrences with gap-preceding `first` occurrences, greedily in source order;
|
|
1748
1906
|
/// a faithful port of mergeGroups in duplication.ts (at least two pairs, at least one group fully
|
|
1749
|
-
///
|
|
1907
|
+
/// paired, merged spans never overlap).
|
|
1750
1908
|
fn merge_groups(
|
|
1751
1909
|
first: &[CountedOccurrence],
|
|
1752
1910
|
second: &[CountedOccurrence],
|
|
@@ -1756,13 +1914,21 @@ fn merge_groups(
|
|
|
1756
1914
|
// again: their spans already live inside that merged group, so re-pairing them would assemble
|
|
1757
1915
|
// a second, competing merged group instead of letting the existing merged group extend (and
|
|
1758
1916
|
// would count the same span twice). Consumption is still judged against the FULL group, so a
|
|
1759
|
-
// group holding shared occurrences is never subsumed away
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1917
|
+
// group holding shared occurrences is never subsumed away; nested copies, which the merged
|
|
1918
|
+
// span would not cover anyway, are left out of that judgment so they cannot veto a merge of
|
|
1919
|
+
// the standalone copies.
|
|
1920
|
+
let pairable = |group: &[CountedOccurrence]| -> Vec<usize> {
|
|
1921
|
+
(0..group.len())
|
|
1922
|
+
.filter(|&index| !group[index].shared_with_merged_group)
|
|
1923
|
+
.collect()
|
|
1924
|
+
};
|
|
1925
|
+
let standalone_count = |group: &[CountedOccurrence]| {
|
|
1926
|
+
group
|
|
1927
|
+
.iter()
|
|
1928
|
+
.filter(|occurrence| !occurrence.nested_in_larger_group)
|
|
1929
|
+
.count()
|
|
1930
|
+
};
|
|
1931
|
+
let (leadings, trailings) = (pairable(first), pairable(second));
|
|
1766
1932
|
let mut pairs: Vec<(usize, usize)> = Vec::new();
|
|
1767
1933
|
let mut leading_position = 0usize;
|
|
1768
1934
|
let mut previous_trailing_end: Option<usize> = None;
|
|
@@ -1786,17 +1952,18 @@ fn merge_groups(
|
|
|
1786
1952
|
}
|
|
1787
1953
|
}
|
|
1788
1954
|
}
|
|
1789
|
-
let
|
|
1790
|
-
let
|
|
1791
|
-
if pairs.len() < 2 || (!
|
|
1955
|
+
let first_fully_paired = pairs.len() == standalone_count(first);
|
|
1956
|
+
let second_fully_paired = pairs.len() == standalone_count(second);
|
|
1957
|
+
if pairs.len() < 2 || (!first_fully_paired && !second_fully_paired) {
|
|
1792
1958
|
return None;
|
|
1793
1959
|
}
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1960
|
+
// A group holding nested copies stays even when all its standalone copies pair: the merged
|
|
1961
|
+
// group's content is larger than what those copies matched, so only the original group can
|
|
1962
|
+
// report the nested copies.
|
|
1963
|
+
let holds_no_nested_copy = |group: &[CountedOccurrence]| {
|
|
1964
|
+
group
|
|
1965
|
+
.iter()
|
|
1966
|
+
.all(|occurrence| !occurrence.nested_in_larger_group)
|
|
1800
1967
|
};
|
|
1801
1968
|
let merged = pairs
|
|
1802
1969
|
.iter()
|
|
@@ -1806,6 +1973,7 @@ fn merge_groups(
|
|
|
1806
1973
|
CountedOccurrence {
|
|
1807
1974
|
// A merged occurrence is a fresh span combination; it inherits no shared marks.
|
|
1808
1975
|
shared_with_merged_group: false,
|
|
1976
|
+
nested_in_larger_group: false,
|
|
1809
1977
|
matched_runs: None,
|
|
1810
1978
|
exact_runs: Vec::new(),
|
|
1811
1979
|
segments: [leading.segments.clone(), trailing.segments.clone()].concat(),
|
|
@@ -1819,9 +1987,14 @@ fn merge_groups(
|
|
|
1819
1987
|
.collect();
|
|
1820
1988
|
Some(MergeResult {
|
|
1821
1989
|
merged,
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1990
|
+
first: MergedSide {
|
|
1991
|
+
replaced: first_fully_paired && holds_no_nested_copy(first),
|
|
1992
|
+
paired_indexes: pairs.iter().map(|&(leading, _)| leading).collect(),
|
|
1993
|
+
},
|
|
1994
|
+
second: MergedSide {
|
|
1995
|
+
replaced: second_fully_paired && holds_no_nested_copy(second),
|
|
1996
|
+
paired_indexes: pairs.iter().map(|&(_, trailing)| trailing).collect(),
|
|
1997
|
+
},
|
|
1825
1998
|
})
|
|
1826
1999
|
}
|
|
1827
2000
|
|
|
@@ -1907,28 +2080,39 @@ fn collect_near_miss_groups(
|
|
|
1907
2080
|
// longest common subsequence of the same sequences, and any choice among a copy's partners
|
|
1908
2081
|
// would let their number or their order decide what the copy counts.
|
|
1909
2082
|
//
|
|
1910
|
-
// A pair matching nothing outside
|
|
2083
|
+
// A pair matching nothing outside listed content has nothing to add to it and is dropped
|
|
1911
2084
|
// before clustering, so that it neither collapses the blocks' core nodes nor widens a node
|
|
1912
2085
|
// beyond what the pairs clustering it matched. Any other pair is kept however much of it is
|
|
1913
2086
|
// reported: what the exact pipeline reports of a copy depends on where its copies stand, and
|
|
1914
2087
|
// the lines the copy counts must not.
|
|
1915
|
-
let mut
|
|
1916
|
-
{
|
|
2088
|
+
let reported_prefix_of = |groups: &mut dyn Iterator<Item = &Vec<CountedOccurrence>>| {
|
|
1917
2089
|
let mut reported = vec![false; tokens.len()];
|
|
1918
|
-
for occurrence in
|
|
2090
|
+
for occurrence in groups.flatten() {
|
|
1919
2091
|
for &(start, end) in &occurrence.segments {
|
|
1920
2092
|
reported[start..end].fill(true);
|
|
1921
2093
|
}
|
|
1922
2094
|
}
|
|
2095
|
+
let mut prefix = vec![0usize; tokens.len() + 1];
|
|
1923
2096
|
for (index, &flag) in reported.iter().enumerate() {
|
|
1924
|
-
|
|
2097
|
+
prefix[index + 1] = prefix[index] + usize::from(flag);
|
|
1925
2098
|
}
|
|
1926
|
-
|
|
2099
|
+
prefix
|
|
2100
|
+
};
|
|
2101
|
+
let reported_prefix = reported_prefix_of(&mut reported_groups.iter());
|
|
1927
2102
|
let reported_count =
|
|
1928
2103
|
|(start, end): (usize, usize)| reported_prefix[end] - reported_prefix[start];
|
|
2104
|
+
// Listed content is the reported content without the groups holding nested copies. Such a
|
|
2105
|
+
// group lists a part of larger copies with a copy standing elsewhere, which makes no pair
|
|
2106
|
+
// redundant: the blocks around its copies may be copies as wholes, which only their pair can
|
|
2107
|
+
// list at that size, taking the group over.
|
|
2108
|
+
let listed_prefix = reported_prefix_of(&mut reported_groups.iter().filter(|group| {
|
|
2109
|
+
group
|
|
2110
|
+
.iter()
|
|
2111
|
+
.all(|occurrence| !occurrence.nested_in_larger_group)
|
|
2112
|
+
}));
|
|
1929
2113
|
let unreported_count = |runs: &[(usize, usize)]| -> usize {
|
|
1930
2114
|
runs.iter()
|
|
1931
|
-
.map(|&
|
|
2115
|
+
.map(|&(start, end)| end - start - (listed_prefix[end] - listed_prefix[start]))
|
|
1932
2116
|
.sum()
|
|
1933
2117
|
};
|
|
1934
2118
|
let block_span = |index: usize| {
|
|
@@ -1940,7 +2124,7 @@ fn collect_near_miss_groups(
|
|
|
1940
2124
|
let mut edges: Vec<MatchEdge> = Vec::new();
|
|
1941
2125
|
let mut add_edge = |left: usize, right: usize, cores: Option<CorePair>, alignment| {
|
|
1942
2126
|
let (left_span, right_span) = cores.unwrap_or((block_span(left), block_span(right)));
|
|
1943
|
-
// Fully
|
|
2127
|
+
// Fully listed spans need no alignment to be dropped.
|
|
1944
2128
|
if unreported_count(&[left_span, right_span]) == 0 {
|
|
1945
2129
|
return;
|
|
1946
2130
|
}
|
|
@@ -2092,6 +2276,7 @@ fn collect_near_miss_groups(
|
|
|
2092
2276
|
.collect();
|
|
2093
2277
|
CountedOccurrence {
|
|
2094
2278
|
shared_with_merged_group: false,
|
|
2279
|
+
nested_in_larger_group: false,
|
|
2095
2280
|
matched_runs: Some(merge_overlapping_cores(&matched_runs)),
|
|
2096
2281
|
exact_runs: Vec::new(),
|
|
2097
2282
|
token_count: segments.iter().map(|segment| segment.1 - segment.0).sum(),
|
|
@@ -2228,9 +2413,25 @@ fn collect_near_miss_groups(
|
|
|
2228
2413
|
}
|
|
2229
2414
|
merged.extend(to_occurrences(&unanchored_nodes));
|
|
2230
2415
|
// The rebuild consumed every fully-clustered group, so shared-span marks from earlier
|
|
2231
|
-
// partial merges no longer point at a separate merged group.
|
|
2416
|
+
// partial merges no longer point at a separate merged group. A copy counts the
|
|
2417
|
+
// fragments it took over itself, unless a group left in place still counts all of it:
|
|
2418
|
+
// a larger clone whose matched segments enclose those of the copy, which its
|
|
2419
|
+
// fragments were nested in.
|
|
2232
2420
|
for occurrence in &mut merged {
|
|
2233
|
-
occurrence.shared_with_merged_group =
|
|
2421
|
+
occurrence.shared_with_merged_group = reported_groups
|
|
2422
|
+
.iter()
|
|
2423
|
+
.enumerate()
|
|
2424
|
+
.filter(|(group_index, _)| !fully_clustered.contains(group_index))
|
|
2425
|
+
.flat_map(|(_, group)| group)
|
|
2426
|
+
.any(|counting| {
|
|
2427
|
+
!counting.shared_with_merged_group
|
|
2428
|
+
&& occurrence.segments.iter().all(|segment| {
|
|
2429
|
+
counting
|
|
2430
|
+
.segments
|
|
2431
|
+
.iter()
|
|
2432
|
+
.any(|matched| matched.0 <= segment.0 && segment.1 <= matched.1)
|
|
2433
|
+
})
|
|
2434
|
+
});
|
|
2234
2435
|
}
|
|
2235
2436
|
merged.extend(to_anchor_occurrences(&anchor_nodes));
|
|
2236
2437
|
merged.sort_by_key(|occurrence| {
|
|
@@ -2389,6 +2590,7 @@ fn coalesce_occurrences(occurrences: Vec<CountedOccurrence>) -> CountedOccurrenc
|
|
|
2389
2590
|
.collect();
|
|
2390
2591
|
CountedOccurrence {
|
|
2391
2592
|
shared_with_merged_group: false,
|
|
2593
|
+
nested_in_larger_group: false,
|
|
2392
2594
|
// Without a near-miss part, the segments are matched throughout and say it all.
|
|
2393
2595
|
exact_runs: if matched_runs.is_some() {
|
|
2394
2596
|
merge_overlapping_cores(&exact_runs)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-gauge",
|
|
3
|
-
"version": "10.2.
|
|
3
|
+
"version": "10.2.2",
|
|
4
4
|
"description": "Measure code metrics with tree-sitter.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -93,12 +93,12 @@
|
|
|
93
93
|
"registry": "https://registry.npmjs.org/"
|
|
94
94
|
},
|
|
95
95
|
"optionalDependencies": {
|
|
96
|
-
"code-gauge-linux-x64-gnu": "10.2.
|
|
97
|
-
"code-gauge-linux-arm64-gnu": "10.2.
|
|
98
|
-
"code-gauge-linux-x64-musl": "10.2.
|
|
99
|
-
"code-gauge-linux-arm64-musl": "10.2.
|
|
100
|
-
"code-gauge-darwin-x64": "10.2.
|
|
101
|
-
"code-gauge-darwin-arm64": "10.2.
|
|
102
|
-
"code-gauge-win32-x64-msvc": "10.2.
|
|
96
|
+
"code-gauge-linux-x64-gnu": "10.2.2",
|
|
97
|
+
"code-gauge-linux-arm64-gnu": "10.2.2",
|
|
98
|
+
"code-gauge-linux-x64-musl": "10.2.2",
|
|
99
|
+
"code-gauge-linux-arm64-musl": "10.2.2",
|
|
100
|
+
"code-gauge-darwin-x64": "10.2.2",
|
|
101
|
+
"code-gauge-darwin-arm64": "10.2.2",
|
|
102
|
+
"code-gauge-win32-x64-msvc": "10.2.2"
|
|
103
103
|
}
|
|
104
104
|
}
|