use crate::adapters::analyzers::iosp::{Classification, FunctionAnalysis};
use super::Summary;
pub fn print_github(results: &[FunctionAnalysis], summary: &Summary) {
print_violation_annotations(results);
print_complexity_annotations(results);
print_summary_annotation(summary);
}
fn print_violation_annotations(results: &[FunctionAnalysis]) {
for func in results {
if func.suppressed {
continue;
}
if let Classification::Violation {
logic_locations,
call_locations,
..
} = &func.classification
{
let logic_desc: Vec<String> = logic_locations.iter().map(|l| l.to_string()).collect();
let call_desc: Vec<String> = call_locations.iter().map(|c| c.to_string()).collect();
let effort_tag = func
.effort_score
.map(|e| format!(", effort={e:.1}"))
.unwrap_or_default();
println!(
"::warning file={},line={}::IOSP violation in {}: logic=[{}], calls=[{}]{}",
func.file,
func.line,
func.qualified_name,
logic_desc.join(", "),
call_desc.join(", "),
effort_tag,
);
}
}
}
fn build_annotation_pairs(
func: &FunctionAnalysis,
m: &crate::adapters::analyzers::iosp::ComplexityMetrics,
) -> Vec<(&'static str, String)> {
let q = &func.qualified_name;
let magic_msg = (!m.magic_numbers.is_empty()).then(|| {
let nums: Vec<String> = m.magic_numbers.iter().map(|n| n.value.clone()).collect();
format!("Magic numbers in {q}: {}", nums.join(", "))
});
[
func.cognitive_warning.then(|| {
(
"notice",
format!(
"Cognitive complexity {} in {q} exceeds threshold",
m.cognitive_complexity
),
)
}),
func.cyclomatic_warning.then(|| {
(
"notice",
format!(
"Cyclomatic complexity {} in {q} exceeds threshold",
m.cyclomatic_complexity
),
)
}),
magic_msg.map(|msg| ("warning", msg)),
func.nesting_depth_warning.then(|| {
(
"notice",
format!("Nesting depth {} in {q} exceeds threshold", m.max_nesting),
)
}),
func.function_length_warning.then(|| {
(
"notice",
format!(
"Function {q} has {} lines (exceeds threshold)",
m.function_lines
),
)
}),
func.unsafe_warning.then(|| {
(
"warning",
format!("{} unsafe block(s) in {q}", m.unsafe_blocks),
)
}),
func.error_handling_warning.then(|| {
(
"warning",
format!(
"Error handling in {q}: unwrap={}, expect={}, panic={}, todo={}",
m.unwrap_count, m.expect_count, m.panic_count, m.todo_count,
),
)
}),
]
.into_iter()
.flatten()
.collect()
}
fn print_complexity_annotations(results: &[FunctionAnalysis]) {
let build = |func: &FunctionAnalysis,
m: &crate::adapters::analyzers::iosp::ComplexityMetrics| {
build_annotation_pairs(func, m)
};
for func in results {
if func.suppressed {
continue;
}
let Some(ref m) = func.complexity else {
continue;
};
let (f, l) = (&func.file, func.line);
build(func, m).iter().for_each(|(level, msg)| {
println!("::{level} file={f},line={l}::{msg}");
});
}
}
fn print_summary_annotation(summary: &Summary) {
if summary.suppression_ratio_exceeded {
println!(
"::warning::Suppression ratio exceeds configured maximum ({} of {} functions suppressed)",
summary.suppressed,
summary.total,
);
}
if summary.violations > 0 {
println!(
"::error::Quality analysis: {} violation(s), {:.1}% quality score",
summary.violations,
summary.quality_score * crate::domain::PERCENTAGE_MULTIPLIER,
);
} else {
println!(
"::notice::Quality score: {:.1}% ({} functions analyzed)",
summary.quality_score * crate::domain::PERCENTAGE_MULTIPLIER,
summary.total,
);
}
}