mago_codex/
diff.rs

1use ahash::HashMap;
2use ahash::HashSet;
3use serde::Deserialize;
4use serde::Serialize;
5
6use mago_source::SourceIdentifier;
7
8use crate::symbol::SymbolIdentifier;
9
10/// Represents the differences between two states of a codebase, typically used for incremental analysis.
11///
12/// It tracks symbols/members to keep, those whose signatures changed but bodies might be reusable,
13/// added/deleted symbols/members, and detailed text diff/deletion ranges per file.
14/// Provides a comprehensive API for modification and querying following established conventions.
15#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
16pub struct CodebaseDiff {
17    /// Set of `(Symbol, Member)` pairs whose definition and signature are unchanged and can be kept as is.
18    /// Member is empty for top-level symbols.
19    keep: HashSet<SymbolIdentifier>,
20
21    /// Set of `(Symbol, Member)` pairs whose signature (e.g., parameter types, return type)
22    /// is unchanged, allowing potential reuse of inferred body information, even if the body itself changed.
23    /// Member is empty for top-level symbols.
24    keep_signature: HashSet<SymbolIdentifier>,
25
26    /// Set of `(Symbol, Member)` pairs that were either added or deleted entirely between states.
27    /// Member is empty for top-level symbols.
28    add_or_delete: HashSet<SymbolIdentifier>,
29
30    /// Map from source file identifier to a vector of text diff hunks.
31    /// Each tuple typically represents `(old_start, old_len, new_start, new_len)` line info for a change.
32    /// (Exact tuple meaning depends on the diffing library used).
33    diff_map: HashMap<SourceIdentifier, Vec<(usize, usize, isize, isize)>>,
34
35    /// Map from source file identifier to a vector of deleted line ranges `(start_line, end_line)`.
36    deletion_ranges_map: HashMap<SourceIdentifier, Vec<(usize, usize)>>,
37}
38
39impl CodebaseDiff {
40    #[inline]
41    pub fn new() -> Self {
42        Self::default()
43    }
44
45    /// Merges changes from another `CodebaseDiff` into this one.
46    #[inline]
47    pub fn extend(&mut self, other: Self) {
48        self.keep.extend(other.keep);
49        self.keep_signature.extend(other.keep_signature);
50        self.add_or_delete.extend(other.add_or_delete);
51        for (source, diffs) in other.diff_map {
52            self.diff_map.entry(source).or_default().extend(diffs);
53        }
54        for (source, ranges) in other.deletion_ranges_map {
55            self.deletion_ranges_map.entry(source).or_default().extend(ranges);
56        }
57    }
58
59    /// Returns a reference to the set of symbols/members to keep unchanged.
60    #[inline]
61    pub fn get_keep(&self) -> &HashSet<SymbolIdentifier> {
62        &self.keep
63    }
64
65    /// Returns a reference to the set of symbols/members whose signatures can be kept.
66    #[inline]
67    pub fn get_keep_signature(&self) -> &HashSet<SymbolIdentifier> {
68        &self.keep_signature
69    }
70
71    /// Returns a reference to the set of added or deleted symbols/members.
72    #[inline]
73    pub fn get_add_or_delete(&self) -> &HashSet<SymbolIdentifier> {
74        &self.add_or_delete
75    }
76
77    /// Returns a reference to the map of source files to text diff hunks.
78    #[inline]
79    pub fn get_diff_map(&self) -> &HashMap<SourceIdentifier, Vec<(usize, usize, isize, isize)>> {
80        &self.diff_map
81    }
82
83    /// Returns a reference to the map of source files to deletion ranges.
84    #[inline]
85    pub fn get_deletion_ranges_map(&self) -> &HashMap<SourceIdentifier, Vec<(usize, usize)>> {
86        &self.deletion_ranges_map
87    }
88
89    /// Sets the 'keep' set, replacing the existing one.
90    #[inline]
91    pub fn set_keep(&mut self, keep_set: impl IntoIterator<Item = SymbolIdentifier>) {
92        self.keep = keep_set.into_iter().collect();
93    }
94
95    /// Returns a new instance with the 'keep' set replaced.
96    #[inline]
97    pub fn with_keep(mut self, keep_set: impl IntoIterator<Item = SymbolIdentifier>) -> Self {
98        self.set_keep(keep_set);
99        self
100    }
101
102    /// Adds a single entry to the 'keep' set. Returns `true` if the entry was not already present.
103    #[inline]
104    pub fn add_keep_entry(&mut self, entry: SymbolIdentifier) -> bool {
105        self.keep.insert(entry)
106    }
107
108    /// Returns a new instance with the entry added to the 'keep' set.
109    #[inline]
110    pub fn with_added_keep_entry(mut self, entry: SymbolIdentifier) -> Self {
111        self.add_keep_entry(entry);
112        self
113    }
114
115    /// Adds multiple entries to the 'keep' set.
116    #[inline]
117    pub fn add_keep_entries(&mut self, entries: impl IntoIterator<Item = SymbolIdentifier>) {
118        self.keep.extend(entries);
119    }
120
121    /// Returns a new instance with multiple entries added to the 'keep' set.
122    #[inline]
123    pub fn with_added_keep_entries(mut self, entries: impl IntoIterator<Item = SymbolIdentifier>) -> Self {
124        self.add_keep_entries(entries);
125        self
126    }
127
128    /// Clears the 'keep' set.
129    #[inline]
130    pub fn unset_keep(&mut self) {
131        self.keep.clear();
132    }
133
134    /// Returns a new instance with an empty 'keep' set.
135    #[inline]
136    pub fn without_keep(mut self) -> Self {
137        self.unset_keep();
138        self
139    }
140
141    /// Sets the 'keep_signature' set, replacing the existing one.
142    #[inline]
143    pub fn set_keep_signature(&mut self, keep_set: impl IntoIterator<Item = SymbolIdentifier>) {
144        self.keep_signature = keep_set.into_iter().collect();
145    }
146
147    /// Returns a new instance with the 'keep_signature' set replaced.
148    #[inline]
149    pub fn with_keep_signature(mut self, keep_set: impl IntoIterator<Item = SymbolIdentifier>) -> Self {
150        self.set_keep_signature(keep_set);
151        self
152    }
153
154    /// Adds a single entry to the 'keep_signature' set. Returns `true` if the entry was not already present.
155    #[inline]
156    pub fn add_keep_signature_entry(&mut self, entry: SymbolIdentifier) -> bool {
157        self.keep_signature.insert(entry)
158    }
159
160    /// Returns a new instance with the entry added to the 'keep_signature' set.
161    #[inline]
162    pub fn with_added_keep_signature_entry(mut self, entry: SymbolIdentifier) -> Self {
163        self.add_keep_signature_entry(entry);
164        self
165    }
166
167    /// Adds multiple entries to the 'keep_signature' set.
168    #[inline]
169    pub fn add_keep_signature_entries(&mut self, entries: impl IntoIterator<Item = SymbolIdentifier>) {
170        self.keep_signature.extend(entries);
171    }
172
173    /// Returns a new instance with multiple entries added to the 'keep_signature' set.
174    #[inline]
175    pub fn with_added_keep_signature_entries(mut self, entries: impl IntoIterator<Item = SymbolIdentifier>) -> Self {
176        self.add_keep_signature_entries(entries);
177        self
178    }
179
180    /// Clears the 'keep_signature' set.
181    #[inline]
182    pub fn unset_keep_signature(&mut self) {
183        self.keep_signature.clear();
184    }
185
186    /// Returns a new instance with an empty 'keep_signature' set.
187    #[inline]
188    pub fn without_keep_signature(mut self) -> Self {
189        self.unset_keep_signature();
190        self
191    }
192
193    /// Sets the 'add_or_delete' set, replacing the existing one.
194    #[inline]
195    pub fn set_add_or_delete(&mut self, change_set: impl IntoIterator<Item = SymbolIdentifier>) {
196        self.add_or_delete = change_set.into_iter().collect();
197    }
198
199    /// Returns a new instance with the 'add_or_delete' set replaced.
200    #[inline]
201    pub fn with_add_or_delete(mut self, change_set: impl IntoIterator<Item = SymbolIdentifier>) -> Self {
202        self.set_add_or_delete(change_set);
203        self
204    }
205
206    /// Adds a single entry to the 'add_or_delete' set. Returns `true` if the entry was not already present.
207    #[inline]
208    pub fn add_add_or_delete_entry(&mut self, entry: SymbolIdentifier) -> bool {
209        self.add_or_delete.insert(entry)
210    }
211
212    /// Checks if the 'add_or_delete' set contains a specific entry.
213    #[inline]
214    pub fn contains_add_or_delete_entry(&self, entry: &SymbolIdentifier) -> bool {
215        self.add_or_delete.contains(entry)
216    }
217
218    /// Returns a new instance with the entry added to the 'add_or_delete' set.
219    #[inline]
220    pub fn with_added_add_or_delete_entry(mut self, entry: SymbolIdentifier) -> Self {
221        self.add_add_or_delete_entry(entry);
222        self
223    }
224
225    /// Adds multiple entries to the 'add_or_delete' set.
226    #[inline]
227    pub fn add_add_or_delete_entries(&mut self, entries: impl IntoIterator<Item = SymbolIdentifier>) {
228        self.add_or_delete.extend(entries);
229    }
230
231    /// Returns a new instance with multiple entries added to the 'add_or_delete' set.
232    #[inline]
233    pub fn with_added_add_or_delete_entries(mut self, entries: impl IntoIterator<Item = SymbolIdentifier>) -> Self {
234        self.add_add_or_delete_entries(entries);
235        self
236    }
237
238    /// Clears the 'add_or_delete' set.
239    #[inline]
240    pub fn unset_add_or_delete(&mut self) {
241        self.add_or_delete.clear();
242    }
243
244    /// Returns a new instance with an empty 'add_or_delete' set.
245    #[inline]
246    pub fn without_add_or_delete(mut self) -> Self {
247        self.unset_add_or_delete();
248        self
249    }
250
251    /// Sets the diff map, replacing the existing one.
252    #[inline]
253    pub fn set_diff_map(&mut self, map: HashMap<SourceIdentifier, Vec<(usize, usize, isize, isize)>>) {
254        self.diff_map = map;
255    }
256
257    /// Returns a new instance with the diff map replaced.
258    #[inline]
259    pub fn with_diff_map(mut self, map: HashMap<SourceIdentifier, Vec<(usize, usize, isize, isize)>>) -> Self {
260        self.set_diff_map(map);
261        self
262    }
263
264    /// Adds or replaces the diff hunks for a specific source file. Returns previous hunks if any.
265    #[inline]
266    pub fn add_diff_map_entry(
267        &mut self,
268        source: SourceIdentifier,
269        diffs: Vec<(usize, usize, isize, isize)>,
270    ) -> Option<Vec<(usize, usize, isize, isize)>> {
271        self.diff_map.insert(source, diffs)
272    }
273
274    /// Returns a new instance with the diff hunks for the source file added or updated.
275    #[inline]
276    pub fn with_added_diff_map_entry(
277        mut self,
278        source: SourceIdentifier,
279        diffs: Vec<(usize, usize, isize, isize)>,
280    ) -> Self {
281        self.add_diff_map_entry(source, diffs);
282        self
283    }
284
285    /// Extends the diff hunks for a specific source file.
286    #[inline]
287    pub fn add_diffs_for_source(
288        &mut self,
289        source: SourceIdentifier,
290        diffs: impl IntoIterator<Item = (usize, usize, isize, isize)>,
291    ) {
292        self.diff_map.entry(source).or_default().extend(diffs);
293    }
294
295    /// Returns a new instance with the diff hunks for the source file extended.
296    #[inline]
297    pub fn with_added_diffs_for_source(
298        mut self,
299        source: SourceIdentifier,
300        diffs: impl IntoIterator<Item = (usize, usize, isize, isize)>,
301    ) -> Self {
302        self.add_diffs_for_source(source, diffs);
303        self
304    }
305
306    /// Clears the diff map.
307    #[inline]
308    pub fn unset_diff_map(&mut self) {
309        self.diff_map.clear();
310    }
311
312    /// Returns a new instance with an empty diff map.
313    #[inline]
314    pub fn without_diff_map(mut self) -> Self {
315        self.unset_diff_map();
316        self
317    }
318
319    /// Sets the deletion ranges map, replacing the existing one.
320    #[inline]
321    pub fn set_deletion_ranges_map(&mut self, map: HashMap<SourceIdentifier, Vec<(usize, usize)>>) {
322        self.deletion_ranges_map = map;
323    }
324
325    /// Returns a new instance with the deletion ranges map replaced.
326    #[inline]
327    pub fn with_deletion_ranges_map(mut self, map: HashMap<SourceIdentifier, Vec<(usize, usize)>>) -> Self {
328        self.set_deletion_ranges_map(map);
329        self
330    }
331
332    /// Adds or replaces the deletion ranges for a specific source file. Returns previous ranges if any.
333    #[inline]
334    pub fn add_deletion_ranges_entry(
335        &mut self,
336        source: SourceIdentifier,
337        ranges: Vec<(usize, usize)>,
338    ) -> Option<Vec<(usize, usize)>> {
339        self.deletion_ranges_map.insert(source, ranges)
340    }
341
342    /// Returns a new instance with the deletion ranges for the source file added or updated.
343    #[inline]
344    pub fn with_added_deletion_ranges_entry(mut self, source: SourceIdentifier, ranges: Vec<(usize, usize)>) -> Self {
345        self.add_deletion_ranges_entry(source, ranges);
346        self
347    }
348
349    /// Extends the deletion ranges for a specific source file.
350    #[inline]
351    pub fn add_deletion_ranges_for_source(
352        &mut self,
353        source: SourceIdentifier,
354        ranges: impl IntoIterator<Item = (usize, usize)>,
355    ) {
356        self.deletion_ranges_map.entry(source).or_default().extend(ranges);
357    }
358
359    /// Returns a new instance with the deletion ranges for the source file extended.
360    #[inline]
361    pub fn with_added_deletion_ranges_for_source(
362        mut self,
363        source: SourceIdentifier,
364        ranges: impl IntoIterator<Item = (usize, usize)>,
365    ) -> Self {
366        self.add_deletion_ranges_for_source(source, ranges);
367        self
368    }
369
370    /// Clears the deletion ranges map.
371    #[inline]
372    pub fn unset_deletion_ranges_map(&mut self) {
373        self.deletion_ranges_map.clear();
374    }
375
376    /// Returns a new instance with an empty deletion ranges map.
377    #[inline]
378    pub fn without_deletion_ranges_map(mut self) -> Self {
379        self.unset_deletion_ranges_map();
380        self
381    }
382}