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 .absent_component_props
110 .retain(|finding| line_in_diff(&finding.prop.path, finding.prop.line));
111 results
112 .unused_component_emits
113 .retain(|finding| line_in_diff(&finding.emit.path, finding.emit.line));
114 results
115 .unused_component_inputs
116 .retain(|finding| line_in_diff(&finding.input.path, finding.input.line));
117 results
118 .unused_component_outputs
119 .retain(|finding| line_in_diff(&finding.output.path, finding.output.line));
120 results
121 .unused_svelte_events
122 .retain(|finding| line_in_diff(&finding.event.path, finding.event.line));
123 results
124 .unused_server_actions
125 .retain(|finding| line_in_diff(&finding.action.path, finding.action.line));
126 results
127 .unused_load_data_keys
128 .retain(|finding| line_in_diff(&finding.key.path, finding.key.line));
129 results
130 .unresolved_imports
131 .retain(|finding| line_in_diff(&finding.import.path, finding.import.line));
132}
133
134fn filter_security_findings(
135 results: &mut AnalysisResults,
136 touches_file: &dyn Fn(&Path) -> bool,
137 line_in_diff: &dyn Fn(&Path, u32) -> bool,
138) {
139 results.security_findings.retain(|finding| {
140 line_in_diff(&finding.path, finding.line)
141 || finding.trace.iter().any(|hop| {
142 line_in_diff(&hop.path, hop.line)
143 || (matches!(hop.role, TraceHopRole::SecretSource) && touches_file(&hop.path))
144 })
145 || finding.reachability.as_ref().is_some_and(|reachability| {
146 reachability
151 .untrusted_source_trace
152 .iter()
153 .any(|hop| line_in_diff(&hop.path, hop.line))
154 })
155 });
156 results
157 .security_unresolved_callee_diagnostics
158 .retain(|finding| line_in_diff(&finding.path, finding.line));
159}
160
161fn filter_dependency_findings(
162 results: &mut AnalysisResults,
163 line_in_diff: &dyn Fn(&Path, u32) -> bool,
164) {
165 for finding in &mut results.unlisted_dependencies {
166 finding
167 .dep
168 .imported_from
169 .retain(|source| line_in_diff(&source.path, source.line));
170 }
171 results
172 .unlisted_dependencies
173 .retain(|finding| !finding.dep.imported_from.is_empty());
174}
175
176fn filter_graph_findings(
177 results: &mut AnalysisResults,
178 touches_file: &dyn Fn(&Path) -> bool,
179 line_in_diff: &dyn Fn(&Path, u32) -> bool,
180) {
181 results.duplicate_exports.retain(|finding| {
182 finding
183 .export
184 .locations
185 .iter()
186 .any(|location| line_in_diff(&location.path, location.line))
187 });
188 results
189 .circular_dependencies
190 .retain(|cycle| cycle.cycle.files.iter().any(|path| touches_file(path)));
191 results
192 .re_export_cycles
193 .retain(|cycle| cycle.cycle.files.iter().any(|path| touches_file(path)));
194 results.package_cycles.retain(|cycle| {
195 cycle
196 .cycle
197 .edges
198 .iter()
199 .any(|edge| touches_file(&edge.path))
200 });
201 results
202 .boundary_violations
203 .retain(|finding| line_in_diff(&finding.violation.from_path, finding.violation.line));
204 results
205 .stale_suppressions
206 .retain(|finding| line_in_diff(&finding.path, finding.line));
207}
208
209fn filter_framework_findings(
210 results: &mut AnalysisResults,
211 line_in_diff: &dyn Fn(&Path, u32) -> bool,
212) {
213 results
214 .invalid_client_exports
215 .retain(|finding| line_in_diff(&finding.export.path, finding.export.line));
216 results
217 .mixed_client_server_barrels
218 .retain(|finding| line_in_diff(&finding.barrel.path, finding.barrel.line));
219 results
220 .misplaced_directives
221 .retain(|finding| line_in_diff(&finding.directive_site.path, finding.directive_site.line));
222 results
223 .route_collisions
224 .retain(|finding| line_in_diff(&finding.collision.path, finding.collision.line));
225 results
226 .dynamic_segment_name_conflicts
227 .retain(|finding| line_in_diff(&finding.conflict.path, finding.conflict.line));
228}