1use std::sync::Arc;
4
5use toml_spanner::Context;
6use toml_spanner::Failed;
7use toml_spanner::FromToml;
8use toml_spanner::Item;
9use toml_spanner::Toml;
10use toml_spanner::helper::parse_string;
11use tracing::warn;
12use wdl_ast::Severity;
13use wdl_ast::SupportedVersion;
14use wdl_ast::SyntaxNode;
15
16use crate::Exceptable as _;
17use crate::MisleadingDeclarationOrderRule;
18use crate::Rule;
19use crate::UnnecessaryFunctionCall;
20use crate::UnusedCallRule;
21use crate::UnusedDeclarationRule;
22use crate::UnusedImportRule;
23use crate::UnusedInputRule;
24use crate::UsingFallbackVersion;
25use crate::rules;
26
27#[derive(Clone, PartialEq, Eq)]
32pub struct Config {
33 inner: Arc<ConfigInner>,
35}
36
37impl<'de> FromToml<'de> for Config {
38 fn from_toml(ctx: &mut Context<'de>, item: &Item<'de>) -> Result<Self, Failed> {
39 Ok(Self {
40 inner: ConfigInner::from_toml(ctx, item)?.into(),
41 })
42 }
43}
44
45impl std::fmt::Debug for Config {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 f.debug_struct("Config")
50 .field("diagnostics", &self.inner.diagnostics)
51 .field("fallback_version", &self.inner.fallback_version)
52 .finish()
53 }
54}
55
56impl Default for Config {
57 fn default() -> Self {
58 Self {
59 inner: Arc::new(ConfigInner {
60 diagnostics: Default::default(),
61 fallback_version: None,
62 ignore_filename: None,
63 all_rules: Default::default(),
64 feature_flags: FeatureFlags::default(),
65 }),
66 }
67 }
68}
69
70impl Config {
71 pub fn diagnostics_config(&self) -> &DiagnosticsConfig {
73 &self.inner.diagnostics
74 }
75
76 pub fn fallback_version(&self) -> Option<SupportedVersion> {
79 self.inner.fallback_version
80 }
81
82 pub fn ignore_filename(&self) -> Option<&str> {
84 self.inner.ignore_filename.as_deref()
85 }
86
87 pub fn all_rules(&self) -> &[String] {
89 &self.inner.all_rules
90 }
91
92 pub fn feature_flags(&self) -> &FeatureFlags {
94 &self.inner.feature_flags
95 }
96
97 pub fn with_diagnostics_config(&self, diagnostics: DiagnosticsConfig) -> Self {
100 let mut inner = (*self.inner).clone();
101 inner.diagnostics = diagnostics;
102 Self {
103 inner: Arc::new(inner),
104 }
105 }
106
107 pub fn with_fallback_version(&self, fallback_version: Option<SupportedVersion>) -> Self {
137 let mut inner = (*self.inner).clone();
138 inner.fallback_version = fallback_version;
139 Self {
140 inner: Arc::new(inner),
141 }
142 }
143
144 pub fn with_ignore_filename(&self, filename: Option<String>) -> Self {
156 let mut inner = (*self.inner).clone();
157 inner.ignore_filename = filename;
158 Self {
159 inner: Arc::new(inner),
160 }
161 }
162
163 pub fn with_all_rules(&self, rules: Vec<String>) -> Self {
168 let mut inner = (*self.inner).clone();
169 inner.all_rules = rules;
170 Self {
171 inner: Arc::new(inner),
172 }
173 }
174
175 pub fn with_feature_flags(&self, feature_flags: FeatureFlags) -> Self {
178 let mut inner = (*self.inner).clone();
179 inner.feature_flags = feature_flags;
180 Self {
181 inner: Arc::new(inner),
182 }
183 }
184}
185
186#[derive(Clone, Debug, PartialEq, Eq, Toml)]
188struct ConfigInner {
189 #[toml(default, style = Header)]
191 diagnostics: DiagnosticsConfig,
192 #[toml(FromToml with = parse_string)]
194 fallback_version: Option<SupportedVersion>,
195 ignore_filename: Option<String>,
197 #[toml(default)]
199 all_rules: Vec<String>,
200 #[toml(default)]
202 feature_flags: FeatureFlags,
203}
204
205#[derive(Clone, Copy, Debug, PartialEq, Eq, Toml)]
207pub struct FeatureFlags {
208 #[toml(default = true)]
213 wdl_1_3: bool,
214 #[toml(default)]
219 wdl_1_4: bool,
220}
221
222impl Default for FeatureFlags {
223 fn default() -> Self {
224 Self {
225 wdl_1_3: true,
226 wdl_1_4: false,
227 }
228 }
229}
230
231impl FeatureFlags {
232 pub fn wdl_1_3(&self) -> bool {
237 self.wdl_1_3
238 }
239
240 #[deprecated(note = "WDL 1.3 is now enabled by default; this method is a no-op")]
242 pub fn with_wdl_1_3(self) -> Self {
243 self
244 }
245
246 pub fn wdl_1_4(&self) -> bool {
248 self.wdl_1_4
249 }
250
251 pub fn with_wdl_1_4(mut self) -> Self {
253 self.wdl_1_4 = true;
254 self
255 }
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq, Toml)]
265pub struct DiagnosticsConfig {
266 #[toml(FromToml with = parse_string)]
270 pub unused_import: Option<Severity>,
271 #[toml(FromToml with = parse_string)]
275 pub unused_input: Option<Severity>,
276 #[toml(FromToml with = parse_string)]
280 pub unused_declaration: Option<Severity>,
281 #[toml(FromToml with = parse_string)]
285 pub unused_call: Option<Severity>,
286 #[toml(FromToml with = parse_string)]
290 pub unnecessary_function_call: Option<Severity>,
291 #[toml(FromToml with = parse_string)]
297 pub using_fallback_version: Option<Severity>,
298 #[toml(FromToml with = parse_string)]
302 pub misleading_declaration_order: Option<Severity>,
303}
304
305impl Default for DiagnosticsConfig {
306 fn default() -> Self {
307 Self::new(rules())
308 }
309}
310
311impl DiagnosticsConfig {
312 pub fn new<T: AsRef<dyn Rule>>(rules: impl IntoIterator<Item = T>) -> Self {
314 let mut unused_import = None;
315 let mut unused_input = None;
316 let mut unused_declaration = None;
317 let mut unused_call = None;
318 let mut unnecessary_function_call = None;
319 let mut using_fallback_version = None;
320 let mut misleading_declaration_order = None;
321
322 for rule in rules {
323 let rule = rule.as_ref();
324 match rule.id() {
325 UnusedImportRule::ID => unused_import = Some(rule.severity()),
326 UnusedInputRule::ID => unused_input = Some(rule.severity()),
327 UnusedDeclarationRule::ID => unused_declaration = Some(rule.severity()),
328 UnusedCallRule::ID => unused_call = Some(rule.severity()),
329 UnnecessaryFunctionCall::ID => unnecessary_function_call = Some(rule.severity()),
330 UsingFallbackVersion::ID => using_fallback_version = Some(rule.severity()),
331 MisleadingDeclarationOrderRule::ID => {
332 misleading_declaration_order = Some(rule.severity())
333 }
334 unrecognized => {
335 warn!(unrecognized, "unrecognized rule");
336 if cfg!(test) {
337 panic!("unrecognized rule: {unrecognized}");
338 }
339 }
340 }
341 }
342
343 Self {
344 unused_import,
345 unused_input,
346 unused_declaration,
347 unused_call,
348 unnecessary_function_call,
349 using_fallback_version,
350 misleading_declaration_order,
351 }
352 }
353
354 pub fn excepted_for_node(mut self, node: &SyntaxNode) -> Self {
357 let exceptions = node.rule_exceptions();
358
359 if exceptions.contains(UnusedImportRule::ID) {
360 self.unused_import = None;
361 }
362
363 if exceptions.contains(UnusedInputRule::ID) {
364 self.unused_input = None;
365 }
366
367 if exceptions.contains(UnusedDeclarationRule::ID) {
368 self.unused_declaration = None;
369 }
370
371 if exceptions.contains(UnusedCallRule::ID) {
372 self.unused_call = None;
373 }
374
375 if exceptions.contains(UnnecessaryFunctionCall::ID) {
376 self.unnecessary_function_call = None;
377 }
378
379 if exceptions.contains(UsingFallbackVersion::ID) {
380 self.using_fallback_version = None;
381 }
382
383 self
384 }
385
386 pub fn except_all() -> Self {
388 Self {
389 unused_import: None,
390 unused_input: None,
391 unused_declaration: None,
392 unused_call: None,
393 unnecessary_function_call: None,
394 using_fallback_version: None,
395 misleading_declaration_order: None,
396 }
397 }
398}