orlint/
config.rs

1// Configuration handler for Orbit Analyzer
2// Parses and manages the .orlint.toml configuration file
3
4use crate::reporter::Severity;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::fs;
8use std::path::Path;
9
10/// Configuration for the Orbit Analyzer
11#[derive(Debug, Serialize, Deserialize, Clone, Default)]
12pub struct Config {
13    /// Analyzer settings
14    #[serde(default)]
15    pub analyzer: AnalyzerSettings,
16
17    /// Rules configuration
18    #[serde(default)]
19    pub rules: RulesConfig,
20
21    /// Reporter configuration
22    #[serde(default)]
23    pub reporter: ReporterConfig,
24
25    /// Renderer analysis settings
26    #[serde(default)]
27    pub renderer_analysis: RendererAnalysisConfig,
28
29    /// Rule severity levels (flattened into rules.rule_severity during deserialization)
30    #[serde(default, rename = "rule_severity")]
31    rule_severity_tmp: HashMap<String, Severity>,
32}
33
34/// Analyzer settings
35#[derive(Debug, Serialize, Deserialize, Clone)]
36pub struct AnalyzerSettings {
37    /// Whether to perform syntax checking
38    #[serde(default = "default_true")]
39    pub syntax_check: bool,
40
41    /// Whether to perform semantic analysis
42    #[serde(default = "default_true")]
43    pub semantic_analysis: bool,
44
45    /// Whether to calculate metrics
46    #[serde(default = "default_false")]
47    pub metrics_enabled: bool,
48
49    /// List of enabled rules (if empty, all rules are enabled)
50    #[serde(default)]
51    pub enabled_rules: Vec<String>,
52
53    /// List of disabled rules
54    #[serde(default)]
55    pub disabled_rules: Vec<String>,
56
57    /// Whether to run analysis in parallel
58    #[serde(default = "default_false")]
59    pub parallel: bool,
60
61    /// Whether to use incremental analysis
62    #[serde(default = "default_false")]
63    pub incremental: bool,
64}
65
66/// Rules configuration
67#[derive(Debug, Serialize, Deserialize, Clone, Default)]
68pub struct RulesConfig {
69    /// Component naming rule configuration
70    #[serde(default)]
71    pub component_naming: ComponentNamingConfig,
72
73    /// Custom severity levels for rules
74    #[serde(default)]
75    pub rule_severity: HashMap<String, Severity>,
76}
77
78/// Component naming rule configuration
79#[derive(Debug, Serialize, Deserialize, Clone)]
80pub struct ComponentNamingConfig {
81    /// Regex pattern for component names
82    #[serde(default = "default_component_pattern")]
83    pub pattern: String,
84}
85
86/// Reporter configuration
87#[derive(Debug, Serialize, Deserialize, Clone)]
88pub struct ReporterConfig {
89    /// Output format (text, json, html)
90    #[serde(default = "default_format")]
91    pub format: String,
92
93    /// Output file path (if not specified, output to stdout)
94    pub output_path: Option<String>,
95
96    /// Minimum severity level to report
97    #[serde(default)]
98    pub min_severity: Severity,
99}
100
101/// Renderer analysis configuration
102#[derive(Debug, Serialize, Deserialize, Clone)]
103pub struct RendererAnalysisConfig {
104    /// Whether renderer analysis is enabled
105    #[serde(default = "default_true")]
106    pub enabled: bool,
107
108    /// Default renderer to use for analysis
109    #[serde(default = "default_renderer")]
110    pub default_renderer: String,
111
112    /// Whether to check for renderer metadata in components
113    #[serde(default = "default_true")]
114    pub check_renderer_metadata: bool,
115}
116
117// Default function implementations
118fn default_true() -> bool {
119    true
120}
121
122fn default_false() -> bool {
123    false
124}
125
126fn default_component_pattern() -> String {
127    "^[A-Z][a-zA-Z0-9]*$".to_string()
128}
129
130fn default_format() -> String {
131    "text".to_string()
132}
133
134fn default_renderer() -> String {
135    "auto".to_string()
136}
137
138// Default implementation is now derived
139
140impl Default for AnalyzerSettings {
141    fn default() -> Self {
142        Self {
143            syntax_check: true,
144            semantic_analysis: true,
145            metrics_enabled: false,
146            enabled_rules: Vec::new(),
147            disabled_rules: Vec::new(),
148            parallel: false,
149            incremental: false,
150        }
151    }
152}
153
154// Default implementation is now derived
155
156impl Default for ComponentNamingConfig {
157    fn default() -> Self {
158        Self {
159            pattern: default_component_pattern(),
160        }
161    }
162}
163
164impl Default for ReporterConfig {
165    fn default() -> Self {
166        Self {
167            format: default_format(),
168            output_path: None,
169            min_severity: Severity::Warning,
170        }
171    }
172}
173
174impl Default for RendererAnalysisConfig {
175    fn default() -> Self {
176        Self {
177            enabled: true,
178            default_renderer: default_renderer(),
179            check_renderer_metadata: true,
180        }
181    }
182}
183
184impl Config {
185    /// Load configuration from a file
186    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
187        let content = fs::read_to_string(path)?;
188        let mut config: Config = toml::from_str(&content)?;
189
190        // Move rule severities from the temporary field to the rules config
191        if !config.rule_severity_tmp.is_empty() {
192            config.rules.rule_severity = config.rule_severity_tmp.clone();
193            config.rule_severity_tmp.clear();
194        }
195
196        Ok(config)
197    }
198
199    /// Find and load configuration from the default location
200    pub fn find_and_load() -> Result<Self, Box<dyn std::error::Error>> {
201        // Search in the current directory and parent directories
202        let mut current_dir = std::env::current_dir()?;
203
204        loop {
205            let config_path = current_dir.join(".orlint.toml");
206            if config_path.exists() {
207                return Self::from_file(config_path);
208            }
209
210            if !current_dir.pop() {
211                break;
212            }
213        }
214
215        // No config found, return default
216        Ok(Self::default())
217    }
218
219    /// Check if a rule is enabled
220    pub fn is_rule_enabled(&self, rule_name: &str) -> bool {
221        if !self.analyzer.disabled_rules.is_empty()
222            && self
223                .analyzer
224                .disabled_rules
225                .contains(&rule_name.to_string())
226        {
227            return false;
228        }
229
230        if !self.analyzer.enabled_rules.is_empty() {
231            return self.analyzer.enabled_rules.contains(&rule_name.to_string());
232        }
233
234        true
235    }
236
237    /// Get the severity level for a rule
238    pub fn get_rule_severity(&self, rule_name: &str, default_severity: Severity) -> Severity {
239        self.rules
240            .rule_severity
241            .get(rule_name)
242            .cloned()
243            .unwrap_or(default_severity)
244    }
245}