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, 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;
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;
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    run_dead_code_with_baseline(options, None)
47}
48
49/// Run dead-code analysis and hide the findings of a saved dead-code baseline.
50///
51/// The baseline is the file that `fallow dead-code --save-baseline` writes. It
52/// is applied by the same engine function as `fallow dead-code --baseline`, so
53/// both hide the same findings. A file that another command saved suppresses
54/// nothing, as on the CLI.
55///
56/// # Errors
57///
58/// Returns the errors of [`run_dead_code`], and a structured error when the
59/// baseline cannot be read, is not valid, or was saved with an incompatible
60/// analysis identity.
61pub fn run_dead_code_with_baseline(
62    options: &DeadCodeOptions,
63    baseline: Option<&Path>,
64) -> ProgrammaticResult<DeadCodeProgrammaticOutput> {
65    let resolved = resolve_programmatic_analysis_context_deferred_workspace(&options.analysis)?;
66    resolved.install(|| {
67        let start = Instant::now();
68        resolved.ensure_not_cancelled("config load and file discovery")?;
69        let session = load_dead_code_session(options, &resolved)?;
70        let (mut results, type_aware_meta) =
71            analyze_dead_code_results(options, &resolved, &session, None, |_| {})?;
72        if let Some(baseline) = baseline {
73            apply_baseline(
74                &mut results,
75                baseline,
76                session.root(),
77                type_aware_meta.as_ref(),
78            )?;
79        }
80        Ok(build_dead_code_programmatic_output(
81            options,
82            &resolved,
83            &session,
84            results,
85            type_aware_meta,
86            start,
87        ))
88    })
89}
90
91/// Turn an engine failure into a programmatic error, keeping a cancelled run
92/// distinguishable from a failed one.
93///
94/// The engine's cancellation message already names the pipeline boundary the
95/// run stopped at, so it is carried through rather than replaced.
96pub(super) fn map_engine_error(
97    err: &fallow_engine::EngineError,
98    failure_message: &str,
99    failure_code: &'static str,
100    context: &'static str,
101) -> ProgrammaticError {
102    if err.is_cancelled() {
103        return crate::analysis_context::cancelled_error_message(err.message())
104            .with_context(context);
105    }
106    ProgrammaticError::new(format!("{failure_message}: {err}"), 2)
107        .with_code(failure_code)
108        .with_context(context)
109}
110
111/// Run circular-dependency analysis and return typed API output before JSON.
112///
113/// # Errors
114///
115/// Returns the same structured errors as [`run_dead_code`].
116pub fn run_circular_dependencies(
117    options: &DeadCodeOptions,
118) -> ProgrammaticResult<CircularDependenciesProgrammaticOutput> {
119    let resolved = resolve_programmatic_analysis_context_deferred_workspace(&options.analysis)?;
120    resolved.install(|| {
121        run_dead_code_inner(options, &resolved, keep_circular_dependencies).map(Into::into)
122    })
123}
124
125/// Run boundary-family analysis and return typed API output before JSON.
126///
127/// # Errors
128///
129/// Returns the same structured errors as [`run_dead_code`].
130pub fn run_boundary_violations(
131    options: &DeadCodeOptions,
132) -> ProgrammaticResult<BoundaryViolationsProgrammaticOutput> {
133    let resolved = resolve_programmatic_analysis_context_deferred_workspace(&options.analysis)?;
134    resolved.install(|| {
135        run_dead_code_inner(options, &resolved, keep_boundary_violations).map(Into::into)
136    })
137}
138
139fn run_dead_code_inner(
140    options: &DeadCodeOptions,
141    resolved: &ProgrammaticAnalysisContext,
142    post_filter: impl FnOnce(&mut AnalysisResults),
143) -> ProgrammaticResult<DeadCodeProgrammaticOutput> {
144    let start = Instant::now();
145    resolved.ensure_not_cancelled("config load and file discovery")?;
146    let session = load_dead_code_session(options, resolved)?;
147    run_dead_code_with_session(options, resolved, &session, None, post_filter, start)
148}
149
150pub(super) fn run_dead_code_with_session(
151    options: &DeadCodeOptions,
152    resolved: &ProgrammaticAnalysisContext,
153    session: &AnalysisSession,
154    changed_files: Option<&FxHashSet<std::path::PathBuf>>,
155    post_filter: impl FnOnce(&mut AnalysisResults),
156    start: Instant,
157) -> ProgrammaticResult<DeadCodeProgrammaticOutput> {
158    let (results, type_aware_meta) =
159        analyze_dead_code_results(options, resolved, session, changed_files, post_filter)?;
160    Ok(build_dead_code_programmatic_output(
161        options,
162        resolved,
163        session,
164        results,
165        type_aware_meta,
166        start,
167    ))
168}
169
170/// The reported dead-code findings of one session run, with the type-aware
171/// metadata of the run.
172fn analyze_dead_code_results(
173    options: &DeadCodeOptions,
174    resolved: &ProgrammaticAnalysisContext,
175    session: &AnalysisSession,
176    changed_files: Option<&FxHashSet<std::path::PathBuf>>,
177    post_filter: impl FnOnce(&mut AnalysisResults),
178) -> ProgrammaticResult<(
179    AnalysisResults,
180    Option<fallow_types::envelope::TypeAwareMeta>,
181)> {
182    resolved.ensure_not_cancelled("dead-code analysis")?;
183    let analysis = session.analyze_dead_code().map_err(|err| {
184        map_engine_error(
185            &err,
186            "dead-code analysis failed",
187            "FALLOW_DEAD_CODE_FAILED",
188            "dead-code",
189        )
190    })?;
191    let mut results = analysis.results;
192    let unfiltered_unused_files = results.unused_files.clone();
193
194    let type_aware_meta = finish_dead_code_results(
195        DeadCodeReportInputs {
196            options,
197            resolved,
198            session,
199            changed_files,
200            unfiltered_unused_files,
201        },
202        &mut results,
203        post_filter,
204    )?;
205    Ok((results, type_aware_meta))
206}
207
208/// Hide the findings of a saved dead-code baseline, as `--baseline` does.
209fn apply_baseline(
210    results: &mut AnalysisResults,
211    baseline: &Path,
212    root: &Path,
213    type_aware_meta: Option<&fallow_types::envelope::TypeAwareMeta>,
214) -> ProgrammaticResult<()> {
215    use fallow_engine::baseline::{DeadCodeBaselineError, apply_dead_code_baseline};
216
217    let path = if is_absolute_path_any_platform(baseline) {
218        baseline.to_path_buf()
219    } else {
220        root.join(baseline)
221    };
222    let content = std::fs::read_to_string(&path).map_err(|err| {
223        ProgrammaticError::new(
224            format!("failed to read baseline {}: {err}", path.display()),
225            2,
226        )
227        .with_code("FALLOW_BASELINE_READ_FAILED")
228        .with_context("baseline")
229    })?;
230    let identity = type_aware_meta
231        .and_then(|meta| meta.identity.clone())
232        .unwrap_or_default();
233    apply_dead_code_baseline(results, &content, root, &identity, false)
234        .map(|_| ())
235        .map_err(|err| match err {
236            DeadCodeBaselineError::Parse(message) => ProgrammaticError::new(
237                format!("failed to parse baseline {}: {message}", path.display()),
238                2,
239            )
240            .with_code("FALLOW_BASELINE_INVALID")
241            .with_context("baseline"),
242            DeadCodeBaselineError::IncompatibleIdentity(fields) => ProgrammaticError::new(
243                format!(
244                    "baseline analysis identity is incompatible in: {}. Save it again with the same analysis options",
245                    fields.join(", ")
246                ),
247                2,
248            )
249            .with_code("FALLOW_BASELINE_IDENTITY_INCOMPATIBLE")
250            .with_context("baseline"),
251        })
252}
253
254pub(super) fn run_dead_code_with_session_artifacts(
255    options: &DeadCodeOptions,
256    resolved: &ProgrammaticAnalysisContext,
257    session: &AnalysisSession,
258    changed_files: Option<&FxHashSet<std::path::PathBuf>>,
259    post_filter: impl FnOnce(&mut AnalysisResults),
260    start: Instant,
261) -> ProgrammaticResult<DeadCodeProgrammaticRunWithArtifacts> {
262    resolved.ensure_not_cancelled("dead-code analysis")?;
263    let mut artifacts = session
264        .analyze_dead_code_with_artifacts(true, true)
265        .map_err(|err| {
266            map_engine_error(
267                &err,
268                "dead-code analysis failed",
269                "FALLOW_DEAD_CODE_FAILED",
270                "dead-code",
271            )
272        })?;
273    let unfiltered_unused_files = artifacts.results.unused_files.clone();
274
275    let type_aware_meta = finish_dead_code_results(
276        DeadCodeReportInputs {
277            options,
278            resolved,
279            session,
280            changed_files,
281            unfiltered_unused_files,
282        },
283        &mut artifacts.results,
284        post_filter,
285    )?;
286
287    Ok(build_dead_code_run_with_artifacts(
288        options,
289        resolved,
290        session,
291        artifacts,
292        type_aware_meta,
293        start,
294    ))
295}
296
297pub(super) fn run_dead_code_from_artifacts(
298    options: &DeadCodeOptions,
299    resolved: &ProgrammaticAnalysisContext,
300    session: &AnalysisSession,
301    changed_files: Option<&FxHashSet<std::path::PathBuf>>,
302    mut artifacts: DeadCodeAnalysisArtifacts,
303    start: Instant,
304) -> ProgrammaticResult<DeadCodeProgrammaticRunWithArtifacts> {
305    let unfiltered_unused_files = artifacts.results.unused_files.clone();
306    let type_aware_meta = finish_dead_code_results(
307        DeadCodeReportInputs {
308            options,
309            resolved,
310            session,
311            changed_files,
312            unfiltered_unused_files,
313        },
314        &mut artifacts.results,
315        |_| {},
316    )?;
317
318    Ok(build_dead_code_run_with_artifacts(
319        options,
320        resolved,
321        session,
322        artifacts,
323        type_aware_meta,
324        start,
325    ))
326}
327
328/// Inputs the reporting tail needs from whichever entry point produced the
329/// analysis.
330struct DeadCodeReportInputs<'a> {
331    options: &'a DeadCodeOptions,
332    resolved: &'a ProgrammaticAnalysisContext,
333    session: &'a AnalysisSession,
334    changed_files: Option<&'a FxHashSet<std::path::PathBuf>>,
335    /// Unused files as the engine reported them, before scope narrowing, which
336    /// type-aware refinement needs to reason about the whole project.
337    unfiltered_unused_files: Vec<fallow_types::output_dead_code::UnusedFileFinding>,
338}
339
340/// Shared reporting tail for every programmatic dead-code entry point: scope,
341/// issue-type filters, effective rule severities, the caller's family filter,
342/// and type-aware refinement.
343///
344/// The severity pass belongs here rather than at each entry point. Spelling
345/// the sequence out per entry point is what let the programmatic runtime
346/// report findings for rules a project had turned off while the CLI and the
347/// editor did not.
348fn finish_dead_code_results(
349    inputs: DeadCodeReportInputs<'_>,
350    results: &mut AnalysisResults,
351    post_filter: impl FnOnce(&mut AnalysisResults),
352) -> ProgrammaticResult<Option<fallow_types::envelope::TypeAwareMeta>> {
353    let DeadCodeReportInputs {
354        options,
355        resolved,
356        session,
357        changed_files,
358        unfiltered_unused_files,
359    } = inputs;
360    apply_dead_code_scope(options, resolved, session, changed_files, results)?;
361    apply_dead_code_filters(&options.filters, results);
362    fallow_engine::dead_code::apply_rule_severities(results, session.config());
363    post_filter(results);
364    refine_with_unfiltered_unused_files(
365        &options.analysis.type_aware,
366        &options.filters,
367        session,
368        results,
369        unfiltered_unused_files,
370    )
371}
372
373fn refine_with_unfiltered_unused_files(
374    options: &crate::TypeAwareOptions,
375    filters: &DeadCodeFilters,
376    session: &AnalysisSession,
377    results: &mut AnalysisResults,
378    unfiltered_unused_files: Vec<fallow_types::output_dead_code::UnusedFileFinding>,
379) -> ProgrammaticResult<Option<fallow_types::envelope::TypeAwareMeta>> {
380    let reported_unused_files =
381        std::mem::replace(&mut results.unused_files, unfiltered_unused_files);
382    let outcome =
383        crate::type_aware::refine_programmatic_dead_code(options, filters, session, results);
384    results.unused_files = reported_unused_files;
385    if options.enabled {
386        // Reconciliation can add findings, so rule severities are resolved
387        // again over the refined set. The pass removes findings and writes
388        // each gate severity again, so repeating it is idempotent. Mirrors
389        // EditorAnalysisSession.
390        fallow_engine::dead_code::apply_rule_severities(results, session.config());
391    }
392    outcome
393}
394
395fn build_dead_code_run_with_artifacts(
396    options: &DeadCodeOptions,
397    resolved: &ProgrammaticAnalysisContext,
398    session: &AnalysisSession,
399    artifacts: DeadCodeAnalysisArtifacts,
400    type_aware_meta: Option<fallow_types::envelope::TypeAwareMeta>,
401    start: Instant,
402) -> DeadCodeProgrammaticRunWithArtifacts {
403    let output = build_dead_code_programmatic_output(
404        options,
405        resolved,
406        session,
407        artifacts.results.clone(),
408        type_aware_meta,
409        start,
410    );
411    DeadCodeProgrammaticRunWithArtifacts { output, artifacts }
412}
413
414fn build_dead_code_programmatic_output(
415    options: &DeadCodeOptions,
416    resolved: &ProgrammaticAnalysisContext,
417    session: &AnalysisSession,
418    results: AnalysisResults,
419    type_aware_meta: Option<fallow_types::envelope::TypeAwareMeta>,
420    start: Instant,
421) -> DeadCodeProgrammaticOutput {
422    let root = session.root();
423    let next_steps = build_dead_code_next_steps(DeadCodeNextStepsInput {
424        suggestions_enabled: suggestions_enabled(),
425        results: &results,
426        root,
427        offer_setup: setup_pointer_applicable(root),
428        impact_digest: None,
429        workspace_ref: default_workspace_ref_for_workspaces(root, session.workspaces()).as_deref(),
430        audit_changed: fallow_engine::churn::is_git_repo(root),
431        has_external_plugins: !fallow_config::discover_external_plugins(root, &[]).is_empty(),
432        // The programmatic runtime loads no baseline, so there is never one to
433        // re-check.
434        baseline_recheck: None,
435    });
436    let config_fixable =
437        fallow_config::is_config_fixable(&resolved.root, resolved.config_path.as_ref());
438    let mut meta = options.analysis.explain.then(check_meta);
439    if let Some(type_aware) = type_aware_meta {
440        meta.get_or_insert_with(Default::default).type_aware = Some(type_aware);
441    }
442    let mut output = build_check_output(CheckOutputInput {
443        schema_version: CHECK_SCHEMA_VERSION,
444        version: env!("CARGO_PKG_VERSION").to_string(),
445        elapsed: start.elapsed(),
446        results,
447        config_fixable,
448        meta,
449        workspace_diagnostics: session.current_workspace_diagnostics(),
450        next_steps,
451    });
452    output.request_outcomes = resolved.request_outcomes();
453    DeadCodeProgrammaticOutput {
454        output,
455        root: session.root().to_path_buf(),
456        config_fixable,
457        telemetry_analysis_run_id: None,
458    }
459}
460
461fn keep_circular_dependencies(results: &mut AnalysisResults) {
462    let entry_point_summary = results.entry_point_summary.take();
463    let circular_dependencies = std::mem::take(&mut results.circular_dependencies);
464    *results = AnalysisResults::default();
465    results.entry_point_summary = entry_point_summary;
466    results.circular_dependencies = circular_dependencies;
467}
468
469fn keep_boundary_violations(results: &mut AnalysisResults) {
470    let entry_point_summary = results.entry_point_summary.take();
471    let boundary_violations = std::mem::take(&mut results.boundary_violations);
472    let boundary_coverage_violations = std::mem::take(&mut results.boundary_coverage_violations);
473    let boundary_call_violations = std::mem::take(&mut results.boundary_call_violations);
474    *results = AnalysisResults::default();
475    results.entry_point_summary = entry_point_summary;
476    results.boundary_violations = boundary_violations;
477    results.boundary_coverage_violations = boundary_coverage_violations;
478    results.boundary_call_violations = boundary_call_violations;
479}
480
481pub(super) fn load_dead_code_session(
482    options: &DeadCodeOptions,
483    resolved: &ProgrammaticAnalysisContext,
484) -> ProgrammaticResult<AnalysisSession> {
485    let project_config = fallow_engine::project_config::config_for_project_analysis(
486        &resolved.root,
487        resolved.config_path.as_deref(),
488        ProjectConfigOptions {
489            output: OutputFormat::Json,
490            no_cache: resolved.no_cache,
491            threads: resolved.threads,
492            production_override: resolved.production_override,
493            quiet: true,
494            analysis: ProductionAnalysis::DeadCode,
495            allow_remote_extends: resolved.allow_remote_extends,
496        },
497    )
498    .map_err(|err| {
499        ProgrammaticError::new(format!("failed to load config: {err}"), 2)
500            .with_code("FALLOW_CONFIG_LOAD_FAILED")
501            .with_context("analysis.configPath")
502    })?;
503    let project_config = configure_project_for_dead_code(project_config, options);
504    Ok(attach_cancellation(
505        AnalysisSession::from_config(project_config),
506        resolved,
507    ))
508}
509
510/// Hand the caller's cancellation token to the engine session.
511///
512/// Without this the session runs to completion no matter what the API layer
513/// checks between its own stages.
514pub(super) fn attach_cancellation(
515    session: AnalysisSession,
516    resolved: &ProgrammaticAnalysisContext,
517) -> AnalysisSession {
518    match resolved.cancellation() {
519        Some(cancellation) => session.with_cancellation(std::sync::Arc::clone(cancellation)),
520        None => session,
521    }
522}
523
524pub(super) fn default_dead_code_options_for_context(
525    resolved: &ProgrammaticAnalysisContext,
526) -> DeadCodeOptions {
527    DeadCodeOptions {
528        analysis: AnalysisOptions {
529            root: Some(resolved.root().to_path_buf()),
530            config_path: resolved.config_path().clone(),
531            no_cache: resolved.no_cache(),
532            threads: Some(resolved.threads()),
533            production_override: resolved.production_override(),
534            changed_since: resolved.changed_since().map(str::to_owned),
535            workspace: resolved.workspace().map(<[String]>::to_vec),
536            changed_workspaces: resolved.changed_workspaces().map(str::to_owned),
537            explain: resolved.explain_enabled(),
538            ..AnalysisOptions::default()
539        },
540        filters: DeadCodeFilters::default(),
541        files: Vec::new(),
542        include_entry_exports: false,
543    }
544}
545
546fn configure_project_for_dead_code(
547    mut project_config: ProjectConfig,
548    options: &DeadCodeOptions,
549) -> ProjectConfig {
550    if options.include_entry_exports {
551        project_config.config.include_entry_exports = true;
552    }
553    activate_explicit_dead_code_opt_ins(&options.filters, &mut project_config.config.rules);
554    project_config
555}
556
557fn activate_explicit_dead_code_opt_ins(
558    filters: &DeadCodeFilters,
559    rules: &mut fallow_config::RulesConfig,
560) {
561    if filters.private_type_leaks && rules.private_type_leaks == fallow_config::Severity::Off {
562        rules.private_type_leaks = fallow_config::Severity::Warn;
563    }
564    if filters.deprecated_exports_in_use
565        && rules.deprecated_exports_in_use == fallow_config::Severity::Off
566    {
567        rules.deprecated_exports_in_use = fallow_config::Severity::Warn;
568    }
569}
570
571fn apply_dead_code_scope(
572    options: &DeadCodeOptions,
573    resolved: &ProgrammaticAnalysisContext,
574    session: &AnalysisSession,
575    changed_files: Option<&FxHashSet<std::path::PathBuf>>,
576    results: &mut AnalysisResults,
577) -> ProgrammaticResult<()> {
578    let workspace_roots = workspace_roots_for_session(resolved, session.workspaces())?;
579    let resolved_changed_files = if changed_files.is_some() {
580        None
581    } else {
582        changed_files_for_run(resolved)?
583    };
584    if changed_files.or(resolved_changed_files.as_ref()).is_some() {
585        resolved
586            .measure_changed_since_scope(session.files().iter().map(|file| file.path.as_path()));
587    }
588    let files = file_scope(options, session.root());
589    fallow_engine::dead_code::apply_scope(
590        results,
591        &fallow_engine::dead_code::DeadCodeScope {
592            workspace_roots: workspace_roots.as_deref(),
593            changed_files: changed_files.or(resolved_changed_files.as_ref()),
594            diff: resolved.diff.as_ref().map(|diff| (diff, session.root())),
595            files: files.as_ref(),
596        },
597        session.config(),
598    );
599    Ok(())
600}
601
602/// The `files` option resolved against the root, or `None` when it is empty.
603fn file_scope(options: &DeadCodeOptions, root: &Path) -> Option<FxHashSet<std::path::PathBuf>> {
604    if options.files.is_empty() {
605        return None;
606    }
607    Some(
608        options
609            .files
610            .iter()
611            .map(|path| {
612                if is_absolute_path_any_platform(path) {
613                    path.clone()
614                } else {
615                    root.join(path)
616                }
617            })
618            .collect(),
619    )
620}
621
622fn apply_dead_code_filters(filters: &DeadCodeFilters, results: &mut AnalysisResults) {
623    if !dead_code_filters_active(filters) {
624        return;
625    }
626    apply_dead_code_core_filters(filters, results);
627    apply_dead_code_component_filters(filters, results);
628    apply_dead_code_graph_filters(filters, results);
629    apply_dead_code_policy_filters(filters, results);
630    apply_dead_code_catalog_filters(filters, results);
631}
632
633fn dead_code_filters_active(filters: &DeadCodeFilters) -> bool {
634    filters.unused_files
635        || filters.unused_exports
636        || filters.unused_deps
637        || filters.unused_types
638        || filters.private_type_leaks
639        || filters.deprecated_exports_in_use
640        || filters.unused_enum_members
641        || filters.unused_class_members
642        || filters.unused_store_members
643        || filters.unprovided_injects
644        || filters.unrendered_components
645        || filters.unused_component_props
646        || filters.unused_component_emits
647        || filters.unused_component_inputs
648        || filters.unused_component_outputs
649        || filters.unused_svelte_events
650        || filters.unused_server_actions
651        || filters.unused_load_data_keys
652        || filters.unresolved_imports
653        || filters.unlisted_deps
654        || filters.duplicate_exports
655        || filters.circular_deps
656        || filters.re_export_cycles
657        || filters.boundary_violations
658        || filters.policy_violations
659        || filters.stale_suppressions
660        || filters.unused_catalog_entries
661        || filters.empty_catalog_groups
662        || filters.unresolved_catalog_references
663        || filters.unused_dependency_overrides
664        || filters.misconfigured_dependency_overrides
665}
666
667fn apply_dead_code_core_filters(filters: &DeadCodeFilters, results: &mut AnalysisResults) {
668    if !filters.unused_files {
669        results.unused_files.clear();
670    }
671    if !filters.unused_exports {
672        results.unused_exports.clear();
673    }
674    if !filters.unused_types {
675        results.unused_types.clear();
676    }
677    if !filters.private_type_leaks {
678        results.private_type_leaks.clear();
679    }
680    if !filters.deprecated_exports_in_use {
681        results.deprecated_exports_in_use.clear();
682    }
683    if !filters.unused_deps {
684        clear_dead_code_dependency_findings(results);
685    }
686    if !filters.unused_enum_members {
687        results.unused_enum_members.clear();
688    }
689    if !filters.unused_class_members {
690        results.unused_class_members.clear();
691    }
692    if !filters.unused_store_members {
693        results.unused_store_members.clear();
694    }
695    if !filters.unlisted_deps {
696        results.unlisted_dependencies.clear();
697    }
698}
699
700fn clear_dead_code_dependency_findings(results: &mut AnalysisResults) {
701    results.unused_dependencies.clear();
702    results.unused_dev_dependencies.clear();
703    results.unused_optional_dependencies.clear();
704    results.type_only_dependencies.clear();
705    results.test_only_dependencies.clear();
706    results.dev_dependencies_in_production.clear();
707}
708
709fn apply_dead_code_component_filters(filters: &DeadCodeFilters, results: &mut AnalysisResults) {
710    if !filters.unprovided_injects {
711        results.unprovided_injects.clear();
712    }
713    if !filters.unrendered_components {
714        results.unrendered_components.clear();
715    }
716    if !filters.unused_component_props {
717        results.unused_component_props.clear();
718    }
719    if !filters.unused_component_emits {
720        results.unused_component_emits.clear();
721    }
722    if !filters.unused_component_inputs {
723        results.unused_component_inputs.clear();
724    }
725    if !filters.unused_component_outputs {
726        results.unused_component_outputs.clear();
727    }
728    if !filters.unused_svelte_events {
729        results.unused_svelte_events.clear();
730    }
731    if !filters.unused_server_actions {
732        results.unused_server_actions.clear();
733    }
734    if !filters.unused_load_data_keys {
735        results.unused_load_data_keys.clear();
736    }
737    if !filters.unresolved_imports {
738        results.unresolved_imports.clear();
739    }
740}
741
742fn apply_dead_code_graph_filters(filters: &DeadCodeFilters, results: &mut AnalysisResults) {
743    if !filters.duplicate_exports {
744        results.duplicate_exports.clear();
745    }
746    if !filters.circular_deps {
747        results.circular_dependencies.clear();
748    }
749    if !filters.re_export_cycles {
750        results.re_export_cycles.clear();
751    }
752    if !filters.boundary_violations {
753        results.boundary_violations.clear();
754        results.boundary_coverage_violations.clear();
755        results.boundary_call_violations.clear();
756    }
757}
758
759fn apply_dead_code_policy_filters(filters: &DeadCodeFilters, results: &mut AnalysisResults) {
760    if !filters.policy_violations {
761        results.policy_violations.clear();
762    }
763    if !filters.stale_suppressions {
764        results.stale_suppressions.clear();
765    }
766}
767
768fn apply_dead_code_catalog_filters(filters: &DeadCodeFilters, results: &mut AnalysisResults) {
769    if !filters.unused_catalog_entries {
770        results.unused_catalog_entries.clear();
771    }
772    if !filters.empty_catalog_groups {
773        results.empty_catalog_groups.clear();
774    }
775    if !filters.unresolved_catalog_references {
776        results.unresolved_catalog_references.clear();
777    }
778    if !filters.unused_dependency_overrides {
779        results.unused_dependency_overrides.clear();
780    }
781    if !filters.misconfigured_dependency_overrides {
782        results.misconfigured_dependency_overrides.clear();
783    }
784}