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