Skip to main content

fallow_api/runtime/
dead_code.rs

1use std::path::Path;
2use std::time::Instant;
3
4use fallow_config::ProductionAnalysis;
5use fallow_engine::{
6    dead_code::DeadCodeAnalysisArtifacts,
7    project_config::{ProjectConfig, ProjectConfigOptions},
8    session::AnalysisSession,
9};
10use fallow_output::{
11    CHECK_SCHEMA_VERSION, CheckOutputInput, DeadCodeNextStepsInput, DiffIndex, build_check_output,
12    build_dead_code_next_steps, check_meta, relative_to_diff_path,
13};
14use fallow_types::output_format::OutputFormat;
15use fallow_types::path_util::is_absolute_path_any_platform;
16use fallow_types::results::{AnalysisResults, TraceHopRole};
17use rustc_hash::FxHashSet;
18
19use crate::{
20    AnalysisOptions, BoundaryViolationsProgrammaticOutput, CircularDependenciesProgrammaticOutput,
21    DeadCodeFilters, DeadCodeOptions, DeadCodeProgrammaticOutput, ProgrammaticError,
22    analysis_context::{
23        ProgrammaticAnalysisContext, changed_files_for_run,
24        resolve_programmatic_analysis_context_deferred_workspace, workspace_roots_for_session,
25    },
26    next_steps::{
27        default_workspace_ref_for_workspaces, setup_pointer_applicable, suggestions_enabled,
28    },
29};
30
31use super::{ProgrammaticResult, root_envelope_mode};
32
33pub(super) struct DeadCodeProgrammaticRunWithArtifacts {
34    pub output: DeadCodeProgrammaticOutput,
35    pub artifacts: DeadCodeAnalysisArtifacts,
36}
37
38/// Run dead-code analysis and return typed API output before serialization.
39///
40/// # Errors
41///
42/// Returns a structured programmatic error for unsupported options, invalid
43/// options, config load failures, analysis failures, or git changed-file
44/// failures.
45pub fn run_dead_code(options: &DeadCodeOptions) -> ProgrammaticResult<DeadCodeProgrammaticOutput> {
46    let resolved = resolve_programmatic_analysis_context_deferred_workspace(&options.analysis)?;
47    resolved.install(|| run_dead_code_inner(options, &resolved, |_| {}))
48}
49
50/// Run circular-dependency analysis and return typed API output before JSON.
51///
52/// # Errors
53///
54/// Returns the same structured errors as [`run_dead_code`].
55pub fn run_circular_dependencies(
56    options: &DeadCodeOptions,
57) -> ProgrammaticResult<CircularDependenciesProgrammaticOutput> {
58    let resolved = resolve_programmatic_analysis_context_deferred_workspace(&options.analysis)?;
59    resolved.install(|| {
60        run_dead_code_inner(options, &resolved, keep_circular_dependencies).map(Into::into)
61    })
62}
63
64/// Run boundary-family analysis and return typed API output before JSON.
65///
66/// # Errors
67///
68/// Returns the same structured errors as [`run_dead_code`].
69pub fn run_boundary_violations(
70    options: &DeadCodeOptions,
71) -> ProgrammaticResult<BoundaryViolationsProgrammaticOutput> {
72    let resolved = resolve_programmatic_analysis_context_deferred_workspace(&options.analysis)?;
73    resolved.install(|| {
74        run_dead_code_inner(options, &resolved, keep_boundary_violations).map(Into::into)
75    })
76}
77
78fn run_dead_code_inner(
79    options: &DeadCodeOptions,
80    resolved: &ProgrammaticAnalysisContext,
81    post_filter: impl FnOnce(&mut AnalysisResults),
82) -> ProgrammaticResult<DeadCodeProgrammaticOutput> {
83    let start = Instant::now();
84    let session = load_dead_code_session(options, resolved)?;
85    run_dead_code_with_session(options, resolved, &session, None, post_filter, start)
86}
87
88pub(super) fn run_dead_code_with_session(
89    options: &DeadCodeOptions,
90    resolved: &ProgrammaticAnalysisContext,
91    session: &AnalysisSession,
92    changed_files: Option<&FxHashSet<std::path::PathBuf>>,
93    post_filter: impl FnOnce(&mut AnalysisResults),
94    start: Instant,
95) -> ProgrammaticResult<DeadCodeProgrammaticOutput> {
96    let analysis = session.analyze_dead_code().map_err(|err| {
97        ProgrammaticError::new(format!("dead-code analysis failed: {err}"), 2)
98            .with_code("FALLOW_DEAD_CODE_FAILED")
99            .with_context("dead-code")
100    })?;
101    let mut results = analysis.results;
102
103    apply_dead_code_scope(options, resolved, session, changed_files, &mut results)?;
104    apply_dead_code_filters(&options.filters, &mut results);
105    post_filter(&mut results);
106
107    Ok(build_dead_code_programmatic_output(
108        options, resolved, session, results, start,
109    ))
110}
111
112pub(super) fn run_dead_code_with_session_artifacts(
113    options: &DeadCodeOptions,
114    resolved: &ProgrammaticAnalysisContext,
115    session: &AnalysisSession,
116    changed_files: Option<&FxHashSet<std::path::PathBuf>>,
117    post_filter: impl FnOnce(&mut AnalysisResults),
118    start: Instant,
119) -> ProgrammaticResult<DeadCodeProgrammaticRunWithArtifacts> {
120    let mut artifacts = session
121        .analyze_dead_code_with_artifacts(true, true)
122        .map_err(|err| {
123            ProgrammaticError::new(format!("dead-code analysis failed: {err}"), 2)
124                .with_code("FALLOW_DEAD_CODE_FAILED")
125                .with_context("dead-code")
126        })?;
127
128    apply_dead_code_scope(
129        options,
130        resolved,
131        session,
132        changed_files,
133        &mut artifacts.results,
134    )?;
135    apply_dead_code_filters(&options.filters, &mut artifacts.results);
136    post_filter(&mut artifacts.results);
137
138    Ok(build_dead_code_run_with_artifacts(
139        options, resolved, session, artifacts, start,
140    ))
141}
142
143pub(super) fn run_dead_code_from_artifacts(
144    options: &DeadCodeOptions,
145    resolved: &ProgrammaticAnalysisContext,
146    session: &AnalysisSession,
147    changed_files: Option<&FxHashSet<std::path::PathBuf>>,
148    mut artifacts: DeadCodeAnalysisArtifacts,
149    start: Instant,
150) -> ProgrammaticResult<DeadCodeProgrammaticRunWithArtifacts> {
151    apply_dead_code_scope(
152        options,
153        resolved,
154        session,
155        changed_files,
156        &mut artifacts.results,
157    )?;
158    apply_dead_code_filters(&options.filters, &mut artifacts.results);
159
160    Ok(build_dead_code_run_with_artifacts(
161        options, resolved, session, artifacts, start,
162    ))
163}
164
165fn build_dead_code_run_with_artifacts(
166    options: &DeadCodeOptions,
167    resolved: &ProgrammaticAnalysisContext,
168    session: &AnalysisSession,
169    artifacts: DeadCodeAnalysisArtifacts,
170    start: Instant,
171) -> DeadCodeProgrammaticRunWithArtifacts {
172    let output = build_dead_code_programmatic_output(
173        options,
174        resolved,
175        session,
176        artifacts.results.clone(),
177        start,
178    );
179    DeadCodeProgrammaticRunWithArtifacts { output, artifacts }
180}
181
182fn build_dead_code_programmatic_output(
183    options: &DeadCodeOptions,
184    resolved: &ProgrammaticAnalysisContext,
185    session: &AnalysisSession,
186    results: AnalysisResults,
187    start: Instant,
188) -> DeadCodeProgrammaticOutput {
189    let root = session.root();
190    let next_steps = build_dead_code_next_steps(DeadCodeNextStepsInput {
191        suggestions_enabled: suggestions_enabled(),
192        results: &results,
193        root,
194        offer_setup: setup_pointer_applicable(root),
195        impact_digest: None,
196        workspace_ref: default_workspace_ref_for_workspaces(root, session.workspaces()).as_deref(),
197        audit_changed: fallow_engine::churn::is_git_repo(root),
198    });
199    let config_fixable =
200        fallow_config::is_config_fixable(&resolved.root, resolved.config_path.as_ref());
201    let output = build_check_output(CheckOutputInput {
202        schema_version: CHECK_SCHEMA_VERSION,
203        version: env!("CARGO_PKG_VERSION").to_string(),
204        elapsed: start.elapsed(),
205        results,
206        config_fixable,
207        meta: options.analysis.explain.then(check_meta),
208        workspace_diagnostics: session.workspace_diagnostics().to_vec(),
209        next_steps,
210    });
211    DeadCodeProgrammaticOutput {
212        output,
213        root: session.root().to_path_buf(),
214        config_fixable,
215        envelope_mode: root_envelope_mode(),
216        telemetry_analysis_run_id: None,
217    }
218}
219
220fn keep_circular_dependencies(results: &mut AnalysisResults) {
221    let entry_point_summary = results.entry_point_summary.take();
222    let circular_dependencies = std::mem::take(&mut results.circular_dependencies);
223    *results = AnalysisResults::default();
224    results.entry_point_summary = entry_point_summary;
225    results.circular_dependencies = circular_dependencies;
226}
227
228fn keep_boundary_violations(results: &mut AnalysisResults) {
229    let entry_point_summary = results.entry_point_summary.take();
230    let boundary_violations = std::mem::take(&mut results.boundary_violations);
231    let boundary_coverage_violations = std::mem::take(&mut results.boundary_coverage_violations);
232    let boundary_call_violations = std::mem::take(&mut results.boundary_call_violations);
233    *results = AnalysisResults::default();
234    results.entry_point_summary = entry_point_summary;
235    results.boundary_violations = boundary_violations;
236    results.boundary_coverage_violations = boundary_coverage_violations;
237    results.boundary_call_violations = boundary_call_violations;
238}
239
240pub(super) fn load_dead_code_session(
241    options: &DeadCodeOptions,
242    resolved: &ProgrammaticAnalysisContext,
243) -> ProgrammaticResult<AnalysisSession> {
244    let project_config = fallow_engine::project_config::config_for_project_analysis(
245        &resolved.root,
246        resolved.config_path.as_deref(),
247        ProjectConfigOptions {
248            output: OutputFormat::Json,
249            no_cache: resolved.no_cache,
250            threads: resolved.threads,
251            production_override: resolved.production_override,
252            quiet: true,
253            analysis: ProductionAnalysis::DeadCode,
254        },
255    )
256    .map_err(|err| {
257        ProgrammaticError::new(format!("failed to load config: {err}"), 2)
258            .with_code("FALLOW_CONFIG_LOAD_FAILED")
259            .with_context("analysis.configPath")
260    })?;
261    let project_config = configure_project_for_dead_code(project_config, options);
262    Ok(AnalysisSession::from_config(project_config))
263}
264
265pub(super) fn default_dead_code_options_for_context(
266    resolved: &ProgrammaticAnalysisContext,
267) -> DeadCodeOptions {
268    DeadCodeOptions {
269        analysis: AnalysisOptions {
270            root: Some(resolved.root().to_path_buf()),
271            config_path: resolved.config_path().clone(),
272            no_cache: resolved.no_cache(),
273            threads: Some(resolved.threads()),
274            production_override: resolved.production_override(),
275            changed_since: resolved.changed_since().map(str::to_owned),
276            workspace: resolved.workspace().map(<[String]>::to_vec),
277            changed_workspaces: resolved.changed_workspaces().map(str::to_owned),
278            explain: resolved.explain_enabled(),
279            ..AnalysisOptions::default()
280        },
281        filters: DeadCodeFilters::default(),
282        files: Vec::new(),
283        include_entry_exports: false,
284    }
285}
286
287fn configure_project_for_dead_code(
288    mut project_config: ProjectConfig,
289    options: &DeadCodeOptions,
290) -> ProjectConfig {
291    if options.include_entry_exports {
292        project_config.config.include_entry_exports = true;
293    }
294    activate_explicit_dead_code_opt_ins(&options.filters, &mut project_config.config.rules);
295    project_config
296}
297
298fn activate_explicit_dead_code_opt_ins(
299    filters: &DeadCodeFilters,
300    rules: &mut fallow_config::RulesConfig,
301) {
302    if filters.private_type_leaks && rules.private_type_leaks == fallow_config::Severity::Off {
303        rules.private_type_leaks = fallow_config::Severity::Warn;
304    }
305}
306
307fn apply_dead_code_scope(
308    options: &DeadCodeOptions,
309    resolved: &ProgrammaticAnalysisContext,
310    session: &AnalysisSession,
311    changed_files: Option<&FxHashSet<std::path::PathBuf>>,
312    results: &mut AnalysisResults,
313) -> ProgrammaticResult<()> {
314    let workspace_roots = workspace_roots_for_session(resolved, session.workspaces())?;
315    if let Some(workspace_roots) = workspace_roots.as_ref() {
316        fallow_engine::dead_code::filter_to_workspaces(results, workspace_roots);
317    }
318    let resolved_changed_files = if changed_files.is_some() {
319        None
320    } else {
321        changed_files_for_run(resolved)?
322    };
323    if let Some(changed_files) = changed_files.or(resolved_changed_files.as_ref()) {
324        fallow_engine::dead_code::filter_by_changed_files(results, changed_files);
325    }
326    if let Some(diff) = resolved.diff.as_ref() {
327        filter_dead_code_by_diff(results, diff, session.root());
328    }
329    apply_dead_code_file_filter(options, session.root(), results);
330    Ok(())
331}
332
333fn filter_dead_code_by_diff(results: &mut AnalysisResults, diff: &DiffIndex, root: &Path) {
334    let touches_file = |path: &Path| -> bool {
335        relative_to_diff_path(path, root).is_none_or(|rel| diff.touches_file(&rel))
336    };
337    let line_in_diff = |path: &Path, line: u32| -> bool {
338        relative_to_diff_path(path, root)
339            .is_none_or(|rel| diff.line_is_added(&rel, u64::from(line)))
340    };
341
342    filter_dead_code_source_findings(results, &touches_file, &line_in_diff);
343    filter_dead_code_security_findings(results, &touches_file, &line_in_diff);
344    filter_dead_code_dependency_findings(results, &line_in_diff);
345    filter_dead_code_graph_findings(results, &touches_file, &line_in_diff);
346    filter_dead_code_framework_findings(results, &line_in_diff);
347}
348
349fn filter_dead_code_source_findings(
350    results: &mut AnalysisResults,
351    touches_file: &dyn Fn(&Path) -> bool,
352    line_in_diff: &dyn Fn(&Path, u32) -> bool,
353) {
354    results
355        .unused_files
356        .retain(|finding| touches_file(&finding.file.path));
357    results
358        .unused_exports
359        .retain(|finding| line_in_diff(&finding.export.path, finding.export.line));
360    results
361        .unused_types
362        .retain(|finding| line_in_diff(&finding.export.path, finding.export.line));
363    results
364        .private_type_leaks
365        .retain(|finding| line_in_diff(&finding.leak.path, finding.leak.line));
366    results
367        .unused_enum_members
368        .retain(|finding| line_in_diff(&finding.member.path, finding.member.line));
369    results
370        .unused_class_members
371        .retain(|finding| line_in_diff(&finding.member.path, finding.member.line));
372    results
373        .unused_store_members
374        .retain(|finding| line_in_diff(&finding.member.path, finding.member.line));
375    results
376        .unprovided_injects
377        .retain(|finding| line_in_diff(&finding.inject.path, finding.inject.line));
378    results
379        .unrendered_components
380        .retain(|finding| line_in_diff(&finding.component.path, finding.component.line));
381    results
382        .unused_component_props
383        .retain(|finding| line_in_diff(&finding.prop.path, finding.prop.line));
384    results
385        .unused_component_emits
386        .retain(|finding| line_in_diff(&finding.emit.path, finding.emit.line));
387    results
388        .unused_component_inputs
389        .retain(|finding| line_in_diff(&finding.input.path, finding.input.line));
390    results
391        .unused_component_outputs
392        .retain(|finding| line_in_diff(&finding.output.path, finding.output.line));
393    results
394        .unused_svelte_events
395        .retain(|finding| line_in_diff(&finding.event.path, finding.event.line));
396    results
397        .unused_server_actions
398        .retain(|finding| line_in_diff(&finding.action.path, finding.action.line));
399    results
400        .unused_load_data_keys
401        .retain(|finding| line_in_diff(&finding.key.path, finding.key.line));
402    results
403        .unresolved_imports
404        .retain(|finding| line_in_diff(&finding.import.path, finding.import.line));
405}
406
407fn filter_dead_code_security_findings(
408    results: &mut AnalysisResults,
409    touches_file: &dyn Fn(&Path) -> bool,
410    line_in_diff: &dyn Fn(&Path, u32) -> bool,
411) {
412    results.security_findings.retain(|finding| {
413        line_in_diff(&finding.path, finding.line)
414            || finding.trace.iter().any(|hop| {
415                line_in_diff(&hop.path, hop.line)
416                    || (matches!(hop.role, TraceHopRole::SecretSource) && touches_file(&hop.path))
417            })
418            || finding.reachability.as_ref().is_some_and(|reachability| {
419                reachability
420                    .untrusted_source_trace
421                    .iter()
422                    .any(|hop| line_in_diff(&hop.path, hop.line))
423            })
424    });
425    results
426        .security_unresolved_callee_diagnostics
427        .retain(|finding| line_in_diff(&finding.path, finding.line));
428}
429
430fn filter_dead_code_dependency_findings(
431    results: &mut AnalysisResults,
432    line_in_diff: &dyn Fn(&Path, u32) -> bool,
433) {
434    for finding in &mut results.unlisted_dependencies {
435        finding
436            .dep
437            .imported_from
438            .retain(|source| line_in_diff(&source.path, source.line));
439    }
440    results
441        .unlisted_dependencies
442        .retain(|finding| !finding.dep.imported_from.is_empty());
443}
444
445fn filter_dead_code_graph_findings(
446    results: &mut AnalysisResults,
447    touches_file: &dyn Fn(&Path) -> bool,
448    line_in_diff: &dyn Fn(&Path, u32) -> bool,
449) {
450    results.duplicate_exports.retain(|finding| {
451        finding
452            .export
453            .locations
454            .iter()
455            .any(|location| line_in_diff(&location.path, location.line))
456    });
457    results
458        .circular_dependencies
459        .retain(|cycle| cycle.cycle.files.iter().any(|path| touches_file(path)));
460    results
461        .re_export_cycles
462        .retain(|cycle| cycle.cycle.files.iter().any(|path| touches_file(path)));
463    results
464        .boundary_violations
465        .retain(|finding| line_in_diff(&finding.violation.from_path, finding.violation.line));
466    results
467        .stale_suppressions
468        .retain(|finding| line_in_diff(&finding.path, finding.line));
469}
470
471fn filter_dead_code_framework_findings(
472    results: &mut AnalysisResults,
473    line_in_diff: &dyn Fn(&Path, u32) -> bool,
474) {
475    results
476        .invalid_client_exports
477        .retain(|finding| line_in_diff(&finding.export.path, finding.export.line));
478    results
479        .mixed_client_server_barrels
480        .retain(|finding| line_in_diff(&finding.barrel.path, finding.barrel.line));
481    results
482        .misplaced_directives
483        .retain(|finding| line_in_diff(&finding.directive_site.path, finding.directive_site.line));
484    results
485        .route_collisions
486        .retain(|finding| line_in_diff(&finding.collision.path, finding.collision.line));
487    results
488        .dynamic_segment_name_conflicts
489        .retain(|finding| line_in_diff(&finding.conflict.path, finding.conflict.line));
490}
491
492fn apply_dead_code_file_filter(
493    options: &DeadCodeOptions,
494    root: &Path,
495    results: &mut AnalysisResults,
496) {
497    if options.files.is_empty() {
498        return;
499    }
500    let file_set = options
501        .files
502        .iter()
503        .map(|path| {
504            if is_absolute_path_any_platform(path) {
505                path.clone()
506            } else {
507                root.join(path)
508            }
509        })
510        .collect::<FxHashSet<_>>();
511    fallow_engine::dead_code::filter_by_changed_files(results, &file_set);
512    clear_dead_code_dependency_findings(results);
513}
514
515fn apply_dead_code_filters(filters: &DeadCodeFilters, results: &mut AnalysisResults) {
516    if !dead_code_filters_active(filters) {
517        return;
518    }
519    apply_dead_code_core_filters(filters, results);
520    apply_dead_code_component_filters(filters, results);
521    apply_dead_code_graph_filters(filters, results);
522    apply_dead_code_policy_filters(filters, results);
523    apply_dead_code_catalog_filters(filters, results);
524}
525
526fn dead_code_filters_active(filters: &DeadCodeFilters) -> bool {
527    filters.unused_files
528        || filters.unused_exports
529        || filters.unused_deps
530        || filters.unused_types
531        || filters.private_type_leaks
532        || filters.unused_enum_members
533        || filters.unused_class_members
534        || filters.unused_store_members
535        || filters.unprovided_injects
536        || filters.unrendered_components
537        || filters.unused_component_props
538        || filters.unused_component_emits
539        || filters.unused_component_inputs
540        || filters.unused_component_outputs
541        || filters.unused_svelte_events
542        || filters.unused_server_actions
543        || filters.unused_load_data_keys
544        || filters.unresolved_imports
545        || filters.unlisted_deps
546        || filters.duplicate_exports
547        || filters.circular_deps
548        || filters.re_export_cycles
549        || filters.boundary_violations
550        || filters.policy_violations
551        || filters.stale_suppressions
552        || filters.unused_catalog_entries
553        || filters.empty_catalog_groups
554        || filters.unresolved_catalog_references
555        || filters.unused_dependency_overrides
556        || filters.misconfigured_dependency_overrides
557}
558
559fn apply_dead_code_core_filters(filters: &DeadCodeFilters, results: &mut AnalysisResults) {
560    if !filters.unused_files {
561        results.unused_files.clear();
562    }
563    if !filters.unused_exports {
564        results.unused_exports.clear();
565    }
566    if !filters.unused_types {
567        results.unused_types.clear();
568    }
569    if !filters.private_type_leaks {
570        results.private_type_leaks.clear();
571    }
572    if !filters.unused_deps {
573        clear_dead_code_dependency_findings(results);
574    }
575    if !filters.unused_enum_members {
576        results.unused_enum_members.clear();
577    }
578    if !filters.unused_class_members {
579        results.unused_class_members.clear();
580    }
581    if !filters.unused_store_members {
582        results.unused_store_members.clear();
583    }
584    if !filters.unlisted_deps {
585        results.unlisted_dependencies.clear();
586    }
587}
588
589fn clear_dead_code_dependency_findings(results: &mut AnalysisResults) {
590    results.unused_dependencies.clear();
591    results.unused_dev_dependencies.clear();
592    results.unused_optional_dependencies.clear();
593    results.type_only_dependencies.clear();
594    results.test_only_dependencies.clear();
595}
596
597fn apply_dead_code_component_filters(filters: &DeadCodeFilters, results: &mut AnalysisResults) {
598    if !filters.unprovided_injects {
599        results.unprovided_injects.clear();
600    }
601    if !filters.unrendered_components {
602        results.unrendered_components.clear();
603    }
604    if !filters.unused_component_props {
605        results.unused_component_props.clear();
606    }
607    if !filters.unused_component_emits {
608        results.unused_component_emits.clear();
609    }
610    if !filters.unused_component_inputs {
611        results.unused_component_inputs.clear();
612    }
613    if !filters.unused_component_outputs {
614        results.unused_component_outputs.clear();
615    }
616    if !filters.unused_svelte_events {
617        results.unused_svelte_events.clear();
618    }
619    if !filters.unused_server_actions {
620        results.unused_server_actions.clear();
621    }
622    if !filters.unused_load_data_keys {
623        results.unused_load_data_keys.clear();
624    }
625    if !filters.unresolved_imports {
626        results.unresolved_imports.clear();
627    }
628}
629
630fn apply_dead_code_graph_filters(filters: &DeadCodeFilters, results: &mut AnalysisResults) {
631    if !filters.duplicate_exports {
632        results.duplicate_exports.clear();
633    }
634    if !filters.circular_deps {
635        results.circular_dependencies.clear();
636    }
637    if !filters.re_export_cycles {
638        results.re_export_cycles.clear();
639    }
640    if !filters.boundary_violations {
641        results.boundary_violations.clear();
642        results.boundary_coverage_violations.clear();
643        results.boundary_call_violations.clear();
644    }
645}
646
647fn apply_dead_code_policy_filters(filters: &DeadCodeFilters, results: &mut AnalysisResults) {
648    if !filters.policy_violations {
649        results.policy_violations.clear();
650    }
651    if !filters.stale_suppressions {
652        results.stale_suppressions.clear();
653    }
654}
655
656fn apply_dead_code_catalog_filters(filters: &DeadCodeFilters, results: &mut AnalysisResults) {
657    if !filters.unused_catalog_entries {
658        results.unused_catalog_entries.clear();
659    }
660    if !filters.empty_catalog_groups {
661        results.empty_catalog_groups.clear();
662    }
663    if !filters.unresolved_catalog_references {
664        results.unresolved_catalog_references.clear();
665    }
666    if !filters.unused_dependency_overrides {
667        results.unused_dependency_overrides.clear();
668    }
669    if !filters.misconfigured_dependency_overrides {
670        results.misconfigured_dependency_overrides.clear();
671    }
672}