weavatrix_clone/
detector.rs1use crate::canonical::suppress_contained;
2use crate::cluster::{families_for_pairs, pair_id};
3use crate::fingerprint::winnow;
4use crate::index::candidates;
5use crate::token::{Interner, Tokenized, tokenize};
6use crate::verify::{Verifier, evidence};
7use crate::{
8 CloneConfig, CloneError, CloneLocation, ClonePair, CloneReport, CloneStatistics, DetectionMode,
9 Result, SourceFragment,
10};
11use std::collections::HashSet;
12
13#[derive(Debug, Clone, Copy, Default)]
14pub struct CloneDetector {
15 config: CloneConfig,
16}
17
18impl CloneDetector {
19 pub fn new(config: CloneConfig) -> Result<Self> {
25 Ok(Self {
26 config: config.validate()?,
27 })
28 }
29
30 #[must_use]
31 pub const fn config(&self) -> CloneConfig {
32 self.config
33 }
34
35 #[allow(clippy::too_many_lines)]
43 pub fn detect(&self, fragments: &[SourceFragment]) -> Result<CloneReport> {
44 if fragments.len() > self.config.max_fragments {
45 return Err(CloneError::CapacityExceeded {
46 resource: "input fragments",
47 limit: self.config.max_fragments,
48 });
49 }
50 validate_fragments(fragments)?;
51 let mut order = (0..fragments.len()).collect::<Vec<_>>();
52 order.sort_unstable_by(|left, right| {
53 let left = &fragments[*left];
54 let right = &fragments[*right];
55 (&left.path, left.span, &left.id).cmp(&(&right.path, right.span, &right.id))
56 });
57
58 let mut interner = Interner::default();
59 let mut prepared = Vec::with_capacity(fragments.len());
60 let mut statistics = CloneStatistics {
61 input_fragments: fragments.len(),
62 ..CloneStatistics::default()
63 };
64 for index in order {
65 let fragment = &fragments[index];
66 let tokens = tokenize(
67 &fragment.text,
68 fragment.language,
69 self.config,
70 &mut interner,
71 )?;
72 if tokens.strict.len() < self.config.min_tokens {
73 statistics.skipped_small_fragments += 1;
74 continue;
75 }
76 statistics.tokens = statistics.tokens.saturating_add(tokens.strict.len());
77 let fingerprint_tokens = if self.config.mode == DetectionMode::Exact {
78 &tokens.strict
79 } else {
80 &tokens.renamed
81 };
82 let fingerprints = winnow(
83 fingerprint_tokens,
84 self.config.k_gram,
85 self.config.winnowing_window,
86 );
87 statistics.fingerprints = statistics.fingerprints.saturating_add(fingerprints.len());
88 prepared.push(Prepared {
89 source_index: index,
90 tokens,
91 fingerprints,
92 });
93 }
94 statistics.analyzed_fragments = prepared.len();
95 let fingerprint_sets = prepared
96 .iter()
97 .map(|item| item.fingerprints.clone())
98 .collect::<Vec<_>>();
99 let index = candidates(&fingerprint_sets, self.config)?;
100 statistics.candidate_pairs = index.candidates.len();
101 statistics.suppressed_buckets = index.suppressed_buckets;
102
103 let locations = prepared
104 .iter()
105 .map(|item| CloneLocation::from_fragment(&fragments[item.source_index]))
106 .collect::<Vec<_>>();
107 let mut pairs = Vec::new();
108 let mut verifier = Verifier::default();
109 for candidate in index.candidates {
110 let left = &prepared[candidate.left];
111 let right = &prepared[candidate.right];
112 let left_fragment = &fragments[left.source_index];
113 let right_fragment = &fragments[right.source_index];
114 if !self.config.compare_overlapping_fragments
115 && left_fragment.path == right_fragment.path
116 && left_fragment.span.overlaps(right_fragment.span)
117 {
118 continue;
119 }
120 let Some(match_result) = verifier.verify(
121 &left.tokens.strict,
122 &right.tokens.strict,
123 &left.tokens.renamed,
124 &right.tokens.renamed,
125 self.config,
126 ) else {
127 continue;
128 };
129 let left_location = &locations[candidate.left];
130 let right_location = &locations[candidate.right];
131 let id = pair_id(left_location, right_location);
132 pairs.push(ClonePair {
133 id,
134 left: left_location.clone(),
135 right: right_location.clone(),
136 kind: match_result.kind,
137 similarity: match_result.similarity,
138 evidence: evidence(
139 &match_result,
140 candidate.shared,
141 candidate.jaccard,
142 candidate.containment,
143 left.tokens.renamed.len().max(right.tokens.renamed.len()),
144 ),
145 });
146 }
147 let pairs = suppress_contained(pairs);
148 statistics.verified_pairs = pairs.len();
149 Ok(CloneReport {
150 families: families_for_pairs(&pairs),
151 pairs,
152 statistics,
153 })
154 }
155}
156
157struct Prepared {
158 source_index: usize,
159 tokens: Tokenized,
160 fingerprints: Vec<u64>,
161}
162
163fn validate_fragments(fragments: &[SourceFragment]) -> Result<()> {
164 let mut ids = HashSet::<&str>::with_capacity(fragments.len());
165 for fragment in fragments {
166 fragment.validate()?;
167 if !ids.insert(&fragment.id) {
168 return Err(CloneError::DuplicateFragment(fragment.id.clone()));
169 }
170 }
171 Ok(())
172}