Skip to main content

react_auditor/
config.rs

1use std::collections::HashMap;
2use std::path::Path;
3
4use serde::Deserialize;
5
6#[derive(Debug, Clone, Deserialize)]
7pub struct Config {
8    #[serde(default)]
9    pub rules: HashMap<String, String>,
10
11    #[serde(default)]
12    pub log: Option<String>,
13
14    #[serde(default = "default_format")]
15    #[allow(dead_code)]
16    pub format: String,
17
18    #[serde(default)]
19    pub max_warnings: Option<u32>,
20
21    /// Multi-root workspace patterns (monorepo support).
22    /// e.g. ["packages/*", "apps/*"]
23    #[serde(default)]
24    pub workspaces: Vec<String>,
25
26    /// Per-file-type rule overrides.
27    /// Key is file extension (e.g. "jsx"), value is rule_id → severity.
28    #[serde(default)]
29    pub file_types: HashMap<String, HashMap<String, String>>,
30}
31
32fn default_format() -> String {
33    "stylish".to_string()
34}
35
36impl Default for Config {
37    fn default() -> Self {
38        Self {
39            rules: HashMap::new(),
40            log: None,
41            format: "stylish".to_string(),
42            max_warnings: None,
43            workspaces: Vec::new(),
44            file_types: HashMap::new(),
45        }
46    }
47}
48
49impl Config {
50    pub fn load(path: Option<&Path>) -> anyhow::Result<Self> {
51        let mut config = Self::default();
52
53        if let Some(config_path) = path {
54            if config_path.exists() {
55                let content = std::fs::read_to_string(config_path)?;
56                match config_path.extension().and_then(|e| e.to_str()) {
57                    Some("toml") => config = toml::from_str(&content)?,
58                    Some("json") | Some("jsonc") => config = serde_json::from_str(&content)?,
59                    _ => config = serde_json::from_str(&content)?,
60                }
61            }
62        } else {
63            // Try standard config file paths
64            let candidates = [".rauditrc.toml", ".rauditrc.json", ".rauditrc"];
65
66            for candidate in &candidates {
67                let p = Path::new(candidate);
68                if p.exists() {
69                    config = Self::load(Some(p))?;
70                    break;
71                }
72            }
73
74            // Try package.json
75            let pkg_path = Path::new("package.json");
76            if pkg_path.exists()
77                && let Ok(content) = std::fs::read_to_string(pkg_path)
78                && let Ok(pkg) = serde_json::from_str::<PackageJson>(&content)
79                && let Some(auditor) = pkg.react_auditor
80            {
81                config.rules.extend(auditor.rules);
82                config.log = config.log.or(auditor.log);
83                config.max_warnings = config.max_warnings.or(auditor.max_warnings);
84            }
85        }
86
87        Ok(config)
88    }
89}
90
91#[derive(Debug, Deserialize)]
92struct PackageJson {
93    #[serde(rename = "reactAuditor")]
94    react_auditor: Option<PackageConfig>,
95}
96
97#[derive(Debug, Deserialize)]
98struct PackageConfig {
99    #[serde(default)]
100    rules: HashMap<String, String>,
101    #[serde(default)]
102    log: Option<String>,
103    #[serde(default)]
104    max_warnings: Option<u32>,
105}