Skip to main content

rma_common/
lib.rs

1//! Common types and utilities for Rust Monorepo Analyzer (RMA)
2//!
3//! This crate provides shared data structures, error types, and utilities
4//! used across all RMA components.
5
6pub mod config;
7
8pub use config::{
9    parse_inline_suppressions, AllowConfig, AllowType, Baseline, BaselineConfig, BaselineEntry,
10    BaselineMode, ConfigLoadResult, ConfigSource, ConfigWarning, EffectiveConfig, Fingerprint,
11    InlineSuppression, Profile, ProfileThresholds, ProfilesConfig, RmaTomlConfig, RulesConfig,
12    RulesetsConfig, ScanConfig, SuppressionType, ThresholdOverride, WarningLevel,
13    CURRENT_CONFIG_VERSION,
14};
15
16use serde::{Deserialize, Serialize};
17use std::path::PathBuf;
18use thiserror::Error;
19
20/// Core error types for RMA operations
21#[derive(Error, Debug)]
22pub enum RmaError {
23    #[error("IO error: {0}")]
24    Io(#[from] std::io::Error),
25
26    #[error("Parse error in {file}: {message}")]
27    Parse { file: PathBuf, message: String },
28
29    #[error("Analysis error: {0}")]
30    Analysis(String),
31
32    #[error("Index error: {0}")]
33    Index(String),
34
35    #[error("Unsupported language: {0}")]
36    UnsupportedLanguage(String),
37
38    #[error("Configuration error: {0}")]
39    Config(String),
40}
41
42/// Supported programming languages
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
44#[serde(rename_all = "lowercase")]
45pub enum Language {
46    Rust,
47    JavaScript,
48    TypeScript,
49    Python,
50    Go,
51    Java,
52    Unknown,
53}
54
55impl Language {
56    /// Detect language from file extension
57    pub fn from_extension(ext: &str) -> Self {
58        match ext.to_lowercase().as_str() {
59            "rs" => Language::Rust,
60            "js" | "mjs" | "cjs" => Language::JavaScript,
61            "ts" | "tsx" => Language::TypeScript,
62            "py" | "pyi" => Language::Python,
63            "go" => Language::Go,
64            "java" => Language::Java,
65            _ => Language::Unknown,
66        }
67    }
68
69    /// Get file extensions for this language
70    pub fn extensions(&self) -> &'static [&'static str] {
71        match self {
72            Language::Rust => &["rs"],
73            Language::JavaScript => &["js", "mjs", "cjs"],
74            Language::TypeScript => &["ts", "tsx"],
75            Language::Python => &["py", "pyi"],
76            Language::Go => &["go"],
77            Language::Java => &["java"],
78            Language::Unknown => &[],
79        }
80    }
81}
82
83impl std::fmt::Display for Language {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        match self {
86            Language::Rust => write!(f, "rust"),
87            Language::JavaScript => write!(f, "javascript"),
88            Language::TypeScript => write!(f, "typescript"),
89            Language::Python => write!(f, "python"),
90            Language::Go => write!(f, "go"),
91            Language::Java => write!(f, "java"),
92            Language::Unknown => write!(f, "unknown"),
93        }
94    }
95}
96
97/// Severity levels for findings
98#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
99#[serde(rename_all = "lowercase")]
100pub enum Severity {
101    Info,
102    Warning,
103    Error,
104    Critical,
105}
106
107impl std::fmt::Display for Severity {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        match self {
110            Severity::Info => write!(f, "info"),
111            Severity::Warning => write!(f, "warning"),
112            Severity::Error => write!(f, "error"),
113            Severity::Critical => write!(f, "critical"),
114        }
115    }
116}
117
118/// A source code location
119#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
120pub struct SourceLocation {
121    pub file: PathBuf,
122    pub start_line: usize,
123    pub start_column: usize,
124    pub end_line: usize,
125    pub end_column: usize,
126}
127
128impl SourceLocation {
129    pub fn new(
130        file: PathBuf,
131        start_line: usize,
132        start_column: usize,
133        end_line: usize,
134        end_column: usize,
135    ) -> Self {
136        Self {
137            file,
138            start_line,
139            start_column,
140            end_line,
141            end_column,
142        }
143    }
144}
145
146impl std::fmt::Display for SourceLocation {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        write!(
149            f,
150            "{}:{}:{}-{}:{}",
151            self.file.display(),
152            self.start_line,
153            self.start_column,
154            self.end_line,
155            self.end_column
156        )
157    }
158}
159
160/// A security or code quality finding
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct Finding {
163    pub id: String,
164    pub rule_id: String,
165    pub message: String,
166    pub severity: Severity,
167    pub location: SourceLocation,
168    pub language: Language,
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub snippet: Option<String>,
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub suggestion: Option<String>,
173}
174
175/// Code metrics for a file or function
176#[derive(Debug, Clone, Default, Serialize, Deserialize)]
177pub struct CodeMetrics {
178    pub lines_of_code: usize,
179    pub lines_of_comments: usize,
180    pub blank_lines: usize,
181    pub cyclomatic_complexity: usize,
182    pub cognitive_complexity: usize,
183    pub function_count: usize,
184    pub class_count: usize,
185    pub import_count: usize,
186}
187
188/// Summary of a scan operation
189#[derive(Debug, Clone, Default, Serialize, Deserialize)]
190pub struct ScanSummary {
191    pub files_scanned: usize,
192    pub files_skipped: usize,
193    pub total_lines: usize,
194    pub findings_by_severity: std::collections::HashMap<String, usize>,
195    pub languages: std::collections::HashMap<String, usize>,
196    pub duration_ms: u64,
197}
198
199/// Configuration for RMA operations
200#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct RmaConfig {
202    /// Paths to exclude from scanning
203    #[serde(default)]
204    pub exclude_patterns: Vec<String>,
205
206    /// Languages to scan (empty = all supported)
207    #[serde(default)]
208    pub languages: Vec<Language>,
209
210    /// Minimum severity to report
211    #[serde(default = "default_min_severity")]
212    pub min_severity: Severity,
213
214    /// Maximum file size in bytes
215    #[serde(default = "default_max_file_size")]
216    pub max_file_size: usize,
217
218    /// Number of parallel workers (0 = auto)
219    #[serde(default)]
220    pub parallelism: usize,
221
222    /// Enable incremental mode
223    #[serde(default)]
224    pub incremental: bool,
225}
226
227fn default_min_severity() -> Severity {
228    Severity::Warning
229}
230
231fn default_max_file_size() -> usize {
232    10 * 1024 * 1024 // 10MB
233}
234
235impl Default for RmaConfig {
236    fn default() -> Self {
237        Self {
238            exclude_patterns: vec![
239                "**/node_modules/**".into(),
240                "**/target/**".into(),
241                "**/vendor/**".into(),
242                "**/.git/**".into(),
243                "**/dist/**".into(),
244                "**/build/**".into(),
245            ],
246            languages: vec![],
247            min_severity: default_min_severity(),
248            max_file_size: default_max_file_size(),
249            parallelism: 0,
250            incremental: false,
251        }
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258
259    #[test]
260    fn test_language_from_extension() {
261        assert_eq!(Language::from_extension("rs"), Language::Rust);
262        assert_eq!(Language::from_extension("js"), Language::JavaScript);
263        assert_eq!(Language::from_extension("py"), Language::Python);
264        assert_eq!(Language::from_extension("unknown"), Language::Unknown);
265    }
266
267    #[test]
268    fn test_severity_ordering() {
269        assert!(Severity::Info < Severity::Warning);
270        assert!(Severity::Warning < Severity::Error);
271        assert!(Severity::Error < Severity::Critical);
272    }
273
274    #[test]
275    fn test_source_location_display() {
276        let loc = SourceLocation::new(PathBuf::from("test.rs"), 10, 5, 10, 15);
277        assert_eq!(loc.to_string(), "test.rs:10:5-10:15");
278    }
279}