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