fallow_engine/
dead_code.rs1use std::path::{Path, PathBuf};
4
5use rustc_hash::FxHashSet;
6
7use crate::AnalysisResults;
8
9pub fn filter_to_workspaces(results: &mut AnalysisResults, ws_roots: &[PathBuf]) {
14 let any_under = |path: &Path| ws_roots.iter().any(|root| path.starts_with(root));
15 let pkg_jsons = ws_roots
16 .iter()
17 .map(|root| root.join("package.json"))
18 .collect::<Vec<_>>();
19 let in_pkg_jsons = |path: &Path| pkg_jsons.iter().any(|pkg| path == pkg);
20
21 filter_workspace_source_findings(results, &any_under);
22 filter_workspace_dependency_findings(results, &any_under, &in_pkg_jsons);
23 filter_workspace_graph_findings(results, &any_under);
24 filter_workspace_policy_findings(results, &any_under);
25}
26
27#[expect(
29 clippy::implicit_hasher,
30 reason = "fallow standardizes on FxHashSet across the workspace"
31)]
32pub fn filter_by_changed_files(results: &mut AnalysisResults, changed_files: &FxHashSet<PathBuf>) {
33 fallow_core::changed_files::filter_results_by_changed_files(results, changed_files);
34}
35
36fn filter_workspace_source_findings(
37 results: &mut AnalysisResults,
38 any_under: &dyn Fn(&Path) -> bool,
39) {
40 results
41 .unused_files
42 .retain(|finding| any_under(&finding.file.path));
43 results
44 .unused_exports
45 .retain(|finding| any_under(&finding.export.path));
46 results
47 .unused_types
48 .retain(|finding| any_under(&finding.export.path));
49 results
50 .private_type_leaks
51 .retain(|finding| any_under(&finding.leak.path));
52 results
53 .unused_enum_members
54 .retain(|finding| any_under(&finding.member.path));
55 results
56 .unused_class_members
57 .retain(|finding| any_under(&finding.member.path));
58 results
59 .unused_store_members
60 .retain(|finding| any_under(&finding.member.path));
61 results
62 .unprovided_injects
63 .retain(|finding| any_under(&finding.inject.path));
64 results
65 .unrendered_components
66 .retain(|finding| any_under(&finding.component.path));
67 results
68 .unused_component_props
69 .retain(|finding| any_under(&finding.prop.path));
70 results
71 .unused_component_emits
72 .retain(|finding| any_under(&finding.emit.path));
73 results
74 .unused_component_inputs
75 .retain(|finding| any_under(&finding.input.path));
76 results
77 .unused_component_outputs
78 .retain(|finding| any_under(&finding.output.path));
79 results
80 .unused_svelte_events
81 .retain(|finding| any_under(&finding.event.path));
82 results
83 .unused_server_actions
84 .retain(|finding| any_under(&finding.action.path));
85 results
86 .unused_load_data_keys
87 .retain(|finding| any_under(&finding.key.path));
88 results
89 .unresolved_imports
90 .retain(|finding| any_under(&finding.import.path));
91}
92
93fn filter_workspace_dependency_findings(
94 results: &mut AnalysisResults,
95 any_under: &dyn Fn(&Path) -> bool,
96 in_pkg_jsons: &dyn Fn(&Path) -> bool,
97) {
98 results
99 .unused_dependencies
100 .retain(|finding| in_pkg_jsons(&finding.dep.path));
101 results
102 .unused_dev_dependencies
103 .retain(|finding| in_pkg_jsons(&finding.dep.path));
104 results
105 .unused_optional_dependencies
106 .retain(|finding| in_pkg_jsons(&finding.dep.path));
107 results
108 .type_only_dependencies
109 .retain(|finding| in_pkg_jsons(&finding.dep.path));
110 results
111 .test_only_dependencies
112 .retain(|finding| in_pkg_jsons(&finding.dep.path));
113
114 results.unlisted_dependencies.retain(|finding| {
115 finding
116 .dep
117 .imported_from
118 .iter()
119 .any(|source| any_under(&source.path))
120 });
121 results.unused_dependency_overrides.clear();
122 results.misconfigured_dependency_overrides.clear();
123}
124
125fn filter_workspace_graph_findings(
126 results: &mut AnalysisResults,
127 any_under: &dyn Fn(&Path) -> bool,
128) {
129 for duplicate in &mut results.duplicate_exports {
130 duplicate
131 .export
132 .locations
133 .retain(|location| any_under(&location.path));
134 }
135 results
136 .duplicate_exports
137 .retain(|duplicate| duplicate.export.locations.len() >= 2);
138
139 results
140 .circular_dependencies
141 .retain(|cycle| cycle.cycle.files.iter().any(|path| any_under(path)));
142
143 results
144 .re_export_cycles
145 .retain(|cycle| cycle.cycle.files.iter().any(|path| any_under(path)));
146}
147
148fn filter_workspace_policy_findings(
149 results: &mut AnalysisResults,
150 any_under: &dyn Fn(&Path) -> bool,
151) {
152 results
153 .boundary_violations
154 .retain(|finding| any_under(&finding.violation.from_path));
155 results
156 .boundary_coverage_violations
157 .retain(|finding| any_under(&finding.violation.path));
158 results
159 .boundary_call_violations
160 .retain(|finding| any_under(&finding.violation.path));
161 results
162 .policy_violations
163 .retain(|finding| any_under(&finding.violation.path));
164
165 results
166 .stale_suppressions
167 .retain(|finding| any_under(&finding.path));
168
169 results
170 .security_findings
171 .retain(|finding| any_under(&finding.path));
172 results
173 .security_unresolved_callee_diagnostics
174 .retain(|finding| any_under(&finding.path));
175
176 results.unused_catalog_entries.clear();
177 results.empty_catalog_groups.clear();
178 results
179 .unresolved_catalog_references
180 .retain(|finding| any_under(&finding.reference.path));
181
182 results
183 .invalid_client_exports
184 .retain(|finding| any_under(&finding.export.path));
185
186 results
187 .mixed_client_server_barrels
188 .retain(|finding| any_under(&finding.barrel.path));
189
190 results
191 .misplaced_directives
192 .retain(|finding| any_under(&finding.directive_site.path));
193
194 results
195 .route_collisions
196 .retain(|finding| any_under(&finding.collision.path));
197
198 results
199 .dynamic_segment_name_conflicts
200 .retain(|finding| any_under(&finding.conflict.path));
201}
202
203#[cfg(test)]
204mod tests {
205 use std::path::PathBuf;
206
207 use super::*;
208 use fallow_types::output_dead_code::UnusedFileFinding;
209 use fallow_types::results::UnusedFile;
210
211 #[test]
212 fn workspace_filter_keeps_findings_under_workspace_root() {
213 let root = PathBuf::from("/repo/packages/app");
214 let mut results = AnalysisResults::default();
215 results
216 .unused_files
217 .push(UnusedFileFinding::with_actions(UnusedFile {
218 path: root.join("src/unused.ts"),
219 }));
220 results
221 .unused_files
222 .push(UnusedFileFinding::with_actions(UnusedFile {
223 path: PathBuf::from("/repo/packages/docs/src/unused.ts"),
224 }));
225
226 filter_to_workspaces(&mut results, std::slice::from_ref(&root));
227
228 assert_eq!(results.unused_files.len(), 1);
229 assert_eq!(
230 results.unused_files[0].file.path,
231 root.join("src/unused.ts")
232 );
233 }
234}