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