use std::collections::HashMap;
use crate::adapters::analyzers::iosp::FunctionAnalysis;
use crate::config::Config;
use crate::findings::Suppression;
use crate::report::Summary;
pub(super) fn compute_coupling(
parsed: &[(String, String, syn::File)],
config: &Config,
) -> Option<crate::adapters::analyzers::coupling::CouplingAnalysis> {
if !config.coupling.enabled {
return None;
}
Some(crate::adapters::analyzers::coupling::analyze_coupling(
parsed,
))
}
pub(super) fn mark_coupling_suppressions(
analysis: Option<&mut crate::adapters::analyzers::coupling::CouplingAnalysis>,
suppression_lines: &std::collections::HashMap<String, Vec<Suppression>>,
) {
let Some(analysis) = analysis else { return };
let suppressed_modules: std::collections::HashSet<String> = suppression_lines
.iter()
.filter(|(_, sups)| {
sups.iter()
.any(|s| s.covers(crate::findings::Dimension::Coupling))
})
.map(|(path, _)| crate::adapters::analyzers::coupling::file_to_module(path))
.collect();
for m in &mut analysis.metrics {
if suppressed_modules.contains(&m.module_name) {
m.suppressed = true;
}
}
}
pub(super) fn count_coupling_warnings(
analysis: Option<&mut crate::adapters::analyzers::coupling::CouplingAnalysis>,
config: &crate::config::sections::CouplingConfig,
summary: &mut Summary,
) {
let Some(analysis) = analysis else { return };
for m in &mut analysis.metrics {
m.warning = false;
if m.suppressed {
continue;
}
let instability_exceeded = m.afferent > 0 && m.instability > config.max_instability;
if instability_exceeded || m.afferent > config.max_fan_in || m.efferent > config.max_fan_out
{
m.warning = true;
summary.coupling_warnings += 1;
}
}
summary.coupling_cycles = analysis.cycles.len();
}
pub(super) fn run_guarded_detection<T>(
enabled: bool,
detect: impl FnOnce(&[(String, String, syn::File)], &Config) -> Vec<T>,
parsed: &[(String, String, syn::File)],
config: &Config,
) -> Vec<T> {
if !enabled {
return vec![];
}
detect(parsed, config)
}
pub(super) struct DryResults {
pub(super) duplicates: Vec<crate::adapters::analyzers::dry::functions::DuplicateGroup>,
pub(super) dead_code: Vec<crate::adapters::analyzers::dry::dead_code::DeadCodeWarning>,
pub(super) fragments: Vec<crate::adapters::analyzers::dry::fragments::FragmentGroup>,
pub(super) boilerplate: Vec<crate::adapters::analyzers::dry::boilerplate::BoilerplateFind>,
pub(super) wildcard_warnings:
Vec<crate::adapters::analyzers::dry::wildcards::WildcardImportWarning>,
}
pub(super) fn run_dry_detection(
parsed: &[(String, String, syn::File)],
config: &Config,
suppression_lines: &std::collections::HashMap<String, Vec<Suppression>>,
api_lines: &std::collections::HashMap<String, std::collections::HashSet<usize>>,
cfg_test_files: &std::collections::HashSet<String>,
) -> DryResults {
let duplicates = run_guarded_detection(
config.duplicates.enabled,
|p, c| crate::adapters::analyzers::dry::functions::detect_duplicates(p, &c.duplicates),
parsed,
config,
);
let dead_code = run_guarded_detection(
config.duplicates.enabled,
|p, c| {
if !c.duplicates.detect_dead_code {
return vec![];
}
crate::adapters::analyzers::dry::dead_code::detect_dead_code(
p,
c,
api_lines,
cfg_test_files,
)
},
parsed,
config,
);
let fragments = run_guarded_detection(
config.duplicates.enabled,
|p, c| crate::adapters::analyzers::dry::fragments::detect_fragments(p, &c.duplicates),
parsed,
config,
);
let boilerplate = run_guarded_detection(
config.boilerplate.enabled,
|p, c| crate::adapters::analyzers::dry::boilerplate::detect_boilerplate(p, &c.boilerplate),
parsed,
config,
);
let mut wildcard_warnings = run_guarded_detection(
config.duplicates.detect_wildcard_imports,
|p, c| {
if !c.duplicates.enabled {
return vec![];
}
crate::adapters::analyzers::dry::wildcards::detect_wildcard_imports(p)
},
parsed,
config,
);
mark_wildcard_suppressions(&mut wildcard_warnings, suppression_lines);
DryResults {
duplicates,
dead_code,
fragments,
boilerplate,
wildcard_warnings,
}
}
pub(super) fn count_dry_findings(
dry: &DryResults,
repeated_matches: &[crate::adapters::analyzers::dry::match_patterns::RepeatedMatchGroup],
summary: &mut Summary,
) {
summary.dead_code_warnings = dry.dead_code.len();
summary.wildcard_import_warnings = dry
.wildcard_warnings
.iter()
.filter(|w| !w.suppressed)
.count();
summary.duplicate_groups = dry
.duplicates
.iter()
.filter(|g| !g.suppressed)
.map(|g| g.entries.len())
.sum();
summary.fragment_groups = dry
.fragments
.iter()
.filter(|g| !g.suppressed)
.map(|g| g.entries.len())
.sum();
summary.boilerplate_warnings = dry.boilerplate.iter().filter(|b| !b.suppressed).count();
summary.repeated_match_groups = repeated_matches
.iter()
.filter(|g| !g.suppressed)
.map(|g| g.entries.len())
.sum();
}
pub(super) fn mark_srp_suppressions(
srp: Option<&mut crate::adapters::analyzers::srp::SrpAnalysis>,
suppression_lines: &std::collections::HashMap<String, Vec<Suppression>>,
) {
let Some(srp) = srp else { return };
const SRP_STRUCT_SUPPRESSION_WINDOW: usize = 5;
let srp_dim = crate::findings::Dimension::Srp;
srp.struct_warnings.iter_mut().for_each(|w| {
if let Some(sups) = suppression_lines.get(&w.file) {
w.suppressed = sups.iter().any(|sup| {
let in_window =
sup.line <= w.line && w.line - sup.line <= SRP_STRUCT_SUPPRESSION_WINDOW;
in_window && sup.covers(srp_dim)
});
}
});
srp.module_warnings.iter_mut().for_each(|w| {
if let Some(sups) = suppression_lines.get(&w.file) {
w.suppressed = sups.iter().any(|sup| sup.covers(srp_dim));
}
});
srp.param_warnings.iter_mut().for_each(|w| {
if let Some(sups) = suppression_lines.get(&w.file) {
w.suppressed = sups.iter().any(|sup| {
let in_window =
sup.line <= w.line && w.line - sup.line <= SRP_STRUCT_SUPPRESSION_WINDOW;
in_window && sup.covers(srp_dim)
});
}
});
}
pub(super) fn mark_wildcard_suppressions(
warnings: &mut [crate::adapters::analyzers::dry::wildcards::WildcardImportWarning],
suppression_lines: &std::collections::HashMap<String, Vec<Suppression>>,
) {
let dry_dim = crate::findings::Dimension::Dry;
warnings.iter_mut().for_each(|w| {
if let Some(sups) = suppression_lines.get(&w.file) {
w.suppressed = sups.iter().any(|sup| {
(sup.line == w.line || (w.line > 0 && sup.line == w.line.saturating_sub(1)))
&& sup.covers(dry_dim)
});
}
});
}
pub(super) fn count_sdp_violations(
coupling: Option<&crate::adapters::analyzers::coupling::CouplingAnalysis>,
config: &crate::config::sections::CouplingConfig,
summary: &mut Summary,
) {
let Some(coupling) = coupling else { return };
if !config.check_sdp {
return;
}
summary.sdp_violations = coupling
.sdp_violations
.iter()
.filter(|v| !v.suppressed)
.count();
}
pub(super) fn build_file_call_graph(
results: &[FunctionAnalysis],
) -> HashMap<String, Vec<(String, Vec<String>)>> {
let mut map: HashMap<String, Vec<(String, Vec<String>)>> = HashMap::new();
for fa in results {
map.entry(fa.file.clone())
.or_default()
.push((fa.name.clone(), fa.own_calls.clone()));
}
map
}
pub(super) fn compute_srp(
parsed: &[(String, String, syn::File)],
config: &Config,
file_call_graph: &HashMap<String, Vec<(String, Vec<String>)>>,
) -> Option<crate::adapters::analyzers::srp::SrpAnalysis> {
if !config.srp.enabled {
return None;
}
Some(crate::adapters::analyzers::srp::analyze_srp(
parsed,
&config.srp,
file_call_graph,
))
}
pub(super) fn count_srp_warnings(
srp: Option<&crate::adapters::analyzers::srp::SrpAnalysis>,
summary: &mut Summary,
) {
let Some(srp) = srp else { return };
summary.srp_struct_warnings = srp.struct_warnings.iter().filter(|w| !w.suppressed).count();
summary.srp_module_warnings = srp.module_warnings.iter().filter(|w| !w.suppressed).count();
summary.srp_param_warnings = srp.param_warnings.iter().filter(|w| !w.suppressed).count();
}
pub(super) fn apply_parameter_warnings(
results: &[crate::adapters::analyzers::iosp::FunctionAnalysis],
srp: Option<&mut crate::adapters::analyzers::srp::SrpAnalysis>,
config: &crate::config::sections::SrpConfig,
) {
let Some(srp) = srp else { return };
let max = config.max_parameters;
srp.param_warnings = results
.iter()
.filter(|fa| !fa.suppressed && !fa.is_trait_impl && fa.parameter_count > max)
.map(|fa| crate::adapters::analyzers::srp::ParamSrpWarning {
function_name: fa.qualified_name.clone(),
file: fa.file.clone(),
line: fa.line,
parameter_count: fa.parameter_count,
suppressed: false,
})
.collect();
}