Skip to main content

fallow_engine/
dead_code_config_patterns.rs

1//! The config entries that had no effect in a dead-code pass, as
2//! `workspace_diagnostics[]` entries.
3
4use fallow_config::{
5    ResolvedConfig, RulesConfig, Severity, WorkspaceDiagnostic, WorkspaceDiagnosticKind,
6};
7
8/// `workspace_diagnostics[]` entries for the config entries that had no
9/// effect in the latest dead-code pass over `config`.
10///
11/// In config order per setting:
12///
13/// - each `ignoreFindings` pattern that matched no finding;
14/// - each `ignoreDependencies` glob that matched no declared dependency, and
15///   each `ignoreDependencies` entry that hid no dependency finding;
16/// - each `entry` pattern that matched only files that are entry points
17///   anyway;
18/// - each `ignorePatterns` glob that excluded no file the run would otherwise
19///   analyze.
20///
21/// The `ignoreDependencies` entries need `reports_dependencies`. A surface
22/// passes `reports_dependencies = false` when its run does not report
23/// dependency findings (an issue-type filter without the dependency types, or
24/// a file scope), because a dependency entry is then not relevant to what the
25/// run shows. A `--production` run checks fewer files and dependency
26/// sections, so it reports no redundant entry (issue #3824).
27///
28/// The CLI, the programmatic API and the MCP typed path all build their
29/// envelope from this one function, and the human note reads the same
30/// entries, so every output states the same patterns.
31#[must_use]
32pub fn config_pattern_diagnostics(
33    config: &ResolvedConfig,
34    reports_dependencies: bool,
35) -> Vec<WorkspaceDiagnostic> {
36    let dependencies_reported = reports_dependencies && dependency_rules_on(&config.rules);
37    let dependency_globs = if dependencies_reported {
38        config.ignore_dependencies.unmatched_globs()
39    } else {
40        Vec::new()
41    };
42    let checks_redundancy = !config.production;
43    let redundant_dependencies = if dependencies_reported && checks_redundancy {
44        config.ignore_dependencies.redundant_entries()
45    } else {
46        Vec::new()
47    };
48    let (redundant_entries, redundant_ignores) = if checks_redundancy {
49        (
50            config
51                .entry_pattern_effect
52                .ineffective(&config.entry_patterns),
53            config.ignore_patterns.redundant_user_patterns(),
54        )
55    } else {
56        (Vec::new(), Vec::new())
57    };
58    let finding_patterns = config.ignore_findings.unmatched_patterns();
59    let kinds = finding_patterns
60        .into_iter()
61        .map(
62            |pattern| WorkspaceDiagnosticKind::IgnoreFindingsPatternUnmatched {
63                pattern: pattern.to_owned(),
64            },
65        )
66        .chain(dependency_globs.into_iter().map(|pattern| {
67            WorkspaceDiagnosticKind::IgnoreDependenciesGlobUnmatched {
68                pattern: pattern.to_owned(),
69            }
70        }))
71        .chain(redundant_entries.into_iter().map(|pattern| {
72            WorkspaceDiagnosticKind::EntryPatternRedundant {
73                pattern: pattern.to_owned(),
74            }
75        }))
76        .chain(redundant_dependencies.into_iter().map(|pattern| {
77            WorkspaceDiagnosticKind::IgnoreDependenciesEntryRedundant {
78                pattern: pattern.to_owned(),
79            }
80        }))
81        .chain(redundant_ignores.into_iter().map(|pattern| {
82            WorkspaceDiagnosticKind::IgnorePatternsEntryRedundant {
83                pattern: pattern.to_owned(),
84            }
85        }));
86    kinds
87        .map(|kind| {
88            WorkspaceDiagnostic::new(&config.root, config.root.clone(), kind)
89                .into_root_relative(&config.root)
90        })
91        .collect()
92}
93
94/// Whether at least one rule that `ignoreDependencies` controls is on. With
95/// every such rule off, the run reports no dependency finding at all.
96fn dependency_rules_on(rules: &RulesConfig) -> bool {
97    [
98        rules.unused_dependencies,
99        rules.unused_dev_dependencies,
100        rules.unused_optional_dependencies,
101        rules.unlisted_dependencies,
102        rules.type_only_dependencies,
103        rules.test_only_dependencies,
104        rules.dev_dependencies_in_production,
105    ]
106    .into_iter()
107    .any(|severity| severity != Severity::Off)
108}