use std::path::Path;
use fallow_output::DiffIndex;
use fallow_types::duplicates::{CloneInstance, DuplicationReport};
use fallow_types::results::{AnalysisResults, TraceHopRole};
pub fn filter_dead_code_by_diff(results: &mut AnalysisResults, diff: &DiffIndex, root: &Path) {
let touches_file = |path: &Path| -> bool {
diff.key_for(path, root)
.is_none_or(|rel| diff.touches_file(&rel))
};
let line_in_diff = |path: &Path, line: u32| -> bool {
diff.key_for(path, root)
.is_none_or(|rel| diff.line_is_added(&rel, u64::from(line)))
};
filter_source_findings(results, &touches_file, &line_in_diff);
filter_security_findings(results, &touches_file, &line_in_diff);
filter_dependency_findings(results, &line_in_diff);
filter_graph_findings(results, &touches_file, &line_in_diff);
filter_framework_findings(results, &line_in_diff);
}
pub fn filter_duplication_by_diff(report: &mut DuplicationReport, diff: &DiffIndex, root: &Path) {
let instance_overlaps = |instance: &CloneInstance| -> bool {
let Some(rel) = diff.key_for(&instance.file, root) else {
return true;
};
let start = u64::try_from(instance.start_line).unwrap_or(u64::MAX);
let end = u64::try_from(instance.end_line).unwrap_or(u64::MAX);
diff.range_overlaps_added(&rel, start, end)
};
report
.clone_groups
.retain(|group| group.instances.iter().any(instance_overlaps));
crate::duplicates::refresh_scoped_report(report, root);
}
fn filter_source_findings(
results: &mut AnalysisResults,
touches_file: &dyn Fn(&Path) -> bool,
line_in_diff: &dyn Fn(&Path, u32) -> bool,
) {
results
.unused_files
.retain(|finding| touches_file(&finding.file.path));
results
.unused_exports
.retain(|finding| line_in_diff(&finding.export.path, finding.export.line));
results
.unused_types
.retain(|finding| line_in_diff(&finding.export.path, finding.export.line));
results
.private_type_leaks
.retain(|finding| line_in_diff(&finding.leak.path, finding.leak.line));
results
.deprecated_exports_in_use
.retain(|finding| line_in_diff(&finding.export.path, finding.export.line));
results
.unused_enum_members
.retain(|finding| line_in_diff(&finding.member.path, finding.member.line));
results
.unused_class_members
.retain(|finding| line_in_diff(&finding.member.path, finding.member.line));
results
.unused_store_members
.retain(|finding| line_in_diff(&finding.member.path, finding.member.line));
results
.unprovided_injects
.retain(|finding| line_in_diff(&finding.inject.path, finding.inject.line));
results
.unrendered_components
.retain(|finding| line_in_diff(&finding.component.path, finding.component.line));
results
.unused_component_props
.retain(|finding| line_in_diff(&finding.prop.path, finding.prop.line));
results
.unused_component_emits
.retain(|finding| line_in_diff(&finding.emit.path, finding.emit.line));
results
.unused_component_inputs
.retain(|finding| line_in_diff(&finding.input.path, finding.input.line));
results
.unused_component_outputs
.retain(|finding| line_in_diff(&finding.output.path, finding.output.line));
results
.unused_svelte_events
.retain(|finding| line_in_diff(&finding.event.path, finding.event.line));
results
.unused_server_actions
.retain(|finding| line_in_diff(&finding.action.path, finding.action.line));
results
.unused_load_data_keys
.retain(|finding| line_in_diff(&finding.key.path, finding.key.line));
results
.unresolved_imports
.retain(|finding| line_in_diff(&finding.import.path, finding.import.line));
}
fn filter_security_findings(
results: &mut AnalysisResults,
touches_file: &dyn Fn(&Path) -> bool,
line_in_diff: &dyn Fn(&Path, u32) -> bool,
) {
results.security_findings.retain(|finding| {
line_in_diff(&finding.path, finding.line)
|| finding.trace.iter().any(|hop| {
line_in_diff(&hop.path, hop.line)
|| (matches!(hop.role, TraceHopRole::SecretSource) && touches_file(&hop.path))
})
|| finding.reachability.as_ref().is_some_and(|reachability| {
reachability
.untrusted_source_trace
.iter()
.any(|hop| line_in_diff(&hop.path, hop.line))
})
});
results
.security_unresolved_callee_diagnostics
.retain(|finding| line_in_diff(&finding.path, finding.line));
}
fn filter_dependency_findings(
results: &mut AnalysisResults,
line_in_diff: &dyn Fn(&Path, u32) -> bool,
) {
for finding in &mut results.unlisted_dependencies {
finding
.dep
.imported_from
.retain(|source| line_in_diff(&source.path, source.line));
}
results
.unlisted_dependencies
.retain(|finding| !finding.dep.imported_from.is_empty());
}
fn filter_graph_findings(
results: &mut AnalysisResults,
touches_file: &dyn Fn(&Path) -> bool,
line_in_diff: &dyn Fn(&Path, u32) -> bool,
) {
results.duplicate_exports.retain(|finding| {
finding
.export
.locations
.iter()
.any(|location| line_in_diff(&location.path, location.line))
});
results
.circular_dependencies
.retain(|cycle| cycle.cycle.files.iter().any(|path| touches_file(path)));
results
.re_export_cycles
.retain(|cycle| cycle.cycle.files.iter().any(|path| touches_file(path)));
results
.boundary_violations
.retain(|finding| line_in_diff(&finding.violation.from_path, finding.violation.line));
results
.stale_suppressions
.retain(|finding| line_in_diff(&finding.path, finding.line));
}
fn filter_framework_findings(
results: &mut AnalysisResults,
line_in_diff: &dyn Fn(&Path, u32) -> bool,
) {
results
.invalid_client_exports
.retain(|finding| line_in_diff(&finding.export.path, finding.export.line));
results
.mixed_client_server_barrels
.retain(|finding| line_in_diff(&finding.barrel.path, finding.barrel.line));
results
.misplaced_directives
.retain(|finding| line_in_diff(&finding.directive_site.path, finding.directive_site.line));
results
.route_collisions
.retain(|finding| line_in_diff(&finding.collision.path, finding.collision.line));
results
.dynamic_segment_name_conflicts
.retain(|finding| line_in_diff(&finding.conflict.path, finding.conflict.line));
}