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 results.prop_drilling_chains.retain(|finding| {
47 finding.effective_severity != Some(fallow_types::output_dead_code::EffectiveSeverity::Error)
48 || finding.chain.hops.is_empty()
49 || finding
50 .chain
51 .hops
52 .iter()
53 .any(|hop| line_in_diff(&hop.file, hop.line))
54 });
55}
56
57pub fn filter_duplication_by_diff(report: &mut DuplicationReport, diff: &DiffIndex, root: &Path) {
65 let instance_overlaps = |instance: &CloneInstance| -> bool {
66 let Some(rel) = diff.key_for(&instance.file, root) else {
67 return true;
68 };
69 let start = u64::try_from(instance.start_line).unwrap_or(u64::MAX);
70 let end = u64::try_from(instance.end_line).unwrap_or(u64::MAX);
71 diff.range_overlaps_added(&rel, start, end)
72 };
73 report
74 .clone_groups
75 .retain(|group| group.instances.iter().any(instance_overlaps));
76 crate::duplicates::refresh_scoped_report(report, root);
77}
78
79fn filter_source_findings(
80 results: &mut AnalysisResults,
81 touches_file: &dyn Fn(&Path) -> bool,
82 line_in_diff: &dyn Fn(&Path, u32) -> bool,
83) {
84 results
85 .unused_files
86 .retain(|finding| touches_file(&finding.file.path));
87 results
88 .unused_exports
89 .retain(|finding| line_in_diff(&finding.export.path, finding.export.line));
90 results
91 .unused_types
92 .retain(|finding| line_in_diff(&finding.export.path, finding.export.line));
93 results
94 .private_type_leaks
95 .retain(|finding| line_in_diff(&finding.leak.path, finding.leak.line));
96 results
97 .deprecated_exports_in_use
98 .retain(|finding| line_in_diff(&finding.export.path, finding.export.line));
99 results
100 .unused_enum_members
101 .retain(|finding| line_in_diff(&finding.member.path, finding.member.line));
102 results
103 .unused_class_members
104 .retain(|finding| line_in_diff(&finding.member.path, finding.member.line));
105 results
106 .unused_store_members
107 .retain(|finding| line_in_diff(&finding.member.path, finding.member.line));
108 results
109 .unprovided_injects
110 .retain(|finding| line_in_diff(&finding.inject.path, finding.inject.line));
111 results
112 .unrendered_components
113 .retain(|finding| line_in_diff(&finding.component.path, finding.component.line));
114 results
115 .unused_component_props
116 .retain(|finding| line_in_diff(&finding.prop.path, finding.prop.line));
117 results
118 .absent_component_props
119 .retain(|finding| line_in_diff(&finding.prop.path, finding.prop.line));
120 results
121 .unused_component_emits
122 .retain(|finding| line_in_diff(&finding.emit.path, finding.emit.line));
123 results
124 .unused_component_inputs
125 .retain(|finding| line_in_diff(&finding.input.path, finding.input.line));
126 results
127 .unused_component_outputs
128 .retain(|finding| line_in_diff(&finding.output.path, finding.output.line));
129 results
130 .unused_svelte_events
131 .retain(|finding| line_in_diff(&finding.event.path, finding.event.line));
132 results
133 .unused_effect_layers
134 .retain(|finding| line_in_diff(&finding.layer.path, finding.layer.line));
135 results
136 .unused_tagged_errors
137 .retain(|finding| line_in_diff(&finding.error.path, finding.error.line));
138 results
139 .unproduced_catch_tags
140 .retain(|finding| line_in_diff(&finding.catch_tag.path, finding.catch_tag.line));
141 results
142 .unused_server_actions
143 .retain(|finding| line_in_diff(&finding.action.path, finding.action.line));
144 results
145 .unused_load_data_keys
146 .retain(|finding| line_in_diff(&finding.key.path, finding.key.line));
147 results
148 .unresolved_imports
149 .retain(|finding| line_in_diff(&finding.import.path, finding.import.line));
150}
151
152fn filter_security_findings(
153 results: &mut AnalysisResults,
154 touches_file: &dyn Fn(&Path) -> bool,
155 line_in_diff: &dyn Fn(&Path, u32) -> bool,
156) {
157 results.security_findings.retain(|finding| {
158 line_in_diff(&finding.path, finding.line)
159 || finding.trace.iter().any(|hop| {
160 line_in_diff(&hop.path, hop.line)
161 || (matches!(hop.role, TraceHopRole::SecretSource) && touches_file(&hop.path))
162 })
163 || finding.reachability.as_ref().is_some_and(|reachability| {
164 reachability
169 .untrusted_source_trace
170 .iter()
171 .any(|hop| line_in_diff(&hop.path, hop.line))
172 })
173 });
174 results
175 .security_unresolved_callee_diagnostics
176 .retain(|finding| line_in_diff(&finding.path, finding.line));
177}
178
179fn filter_dependency_findings(
180 results: &mut AnalysisResults,
181 line_in_diff: &dyn Fn(&Path, u32) -> bool,
182) {
183 for finding in &mut results.unlisted_dependencies {
184 finding
185 .dep
186 .imported_from
187 .retain(|source| line_in_diff(&source.path, source.line));
188 }
189 results
190 .unlisted_dependencies
191 .retain(|finding| !finding.dep.imported_from.is_empty());
192}
193
194fn filter_graph_findings(
195 results: &mut AnalysisResults,
196 touches_file: &dyn Fn(&Path) -> bool,
197 line_in_diff: &dyn Fn(&Path, u32) -> bool,
198) {
199 results.duplicate_exports.retain(|finding| {
200 finding
201 .export
202 .locations
203 .iter()
204 .any(|location| line_in_diff(&location.path, location.line))
205 });
206 results
207 .circular_dependencies
208 .retain(|cycle| cycle.cycle.files.iter().any(|path| touches_file(path)));
209 results
210 .re_export_cycles
211 .retain(|cycle| cycle.cycle.files.iter().any(|path| touches_file(path)));
212 results.package_cycles.retain(|cycle| {
213 cycle
214 .cycle
215 .edges
216 .iter()
217 .any(|edge| touches_file(&edge.path))
218 });
219 results
220 .boundary_violations
221 .retain(|finding| line_in_diff(&finding.violation.from_path, finding.violation.line));
222 results
223 .stale_suppressions
224 .retain(|finding| line_in_diff(&finding.path, finding.line));
225}
226
227fn filter_framework_findings(
228 results: &mut AnalysisResults,
229 line_in_diff: &dyn Fn(&Path, u32) -> bool,
230) {
231 results
232 .invalid_client_exports
233 .retain(|finding| line_in_diff(&finding.export.path, finding.export.line));
234 results
235 .mixed_client_server_barrels
236 .retain(|finding| line_in_diff(&finding.barrel.path, finding.barrel.line));
237 results
238 .misplaced_directives
239 .retain(|finding| line_in_diff(&finding.directive_site.path, finding.directive_site.line));
240 results
241 .route_collisions
242 .retain(|finding| line_in_diff(&finding.collision.path, finding.collision.line));
243 results
244 .dynamic_segment_name_conflicts
245 .retain(|finding| line_in_diff(&finding.conflict.path, finding.conflict.line));
246}