mod adapters;
mod app;
mod cli;
mod domain;
mod ports;
use adapters::config;
use adapters::report;
use adapters::source::watch;
use adapters::suppression::qual_allow as findings;
use cli::handlers::{handle_compare, handle_completions, handle_init, handle_save_baseline};
use clap::Parser;
use cli::{Cli, OutputFormat};
pub(crate) fn determine_output_format(cli: &Cli) -> OutputFormat {
if let Some(ref fmt) = cli.format {
fmt.clone()
} else if cli.json {
OutputFormat::Json
} else {
OutputFormat::Text
}
}
use app::{apply_exit_gates, setup_config};
fn sort_by_effort(results: &mut [crate::adapters::analyzers::iosp::FunctionAnalysis]) {
results.sort_by(|a, b| {
b.effort_score
.unwrap_or(0.0)
.partial_cmp(&a.effort_score.unwrap_or(0.0))
.unwrap_or(std::cmp::Ordering::Equal)
});
}
pub fn run() -> Result<(), i32> {
let mut args: Vec<String> = std::env::args().collect();
if args.len() > 1 && args[1] == "qual" {
args.remove(1);
}
let mut cli = Cli::parse_from(args);
let normalized = cli.path.to_string_lossy().replace('\\', "/");
cli.path = std::path::PathBuf::from(normalized);
if cli.init {
let content = config::init::prepare_init_content(&cli.path);
return handle_init(&content);
}
if let Some(shell) = cli.completions {
handle_completions(shell);
return Ok(());
}
let output_format = determine_output_format(&cli);
let config = setup_config(&cli)?;
if let Some(ref target) = cli.explain {
return crate::cli::explain::handle_explain(target, &config);
}
if cli.watch {
return watch::run_watch_mode(&cli.path, || {
app::analyze_and_output(
&cli.path,
&config,
&output_format,
cli.verbose,
cli.suggestions,
);
});
}
let files = adapters::source::filesystem::collect_filtered_files(&cli.path, &config);
let files = if let Some(ref git_ref) = cli.diff {
match adapters::source::filesystem::get_git_changed_files(&cli.path, git_ref) {
Ok(changed) => {
let filtered = adapters::source::filesystem::filter_to_changed(files, &changed);
eprintln!(
"[diff mode: {} changed file(s) vs {git_ref}]",
filtered.len()
);
filtered
}
Err(e) => {
eprintln!("Warning: {e}. Analyzing all files.");
files
}
}
} else {
files
};
if files.is_empty() {
eprintln!("No Rust source files found in {}", cli.path.display());
return Ok(());
}
let parsed = adapters::source::filesystem::read_and_parse_files(&files, &cli.path);
let mut analysis = app::run_analysis(&parsed, &config);
if cli.sort_by_effort {
sort_by_effort(&mut analysis.results);
}
if cli.findings {
let entries = crate::report::findings_list::collect_all_findings(&analysis);
if entries.is_empty() {
println!("No findings.");
} else {
crate::report::findings_list::print_findings(&entries);
}
} else {
app::output_results(
&analysis,
&output_format,
cli.verbose,
cli.suggestions,
&config,
);
}
cli.save_baseline
.as_ref()
.map(|p| handle_save_baseline(p, &analysis.results, &analysis.summary))
.transpose()?;
if let Some(ref compare_path) = cli.compare {
let regressed = handle_compare(compare_path, &analysis.results, &analysis.summary)?;
if cli.fail_on_regression && regressed {
return Err(1);
}
}
apply_exit_gates(&cli, &config, &analysis.summary)
}