use std::path::Path;
use std::sync::Arc;
use std::sync::OnceLock;
use crate::CodeAnalysisCheckerContainer;
use shared::cli_commands::taxonomy_result_vo::LintResult;
use shared::cli_commands::taxonomy_result_vo::LintResultList;
use shared::cli_commands::taxonomy_score_vo::compute_score;
use shared::cli_commands::taxonomy_severity_vo::Severity;
use shared::code_analysis::contract_code_analysis_aggregate::ICodeAnalysisAggregate;
use shared::code_analysis::taxonomy_code_analysis_rule_vo::CodeAnalysisRuleVO;
use shared::common::taxonomy_path_vo::{DirectoryPath, FilePath};
use shared::config_system::taxonomy_config_vo::ArchitectureConfig;
static GLOBAL_CONTAINER: OnceLock<Arc<CodeAnalysisCheckerContainer>> = OnceLock::new();
pub fn init_global_checker(container: Arc<CodeAnalysisCheckerContainer>) {
GLOBAL_CONTAINER.set(container).ok();
}
pub fn detect_source_dir(project_root: &Path) -> std::path::PathBuf {
for name in &["packages", "crates", "modules"] {
let candidate = project_root.join(name);
if candidate.is_dir() {
return candidate;
}
}
project_root.to_path_buf()
}
pub fn collect_source_files(
root_dir: &Path,
dir_path: &DirectoryPath,
ignored: &[String],
) -> Vec<FilePath> {
shared::common::taxonomy_file_collector_helper::collect_source_files(
root_dir, dir_path, ignored,
)
}
pub struct CodeAnalysisOrchestrator {
container: Arc<CodeAnalysisCheckerContainer>,
}
impl Default for CodeAnalysisOrchestrator {
fn default() -> Self {
Self::new()
}
}
pub fn resolve_target(path: Option<String>) -> String {
match path {
Some(p) => p,
None => ".".to_string(),
}
}
pub fn lint_path(path: &str) -> Vec<LintResult> {
let root = match FilePath::new(path.to_string()) {
Ok(fp) => fp,
Err(_) => match FilePath::new(".".to_string()) {
Ok(fp) => fp,
Err(_) => return Vec::new(),
},
};
let orchestrator = CodeAnalysisOrchestrator::new();
orchestrator.run_self_lint(&root.value)
}
pub fn has_critical(results: &[LintResult]) -> bool {
results.iter().any(|r| r.severity == Severity::CRITICAL)
}
impl CodeAnalysisOrchestrator {
pub fn new() -> Self {
Self {
container: match GLOBAL_CONTAINER.get().cloned() {
Some(c) => c,
None => Arc::new(CodeAnalysisCheckerContainer::default()),
},
}
}
pub fn new_with_container(container: Arc<CodeAnalysisCheckerContainer>) -> Self {
Self { container }
}
pub fn run_self_lint(&self, project_root: &str) -> Vec<LintResult> {
let root = Path::new(project_root);
let src_dir = detect_source_dir(root);
self.run_lint_at(&src_dir)
}
pub fn run_scan(&self, target_dir: &str) -> Vec<LintResult> {
self.run_lint_at(Path::new(target_dir))
}
fn run_lint_at(&self, src_dir: &Path) -> Vec<LintResult> {
let config = self.container.analyzer().config();
let ignored: Vec<String> = config
.ignored_paths
.values
.iter()
.map(|fp| fp.value.replace('/', std::path::MAIN_SEPARATOR_STR))
.collect();
let dir_path = match DirectoryPath::new(src_dir.to_string_lossy().to_string()) {
Ok(dp) => dp,
Err(_) => return Vec::new(),
};
let files = collect_source_files(src_dir, &dir_path, &ignored);
if files.is_empty() {
return Vec::new();
}
let root_dir = src_dir.to_string_lossy().to_string();
let files_str: Vec<String> = files.iter().map(|f| f.value.clone()).collect();
self.run_all_checks(config, &files_str, &root_dir)
}
pub fn run_all_checks(
&self,
config: &ArchitectureConfig,
files: &[String],
root_dir: &str,
) -> Vec<LintResult> {
if !config.enabled.value {
return Vec::new();
}
let mut violations: Vec<LintResult> = Vec::new();
let mut entries: Vec<(String, String)> = Vec::new();
if config.is_rule_enabled("AES304") {
let root_path = Path::new(root_dir);
let mut cargo_candidates: Vec<std::path::PathBuf> = Vec::new();
cargo_candidates.push(root_path.join("Cargo.toml"));
if let Some(parent) = root_path.parent() {
cargo_candidates.push(parent.join("Cargo.toml"));
}
for cargo_path in &cargo_candidates {
if cargo_path.exists() {
if let Ok(cargo_content) = std::fs::read_to_string(cargo_path) {
self.container
.bypass_checker()
.check_cargo_toml(&cargo_content, &mut violations);
}
}
}
}
for file in files {
let filename = Path::new(file)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or_default();
let c = match std::fs::read_to_string(file) {
Ok(content) => content,
Err(_) => continue,
};
entries.push((file.clone(), c.clone()));
if config.is_rule_enabled("AES304") {
self.container
.bypass_checker()
.check_bypass_comments(file, &c, &mut violations);
}
if config.is_rule_enabled("AES303") {
self.container
.dead_inheritance_checker()
.check_dead_inheritance(file, &c, &mut violations);
}
if matches!(filename, "__init__.py" | "mod.rs" | "index.ts" | "index.js") {
continue;
}
let layer = match self.container.detect_layer(file, root_dir) {
Some(l) => l,
None => continue,
};
let def = match self.container.get_layer_def(&layer) {
Some(d) => d,
None => continue,
};
if def.exceptions.values.contains(&filename.to_string()) {
continue;
}
if config.is_rule_enabled("AES301") || config.is_rule_enabled("AES302") {
self.container.line_checker().check_line_counts(
file,
Some(def),
&c,
&mut violations,
);
}
if config.is_rule_enabled("AES303") {
self.container
.class_checker()
.check_mandatory_class_definition(file, Some(def), &c, &mut violations);
}
}
if config.is_rule_enabled("AES305") {
let min_dup_lines: usize = 5;
let threshold_pct: f64 = 50.0;
let dup_violations = self
.container
.duplication_checker()
.check_file_similarity_entries(&entries, min_dup_lines, threshold_pct);
for (file_path, dv) in dup_violations {
violations.push(LintResult::new_arch(
&file_path,
0,
"AES305",
Severity::HIGH,
dv.to_string(),
));
}
}
violations
}
pub fn format_report(&self, results: &[LintResult], project_root: &str) -> String {
let mut output = String::new();
output.push_str(&"=".repeat(60));
output.push_str("\n AES Architecture Compliance Report \n");
output.push_str(&"=".repeat(60));
output.push_str(&format!("\n Project: {}\n", project_root));
output.push_str(&format!(" Violations: {}\n", results.len()));
output.push('\n');
for r in results {
output.push_str(&format!(
" [{}] {} - {}\n",
r.code, r.file.value, r.message.value
));
}
output
}
}
impl ICodeAnalysisAggregate for CodeAnalysisOrchestrator {
fn run_code_analysis(&self, project_root: &str) -> LintResultList {
LintResultList::new(self.run_self_lint(project_root))
}
fn run_code_analysis_dir(&self, src_dir: &str) -> LintResultList {
LintResultList::new(self.run_scan(src_dir))
}
fn run_code_analysis_path(&self, path: &str) -> Vec<LintResult> {
self.run_self_lint(path)
}
fn calc_score(&self, results: &[LintResult]) -> f64 {
compute_score(results)
}
fn check_critical(&self, results: &[LintResult]) -> bool {
has_critical(results)
}
fn format_report(&self, results: &LintResultList, project_root: &str) -> String {
self.format_report(&results.values, project_root)
}
fn active_rules(&self) -> Vec<CodeAnalysisRuleVO> {
Vec::new()
}
}