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        has_external_plugins: !fallow_config::discover_external_plugins(root, &[]).is_empty(),
199    });
200    let config_fixable =
201        fallow_config::is_config_fixable(&resolved.root, resolved.config_path.as_ref());
202    let output = build_check_output(CheckOutputInput {
203        schema_version: CHECK_SCHEMA_VERSION,
204        version: env!("CARGO_PKG_VERSION").to_string(),
205        elapsed: start.elapsed(),
206        results,
207        config_fixable,
208        meta: options.analysis.explain.then(check_meta),
209        workspace_diagnostics: session.workspace_diagnostics().to_vec(),
210        next_steps,
211    });
212    DeadCodeProgrammaticOutput {
213        output,
214        root: session.root().to_path_buf(),
215        config_fixable,
216        envelope_mode: root_envelope_mode(),
217        telemetry_analysis_run_id: None,
218    }
219}
220
221fn keep_circular_dependencies(results: &mut AnalysisResults) {
222    let entry_point_summary = results.entry_point_summary.take();
223    let circular_dependencies = std::mem::take(&mut results.circular_dependencies);
224    *results = AnalysisResults::default();
225    results.entry_point_summary = entry_point_summary;
226    results.circular_dependencies = circular_dependencies;
227}
228
229fn keep_boundary_violations(results: &mut AnalysisResults) {
230    let entry_point_summary = results.entry_point_summary.take();
231    let boundary_violations = std::mem::take(&mut results.boundary_violations);
232    let boundary_coverage_violations = std::mem::take(&mut results.boundary_coverage_violations);
233    let boundary_call_violations = std::mem::take(&mut results.boundary_call_violations);
234    *results = AnalysisResults::default();
235    results.entry_point_summary = entry_point_summary;
236    results.boundary_violations = boundary_violations;
237    results.boundary_coverage_violations = boundary_coverage_violations;
238    results.boundary_call_violations = boundary_call_violations;
239}
240
241pub(super) fn load_dead_code_session(
242    options: &DeadCodeOptions,
243    resolved: &ProgrammaticAnalysisContext,
244) -> ProgrammaticResult<AnalysisSession> {
245    let project_config = fallow_engine::project_config::config_for_project_analysis(
246        &resolved.root,
247        resolved.config_path.as_deref(),
248        ProjectConfigOptions {
249            output: OutputFormat::Json,
250            no_cache: resolved.no_cache,
251            threads: resolved.threads,
252            production_override: resolved.production_override,
253            quiet: true,
254            analysis: ProductionAnalysis::DeadCode,
255        },
256    )
257    .map_err(|err| {
258        ProgrammaticError::new(format!("failed to load config: {err}"), 2)
259            .with_code("FALLOW_CONFIG_LOAD_FAILED")
260            .with_context("analysis.configPath")
261    })?;
262    let project_config = configure_project_for_dead_code(project_config, options);
263    Ok(AnalysisSession::from_config(project_config))
264}
265
266pub(super) fn default_dead_code_options_for_context(
267    resolved: &ProgrammaticAnalysisContext,
268) -> DeadCodeOptions {
269    DeadCodeOptions {
270        analysis: AnalysisOptions {
271            root: Some(resolved.root().to_path_buf()),
272            config_path: resolved.config_path().clone(),
273            no_cache: resolved.no_cache(),
274            threads: Some(resolved.threads()),
275            production_override: resolved.production_override(),
276            changed_since: resolved.changed_since().map(str::to_owned),
277            workspace: resolved.workspace().map(<[String]>::to_vec),
278            changed_workspaces: resolved.changed_workspaces().map(str::to_owned),
279            explain: resolved.explain_enabled(),
280            ..AnalysisOptions::default()
281        },
282        filters: DeadCodeFilters::default(),
283        files: Vec::new(),
284        include_entry_exports: false,
285    }
286}
287
288fn configure_project_for_dead_code(
289    mut project_config: ProjectConfig,
290    options: &DeadCodeOptions,
291) -> ProjectConfig {
292    if options.include_entry_exports {
293        project_config.config.include_entry_exports = true;
294    }
295    activate_explicit_dead_code_opt_ins(&options.filters, &mut project_config.config.rules);
296    project_config
297}
298
299fn activate_explicit_dead_code_opt_ins(
300    filters: &DeadCodeFilters,
301    rules: &mut fallow_config::RulesConfig,
302) {
303    if filters.private_type_leaks && rules.private_type_leaks == fallow_config::Severity::Off {
304        rules.private_type_leaks = fallow_config::Severity::Warn;
305    }
306}
307
308fn apply_dead_code_scope(
309    options: &DeadCodeOptions,
310    resolved: &ProgrammaticAnalysisContext,
311    session: &AnalysisSession,
312    changed_files: Option<&FxHashSet<std::path::PathBuf>>,
313    results: &mut AnalysisResults,
314) -> ProgrammaticResult<()> {
315    let workspace_roots = workspace_roots_for_session(resolved, session.workspaces())?;
316    if let Some(workspace_roots) = workspace_roots.as_ref() {
317        fallow_engine::dead_code::filter_to_workspaces(results, workspace_roots);
318    }
319    let resolved_changed_files = if changed_files.is_some() {
320        None
321    } else {
322        changed_files_for_run(resolved)?
323    };
324    if let Some(changed_files) = changed_files.or(resolved_changed_files.as_ref()) {
325        fallow_engine::dead_code::filter_by_changed_files(results, changed_files);
326    }
327    if let Some(diff) = resolved.diff.as_ref() {
328        filter_dead_code_by_diff(results, diff, session.root());
329    }
330    apply_dead_code_file_filter(options, session.root(), results);
331    Ok(())
332}
333
334fn filter_dead_code_by_diff(results: &mut AnalysisResults, diff: &DiffIndex, root: &Path) {
335    let touches_file = |path: &Path| -> bool {
336        relative_to_diff_path(path, root).is_none_or(|rel| diff.touches_file(&rel))
337    };
338    let line_in_diff = |path: &Path, line: u32| -> bool {
339        relative_to_diff_path(path, root)
340            .is_none_or(|rel| diff.line_is_added(&rel, u64::from(line)))
341    };
342
343    filter_dead_code_source_findings(results, &touches_file, &line_in_diff);
344    filter_dead_code_security_findings(results, &touches_file, &line_in_diff);
345    filter_dead_code_dependency_findings(results, &line_in_diff);
346    filter_dead_code_graph_findings(results, &touches_file, &line_in_diff);
347    filter_dead_code_framework_findings(results, &line_in_diff);
348}
349
350fn filter_dead_code_source_findings(
351    results: &mut AnalysisResults,
352    touches_file: &dyn Fn(&Path) -> bool,
353    line_in_diff: &dyn Fn(&Path, u32) -> bool,
354) {
355    results
356        .unused_files
357        .retain(|finding| touches_file(&finding.file.path));
358    results
359        .unused_exports
360        .retain(|finding| line_in_diff(&finding.export.path, finding.export.line));
361    results
362        .unused_types
363        .retain(|finding| line_in_diff(&finding.export.path, finding.export.line));
364    results
365        .private_type_leaks
366        .retain(|finding| line_in_diff(&finding.leak.path, finding.leak.line));
367    results
368        .unused_enum_members
369        .retain(|finding| line_in_diff(&finding.member.path, finding.member.line));
370    results
371        .unused_class_members
372        .retain(|finding| line_in_diff(&finding.member.path, finding.member.line));
373    results
374        .unused_store_members
375        .retain(|finding| line_in_diff(&finding.member.path, finding.member.line));
376    results
377        .unprovided_injects
378        .retain(|finding| line_in_diff(&finding.inject.path, finding.inject.line));
379    results
380        .unrendered_components
381        .retain(|finding| line_in_diff(&finding.component.path, finding.component.line));
382    results
383        .unused_component_props
384        .retain(|finding| line_in_diff(&finding.prop.path, finding.prop.line));
385    results
386        .unused_component_emits
387        .retain(|finding| line_in_diff(&finding.emit.path, finding.emit.line));
388    results
389        .unused_component_inputs
390        .retain(|finding| line_in_diff(&finding.input.path, finding.input.line));
391    results
392        .unused_component_outputs
393        .retain(|finding| line_in_diff(&finding.output.path, finding.output.line));
394    results
395        .unused_svelte_events
396        .retain(|finding| line_in_diff(&finding.event.path, finding.event.line));
397    results
398        .unused_server_actions
399        .retain(|finding| line_in_diff(&finding.action.path, finding.action.line));
400    results
401        .unused_load_data_keys
402        .retain(|finding| line_in_diff(&finding.key.path, finding.key.line));
403    results
404        .unresolved_imports
405        .retain(|finding| line_in_diff(&finding.import.path, finding.import.line));
406}
407
408fn filter_dead_code_security_findings(
409    results: &mut AnalysisResults,
410    touches_file: &dyn Fn(&Path) -> bool,
411    line_in_diff: &dyn Fn(&Path, u32) -> bool,
412) {
413    results.security_findings.retain(|finding| {
414        line_in_diff(&finding.path, finding.line)
415            || finding.trace.iter().any(|hop| {
416                line_in_diff(&hop.path, hop.line)
417                    || (matches!(hop.role, TraceHopRole::SecretSource) && touches_file(&hop.path))
418            })
419            || finding.reachability.as_ref().is_some_and(|reachability| {
420                reachability
421                    .untrusted_source_trace
422                    .iter()
423                    .any(|hop| line_in_diff(&hop.path, hop.line))
424            })
425    });
426    results
427        .security_unresolved_callee_diagnostics
428        .retain(|finding| line_in_diff(&finding.path, finding.line));
429}
430
431fn filter_dead_code_dependency_findings(
432    results: &mut AnalysisResults,
433    line_in_diff: &dyn Fn(&Path, u32) -> bool,
434) {
435    for finding in &mut results.unlisted_dependencies {
436        finding
437            .dep
438            .imported_from
439            .retain(|source| line_in_diff(&source.path, source.line));
440    }
441    results
442        .unlisted_dependencies
443        .retain(|finding| !finding.dep.imported_from.is_empty());
444}
445
446fn filter_dead_code_graph_findings(
447    results: &mut AnalysisResults,
448    touches_file: &dyn Fn(&Path) -> bool,
449    line_in_diff: &dyn Fn(&Path, u32) -> bool,
450) {
451    results.duplicate_exports.retain(|finding| {
452        finding
453            .export
454            .locations
455            .iter()
456            .any(|location| line_in_diff(&location.path, location.line))
457    });
458    results
459        .circular_dependencies
460        .retain(|cycle| cycle.cycle.files.iter().any(|path| touches_file(path)));
461    results
462        .re_export_cycles
463        .retain(|cycle| cycle.cycle.files.iter().any(|path| touches_file(path)));
464    results
465        .boundary_violations
466        .retain(|finding| line_in_diff(&finding.violation.from_path, finding.violation.line));
467    results
468        .stale_suppressions
469        .retain(|finding| line_in_diff(&finding.path, finding.line));
470}
471
472fn filter_dead_code_framework_findings(
473    results: &mut AnalysisResults,
474    line_in_diff: &dyn Fn(&Path, u32) -> bool,
475) {
476    results
477        .invalid_client_exports
478        .retain(|finding| line_in_diff(&finding.export.path, finding.export.line));
479    results
480        .mixed_client_server_barrels
481        .retain(|finding| line_in_diff(&finding.barrel.path, finding.barrel.line));
482    results
483        .misplaced_directives
484        .retain(|finding| line_in_diff(&finding.directive_site.path, finding.directive_site.line));
485    results
486        .route_collisions
487        .retain(|finding| line_in_diff(&finding.collision.path, finding.collision.line));
488    results
489        .dynamic_segment_name_conflicts
490        .retain(|finding| line_in_diff(&finding.conflict.path, finding.conflict.line));
491}
492
493fn apply_dead_code_file_filter(
494    options: &DeadCodeOptions,
495    root: &Path,
496    results: &mut AnalysisResults,
497) {
498    if options.files.is_empty() {
499        return;
500    }
501    let file_set = options
502        .files
503        .iter()
504        .map(|path| {
505            if is_absolute_path_any_platform(path) {
506                path.clone()
507            } else {
508                root.join(path)
509            }
510        })
511        .collect::<FxHashSet<_>>();
512    fallow_engine::dead_code::filter_by_changed_files(results, &file_set);
513    clear_dead_code_dependency_findings(results);
514}
515
516fn apply_dead_code_filters(filters: &DeadCodeFilters, results: &mut AnalysisResults) {
517    if !dead_code_filters_active(filters) {
518        return;
519    }
520    apply_dead_code_core_filters(filters, results);
521    apply_dead_code_component_filters(filters, results);
522    apply_dead_code_graph_filters(filters, results);
523    apply_dead_code_policy_filters(filters, results);
524    apply_dead_code_catalog_filters(filters, results);
525}
526
527fn dead_code_filters_active(filters: &DeadCodeFilters) -> bool {
528    filters.unused_files
529        || filters.unused_exports
530        || filters.unused_deps
531        || filters.unused_types
532        || filters.private_type_leaks
533        || filters.unused_enum_members
534        || filters.unused_class_members
535        || filters.unused_store_members
536        || filters.unprovided_injects
537        || filters.unrendered_components
538        || filters.unused_component_props
539        || filters.unused_component_emits
540        || filters.unused_component_inputs
541        || filters.unused_component_outputs
542        || filters.unused_svelte_events
543        || filters.unused_server_actions
544        || filters.unused_load_data_keys
545        || filters.unresolved_imports
546        || filters.unlisted_deps
547        || filters.duplicate_exports
548        || filters.circular_deps
549        || filters.re_export_cycles
550        || filters.boundary_violations
551        || filters.policy_violations
552        || filters.stale_suppressions
553        || filters.unused_catalog_entries
554        || filters.empty_catalog_groups
555        || filters.unresolved_catalog_references
556        || filters.unused_dependency_overrides
557        || filters.misconfigured_dependency_overrides
558}
559
560fn apply_dead_code_core_filters(filters: &DeadCodeFilters, results: &mut AnalysisResults) {
561    if !filters.unused_files {
562        results.unused_files.clear();
563    }
564    if !filters.unused_exports {
565        results.unused_exports.clear();
566    }
567    if !filters.unused_types {
568        results.unused_types.clear();
569    }
570    if !filters.private_type_leaks {
571        results.private_type_leaks.clear();
572    }
573    if !filters.unused_deps {
574        clear_dead_code_dependency_findings(results);
575    }
576    if !filters.unused_enum_members {
577        results.unused_enum_members.clear();
578    }
579    if !filters.unused_class_members {
580        results.unused_class_members.clear();
581    }
582    if !filters.unused_store_members {
583        results.unused_store_members.clear();
584    }
585    if !filters.unlisted_deps {
586        results.unlisted_dependencies.clear();
587    }
588}
589
590fn clear_dead_code_dependency_findings(results: &mut AnalysisResults) {
591    results.unused_dependencies.clear();
592    results.unused_dev_dependencies.clear();
593    results.unused_optional_dependencies.clear();
594    results.type_only_dependencies.clear();
595    results.test_only_dependencies.clear();
596    results.dev_dependencies_in_production.clear();
597}
598
599fn apply_dead_code_component_filters(filters: &DeadCodeFilters, results: &mut AnalysisResults) {
600    if !filters.unprovided_injects {
601        results.unprovided_injects.clear();
602    }
603    if !filters.unrendered_components {
604        results.unrendered_components.clear();
605    }
606    if !filters.unused_component_props {
607        results.unused_component_props.clear();
608    }
609    if !filters.unused_component_emits {
610        results.unused_component_emits.clear();
611    }
612    if !filters.unused_component_inputs {
613        results.unused_component_inputs.clear();
614    }
615    if !filters.unused_component_outputs {
616        results.unused_component_outputs.clear();
617    }
618    if !filters.unused_svelte_events {
619        results.unused_svelte_events.clear();
620    }
621    if !filters.unused_server_actions {
622        results.unused_server_actions.clear();
623    }
624    if !filters.unused_load_data_keys {
625        results.unused_load_data_keys.clear();
626    }
627    if !filters.unresolved_imports {
628        results.unresolved_imports.clear();
629    }
630}
631
632fn apply_dead_code_graph_filters(filters: &DeadCodeFilters, results: &mut AnalysisResults) {
633    if !filters.duplicate_exports {
634        results.duplicate_exports.clear();
635    }
636    if !filters.circular_deps {
637        results.circular_dependencies.clear();
638    }
639    if !filters.re_export_cycles {
640        results.re_export_cycles.clear();
641    }
642    if !filters.boundary_violations {
643        results.boundary_violations.clear();
644        results.boundary_coverage_violations.clear();
645        results.boundary_call_violations.clear();
646    }
647}
648
649fn apply_dead_code_policy_filters(filters: &DeadCodeFilters, results: &mut AnalysisResults) {
650    if !filters.policy_violations {
651        results.policy_violations.clear();
652    }
653    if !filters.stale_suppressions {
654        results.stale_suppressions.clear();
655    }
656}
657
658fn apply_dead_code_catalog_filters(filters: &DeadCodeFilters, results: &mut AnalysisResults) {
659    if !filters.unused_catalog_entries {
660        results.unused_catalog_entries.clear();
661    }
662    if !filters.empty_catalog_groups {
663        results.empty_catalog_groups.clear();
664    }
665    if !filters.unresolved_catalog_references {
666        results.unresolved_catalog_references.clear();
667    }
668    if !filters.unused_dependency_overrides {
669        results.unused_dependency_overrides.clear();
670    }
671    if !filters.misconfigured_dependency_overrides {
672        results.misconfigured_dependency_overrides.clear();
673    }
674}