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}