fallow_engine/
diff_scope.rs1use std::path::Path;
9
10use fallow_output::DiffIndex;
11use fallow_types::duplicates::{CloneInstance, DuplicationReport};
12use fallow_types::results::{AnalysisResults, TraceHopRole};
13
14pub fn filter_dead_code_by_diff(results: &mut AnalysisResults, diff: &DiffIndex, root: &Path) {
32 let touches_file = |path: &Path| -> bool {
33 diff.key_for(path, root)
34 .is_none_or(|rel| diff.touches_file(&rel))
35 };
36 let line_in_diff = |path: &Path, line: u32| -> bool {
37 diff.key_for(path, root)
38 .is_none_or(|rel| diff.line_is_added(&rel, u64::from(line)))
39 };
40
41 filter_source_findings(results, &touches_file, &line_in_diff);
42 filter_security_findings(results, &touches_file, &line_in_diff);
43 filter_dependency_findings(results, &line_in_diff);
44 filter_graph_findings(results, &touches_file, &line_in_diff);
45 filter_framework_findings(results, &line_in_diff);
46}
47
48pub fn filter_duplication_by_diff(report: &mut DuplicationReport, diff: &DiffIndex, root: &Path) {
56 let instance_overlaps = |instance: &CloneInstance| -> bool {
57 let Some(rel) = diff.key_for(&instance.file, root) else {
58 return true;
59 };
60 let start = u64::try_from(instance.start_line).unwrap_or(u64::MAX);
61 let end = u64::try_from(instance.end_line).unwrap_or(u64::MAX);
62 diff.range_overlaps_added(&rel, start, end)
63 };
64 report
65 .clone_groups
66 .retain(|group| group.instances.iter().any(instance_overlaps));
67 crate::duplicates::refresh_scoped_report(report, root);
68}
69
70fn filter_source_findings(
71 results: &mut AnalysisResults,
72 touches_file: &dyn Fn(&Path) -> bool,
73 line_in_diff: &dyn Fn(&Path, u32) -> bool,
74) {
75 results
76 .unused_files
77 .retain(|finding| touches_file(&finding.file.path));
78 results
79 .unused_exports
80 .retain(|finding| line_in_diff(&finding.export.path, finding.export.line));
81 results
82 .unused_types
83 .retain(|finding| line_in_diff(&finding.export.path, finding.export.line));
84 results
85 .private_type_leaks
86 .retain(|finding| line_in_diff(&finding.leak.path, finding.leak.line));
87 results
88 .deprecated_exports_in_use
89 .retain(|finding| line_in_diff(&finding.export.path, finding.export.line));
90 results
91 .unused_enum_members
92 .retain(|finding| line_in_diff(&finding.member.path, finding.member.line));
93 results
94 .unused_class_members
95 .retain(|finding| line_in_diff(&finding.member.path, finding.member.line));
96 results
97 .unused_store_members
98 .retain(|finding| line_in_diff(&finding.member.path, finding.member.line));
99 results
100 .unprovided_injects
101 .retain(|finding| line_in_diff(&finding.inject.path, finding.inject.line));
102 results
103 .unrendered_components
104 .retain(|finding| line_in_diff(&finding.component.path, finding.component.line));
105 results
106 .unused_component_props
107 .retain(|finding| line_in_diff(&finding.prop.path, finding.prop.line));
108 results
109 .unused_component_emits
110 .retain(|finding| line_in_diff(&finding.emit.path, finding.emit.line));
111 results
112 .unused_component_inputs
113 .retain(|finding| line_in_diff(&finding.input.path, finding.input.line));
114 results
115 .unused_component_outputs
116 .retain(|finding| line_in_diff(&finding.output.path, finding.output.line));
117 results
118 .unused_svelte_events
119 .retain(|finding| line_in_diff(&finding.event.path, finding.event.line));
120 results
121 .unused_server_actions
122 .retain(|finding| line_in_diff(&finding.action.path, finding.action.line));
123 results
124 .unused_load_data_keys
125 .retain(|finding| line_in_diff(&finding.key.path, finding.key.line));
126 results
127 .unresolved_imports
128 .retain(|finding| line_in_diff(&finding.import.path, finding.import.line));
129}
130
131fn filter_security_findings(
132 results: &mut AnalysisResults,
133 touches_file: &dyn Fn(&Path) -> bool,
134 line_in_diff: &dyn Fn(&Path, u32) -> bool,
135) {
136 results.security_findings.retain(|finding| {
137 line_in_diff(&finding.path, finding.line)
138 || finding.trace.iter().any(|hop| {
139 line_in_diff(&hop.path, hop.line)
140 || (matches!(hop.role, TraceHopRole::SecretSource) && touches_file(&hop.path))
141 })
142 || finding.reachability.as_ref().is_some_and(|reachability| {
143 reachability
148 .untrusted_source_trace
149 .iter()
150 .any(|hop| line_in_diff(&hop.path, hop.line))
151 })
152 });
153 results
154 .security_unresolved_callee_diagnostics
155 .retain(|finding| line_in_diff(&finding.path, finding.line));
156}
157
158fn filter_dependency_findings(
159 results: &mut AnalysisResults,
160 line_in_diff: &dyn Fn(&Path, u32) -> bool,
161) {
162 for finding in &mut results.unlisted_dependencies {
163 finding
164 .dep
165 .imported_from
166 .retain(|source| line_in_diff(&source.path, source.line));
167 }
168 results
169 .unlisted_dependencies
170 .retain(|finding| !finding.dep.imported_from.is_empty());
171}
172
173fn filter_graph_findings(
174 results: &mut AnalysisResults,
175 touches_file: &dyn Fn(&Path) -> bool,
176 line_in_diff: &dyn Fn(&Path, u32) -> bool,
177) {
178 results.duplicate_exports.retain(|finding| {
179 finding
180 .export
181 .locations
182 .iter()
183 .any(|location| line_in_diff(&location.path, location.line))
184 });
185 results
186 .circular_dependencies
187 .retain(|cycle| cycle.cycle.files.iter().any(|path| touches_file(path)));
188 results
189 .re_export_cycles
190 .retain(|cycle| cycle.cycle.files.iter().any(|path| touches_file(path)));
191 results
192 .boundary_violations
193 .retain(|finding| line_in_diff(&finding.violation.from_path, finding.violation.line));
194 results
195 .stale_suppressions
196 .retain(|finding| line_in_diff(&finding.path, finding.line));
197}
198
199fn filter_framework_findings(
200 results: &mut AnalysisResults,
201 line_in_diff: &dyn Fn(&Path, u32) -> bool,
202) {
203 results
204 .invalid_client_exports
205 .retain(|finding| line_in_diff(&finding.export.path, finding.export.line));
206 results
207 .mixed_client_server_barrels
208 .retain(|finding| line_in_diff(&finding.barrel.path, finding.barrel.line));
209 results
210 .misplaced_directives
211 .retain(|finding| line_in_diff(&finding.directive_site.path, finding.directive_site.line));
212 results
213 .route_collisions
214 .retain(|finding| line_in_diff(&finding.collision.path, finding.collision.line));
215 results
216 .dynamic_segment_name_conflicts
217 .retain(|finding| line_in_diff(&finding.conflict.path, finding.conflict.line));
218}