Skip to main content

cargo_quality/differ/
types.rs

1// SPDX-FileCopyrightText: 2025 RAprogramm <andrey.rozanov.vl@gmail.com>
2// SPDX-License-Identifier: MIT
3
4use crate::analyzer::TextEdit;
5
6/// Represents a single code change.
7///
8/// Stores the location and content of a proposed modification for display, and
9/// the underlying [`TextEdit`] so the same change can be applied through the
10/// shared fix engine.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct DiffEntry {
13    pub line:        usize,
14    pub analyzer:    String,
15    pub original:    String,
16    pub modified:    String,
17    pub description: String,
18    pub import:      Option<String>,
19    pub edit:        TextEdit
20}
21
22/// Diff results for a single file.
23///
24/// Contains all proposed changes grouped by analyzer.
25#[derive(Debug, Clone)]
26pub struct FileDiff {
27    pub path:    String,
28    pub entries: Vec<DiffEntry>
29}
30
31impl FileDiff {
32    /// Creates a new file diff result.
33    ///
34    /// # Arguments
35    ///
36    /// * `path` - File path
37    ///
38    /// # Returns
39    ///
40    /// Empty `FileDiff` structure
41    #[inline]
42    pub fn new(path: String) -> Self {
43        Self {
44            path,
45            entries: Vec::new()
46        }
47    }
48
49    /// Adds a diff entry to the file.
50    ///
51    /// # Arguments
52    ///
53    /// * `entry` - Diff entry to add
54    #[inline]
55    pub fn add_entry(&mut self, entry: DiffEntry) {
56        self.entries.push(entry);
57    }
58
59    /// Returns total number of changes.
60    ///
61    /// # Returns
62    ///
63    /// Number of diff entries
64    #[inline]
65    pub fn total_changes(&self) -> usize {
66        self.entries.len()
67    }
68}
69
70/// Complete diff results for all files.
71///
72/// Aggregates changes across multiple files.
73#[derive(Debug, Clone)]
74pub struct DiffResult {
75    pub files: Vec<FileDiff>
76}
77
78impl DiffResult {
79    /// Creates a new empty diff result.
80    ///
81    /// # Returns
82    ///
83    /// Empty `DiffResult` structure
84    #[inline]
85    pub fn new() -> Self {
86        Self {
87            files: Vec::new()
88        }
89    }
90
91    /// Adds file diff to results.
92    ///
93    /// # Arguments
94    ///
95    /// * `file_diff` - File diff to add
96    #[inline]
97    pub fn add_file(&mut self, file_diff: FileDiff) {
98        if file_diff.total_changes() > 0 {
99            self.files.push(file_diff);
100        }
101    }
102
103    /// Returns total number of changes across all files.
104    ///
105    /// # Returns
106    ///
107    /// Total change count
108    #[inline]
109    pub fn total_changes(&self) -> usize {
110        self.files.iter().map(|f| f.total_changes()).sum()
111    }
112
113    /// Returns number of files with changes.
114    ///
115    /// # Returns
116    ///
117    /// File count
118    #[inline]
119    pub fn total_files(&self) -> usize {
120        self.files.len()
121    }
122}
123
124impl Default for DiffResult {
125    fn default() -> Self {
126        Self::new()
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_diff_entry_creation() {
136        let entry = DiffEntry {
137            line:        10,
138            analyzer:    "test".to_string(),
139            original:    "old".to_string(),
140            modified:    "new".to_string(),
141            description: "desc".to_string(),
142            import:      None,
143            edit:        TextEdit::default()
144        };
145
146        assert_eq!(entry.line, 10);
147        assert_eq!(entry.analyzer, "test");
148    }
149
150    #[test]
151    fn test_file_diff_new() {
152        let diff = FileDiff::new("test.rs".to_string());
153        assert_eq!(diff.path, "test.rs");
154        assert_eq!(diff.total_changes(), 0);
155    }
156
157    #[test]
158    fn test_file_diff_add_entry() {
159        let mut diff = FileDiff::new("test.rs".to_string());
160        let entry = DiffEntry {
161            line:        1,
162            analyzer:    "test".to_string(),
163            original:    "old".to_string(),
164            modified:    "new".to_string(),
165            description: "desc".to_string(),
166            import:      None,
167            edit:        TextEdit::default()
168        };
169
170        diff.add_entry(entry);
171        assert_eq!(diff.total_changes(), 1);
172    }
173
174    #[test]
175    fn test_diff_result_new() {
176        let result = DiffResult::new();
177        assert_eq!(result.total_changes(), 0);
178        assert_eq!(result.total_files(), 0);
179    }
180
181    #[test]
182    fn test_diff_result_add_file() {
183        let mut result = DiffResult::new();
184        let mut file_diff = FileDiff::new("test.rs".to_string());
185
186        let entry = DiffEntry {
187            line:        1,
188            analyzer:    "test".to_string(),
189            original:    "old".to_string(),
190            modified:    "new".to_string(),
191            description: "desc".to_string(),
192            import:      None,
193            edit:        TextEdit::default()
194        };
195
196        file_diff.add_entry(entry);
197        result.add_file(file_diff);
198
199        assert_eq!(result.total_files(), 1);
200        assert_eq!(result.total_changes(), 1);
201    }
202
203    #[test]
204    fn test_diff_result_skip_empty_files() {
205        let mut result = DiffResult::new();
206        let file_diff = FileDiff::new("test.rs".to_string());
207        result.add_file(file_diff);
208
209        assert_eq!(result.total_files(), 0);
210    }
211
212    #[test]
213    fn test_diff_result_multiple_files() {
214        let mut result = DiffResult::new();
215
216        let mut file1 = FileDiff::new("file1.rs".to_string());
217        file1.add_entry(DiffEntry {
218            line:        1,
219            analyzer:    "test".to_string(),
220            original:    "old".to_string(),
221            modified:    "new".to_string(),
222            description: "desc".to_string(),
223            import:      None,
224            edit:        TextEdit::default()
225        });
226
227        let mut file2 = FileDiff::new("file2.rs".to_string());
228        file2.add_entry(DiffEntry {
229            line:        1,
230            analyzer:    "test".to_string(),
231            original:    "old".to_string(),
232            modified:    "new".to_string(),
233            description: "desc".to_string(),
234            import:      None,
235            edit:        TextEdit::default()
236        });
237
238        result.add_file(file1);
239        result.add_file(file2);
240
241        assert_eq!(result.total_files(), 2);
242        assert_eq!(result.total_changes(), 2);
243    }
244}