pub mod thresholds;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;
use thresholds::{
Thresholds, ThresholdsConfig, default_max_depth, default_max_imports, default_max_lines,
default_max_repetition, default_min_duplicate_lines,
};
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Config {
#[serde(default = "default_excludes")]
pub exclude: Vec<String>,
#[serde(default)]
pub thresholds: ThresholdsConfig,
}
fn default_excludes() -> Vec<String> {
vec![
"node_modules".to_string(),
"vendor".to_string(),
"dist".to_string(),
"target".to_string(),
"__pycache__".to_string(),
"build".to_string(),
".git".to_string(),
]
}
impl Config {
#[must_use]
pub fn load(path: &Path) -> Self {
let mut configs = Vec::new();
let mut current = if path.is_file() {
path.parent()
} else {
Some(path)
};
while let Some(p) = current {
let config_path = p.join(".swtrc");
if let Ok(content) = fs::read_to_string(config_path)
&& let Ok(config) = serde_json::from_str::<Self>(&content)
{
configs.push(config);
}
current = p.parent();
}
let mut final_config = Self::default();
for config in configs.into_iter().rev() {
final_config.merge(config);
}
final_config
}
pub fn merge(&mut self, other: Self) {
if !other.exclude.is_empty() {
self.exclude.extend(other.exclude);
}
let og = other.thresholds.global;
if og.max_lines != default_max_lines() {
self.thresholds.global.max_lines = og.max_lines;
}
if og.max_depth != default_max_depth() {
self.thresholds.global.max_depth = og.max_depth;
}
if og.max_imports != default_max_imports() {
self.thresholds.global.max_imports = og.max_imports;
}
if (og.max_repetition - default_max_repetition()).abs() > f64::EPSILON {
self.thresholds.global.max_repetition = og.max_repetition;
}
if og.min_duplicate_lines != default_min_duplicate_lines() {
self.thresholds.global.min_duplicate_lines = og.min_duplicate_lines;
}
self.thresholds.overrides.extend(other.thresholds.overrides);
}
#[must_use]
pub fn get_thresholds(&self, extension: &str) -> Thresholds {
let mut t = self.thresholds.global.clone();
if let Some(over) = self.thresholds.overrides.get(extension) {
if let Some(v) = over.max_lines {
t.max_lines = v;
}
if let Some(v) = over.max_depth {
t.max_depth = v;
}
if let Some(v) = over.max_imports {
t.max_imports = v;
}
if let Some(v) = over.max_repetition {
t.max_repetition = v;
}
if let Some(v) = over.min_duplicate_lines {
t.min_duplicate_lines = v;
}
}
t
}
#[must_use]
pub fn is_supported_file(path: &Path) -> bool {
let extension = path.extension().and_then(|s| s.to_str()).unwrap_or("");
let supported = crate::languages::LanguageRegistry::get()
.get_by_extension(extension)
.is_some();
if path.exists() {
path.is_file() && supported
} else {
supported
}
}
#[must_use]
pub fn is_excluded(&self, path: &Path) -> bool {
let path_str = path.to_string_lossy();
self.exclude
.iter()
.any(|pattern| path_str.contains(pattern))
}
}