Skip to main content

rust_diff_analyzer/types/
scope.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
8/// Reason why a file was excluded from analysis
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub enum ExclusionReason {
11    /// File is not a Rust source file
12    NonRust,
13    /// File matches an ignore pattern
14    IgnorePattern(String),
15    /// File was deleted in the diff
16    Deleted,
17    /// File could not be read
18    ReadError(String),
19    /// File could not be parsed as Rust source
20    ParseError(String),
21}
22
23/// Information about a skipped file
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25pub struct SkippedFile {
26    /// Path to the skipped file
27    pub path: PathBuf,
28    /// Reason for exclusion
29    pub reason: ExclusionReason,
30}
31
32impl SkippedFile {
33    /// Creates a new skipped file entry
34    ///
35    /// # Arguments
36    ///
37    /// * `path` - Path to the file
38    /// * `reason` - Reason for exclusion
39    ///
40    /// # Returns
41    ///
42    /// A new SkippedFile instance
43    ///
44    /// # Examples
45    ///
46    /// ```
47    /// use std::path::PathBuf;
48    ///
49    /// use rust_diff_analyzer::types::{ExclusionReason, SkippedFile};
50    ///
51    /// let skipped = SkippedFile::new(PathBuf::from("README.md"), ExclusionReason::NonRust);
52    /// assert_eq!(skipped.path, PathBuf::from("README.md"));
53    /// ```
54    pub fn new(path: PathBuf, reason: ExclusionReason) -> Self {
55        Self { path, reason }
56    }
57}
58
59/// Scope of the analysis showing what was analyzed and what was skipped
60#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
61pub struct AnalysisScope {
62    /// Files that were analyzed
63    pub analyzed_files: Vec<PathBuf>,
64    /// Files that were skipped
65    pub skipped_files: Vec<SkippedFile>,
66    /// Patterns used for exclusion
67    pub exclusion_patterns: Vec<String>,
68}
69
70impl AnalysisScope {
71    /// Creates a new empty analysis scope
72    ///
73    /// # Returns
74    ///
75    /// A new empty AnalysisScope instance
76    ///
77    /// # Examples
78    ///
79    /// ```
80    /// use rust_diff_analyzer::types::AnalysisScope;
81    ///
82    /// let scope = AnalysisScope::new();
83    /// assert!(scope.analyzed_files.is_empty());
84    /// ```
85    pub fn new() -> Self {
86        Self::default()
87    }
88
89    /// Adds an analyzed file to the scope
90    ///
91    /// # Arguments
92    ///
93    /// * `path` - Path to the analyzed file
94    ///
95    /// # Examples
96    ///
97    /// ```
98    /// use std::path::PathBuf;
99    ///
100    /// use rust_diff_analyzer::types::AnalysisScope;
101    ///
102    /// let mut scope = AnalysisScope::new();
103    /// scope.add_analyzed(PathBuf::from("src/lib.rs"));
104    /// assert_eq!(scope.analyzed_files.len(), 1);
105    /// ```
106    pub fn add_analyzed(&mut self, path: PathBuf) {
107        self.analyzed_files.push(path);
108    }
109
110    /// Adds a skipped file to the scope
111    ///
112    /// # Arguments
113    ///
114    /// * `path` - Path to the skipped file
115    /// * `reason` - Reason for exclusion
116    ///
117    /// # Examples
118    ///
119    /// ```
120    /// use std::path::PathBuf;
121    ///
122    /// use rust_diff_analyzer::types::{AnalysisScope, ExclusionReason};
123    ///
124    /// let mut scope = AnalysisScope::new();
125    /// scope.add_skipped(
126    ///     PathBuf::from("tests/test.rs"),
127    ///     ExclusionReason::IgnorePattern("tests/".to_string()),
128    /// );
129    /// assert_eq!(scope.skipped_files.len(), 1);
130    /// ```
131    pub fn add_skipped(&mut self, path: PathBuf, reason: ExclusionReason) {
132        self.skipped_files.push(SkippedFile::new(path, reason));
133    }
134
135    /// Sets the exclusion patterns
136    ///
137    /// # Arguments
138    ///
139    /// * `patterns` - List of exclusion patterns
140    ///
141    /// # Examples
142    ///
143    /// ```
144    /// use rust_diff_analyzer::types::AnalysisScope;
145    ///
146    /// let mut scope = AnalysisScope::new();
147    /// scope.set_patterns(vec!["tests/".to_string(), "benches/".to_string()]);
148    /// assert_eq!(scope.exclusion_patterns.len(), 2);
149    /// ```
150    pub fn set_patterns(&mut self, patterns: Vec<String>) {
151        self.exclusion_patterns = patterns;
152    }
153
154    /// Returns count of non-Rust files skipped
155    ///
156    /// # Returns
157    ///
158    /// Number of non-Rust files
159    ///
160    /// # Examples
161    ///
162    /// ```
163    /// use std::path::PathBuf;
164    ///
165    /// use rust_diff_analyzer::types::{AnalysisScope, ExclusionReason};
166    ///
167    /// let mut scope = AnalysisScope::new();
168    /// scope.add_skipped(PathBuf::from("README.md"), ExclusionReason::NonRust);
169    /// scope.add_skipped(PathBuf::from("Cargo.toml"), ExclusionReason::NonRust);
170    /// assert_eq!(scope.non_rust_count(), 2);
171    /// ```
172    pub fn non_rust_count(&self) -> usize {
173        self.skipped_files
174            .iter()
175            .filter(|f| matches!(f.reason, ExclusionReason::NonRust))
176            .count()
177    }
178
179    /// Returns count of files skipped due to ignore patterns
180    ///
181    /// # Returns
182    ///
183    /// Number of ignored files
184    ///
185    /// # Examples
186    ///
187    /// ```
188    /// use std::path::PathBuf;
189    ///
190    /// use rust_diff_analyzer::types::{AnalysisScope, ExclusionReason};
191    ///
192    /// let mut scope = AnalysisScope::new();
193    /// scope.add_skipped(
194    ///     PathBuf::from("tests/test.rs"),
195    ///     ExclusionReason::IgnorePattern("tests/".to_string()),
196    /// );
197    /// assert_eq!(scope.ignored_count(), 1);
198    /// ```
199    pub fn ignored_count(&self) -> usize {
200        self.skipped_files
201            .iter()
202            .filter(|f| matches!(f.reason, ExclusionReason::IgnorePattern(_)))
203            .count()
204    }
205
206    /// Returns count of files skipped because they were deleted in the diff
207    ///
208    /// # Returns
209    ///
210    /// Number of deleted files
211    ///
212    /// # Examples
213    ///
214    /// ```
215    /// use std::path::PathBuf;
216    ///
217    /// use rust_diff_analyzer::types::{AnalysisScope, ExclusionReason};
218    ///
219    /// let mut scope = AnalysisScope::new();
220    /// scope.add_skipped(PathBuf::from("src/old.rs"), ExclusionReason::Deleted);
221    /// assert_eq!(scope.deleted_count(), 1);
222    /// ```
223    pub fn deleted_count(&self) -> usize {
224        self.skipped_files
225            .iter()
226            .filter(|f| matches!(f.reason, ExclusionReason::Deleted))
227            .count()
228    }
229
230    /// Returns count of files skipped due to read or parse errors
231    ///
232    /// # Returns
233    ///
234    /// Number of files that failed to read or parse
235    ///
236    /// # Examples
237    ///
238    /// ```
239    /// use std::path::PathBuf;
240    ///
241    /// use rust_diff_analyzer::types::{AnalysisScope, ExclusionReason};
242    ///
243    /// let mut scope = AnalysisScope::new();
244    /// scope.add_skipped(
245    ///     PathBuf::from("src/gone.rs"),
246    ///     ExclusionReason::ReadError("not found".to_string()),
247    /// );
248    /// assert_eq!(scope.error_count(), 1);
249    /// ```
250    pub fn error_count(&self) -> usize {
251        self.skipped_files
252            .iter()
253            .filter(|f| {
254                matches!(
255                    f.reason,
256                    ExclusionReason::ReadError(_) | ExclusionReason::ParseError(_)
257                )
258            })
259            .count()
260    }
261}