use crate::cli::{self, AnalyzeCommands};
use anyhow::Result;
use std::path::PathBuf;
pub(super) async fn route_bottleneck_analysis(cmd: AnalyzeCommands) -> Result<()> {
if let AnalyzeCommands::Bottleneck {
path,
period,
threshold,
format,
output,
} = cmd
{
crate::cli::handlers::bottleneck_handler::handle_bottleneck(
&path,
&format,
period,
threshold,
output.as_deref(),
)
.await
} else {
unreachable!("Expected Bottleneck command")
}
}
pub(super) async fn route_complexity_analysis(cmd: AnalyzeCommands) -> Result<()> {
if let AnalyzeCommands::Complexity {
path,
project_path,
file,
files,
toolchain,
format,
output,
max_cyclomatic,
max_cognitive,
include,
watch,
top_files,
fail_on_violation,
timeout,
ml,
} = cmd
{
super::reject_unimplemented_ml(ml, "analyze complexity", "complexity scores")?;
route_complexity_command(
path,
project_path,
file,
files,
toolchain,
format,
output,
max_cyclomatic,
max_cognitive,
include,
watch,
top_files,
fail_on_violation,
timeout,
)
.await
} else {
unreachable!("Expected Complexity command")
}
}
pub(super) async fn route_churn_analysis(cmd: AnalyzeCommands) -> Result<()> {
if let AnalyzeCommands::Churn {
path,
project_path,
days,
format,
output,
top_files,
include,
exclude,
} = cmd
{
let path = project_path.unwrap_or(path);
crate::cli::handlers::complexity_handlers::handle_analyze_churn(
path, days, format, output, top_files, include, exclude,
)
.await
} else {
unreachable!("Expected Churn command")
}
}
pub(super) async fn route_dead_code_analysis(cmd: AnalyzeCommands) -> Result<()> {
if let AnalyzeCommands::DeadCode {
path,
format,
top_files,
include_unreachable,
min_dead_lines,
include_tests,
output,
fail_on_violation,
max_percentage,
timeout,
include,
exclude,
max_depth,
} = cmd
{
crate::cli::handlers::dead_code_handlers::handle_analyze_dead_code(
path,
format,
top_files,
include_unreachable,
min_dead_lines,
include_tests,
output,
fail_on_violation,
max_percentage,
timeout,
include,
exclude,
max_depth,
)
.await
} else {
unreachable!("Expected DeadCode command")
}
}
pub(super) async fn route_defects_analysis(cmd: AnalyzeCommands) -> Result<()> {
use crate::cli::handlers::analyze_defects_handler::{handle_analyze_defects, OutputFormat};
if let AnalyzeCommands::Defects {
path,
file,
severity,
format,
output: _,
} = cmd
{
let output_format = match format {
cli::DefectsOutputFormat::Text => OutputFormat::Text,
cli::DefectsOutputFormat::Json => OutputFormat::Json,
cli::DefectsOutputFormat::Junit => OutputFormat::Junit,
};
let severity_filter = parse_defect_severity_filter(severity.as_deref())?;
let exit_code = handle_analyze_defects(
path.as_deref(),
file.as_deref(),
severity_filter,
output_format,
)
.await?;
if exit_code != 0 {
std::process::exit(exit_code);
}
Ok(())
} else {
unreachable!("Expected Defects command")
}
}
fn parse_defect_severity_filter(
severity: Option<&str>,
) -> Result<Option<crate::services::defect_detector::Severity>> {
use crate::services::defect_detector::Severity;
let Some(raw) = severity else {
return Ok(None);
};
match raw.to_lowercase().as_str() {
"critical" => Ok(Some(Severity::Critical)),
"high" => Ok(Some(Severity::High)),
"medium" => Ok(Some(Severity::Medium)),
"low" => Ok(Some(Severity::Low)),
_ => {
anyhow::bail!("invalid --severity '{raw}': expected one of critical, high, medium, low")
}
}
}
pub(super) async fn route_dag_analysis(cmd: AnalyzeCommands) -> Result<()> {
if let AnalyzeCommands::Dag {
dag_type,
path,
project_path,
output,
max_depth,
target_nodes,
filter_external,
show_complexity,
include_duplicates,
include_dead_code,
enhanced,
} = cmd
{
let path = project_path.unwrap_or(path);
crate::cli::handlers::complexity_handlers::handle_analyze_dag(
dag_type,
path,
output,
max_depth,
target_nodes,
filter_external,
show_complexity,
include_duplicates,
include_dead_code,
enhanced,
)
.await
} else {
unreachable!("Expected Dag command")
}
}
pub(super) async fn route_satd_analysis(cmd: AnalyzeCommands) -> Result<()> {
if let AnalyzeCommands::Satd {
path,
format,
severity,
critical_only,
include_tests,
strict,
evolution,
days,
metrics,
output,
top_files,
fail_on_violation,
timeout,
include,
exclude,
extended,
} = cmd
{
use crate::cli::handlers::satd_handler::SatdAnalysisConfig;
let config = SatdAnalysisConfig {
path,
format,
severity,
critical_only,
include_tests,
strict,
evolution,
days,
metrics,
output,
top_files,
fail_on_violation,
timeout,
include,
exclude,
extended,
};
crate::cli::handlers::satd_handler::handle_analyze_satd(config).await
} else {
unreachable!("Expected Satd command")
}
}
#[allow(clippy::too_many_arguments)]
async fn route_complexity_command(
path: PathBuf,
project_path: Option<PathBuf>,
file: Option<PathBuf>,
files: Vec<PathBuf>,
toolchain: Option<String>,
format: crate::cli::ComplexityOutputFormat,
output: Option<PathBuf>,
max_cyclomatic: Option<u16>,
max_cognitive: Option<u16>,
include: Vec<String>,
watch: bool,
top_files: usize,
fail_on_violation: bool,
timeout: u64,
) -> Result<()> {
let analysis_path = project_path.unwrap_or(path);
crate::cli::handlers::complexity_handlers::handle_analyze_complexity(
analysis_path,
file,
files,
toolchain,
format,
output,
max_cyclomatic,
max_cognitive,
include,
watch,
top_files,
fail_on_violation,
timeout,
)
.await
}
#[cfg(test)]
mod ml_flag_tests {
use super::*;
fn complexity_command(ml: bool) -> AnalyzeCommands {
AnalyzeCommands::Complexity {
path: PathBuf::from("."),
project_path: None,
file: None,
files: Vec::new(),
toolchain: None,
format: cli::ComplexityOutputFormat::Summary,
output: None,
max_cyclomatic: None,
max_cognitive: None,
include: Vec::new(),
watch: false,
top_files: 10,
fail_on_violation: false,
timeout: 60,
ml,
}
}
#[tokio::test]
async fn ml_flag_is_refused_rather_than_ignored() {
let err = route_complexity_analysis(complexity_command(true))
.await
.expect_err("--ml must not silently return heuristic scores");
let msg = err.to_string();
assert!(
msg.contains("--ml is not implemented"),
"unexpected error: {msg}"
);
}
#[test]
fn unrecognised_severity_is_rejected() {
let err = parse_defect_severity_filter(Some("bogus"))
.expect_err("an unknown --severity value must not mean 'no filter'");
let msg = err.to_string();
assert!(msg.contains("bogus"), "error must quote the value: {msg}");
assert!(
msg.contains("critical") && msg.contains("low"),
"error must list the accepted values: {msg}"
);
}
#[test]
fn recognised_severities_parse_case_insensitively() {
use crate::services::defect_detector::Severity;
assert_eq!(
parse_defect_severity_filter(Some("Critical")).unwrap(),
Some(Severity::Critical)
);
assert_eq!(
parse_defect_severity_filter(Some("high")).unwrap(),
Some(Severity::High)
);
assert_eq!(
parse_defect_severity_filter(Some("MEDIUM")).unwrap(),
Some(Severity::Medium)
);
assert_eq!(
parse_defect_severity_filter(Some("low")).unwrap(),
Some(Severity::Low)
);
assert_eq!(parse_defect_severity_filter(None).unwrap(), None);
}
}