use super::architecture::collect_architecture_findings;
use super::secondary::{run_secondary_analysis, SecondaryContext, SecondaryResults};
use super::warnings;
use crate::adapters::source::filesystem as discovery;
use crate::adapters::source::filesystem::{
collect_filtered_files, collect_suppression_lines, read_and_parse_files,
};
use std::path::Path;
use crate::adapters::analyzers::iosp::scope::ProjectScope;
use crate::adapters::analyzers::iosp::{Analyzer, FunctionAnalysis};
use crate::config::Config;
use crate::report::{AnalysisResult, Summary};
use super::warnings::{
apply_complexity_warnings, apply_extended_warnings, apply_file_suppressions,
check_suppression_ratio, count_all_suppressions, exclude_test_violations,
};
struct PrimaryResults {
all_results: Vec<FunctionAnalysis>,
summary: Summary,
suppression_lines: std::collections::HashMap<String, Vec<crate::findings::Suppression>>,
}
fn run_primary_analysis(
parsed: &[(String, String, syn::File)],
config: &Config,
cfg_test_files: &std::collections::HashSet<String>,
) -> PrimaryResults {
let scope_refs: Vec<(&str, &syn::File)> = parsed
.iter()
.map(|(path, _, file)| (path.as_str(), file))
.collect();
let scope = ProjectScope::from_files(&scope_refs);
let suppression_lines = collect_suppression_lines(parsed);
let analyzer = Analyzer::new(config, &scope).with_cfg_test_files(cfg_test_files);
let mut all_results: Vec<_> = parsed
.iter()
.flat_map(|(path, _, syntax)| {
let file_suppressions = suppression_lines.get(path);
analyzer
.analyze_file(syntax, path)
.into_iter()
.map(move |mut fa| {
if let Some(suppressions) = file_suppressions {
apply_file_suppressions(&mut fa, suppressions);
}
fa
})
})
.collect();
exclude_test_violations(&mut all_results);
let recursive_lines = discovery::collect_recursive_lines(parsed);
warnings::apply_recursive_annotations(&mut all_results, &recursive_lines);
warnings::apply_leaf_reclassification(&mut all_results);
let mut summary = Summary::from_results(&all_results);
apply_complexity_warnings(&mut all_results, config, &mut summary);
let unsafe_allow_lines = discovery::collect_unsafe_allow_lines(parsed);
apply_extended_warnings(&mut all_results, config, &mut summary, &unsafe_allow_lines);
PrimaryResults {
all_results,
summary,
suppression_lines,
}
}
pub(crate) fn run_analysis(
parsed: &[(String, String, syn::File)],
config: &Config,
) -> AnalysisResult {
let cfg_test_files =
crate::adapters::shared::cfg_test_files::collect_cfg_test_file_paths(parsed);
let PrimaryResults {
mut all_results,
mut summary,
suppression_lines,
} = run_primary_analysis(parsed, config, &cfg_test_files);
let secondary_ctx = SecondaryContext {
parsed,
config,
all_results: &all_results,
suppression_lines: &suppression_lines,
cfg_test_files: &cfg_test_files,
};
let secondary = run_secondary_analysis(&secondary_ctx, &mut summary);
let architecture_findings =
collect_architecture_findings(parsed, config, &suppression_lines, &mut summary);
finalize_summary(&mut summary, config, &suppression_lines, parsed);
build_result(&mut all_results, summary, secondary, architecture_findings)
}
fn build_result(
all_results: &mut Vec<FunctionAnalysis>,
summary: Summary,
secondary: SecondaryResults,
architecture_findings: Vec<crate::domain::Finding>,
) -> AnalysisResult {
AnalysisResult {
results: std::mem::take(all_results),
summary,
coupling: secondary.coupling,
duplicates: secondary.duplicates,
dead_code: secondary.dead_code,
fragments: secondary.fragments,
boilerplate: secondary.boilerplate,
wildcard_warnings: secondary.wildcard_warnings,
repeated_matches: secondary.repeated_matches,
srp: secondary.srp,
tq: secondary.tq,
structural: secondary.structural,
architecture_findings,
}
}
fn finalize_summary(
summary: &mut Summary,
config: &Config,
suppression_lines: &std::collections::HashMap<String, Vec<crate::findings::Suppression>>,
parsed: &[(String, String, syn::File)],
) {
summary.compute_quality_score(&config.weights.as_array());
summary.all_suppressions = count_all_suppressions(suppression_lines, parsed);
summary.suppression_ratio_exceeded = check_suppression_ratio(
summary.total,
summary.all_suppressions,
config.max_suppression_ratio,
);
}
pub(crate) fn analyze_and_output(
path: &Path,
config: &Config,
output_format: &crate::cli::OutputFormat,
verbose: bool,
suggestions: bool,
) {
let files = collect_filtered_files(path, config);
let parsed = read_and_parse_files(&files, path);
let analysis = run_analysis(&parsed, config);
output_results(&analysis, output_format, verbose, suggestions, config);
}
pub(crate) fn output_results(
analysis: &AnalysisResult,
output_format: &crate::cli::OutputFormat,
verbose: bool,
suggestions: bool,
config: &crate::config::Config,
) {
use crate::report;
match output_format {
crate::cli::OutputFormat::Json => report::print_json(analysis),
crate::cli::OutputFormat::Github => {
report::print_github(&analysis.results, &analysis.summary);
analysis
.coupling
.iter()
.for_each(|ca| report::print_coupling_annotations(ca, &config.coupling));
report::print_dry_annotations(analysis);
analysis.srp.iter().for_each(report::print_srp_annotations);
analysis.tq.iter().for_each(report::print_tq_annotations);
analysis
.structural
.iter()
.for_each(report::print_structural_annotations);
}
crate::cli::OutputFormat::Dot => report::print_dot(&analysis.results),
crate::cli::OutputFormat::Sarif => report::print_sarif(analysis),
crate::cli::OutputFormat::Html => report::print_html(analysis),
crate::cli::OutputFormat::Ai => report::print_ai(analysis, config),
crate::cli::OutputFormat::AiJson => report::print_ai_json(analysis, config),
crate::cli::OutputFormat::Text => {
let findings = crate::report::findings_list::collect_all_findings(analysis);
report::print_summary_only(&analysis.summary, &findings);
analysis
.coupling
.iter()
.for_each(|ca| report::print_coupling_section(ca, &config.coupling, verbose));
if verbose {
report::print_files_only(&analysis.results);
report::print_dry_section(analysis);
analysis.srp.iter().for_each(report::print_srp_section);
analysis.tq.iter().for_each(report::print_tq_section);
analysis
.structural
.iter()
.for_each(report::print_structural_section);
} else {
crate::report::findings_list::print_findings(&findings);
}
if suggestions {
report::print_suggestions(&analysis.results);
}
}
}
}