mago_codex/
diff.rs

1use ahash::HashMap;
2use ahash::HashSet;
3use serde::Deserialize;
4use serde::Serialize;
5
6use mago_database::file::FileId;
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<FileId, 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<FileId, 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<FileId, 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<FileId, 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<FileId, 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<FileId, 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: FileId,
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(mut self, source: FileId, diffs: Vec<(usize, usize, isize, isize)>) -> Self {
277        self.add_diff_map_entry(source, diffs);
278        self
279    }
280
281    /// Extends the diff hunks for a specific source file.
282    #[inline]
283    pub fn add_diffs_for_source(
284        &mut self,
285        source: FileId,
286        diffs: impl IntoIterator<Item = (usize, usize, isize, isize)>,
287    ) {
288        self.diff_map.entry(source).or_default().extend(diffs);
289    }
290
291    /// Returns a new instance with the diff hunks for the source file extended.
292    #[inline]
293    pub fn with_added_diffs_for_source(
294        mut self,
295        source: FileId,
296        diffs: impl IntoIterator<Item = (usize, usize, isize, isize)>,
297    ) -> Self {
298        self.add_diffs_for_source(source, diffs);
299        self
300    }
301
302    /// Clears the diff map.
303    #[inline]
304    pub fn unset_diff_map(&mut self) {
305        self.diff_map.clear();
306    }
307
308    /// Returns a new instance with an empty diff map.
309    #[inline]
310    pub fn without_diff_map(mut self) -> Self {
311        self.unset_diff_map();
312        self
313    }
314
315    /// Sets the deletion ranges map, replacing the existing one.
316    #[inline]
317    pub fn set_deletion_ranges_map(&mut self, map: HashMap<FileId, Vec<(usize, usize)>>) {
318        self.deletion_ranges_map = map;
319    }
320
321    /// Returns a new instance with the deletion ranges map replaced.
322    #[inline]
323    pub fn with_deletion_ranges_map(mut self, map: HashMap<FileId, Vec<(usize, usize)>>) -> Self {
324        self.set_deletion_ranges_map(map);
325        self
326    }
327
328    /// Adds or replaces the deletion ranges for a specific source file. Returns previous ranges if any.
329    #[inline]
330    pub fn add_deletion_ranges_entry(
331        &mut self,
332        source: FileId,
333        ranges: Vec<(usize, usize)>,
334    ) -> Option<Vec<(usize, usize)>> {
335        self.deletion_ranges_map.insert(source, ranges)
336    }
337
338    /// Returns a new instance with the deletion ranges for the source file added or updated.
339    #[inline]
340    pub fn with_added_deletion_ranges_entry(mut self, file: FileId, ranges: Vec<(usize, usize)>) -> Self {
341        self.add_deletion_ranges_entry(file, ranges);
342        self
343    }
344
345    /// Extends the deletion ranges for a specific source file.
346    #[inline]
347    pub fn add_deletion_ranges_for_source(&mut self, file: FileId, ranges: impl IntoIterator<Item = (usize, usize)>) {
348        self.deletion_ranges_map.entry(file).or_default().extend(ranges);
349    }
350
351    /// Returns a new instance with the deletion ranges for the source file extended.
352    #[inline]
353    pub fn with_added_deletion_ranges_for_source(
354        mut self,
355        file: FileId,
356        ranges: impl IntoIterator<Item = (usize, usize)>,
357    ) -> Self {
358        self.add_deletion_ranges_for_source(file, ranges);
359        self
360    }
361
362    /// Clears the deletion ranges map.
363    #[inline]
364    pub fn unset_deletion_ranges_map(&mut self) {
365        self.deletion_ranges_map.clear();
366    }
367
368    /// Returns a new instance with an empty deletion ranges map.
369    #[inline]
370    pub fn without_deletion_ranges_map(mut self) -> Self {
371        self.unset_deletion_ranges_map();
372        self
373    }
374}