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