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}
16
17/// Information about a skipped file
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub struct SkippedFile {
20    /// Path to the skipped file
21    pub path: PathBuf,
22    /// Reason for exclusion
23    pub reason: ExclusionReason,
24}
25
26impl SkippedFile {
27    /// Creates a new skipped file entry
28    ///
29    /// # Arguments
30    ///
31    /// * `path` - Path to the file
32    /// * `reason` - Reason for exclusion
33    ///
34    /// # Returns
35    ///
36    /// A new SkippedFile instance
37    ///
38    /// # Examples
39    ///
40    /// ```
41    /// use std::path::PathBuf;
42    ///
43    /// use rust_diff_analyzer::types::{ExclusionReason, SkippedFile};
44    ///
45    /// let skipped = SkippedFile::new(PathBuf::from("README.md"), ExclusionReason::NonRust);
46    /// assert_eq!(skipped.path, PathBuf::from("README.md"));
47    /// ```
48    pub fn new(path: PathBuf, reason: ExclusionReason) -> Self {
49        Self { path, reason }
50    }
51}
52
53/// Scope of the analysis showing what was analyzed and what was skipped
54#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
55pub struct AnalysisScope {
56    /// Files that were analyzed
57    pub analyzed_files: Vec<PathBuf>,
58    /// Files that were skipped
59    pub skipped_files: Vec<SkippedFile>,
60    /// Patterns used for exclusion
61    pub exclusion_patterns: Vec<String>,
62}
63
64impl AnalysisScope {
65    /// Creates a new empty analysis scope
66    ///
67    /// # Returns
68    ///
69    /// A new empty AnalysisScope instance
70    ///
71    /// # Examples
72    ///
73    /// ```
74    /// use rust_diff_analyzer::types::AnalysisScope;
75    ///
76    /// let scope = AnalysisScope::new();
77    /// assert!(scope.analyzed_files.is_empty());
78    /// ```
79    pub fn new() -> Self {
80        Self::default()
81    }
82
83    /// Adds an analyzed file to the scope
84    ///
85    /// # Arguments
86    ///
87    /// * `path` - Path to the analyzed file
88    ///
89    /// # Examples
90    ///
91    /// ```
92    /// use std::path::PathBuf;
93    ///
94    /// use rust_diff_analyzer::types::AnalysisScope;
95    ///
96    /// let mut scope = AnalysisScope::new();
97    /// scope.add_analyzed(PathBuf::from("src/lib.rs"));
98    /// assert_eq!(scope.analyzed_files.len(), 1);
99    /// ```
100    pub fn add_analyzed(&mut self, path: PathBuf) {
101        self.analyzed_files.push(path);
102    }
103
104    /// Adds a skipped file to the scope
105    ///
106    /// # Arguments
107    ///
108    /// * `path` - Path to the skipped file
109    /// * `reason` - Reason for exclusion
110    ///
111    /// # Examples
112    ///
113    /// ```
114    /// use std::path::PathBuf;
115    ///
116    /// use rust_diff_analyzer::types::{AnalysisScope, ExclusionReason};
117    ///
118    /// let mut scope = AnalysisScope::new();
119    /// scope.add_skipped(
120    ///     PathBuf::from("tests/test.rs"),
121    ///     ExclusionReason::IgnorePattern("tests/".to_string()),
122    /// );
123    /// assert_eq!(scope.skipped_files.len(), 1);
124    /// ```
125    pub fn add_skipped(&mut self, path: PathBuf, reason: ExclusionReason) {
126        self.skipped_files.push(SkippedFile::new(path, reason));
127    }
128
129    /// Sets the exclusion patterns
130    ///
131    /// # Arguments
132    ///
133    /// * `patterns` - List of exclusion patterns
134    ///
135    /// # Examples
136    ///
137    /// ```
138    /// use rust_diff_analyzer::types::AnalysisScope;
139    ///
140    /// let mut scope = AnalysisScope::new();
141    /// scope.set_patterns(vec!["tests/".to_string(), "benches/".to_string()]);
142    /// assert_eq!(scope.exclusion_patterns.len(), 2);
143    /// ```
144    pub fn set_patterns(&mut self, patterns: Vec<String>) {
145        self.exclusion_patterns = patterns;
146    }
147
148    /// Returns count of non-Rust files skipped
149    ///
150    /// # Returns
151    ///
152    /// Number of non-Rust files
153    ///
154    /// # Examples
155    ///
156    /// ```
157    /// use std::path::PathBuf;
158    ///
159    /// use rust_diff_analyzer::types::{AnalysisScope, ExclusionReason};
160    ///
161    /// let mut scope = AnalysisScope::new();
162    /// scope.add_skipped(PathBuf::from("README.md"), ExclusionReason::NonRust);
163    /// scope.add_skipped(PathBuf::from("Cargo.toml"), ExclusionReason::NonRust);
164    /// assert_eq!(scope.non_rust_count(), 2);
165    /// ```
166    pub fn non_rust_count(&self) -> usize {
167        self.skipped_files
168            .iter()
169            .filter(|f| matches!(f.reason, ExclusionReason::NonRust))
170            .count()
171    }
172
173    /// Returns count of files skipped due to ignore patterns
174    ///
175    /// # Returns
176    ///
177    /// Number of ignored files
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// use std::path::PathBuf;
183    ///
184    /// use rust_diff_analyzer::types::{AnalysisScope, ExclusionReason};
185    ///
186    /// let mut scope = AnalysisScope::new();
187    /// scope.add_skipped(
188    ///     PathBuf::from("tests/test.rs"),
189    ///     ExclusionReason::IgnorePattern("tests/".to_string()),
190    /// );
191    /// assert_eq!(scope.ignored_count(), 1);
192    /// ```
193    pub fn ignored_count(&self) -> usize {
194        self.skipped_files
195            .iter()
196            .filter(|f| matches!(f.reason, ExclusionReason::IgnorePattern(_)))
197            .count()
198    }
199}