1pub 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#[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#[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 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 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#[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#[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#[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#[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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct RmaConfig {
202 #[serde(default)]
204 pub exclude_patterns: Vec<String>,
205
206 #[serde(default)]
208 pub languages: Vec<Language>,
209
210 #[serde(default = "default_min_severity")]
212 pub min_severity: Severity,
213
214 #[serde(default = "default_max_file_size")]
216 pub max_file_size: usize,
217
218 #[serde(default)]
220 pub parallelism: usize,
221
222 #[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 }
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}