code-gauge 10.2.0 → 10.2.1
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 +5 -2
- package/dist/crossFileDuplication.cjs +1 -1
- package/dist/crossFileDuplication.cjs.map +1 -1
- package/dist/crossFileDuplication.d.ts +2 -2
- 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 +7 -4
- package/dist/crossFileNearMiss.js +1 -1
- package/dist/crossFileNearMiss.js.map +1 -1
- package/dist/duplication.cjs +1 -1
- package/dist/duplication.cjs.map +1 -1
- package/dist/duplication.d.ts +7 -1
- package/dist/duplication.js +1 -1
- package/dist/duplication.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/native/code-gauge.wasm +0 -0
- package/native/src/duplication.rs +157 -105
- package/package.json +8 -8
|
@@ -5,7 +5,7 @@ use std::sync::OnceLock;
|
|
|
5
5
|
use tree_sitter::Node;
|
|
6
6
|
|
|
7
7
|
use crate::near_miss::{
|
|
8
|
-
|
|
8
|
+
Block, CorePair, Matcher, PairMatch, TokenRuns, FILTRATION_PERCENT, MAX_LENGTH_RATIO,
|
|
9
9
|
};
|
|
10
10
|
use crate::tree_index::NodeExt;
|
|
11
11
|
use crate::types::{
|
|
@@ -1586,6 +1586,9 @@ struct CountedOccurrence {
|
|
|
1586
1586
|
/// The token runs of a near-miss copy that its partners match, within its segments;
|
|
1587
1587
|
/// `None` for an exact or gapped copy, whose segments are matched throughout.
|
|
1588
1588
|
matched_runs: Option<Vec<(usize, usize)>>,
|
|
1589
|
+
/// The exact fragments coalesced into a near-miss copy: each line they reach is duplicated,
|
|
1590
|
+
/// whatever else of the copy shares the line.
|
|
1591
|
+
exact_runs: Vec<(usize, usize)>,
|
|
1589
1592
|
/// Sum of segment token counts (the gap tokens are not matched content).
|
|
1590
1593
|
token_count: usize,
|
|
1591
1594
|
start_token_index: usize,
|
|
@@ -1594,6 +1597,16 @@ struct CountedOccurrence {
|
|
|
1594
1597
|
end_line: usize,
|
|
1595
1598
|
}
|
|
1596
1599
|
|
|
1600
|
+
impl CountedOccurrence {
|
|
1601
|
+
/// Whether a matched segment overlaps the token range: the gap of a gapped clone is not
|
|
1602
|
+
/// matched content.
|
|
1603
|
+
fn matches_within(&self, (start, end): (usize, usize)) -> bool {
|
|
1604
|
+
self.segments
|
|
1605
|
+
.iter()
|
|
1606
|
+
.any(|&(segment_start, segment_end)| segment_start < end && start < segment_end)
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1597
1610
|
fn to_counted_groups(
|
|
1598
1611
|
counted: &IndexMap<std::rc::Rc<str>, Vec<DuplicateCandidate>>,
|
|
1599
1612
|
) -> Vec<Vec<CountedOccurrence>> {
|
|
@@ -1604,6 +1617,7 @@ fn to_counted_groups(
|
|
|
1604
1617
|
.map(|candidate| CountedOccurrence {
|
|
1605
1618
|
shared_with_merged_group: false,
|
|
1606
1619
|
matched_runs: None,
|
|
1620
|
+
exact_runs: Vec::new(),
|
|
1607
1621
|
segments: vec![(candidate.start_token_index, candidate.end_token_index)],
|
|
1608
1622
|
token_count: candidate.token_count,
|
|
1609
1623
|
start_token_index: candidate.start_token_index,
|
|
@@ -1793,6 +1807,7 @@ fn merge_groups(
|
|
|
1793
1807
|
// A merged occurrence is a fresh span combination; it inherits no shared marks.
|
|
1794
1808
|
shared_with_merged_group: false,
|
|
1795
1809
|
matched_runs: None,
|
|
1810
|
+
exact_runs: Vec::new(),
|
|
1796
1811
|
segments: [leading.segments.clone(), trailing.segments.clone()].concat(),
|
|
1797
1812
|
token_count: leading.token_count + trailing.token_count,
|
|
1798
1813
|
start_token_index: leading.start_token_index,
|
|
@@ -1815,7 +1830,8 @@ struct MatchEdge {
|
|
|
1815
1830
|
left: usize,
|
|
1816
1831
|
right: usize,
|
|
1817
1832
|
cores: Option<CorePair>,
|
|
1818
|
-
|
|
1833
|
+
/// The token runs the pair matches, on the left and on the right.
|
|
1834
|
+
matched_runs: (TokenRuns, TokenRuns),
|
|
1819
1835
|
}
|
|
1820
1836
|
|
|
1821
1837
|
impl MatchEdge {
|
|
@@ -1833,7 +1849,8 @@ impl MatchEdge {
|
|
|
1833
1849
|
/// merged), each clustered with its own partners, so code no verified pair matched never counts
|
|
1834
1850
|
/// as duplicated. A node overlapping a reported occurrence is an anchor: it joins its group at its
|
|
1835
1851
|
/// own size, so the copies of a block are listed at the size they matched it, and is marked as
|
|
1836
|
-
/// counted elsewhere unless the group takes over the reported occurrences it overlaps.
|
|
1852
|
+
/// counted elsewhere unless the group takes over the reported occurrences it overlaps. An anchor
|
|
1853
|
+
/// still counts the lines its partners match outside those occurrences.
|
|
1837
1854
|
fn collect_near_miss_groups(
|
|
1838
1855
|
source: &TokenizedSource<'_>,
|
|
1839
1856
|
settings: &DuplicationSettings,
|
|
@@ -1848,16 +1865,17 @@ fn collect_near_miss_groups(
|
|
|
1848
1865
|
return Vec::new();
|
|
1849
1866
|
}
|
|
1850
1867
|
|
|
1851
|
-
// Reported-group indices whose occurrences overlap a token range
|
|
1852
|
-
//
|
|
1868
|
+
// Reported-group indices whose occurrences' matched segments overlap a token range (the gap
|
|
1869
|
+
// of a gapped clone is not reported content): near-miss nodes covering such content anchor
|
|
1870
|
+
// comparisons but are never re-reported.
|
|
1853
1871
|
let touched_groups_in = |start: usize, end: usize| -> Vec<usize> {
|
|
1854
1872
|
reported_groups
|
|
1855
1873
|
.iter()
|
|
1856
1874
|
.enumerate()
|
|
1857
1875
|
.filter(|(_, group)| {
|
|
1858
|
-
group
|
|
1859
|
-
|
|
1860
|
-
|
|
1876
|
+
group
|
|
1877
|
+
.iter()
|
|
1878
|
+
.any(|occurrence| occurrence.matches_within((start, end)))
|
|
1861
1879
|
})
|
|
1862
1880
|
.map(|(group_index, _)| group_index)
|
|
1863
1881
|
.collect()
|
|
@@ -1884,41 +1902,69 @@ fn collect_near_miss_groups(
|
|
|
1884
1902
|
settings.min_similarity_percent,
|
|
1885
1903
|
);
|
|
1886
1904
|
|
|
1887
|
-
//
|
|
1888
|
-
//
|
|
1889
|
-
//
|
|
1890
|
-
let
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1905
|
+
// The lines of a near-miss copy that are duplicated are those a verified partner matches,
|
|
1906
|
+
// which takes the pairing itself. Every pair kept is aligned: verifying it already cost a
|
|
1907
|
+
// longest common subsequence of the same sequences, and any choice among a copy's partners
|
|
1908
|
+
// would let their number or their order decide what the copy counts.
|
|
1909
|
+
//
|
|
1910
|
+
// A pair matching nothing outside reported content has nothing to add to it and is dropped
|
|
1911
|
+
// before clustering, so that it neither collapses the blocks' core nodes nor widens a node
|
|
1912
|
+
// beyond what the pairs clustering it matched. Any other pair is kept however much of it is
|
|
1913
|
+
// reported: what the exact pipeline reports of a copy depends on where its copies stand, and
|
|
1914
|
+
// the lines the copy counts must not.
|
|
1915
|
+
let mut reported_prefix = vec![0usize; tokens.len() + 1];
|
|
1916
|
+
{
|
|
1917
|
+
let mut reported = vec![false; tokens.len()];
|
|
1918
|
+
for occurrence in reported_groups.iter().flatten() {
|
|
1919
|
+
for &(start, end) in &occurrence.segments {
|
|
1920
|
+
reported[start..end].fill(true);
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
for (index, &flag) in reported.iter().enumerate() {
|
|
1924
|
+
reported_prefix[index + 1] = reported_prefix[index] + usize::from(flag);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
let reported_count =
|
|
1928
|
+
|(start, end): (usize, usize)| reported_prefix[end] - reported_prefix[start];
|
|
1929
|
+
let unreported_count = |runs: &[(usize, usize)]| -> usize {
|
|
1930
|
+
runs.iter()
|
|
1931
|
+
.map(|&run| run.1 - run.0 - reported_count(run))
|
|
1932
|
+
.sum()
|
|
1933
|
+
};
|
|
1934
|
+
let block_span = |index: usize| {
|
|
1935
|
+
(
|
|
1936
|
+
comparable[index].start_token_index,
|
|
1937
|
+
comparable[index].end_token_index,
|
|
1938
|
+
)
|
|
1939
|
+
};
|
|
1895
1940
|
let mut edges: Vec<MatchEdge> = Vec::new();
|
|
1941
|
+
let mut add_edge = |left: usize, right: usize, cores: Option<CorePair>, alignment| {
|
|
1942
|
+
let (left_span, right_span) = cores.unwrap_or((block_span(left), block_span(right)));
|
|
1943
|
+
// Fully reported spans need no alignment to be dropped.
|
|
1944
|
+
if unreported_count(&[left_span, right_span]) == 0 {
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
let matched_runs =
|
|
1948
|
+
matcher.align(&blocks[left], &blocks[right], cores, alignment, &token_keys);
|
|
1949
|
+
if unreported_count(&matched_runs.0) + unreported_count(&matched_runs.1) == 0 {
|
|
1950
|
+
return;
|
|
1951
|
+
}
|
|
1952
|
+
edges.push(MatchEdge {
|
|
1953
|
+
left,
|
|
1954
|
+
right,
|
|
1955
|
+
cores,
|
|
1956
|
+
matched_runs,
|
|
1957
|
+
});
|
|
1958
|
+
};
|
|
1896
1959
|
for_each_candidate_pair(
|
|
1897
1960
|
&blocks,
|
|
1898
1961
|
settings.min_similarity_percent,
|
|
1899
1962
|
|left_index, right_index| match matcher.verify(&blocks[left_index], &blocks[right_index]) {
|
|
1900
1963
|
None => {}
|
|
1901
|
-
Some(PairMatch::Whole(alignment))
|
|
1902
|
-
if !(block_touched[left_index] && block_touched[right_index]) =>
|
|
1903
|
-
{
|
|
1904
|
-
edges.push(MatchEdge {
|
|
1905
|
-
left: left_index,
|
|
1906
|
-
right: right_index,
|
|
1907
|
-
cores: None,
|
|
1908
|
-
alignment,
|
|
1909
|
-
})
|
|
1910
|
-
}
|
|
1911
|
-
Some(PairMatch::Whole(_)) => {}
|
|
1964
|
+
Some(PairMatch::Whole(alignment)) => add_edge(left_index, right_index, None, alignment),
|
|
1912
1965
|
Some(PairMatch::Local(cores)) => {
|
|
1913
1966
|
for (core_pair, alignment) in cores {
|
|
1914
|
-
|
|
1915
|
-
edges.push(MatchEdge {
|
|
1916
|
-
left: left_index,
|
|
1917
|
-
right: right_index,
|
|
1918
|
-
cores: Some(core_pair),
|
|
1919
|
-
alignment,
|
|
1920
|
-
});
|
|
1921
|
-
}
|
|
1967
|
+
add_edge(left_index, right_index, Some(core_pair), alignment);
|
|
1922
1968
|
}
|
|
1923
1969
|
}
|
|
1924
1970
|
},
|
|
@@ -2006,21 +2052,10 @@ fn collect_near_miss_groups(
|
|
|
2006
2052
|
parent[left_root.max(right_root)] = left_root.min(right_root);
|
|
2007
2053
|
}
|
|
2008
2054
|
|
|
2009
|
-
// The lines of a near-miss copy that are duplicated are those a verified partner matches,
|
|
2010
|
-
// which takes the pairing itself. Every pair is aligned: verifying it already cost a longest
|
|
2011
|
-
// common subsequence of the same sequences, and any choice among a copy's partners would let
|
|
2012
|
-
// their number or their order decide what the copy counts.
|
|
2013
2055
|
let mut matched_runs_by_node: Vec<Vec<(usize, usize)>> = vec![Vec::new(); node_blocks.len()];
|
|
2014
2056
|
for (edge, nodes) in edges.iter().zip(&edge_nodes) {
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
&blocks[edge.right],
|
|
2018
|
-
edge.cores,
|
|
2019
|
-
edge.alignment,
|
|
2020
|
-
&token_keys,
|
|
2021
|
-
);
|
|
2022
|
-
matched_runs_by_node[nodes[0]].extend(left_runs);
|
|
2023
|
-
matched_runs_by_node[nodes[1]].extend(right_runs);
|
|
2057
|
+
matched_runs_by_node[nodes[0]].extend(&edge.matched_runs.0);
|
|
2058
|
+
matched_runs_by_node[nodes[1]].extend(&edge.matched_runs.1);
|
|
2024
2059
|
}
|
|
2025
2060
|
|
|
2026
2061
|
let mut members_by_root: IndexMap<usize, Vec<usize>> = IndexMap::new();
|
|
@@ -2058,6 +2093,7 @@ fn collect_near_miss_groups(
|
|
|
2058
2093
|
CountedOccurrence {
|
|
2059
2094
|
shared_with_merged_group: false,
|
|
2060
2095
|
matched_runs: Some(merge_overlapping_cores(&matched_runs)),
|
|
2096
|
+
exact_runs: Vec::new(),
|
|
2061
2097
|
token_count: segments.iter().map(|segment| segment.1 - segment.0).sum(),
|
|
2062
2098
|
segments,
|
|
2063
2099
|
start_token_index: start,
|
|
@@ -2106,17 +2142,42 @@ fn collect_near_miss_groups(
|
|
|
2106
2142
|
}
|
|
2107
2143
|
continue;
|
|
2108
2144
|
}
|
|
2109
|
-
if
|
|
2145
|
+
if members.iter().all(|&node| {
|
|
2146
|
+
let (start, end) = node_range(node);
|
|
2147
|
+
reported_count((start, end)) == end - start
|
|
2148
|
+
}) {
|
|
2110
2149
|
continue;
|
|
2111
2150
|
}
|
|
2112
|
-
// An anchored cluster extends a reported group only when
|
|
2113
|
-
//
|
|
2114
|
-
// reports content the cluster does not share
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2151
|
+
// An anchored cluster extends a reported group only when it stands for the same copies:
|
|
2152
|
+
// every occurrence of the group overlaps the member nodes of exactly one block (one
|
|
2153
|
+
// disjoint from all members reports content the cluster does not share, and one reaching
|
|
2154
|
+
// into two members would make one copy take over lines of the next), exceeds the span of
|
|
2155
|
+
// those nodes by less than `min_tokens` (a copy is listed at the size it matched, not at
|
|
2156
|
+
// that of a clone larger by a reportable part, which may run into code no member
|
|
2157
|
+
// matched), and shares that block with no other occurrence of the group (those repeat
|
|
2158
|
+
// within the copy, not between the copies).
|
|
2159
|
+
let member_block_of = |occurrence: &CountedOccurrence| {
|
|
2160
|
+
let mut blocks = members
|
|
2161
|
+
.iter()
|
|
2162
|
+
.filter(|&&index| occurrence.matches_within(node_range(index)))
|
|
2163
|
+
.map(|&index| node_blocks[index]);
|
|
2164
|
+
let block = blocks.next()?;
|
|
2165
|
+
let mut spans = members
|
|
2166
|
+
.iter()
|
|
2167
|
+
.filter(|&&index| node_blocks[index] == block)
|
|
2168
|
+
.map(|&index| node_range(index));
|
|
2169
|
+
let first = spans.next()?;
|
|
2170
|
+
let span = spans.fold(first, |span, node| (span.0.min(node.0), span.1.max(node.1)));
|
|
2171
|
+
let excess = span.0.saturating_sub(occurrence.start_token_index)
|
|
2172
|
+
+ occurrence.end_token_index.saturating_sub(span.1);
|
|
2173
|
+
(blocks.all(|other| other == block) && excess < settings.min_tokens).then_some(block)
|
|
2174
|
+
};
|
|
2175
|
+
let stands_for_members = |group: &[CountedOccurrence]| {
|
|
2176
|
+
let mut blocks: FxHashSet<usize> = FxHashSet::default();
|
|
2177
|
+
!group.is_empty()
|
|
2178
|
+
&& group.iter().all(|occurrence| {
|
|
2179
|
+
member_block_of(occurrence).is_some_and(|block| blocks.insert(block))
|
|
2180
|
+
})
|
|
2120
2181
|
};
|
|
2121
2182
|
// Ascending by construction: BTreeSet iteration is sorted and filter preserves order.
|
|
2122
2183
|
let fully_clustered: Vec<usize> = covered
|
|
@@ -2124,16 +2185,12 @@ fn collect_near_miss_groups(
|
|
|
2124
2185
|
.flat_map(|&index| touched_groups_of(index).iter().copied())
|
|
2125
2186
|
.collect::<std::collections::BTreeSet<usize>>()
|
|
2126
2187
|
.into_iter()
|
|
2127
|
-
.filter(|&group_index|
|
|
2128
|
-
let group = &reported_groups[group_index];
|
|
2129
|
-
!group.is_empty() && group.iter().all(&overlaps_member)
|
|
2130
|
-
})
|
|
2188
|
+
.filter(|&group_index| stands_for_members(&reported_groups[group_index]))
|
|
2131
2189
|
.collect();
|
|
2132
2190
|
if let Some((&target_index, source_indexes)) = fully_clustered.split_first() {
|
|
2133
2191
|
// Rebuild the component as ONE group with one coalesced occurrence per member block:
|
|
2134
|
-
// the fragments
|
|
2192
|
+
// the fragments the nodes of a block overlap are collected together, since a block's
|
|
2135
2193
|
// cores are parts of one copy.
|
|
2136
|
-
let mut consumed: FxHashSet<(usize, usize)> = FxHashSet::default();
|
|
2137
2194
|
let mut merged: Vec<CountedOccurrence> = Vec::new();
|
|
2138
2195
|
let mut unanchored_nodes: Vec<usize> = Vec::new();
|
|
2139
2196
|
let mut anchor_nodes: Vec<usize> = Vec::new();
|
|
@@ -2145,26 +2202,17 @@ fn collect_near_miss_groups(
|
|
|
2145
2202
|
.push(member_index);
|
|
2146
2203
|
}
|
|
2147
2204
|
for block_nodes in nodes_by_block.values() {
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
&& occurrence.start_token_index < range_end
|
|
2160
|
-
&& range_start < occurrence.end_token_index
|
|
2161
|
-
{
|
|
2162
|
-
consumed.insert((group_index, occurrence_index));
|
|
2163
|
-
fragments.push((occurrence.clone(), group_index));
|
|
2164
|
-
}
|
|
2165
|
-
}
|
|
2166
|
-
}
|
|
2167
|
-
}
|
|
2205
|
+
let overlaps_block_node = |occurrence: &&CountedOccurrence| {
|
|
2206
|
+
block_nodes
|
|
2207
|
+
.iter()
|
|
2208
|
+
.any(|&node| occurrence.matches_within(node_range(node)))
|
|
2209
|
+
};
|
|
2210
|
+
let mut fragments: Vec<CountedOccurrence> = fully_clustered
|
|
2211
|
+
.iter()
|
|
2212
|
+
.flat_map(|&group_index| &reported_groups[group_index])
|
|
2213
|
+
.filter(overlaps_block_node)
|
|
2214
|
+
.cloned()
|
|
2215
|
+
.collect();
|
|
2168
2216
|
if fragments.is_empty() {
|
|
2169
2217
|
let (anchors, plain): (Vec<usize>, Vec<usize>) = block_nodes
|
|
2170
2218
|
.iter()
|
|
@@ -2174,27 +2222,9 @@ fn collect_near_miss_groups(
|
|
|
2174
2222
|
continue;
|
|
2175
2223
|
}
|
|
2176
2224
|
// The block's nodes are part of the same copy as its fragments: a block matched
|
|
2177
|
-
// beyond the fragments it holds is a copy at that size.
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
fragments.push((occurrence, usize::MAX));
|
|
2181
|
-
}
|
|
2182
|
-
fragments.sort_by_key(|(occurrence, _)| {
|
|
2183
|
-
(occurrence.start_token_index, occurrence.end_token_index)
|
|
2184
|
-
});
|
|
2185
|
-
let mut copy_parts: Vec<CountedOccurrence> = Vec::new();
|
|
2186
|
-
let mut copy_groups: FxHashSet<usize> = FxHashSet::default();
|
|
2187
|
-
for (occurrence, group_index) in fragments {
|
|
2188
|
-
if copy_groups.contains(&group_index) {
|
|
2189
|
-
merged.push(coalesce_occurrences(std::mem::take(&mut copy_parts)));
|
|
2190
|
-
copy_groups.clear();
|
|
2191
|
-
}
|
|
2192
|
-
copy_parts.push(occurrence);
|
|
2193
|
-
copy_groups.insert(group_index);
|
|
2194
|
-
}
|
|
2195
|
-
if !copy_parts.is_empty() {
|
|
2196
|
-
merged.push(coalesce_occurrences(copy_parts));
|
|
2197
|
-
}
|
|
2225
|
+
// beyond the fragments it holds is a copy at that size.
|
|
2226
|
+
fragments.extend(to_occurrences(block_nodes));
|
|
2227
|
+
merged.push(coalesce_occurrences(fragments));
|
|
2198
2228
|
}
|
|
2199
2229
|
merged.extend(to_occurrences(&unanchored_nodes));
|
|
2200
2230
|
// The rebuild consumed every fully-clustered group, so shared-span marks from earlier
|
|
@@ -2349,8 +2379,22 @@ fn coalesce_occurrences(occurrences: Vec<CountedOccurrence>) -> CountedOccurrenc
|
|
|
2349
2379
|
.collect();
|
|
2350
2380
|
merge_overlapping_cores(&runs)
|
|
2351
2381
|
});
|
|
2382
|
+
let exact_runs: Vec<(usize, usize)> = occurrences
|
|
2383
|
+
.iter()
|
|
2384
|
+
.flat_map(|occurrence| match occurrence.matched_runs {
|
|
2385
|
+
Some(_) => &occurrence.exact_runs,
|
|
2386
|
+
None => &occurrence.segments,
|
|
2387
|
+
})
|
|
2388
|
+
.copied()
|
|
2389
|
+
.collect();
|
|
2352
2390
|
CountedOccurrence {
|
|
2353
2391
|
shared_with_merged_group: false,
|
|
2392
|
+
// Without a near-miss part, the segments are matched throughout and say it all.
|
|
2393
|
+
exact_runs: if matched_runs.is_some() {
|
|
2394
|
+
merge_overlapping_cores(&exact_runs)
|
|
2395
|
+
} else {
|
|
2396
|
+
Vec::new()
|
|
2397
|
+
},
|
|
2354
2398
|
matched_runs,
|
|
2355
2399
|
token_count: segments.iter().map(|segment| segment.1 - segment.0).sum(),
|
|
2356
2400
|
start_token_index: occurrences
|
|
@@ -2532,6 +2576,7 @@ fn count_redundant_fragments(group: &[CountedOccurrence]) -> usize {
|
|
|
2532
2576
|
/// of line coverage. A line of a near-miss copy counts when its partners match more than half of
|
|
2533
2577
|
/// its tokens: a line the copy adds or rewrites is not duplicated, while a longest common
|
|
2534
2578
|
/// subsequence still pairs a stray token of it (a bracket, a separator) with one of the partner.
|
|
2579
|
+
/// A line an exact fragment of the copy reaches counts in any case.
|
|
2535
2580
|
fn collect_matched_lines(
|
|
2536
2581
|
occurrence: &CountedOccurrence,
|
|
2537
2582
|
code_line_numbers: &FxHashSet<usize>,
|
|
@@ -2552,10 +2597,17 @@ fn collect_matched_lines(
|
|
|
2552
2597
|
}
|
|
2553
2598
|
}
|
|
2554
2599
|
}
|
|
2600
|
+
let exact_rows: FxHashSet<usize> = occurrence
|
|
2601
|
+
.exact_runs
|
|
2602
|
+
.iter()
|
|
2603
|
+
.flat_map(|&(start, end)| &tokens[start..end])
|
|
2604
|
+
.flat_map(|token| token.start_row + 1..=token.end_row + 1)
|
|
2605
|
+
.collect();
|
|
2555
2606
|
token_counts_by_row
|
|
2556
2607
|
.into_iter()
|
|
2557
2608
|
.filter(|(line, (matched_count, token_count))| {
|
|
2558
|
-
code_line_numbers.contains(line)
|
|
2609
|
+
code_line_numbers.contains(line)
|
|
2610
|
+
&& (matched_count * 2 > *token_count || exact_rows.contains(line))
|
|
2559
2611
|
})
|
|
2560
2612
|
.map(|(line, _)| line)
|
|
2561
2613
|
.collect()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-gauge",
|
|
3
|
-
"version": "10.2.
|
|
3
|
+
"version": "10.2.1",
|
|
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.1",
|
|
97
|
+
"code-gauge-linux-arm64-gnu": "10.2.1",
|
|
98
|
+
"code-gauge-linux-x64-musl": "10.2.1",
|
|
99
|
+
"code-gauge-linux-arm64-musl": "10.2.1",
|
|
100
|
+
"code-gauge-darwin-x64": "10.2.1",
|
|
101
|
+
"code-gauge-darwin-arm64": "10.2.1",
|
|
102
|
+
"code-gauge-win32-x64-msvc": "10.2.1"
|
|
103
103
|
}
|
|
104
104
|
}
|