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, 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}
186
187impl AnalysisResult {
188    /// Creates a new analysis result
189    ///
190    /// # Arguments
191    ///
192    /// * `changes` - List of changes
193    /// * `summary` - Aggregated summary
194    ///
195    /// # Returns
196    ///
197    /// A new AnalysisResult instance
198    ///
199    /// # Examples
200    ///
201    /// ```
202    /// use rust_diff_analyzer::types::{AnalysisResult, Summary};
203    ///
204    /// let result = AnalysisResult::new(vec![], Summary::default());
205    /// assert!(result.changes.is_empty());
206    /// ```
207    pub fn new(changes: Vec<Change>, summary: Summary) -> Self {
208        Self { changes, summary }
209    }
210
211    /// Returns only production changes
212    ///
213    /// # Returns
214    ///
215    /// Iterator over production changes
216    ///
217    /// # Examples
218    ///
219    /// ```
220    /// use rust_diff_analyzer::types::{AnalysisResult, Summary};
221    ///
222    /// let result = AnalysisResult::new(vec![], Summary::default());
223    /// assert_eq!(result.production_changes().count(), 0);
224    /// ```
225    pub fn production_changes(&self) -> impl Iterator<Item = &Change> {
226        self.changes
227            .iter()
228            .filter(|c| c.classification.is_production())
229    }
230
231    /// Returns only test-related changes
232    ///
233    /// # Returns
234    ///
235    /// Iterator over test-related changes
236    ///
237    /// # Examples
238    ///
239    /// ```
240    /// use rust_diff_analyzer::types::{AnalysisResult, Summary};
241    ///
242    /// let result = AnalysisResult::new(vec![], Summary::default());
243    /// assert_eq!(result.test_changes().count(), 0);
244    /// ```
245    pub fn test_changes(&self) -> impl Iterator<Item = &Change> {
246        self.changes
247            .iter()
248            .filter(|c| c.classification.is_test_related())
249    }
250}