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