mod complexity_predicates;
use std::collections::HashMap;
use crate::adapters::analyzers::iosp::Classification;
use crate::findings::Suppression;
use crate::report::OrphanSuppressionWarning;
use super::suppression_windows as windows;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MatchMode {
LineWindow(usize),
FileScope,
}
pub(crate) fn detect_orphan_suppressions(
suppression_lines: &HashMap<String, Vec<Suppression>>,
analysis: &crate::report::AnalysisResult,
config: &crate::config::Config,
) -> Vec<OrphanSuppressionWarning> {
let positions = enumerate_finding_positions(analysis, config);
let mut orphans: Vec<OrphanSuppressionWarning> = suppression_lines
.iter()
.flat_map(|(file, sups)| {
sups.iter()
.filter(|sup| is_verifiable(sup, file, &positions))
.filter(|sup| !has_matching_finding(file, sup, &positions))
.map(|sup| OrphanSuppressionWarning {
file: file.clone(),
line: sup.line,
dimensions: sup.dimensions.clone(),
reason: sup.reason.clone(),
})
.collect::<Vec<_>>()
})
.collect();
orphans.sort_by(|a, b| a.file.cmp(&b.file).then(a.line.cmp(&b.line)));
orphans
}
fn is_verifiable(
sup: &Suppression,
file: &str,
positions: &HashMap<String, Vec<FindingPosition>>,
) -> bool {
use crate::findings::Dimension;
if sup.dimensions.is_empty() {
return true;
}
if sup.dimensions.iter().any(|d| *d != Dimension::Coupling) {
return true;
}
positions
.get(file)
.is_some_and(|ps| ps.iter().any(|p| p.dim == Dimension::Coupling))
}
fn has_matching_finding(
file: &str,
sup: &Suppression,
positions: &HashMap<String, Vec<FindingPosition>>,
) -> bool {
let Some(file_positions) = positions.get(file) else {
return false;
};
file_positions
.iter()
.any(|p| sup.covers(p.dim) && mode_accepts(sup.line, p.line, p.mode))
}
fn mode_accepts(sup_line: usize, finding_line: usize, mode: MatchMode) -> bool {
match mode {
MatchMode::FileScope => true,
MatchMode::LineWindow(n) => finding_line >= sup_line && finding_line - sup_line <= n,
}
}
#[derive(Debug, Clone, Copy)]
struct FindingPosition {
line: usize,
dim: crate::findings::Dimension,
mode: MatchMode,
}
fn enumerate_finding_positions(
analysis: &crate::report::AnalysisResult,
config: &crate::config::Config,
) -> HashMap<String, Vec<FindingPosition>> {
let mut out: HashMap<String, Vec<FindingPosition>> = HashMap::new();
let mut push = |file: &str, line: usize, dim: crate::findings::Dimension, mode: MatchMode| {
if !file.is_empty() {
out.entry(file.to_string())
.or_default()
.push(FindingPosition { line, dim, mode });
}
};
collect_iosp_complexity_positions(analysis, config, &mut push);
collect_dry_positions(analysis, config, &mut push);
collect_srp_positions(analysis, config, &mut push);
collect_tq_positions(analysis, config, &mut push);
collect_structural_positions(analysis, config, &mut push);
collect_architecture_positions(analysis, config, &mut push);
out
}
fn collect_iosp_complexity_positions<F>(
analysis: &crate::report::AnalysisResult,
config: &crate::config::Config,
push: &mut F,
) where
F: FnMut(&str, usize, crate::findings::Dimension, MatchMode),
{
use crate::findings::Dimension;
let mode = MatchMode::LineWindow(windows::DEFAULT);
let complexity_enabled = config.complexity.enabled;
analysis.results.iter().for_each(|f| {
if matches!(f.classification, Classification::Violation { .. }) {
push(&f.file, f.line, Dimension::Iosp, mode);
}
if !complexity_enabled {
return;
}
if let Some(c) = &f.complexity {
if complexity_predicates::would_trigger(f, c, &config.complexity) {
push(&f.file, f.line, Dimension::Complexity, mode);
}
push_magic_numbers(f, c, &config.complexity, push);
}
});
}
fn push_magic_numbers<F>(
f: &crate::adapters::analyzers::iosp::FunctionAnalysis,
c: &crate::adapters::analyzers::iosp::ComplexityMetrics,
cx: &crate::config::sections::ComplexityConfig,
push: &mut F,
) where
F: FnMut(&str, usize, crate::findings::Dimension, MatchMode),
{
if f.is_test || !cx.detect_magic_numbers {
return;
}
let mode = MatchMode::LineWindow(windows::DEFAULT);
c.magic_numbers.iter().for_each(|m| {
push(
&f.file,
m.line,
crate::findings::Dimension::Complexity,
mode,
)
});
}
fn collect_dry_positions<F>(
analysis: &crate::report::AnalysisResult,
config: &crate::config::Config,
push: &mut F,
) where
F: FnMut(&str, usize, crate::findings::Dimension, MatchMode),
{
use crate::findings::Dimension;
if !config.duplicates.enabled && !config.boilerplate.enabled {
return;
}
let mode = MatchMode::LineWindow(windows::DEFAULT);
analysis.duplicates.iter().for_each(|g| {
g.entries
.iter()
.for_each(|e| push(&e.file, e.line, Dimension::Dry, mode));
});
analysis.fragments.iter().for_each(|g| {
g.entries
.iter()
.for_each(|e| push(&e.file, e.start_line, Dimension::Dry, mode));
});
analysis
.boilerplate
.iter()
.for_each(|b| push(&b.file, b.line, Dimension::Dry, mode));
let wildcard_mode = MatchMode::LineWindow(windows::WILDCARD);
analysis
.wildcard_warnings
.iter()
.for_each(|w| push(&w.file, w.line, Dimension::Dry, wildcard_mode));
analysis.repeated_matches.iter().for_each(|g| {
g.entries
.iter()
.for_each(|e| push(&e.file, e.line, Dimension::Dry, mode));
});
}
fn collect_srp_positions<F>(
analysis: &crate::report::AnalysisResult,
config: &crate::config::Config,
push: &mut F,
) where
F: FnMut(&str, usize, crate::findings::Dimension, MatchMode),
{
use crate::findings::Dimension;
if !config.srp.enabled {
return;
}
let Some(srp) = &analysis.srp else { return };
let line_mode = MatchMode::LineWindow(windows::SRP_STRUCT_PARAM);
srp.struct_warnings
.iter()
.for_each(|w| push(&w.file, w.line, Dimension::Srp, line_mode));
srp.module_warnings
.iter()
.for_each(|w| push(&w.file, 1, Dimension::Srp, MatchMode::FileScope));
srp.param_warnings
.iter()
.for_each(|w| push(&w.file, w.line, Dimension::Srp, line_mode));
}
fn collect_tq_positions<F>(
analysis: &crate::report::AnalysisResult,
config: &crate::config::Config,
push: &mut F,
) where
F: FnMut(&str, usize, crate::findings::Dimension, MatchMode),
{
use crate::findings::Dimension;
if !config.test_quality.enabled {
return;
}
let Some(tq) = &analysis.tq else { return };
let mode = MatchMode::LineWindow(windows::TQ);
tq.warnings
.iter()
.for_each(|w| push(&w.file, w.line, Dimension::TestQuality, mode));
}
fn collect_structural_positions<F>(
analysis: &crate::report::AnalysisResult,
config: &crate::config::Config,
push: &mut F,
) where
F: FnMut(&str, usize, crate::findings::Dimension, MatchMode),
{
if !config.structural.enabled {
return;
}
let Some(st) = &analysis.structural else {
return;
};
let mode = MatchMode::LineWindow(windows::STRUCTURAL);
st.warnings
.iter()
.for_each(|w| push(&w.file, w.line, w.dimension, mode));
}
fn collect_architecture_positions<F>(
analysis: &crate::report::AnalysisResult,
config: &crate::config::Config,
push: &mut F,
) where
F: FnMut(&str, usize, crate::findings::Dimension, MatchMode),
{
use crate::findings::Dimension;
if !config.architecture.enabled {
return;
}
analysis.architecture_findings.iter().for_each(|f| {
push(
&f.file,
f.line,
Dimension::Architecture,
MatchMode::FileScope,
)
});
}