rust_diff_analyzer/types/change.rs
1// SPDX-FileCopyrightText: 2025 RAprogramm <andrey.rozanov.vl@gmail.com>
2// SPDX-License-Identifier: MIT
3
4use std::path::PathBuf;
5
6use serde::{Deserialize, Serialize};
7
8use super::{classification::CodeType, scope::AnalysisScope, semantic_unit::SemanticUnit};
9
10/// A change to a semantic unit
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct Change {
13 /// Path to the file containing the change
14 pub file_path: PathBuf,
15 /// The semantic unit that was changed
16 pub unit: SemanticUnit,
17 /// Classification of the code
18 pub classification: CodeType,
19 /// Number of lines added
20 pub lines_added: usize,
21 /// Number of lines removed
22 pub lines_removed: usize,
23}
24
25impl Change {
26 /// Creates a new change
27 ///
28 /// # Arguments
29 ///
30 /// * `file_path` - Path to the file
31 /// * `unit` - The semantic unit that was changed
32 /// * `classification` - Classification of the code
33 /// * `lines_added` - Number of lines added
34 /// * `lines_removed` - Number of lines removed
35 ///
36 /// # Returns
37 ///
38 /// A new Change instance
39 ///
40 /// # Examples
41 ///
42 /// ```
43 /// use std::path::PathBuf;
44 ///
45 /// use rust_diff_analyzer::types::{
46 /// Change, CodeType, LineSpan, SemanticUnit, SemanticUnitKind, Visibility,
47 /// };
48 ///
49 /// let unit = SemanticUnit::new(
50 /// SemanticUnitKind::Function,
51 /// "parse".to_string(),
52 /// Visibility::Public,
53 /// LineSpan::new(10, 30),
54 /// vec![],
55 /// );
56 ///
57 /// let change = Change::new(
58 /// PathBuf::from("src/parser.rs"),
59 /// unit,
60 /// CodeType::Production,
61 /// 10,
62 /// 5,
63 /// );
64 ///
65 /// assert_eq!(change.lines_added, 10);
66 /// ```
67 pub fn new(
68 file_path: PathBuf,
69 unit: SemanticUnit,
70 classification: CodeType,
71 lines_added: usize,
72 lines_removed: usize,
73 ) -> Self {
74 Self {
75 file_path,
76 unit,
77 classification,
78 lines_added,
79 lines_removed,
80 }
81 }
82
83 /// Returns total lines changed (added + removed)
84 ///
85 /// # Returns
86 ///
87 /// Sum of lines added and removed
88 ///
89 /// # Examples
90 ///
91 /// ```
92 /// use std::path::PathBuf;
93 ///
94 /// use rust_diff_analyzer::types::{
95 /// Change, CodeType, LineSpan, SemanticUnit, SemanticUnitKind, Visibility,
96 /// };
97 ///
98 /// let unit = SemanticUnit::new(
99 /// SemanticUnitKind::Function,
100 /// "parse".to_string(),
101 /// Visibility::Public,
102 /// LineSpan::new(10, 30),
103 /// vec![],
104 /// );
105 ///
106 /// let change = Change::new(
107 /// PathBuf::from("src/parser.rs"),
108 /// unit,
109 /// CodeType::Production,
110 /// 10,
111 /// 5,
112 /// );
113 ///
114 /// assert_eq!(change.total_lines(), 15);
115 /// ```
116 pub fn total_lines(&self) -> usize {
117 self.lines_added + self.lines_removed
118 }
119}
120
121/// Summary of analysis results
122#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
123pub struct Summary {
124 /// Number of production functions changed
125 pub prod_functions: usize,
126 /// Number of production structs changed
127 pub prod_structs: usize,
128 /// Number of other production units changed
129 pub prod_other: usize,
130 /// Total number of test-related units changed
131 pub test_units: usize,
132 /// Lines added in production code
133 pub prod_lines_added: usize,
134 /// Lines removed from production code
135 pub prod_lines_removed: usize,
136 /// Lines added in test code
137 pub test_lines_added: usize,
138 /// Lines removed from test code
139 pub test_lines_removed: usize,
140 /// Weighted score based on configuration
141 pub weighted_score: usize,
142 /// Whether any limit was exceeded
143 pub exceeds_limit: bool,
144}
145
146impl Summary {
147 /// Returns total number of production units changed
148 ///
149 /// # Returns
150 ///
151 /// Sum of all production unit counts
152 ///
153 /// # Examples
154 ///
155 /// ```
156 /// use rust_diff_analyzer::types::Summary;
157 ///
158 /// let summary = Summary {
159 /// prod_functions: 5,
160 /// prod_structs: 2,
161 /// prod_other: 1,
162 /// test_units: 10,
163 /// prod_lines_added: 50,
164 /// prod_lines_removed: 20,
165 /// test_lines_added: 100,
166 /// test_lines_removed: 30,
167 /// weighted_score: 0,
168 /// exceeds_limit: false,
169 /// };
170 ///
171 /// assert_eq!(summary.total_prod_units(), 8);
172 /// ```
173 pub fn total_prod_units(&self) -> usize {
174 self.prod_functions + self.prod_structs + self.prod_other
175 }
176}
177
178/// Complete analysis result
179#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
180pub struct AnalysisResult {
181 /// List of all changes
182 pub changes: Vec<Change>,
183 /// Aggregated summary
184 pub summary: Summary,
185 /// Analysis scope information
186 pub scope: AnalysisScope,
187}
188
189impl AnalysisResult {
190 /// Creates a new analysis result
191 ///
192 /// # Arguments
193 ///
194 /// * `changes` - List of changes
195 /// * `summary` - Aggregated summary
196 /// * `scope` - Analysis scope information
197 ///
198 /// # Returns
199 ///
200 /// A new AnalysisResult instance
201 ///
202 /// # Examples
203 ///
204 /// ```
205 /// use rust_diff_analyzer::types::{AnalysisResult, AnalysisScope, Summary};
206 ///
207 /// let result = AnalysisResult::new(vec![], Summary::default(), AnalysisScope::new());
208 /// assert!(result.changes.is_empty());
209 /// ```
210 pub fn new(changes: Vec<Change>, summary: Summary, scope: AnalysisScope) -> Self {
211 Self {
212 changes,
213 summary,
214 scope,
215 }
216 }
217
218 /// Returns only production changes
219 ///
220 /// # Returns
221 ///
222 /// Iterator over production changes
223 ///
224 /// # Examples
225 ///
226 /// ```
227 /// use rust_diff_analyzer::types::{AnalysisResult, AnalysisScope, Summary};
228 ///
229 /// let result = AnalysisResult::new(vec![], Summary::default(), AnalysisScope::new());
230 /// assert_eq!(result.production_changes().count(), 0);
231 /// ```
232 pub fn production_changes(&self) -> impl Iterator<Item = &Change> {
233 self.changes
234 .iter()
235 .filter(|c| c.classification.is_production())
236 }
237
238 /// Returns only test-related changes
239 ///
240 /// # Returns
241 ///
242 /// Iterator over test-related changes
243 ///
244 /// # Examples
245 ///
246 /// ```
247 /// use rust_diff_analyzer::types::{AnalysisResult, AnalysisScope, Summary};
248 ///
249 /// let result = AnalysisResult::new(vec![], Summary::default(), AnalysisScope::new());
250 /// assert_eq!(result.test_changes().count(), 0);
251 /// ```
252 pub fn test_changes(&self) -> impl Iterator<Item = &Change> {
253 self.changes
254 .iter()
255 .filter(|c| c.classification.is_test_related())
256 }
257}