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}