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