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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! Run configuration: what to scan, what to report, how strict to be.
use std::collections::HashMap;
use std::path::PathBuf;
use crate::framework::Registry;
use cpd_core::deadcode::Category;
/// Configuration for one dead-code run.
#[derive(Debug, Clone)]
pub struct BastaConfig {
/// Roots to scan.
pub paths: Vec<PathBuf>,
/// Categories to report. Empty means [`Category::DEFAULT`].
pub categories: Vec<Category>,
/// Findings below this confidence are dropped. 0 reports everything.
pub min_confidence: u8,
/// Extra entry-point globs beyond the ones detected from the project
/// manifests and conventional layout.
pub entry: Vec<String>,
/// The frameworks this run can recognise, and whether it should try.
/// The built-in table unless the project extended it.
pub frameworks: Registry,
/// Glob patterns of files to skip entirely.
pub ignore: Vec<String>,
/// Report dead code inside test, fixture and example files.
///
/// Tests are entry points either way — a test runner starts them, so a
/// test file is never "unused". This decides whether the dead code
/// *inside* one is worth saying out loud, and it is off by default
/// because it buries the findings in shipped code.
pub include_tests: bool,
/// Report exports of entry-point files.
///
/// An entry point's exports are the project's public surface: nothing
/// inside the scan is supposed to import them, so reporting them by
/// default would flag every published API as dead.
pub include_entry_exports: bool,
/// Report symbols shorter than this many lines. Long-dead functions are
/// worth more than one-line constants; 0 reports everything.
pub min_lines: u32,
/// Do not consult `.gitignore` while walking.
pub no_gitignore: bool,
/// Follow symbolic links while walking.
pub follow_symlinks: bool,
/// Skip files larger than this many bytes.
pub max_size: Option<u64>,
/// Rayon worker count; `None` uses the default.
pub workers: Option<usize>,
/// Restrict the scan to these jscpd format names. Empty means every
/// format basta supports.
pub formats: Vec<String>,
/// Extra extension → format mappings, mirroring jscpd's `--formats-exts`.
pub formats_exts: HashMap<String, Vec<String>>,
}
impl Default for BastaConfig {
fn default() -> Self {
Self {
paths: Vec::new(),
categories: Category::DEFAULT.to_vec(),
min_confidence: 60,
entry: Vec::new(),
frameworks: Registry::default(),
ignore: Vec::new(),
include_tests: false,
include_entry_exports: false,
min_lines: 0,
no_gitignore: false,
follow_symlinks: false,
max_size: None,
workers: None,
formats: Vec::new(),
formats_exts: HashMap::new(),
}
}
}
impl BastaConfig {
/// The categories this run reports, resolving the empty default.
pub fn active_categories(&self) -> &[Category] {
if self.categories.is_empty() {
Category::DEFAULT
} else {
&self.categories
}
}
pub fn reports(&self, category: Category) -> bool {
self.active_categories().contains(&category)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn category_parses_every_spelling() {
for (input, want) in [
("unused-export", Category::UnusedExport),
("unusedExports", Category::UnusedExport),
("unused_exports", Category::UnusedExport),
("exports", Category::UnusedExport),
(" FILES ", Category::UnusedFile),
("unused-member", Category::UnusedMember),
] {
assert_eq!(input.parse::<Category>().unwrap(), want, "input {input}");
}
}
#[test]
fn category_rejects_unknown() {
let err = "unused-everything".parse::<Category>().unwrap_err();
assert!(err.contains("unknown category"), "{err}");
}
#[test]
fn default_config_omits_members_but_keeps_the_rest() {
let cfg = BastaConfig::default();
assert!(cfg.reports(Category::UnusedExport));
assert!(cfg.reports(Category::UnusedImport));
assert!(!cfg.reports(Category::UnusedMember));
}
#[test]
fn empty_categories_fall_back_to_default() {
let cfg = BastaConfig {
categories: Vec::new(),
..BastaConfig::default()
};
assert_eq!(cfg.active_categories(), Category::DEFAULT);
}
}