1use std::sync::Arc;
4
5use schemars::JsonSchema;
6use toml_spanner::Context;
7use toml_spanner::Failed;
8use toml_spanner::FromToml;
9use toml_spanner::Item;
10use toml_spanner::Toml;
11use toml_spanner::helper::parse_string;
12use tracing::warn;
13use wdl_ast::Severity;
14use wdl_ast::SupportedVersion;
15use wdl_ast::SyntaxNode;
16
17use crate::CommandSectionIndentationRule;
18use crate::DeprecatedObjectRule;
19use crate::DeprecatedPlaceholderRule;
20use crate::DeprecatedRuntimeSectionRule;
21use crate::ExceptDirectiveValidRule;
22use crate::Exceptable as _;
23use crate::FormatConfig;
24use crate::KnownRulesRule;
25use crate::MeaninglessLintDirective;
26use crate::MisleadingDeclarationOrderRule;
27use crate::Rule;
28use crate::UnnecessaryFunctionCall;
29use crate::UnusedCallRule;
30use crate::UnusedDeclarationRule;
31use crate::UnusedImportRule;
32use crate::UnusedInputRule;
33use crate::UsingFallbackVersion;
34use crate::rules;
35
36#[derive(Clone, PartialEq, Eq)]
41pub struct Config {
42 inner: Arc<ConfigInner>,
44}
45
46impl<'de> FromToml<'de> for Config {
47 fn from_toml(ctx: &mut Context<'de>, item: &Item<'de>) -> Result<Self, Failed> {
48 Ok(Self {
49 inner: ConfigInner::from_toml(ctx, item)?.into(),
50 })
51 }
52}
53
54impl std::fmt::Debug for Config {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 f.debug_struct("Config")
59 .field("diagnostics", &self.inner.diagnostics)
60 .field("fallback_version", &self.inner.fallback_version)
61 .finish()
62 }
63}
64
65impl Default for Config {
66 fn default() -> Self {
67 Self {
68 inner: Arc::new(ConfigInner {
69 diagnostics: Default::default(),
70 fallback_version: None,
71 format: FormatConfig::default(),
72 ignore_filename: None,
73 all_rules: Default::default(),
74 feature_flags: FeatureFlags::default(),
75 }),
76 }
77 }
78}
79
80impl Config {
81 pub fn diagnostics_config(&self) -> &DiagnosticsConfig {
83 &self.inner.diagnostics
84 }
85
86 pub fn fallback_version(&self) -> Option<SupportedVersion> {
89 self.inner.fallback_version
90 }
91
92 pub fn format(&self) -> &FormatConfig {
95 &self.inner.format
96 }
97
98 pub fn ignore_filename(&self) -> Option<&str> {
100 self.inner.ignore_filename.as_deref()
101 }
102
103 pub fn all_rules(&self) -> &[String] {
105 &self.inner.all_rules
106 }
107
108 pub fn feature_flags(&self) -> &FeatureFlags {
110 &self.inner.feature_flags
111 }
112
113 pub fn with_diagnostics_config(&self, diagnostics: DiagnosticsConfig) -> Self {
116 let mut inner = (*self.inner).clone();
117 inner.diagnostics = diagnostics;
118 Self {
119 inner: Arc::new(inner),
120 }
121 }
122
123 pub fn with_fallback_version(&self, fallback_version: Option<SupportedVersion>) -> Self {
153 let mut inner = (*self.inner).clone();
154 inner.fallback_version = fallback_version;
155 Self {
156 inner: Arc::new(inner),
157 }
158 }
159
160 pub fn with_format_config(&self, format: FormatConfig) -> Self {
163 let mut inner = (*self.inner).clone();
164 inner.format = format;
165 Self {
166 inner: Arc::new(inner),
167 }
168 }
169
170 pub fn with_ignore_filename(&self, filename: Option<String>) -> Self {
182 let mut inner = (*self.inner).clone();
183 inner.ignore_filename = filename;
184 Self {
185 inner: Arc::new(inner),
186 }
187 }
188
189 pub fn with_all_rules(&self, rules: Vec<String>) -> Self {
194 let mut inner = (*self.inner).clone();
195 inner.all_rules = rules;
196 Self {
197 inner: Arc::new(inner),
198 }
199 }
200
201 pub fn with_feature_flags(&self, feature_flags: FeatureFlags) -> Self {
204 let mut inner = (*self.inner).clone();
205 inner.feature_flags = feature_flags;
206 Self {
207 inner: Arc::new(inner),
208 }
209 }
210}
211
212#[derive(Clone, Debug, PartialEq, Eq, Toml)]
214struct ConfigInner {
215 #[toml(default, style = Header)]
217 diagnostics: DiagnosticsConfig,
218 #[toml(FromToml with = parse_string)]
220 fallback_version: Option<SupportedVersion>,
221 #[toml(default, style = Header)]
223 format: FormatConfig,
224 ignore_filename: Option<String>,
226 #[toml(default)]
228 all_rules: Vec<String>,
229 #[toml(default)]
231 feature_flags: FeatureFlags,
232}
233
234fn default_wdl_1_3() -> bool {
236 true
237}
238
239#[derive(Clone, Copy, Debug, PartialEq, Eq, Toml, JsonSchema)]
241pub struct FeatureFlags {
242 #[toml(default = true)]
247 #[schemars(default = "default_wdl_1_3")]
248 wdl_1_3: bool,
249 #[toml(default)]
254 #[schemars(default)]
255 wdl_1_4: bool,
256}
257
258impl Default for FeatureFlags {
259 fn default() -> Self {
260 Self {
261 wdl_1_3: true,
262 wdl_1_4: false,
263 }
264 }
265}
266
267impl FeatureFlags {
268 pub fn wdl_1_3(&self) -> bool {
273 self.wdl_1_3
274 }
275
276 #[deprecated(note = "WDL 1.3 is now enabled by default; this method is a no-op")]
278 pub fn with_wdl_1_3(self) -> Self {
279 self
280 }
281
282 pub fn wdl_1_4(&self) -> bool {
284 self.wdl_1_4
285 }
286
287 pub fn with_wdl_1_4(mut self) -> Self {
289 self.wdl_1_4 = true;
290 self
291 }
292}
293
294#[derive(Debug, Clone, Copy, PartialEq, Eq, Toml)]
301pub struct DiagnosticsConfig {
302 #[toml(FromToml with = parse_string)]
306 pub unused_import: Option<Severity>,
307 #[toml(FromToml with = parse_string)]
311 pub unused_input: Option<Severity>,
312 #[toml(FromToml with = parse_string)]
316 pub unused_declaration: Option<Severity>,
317 #[toml(FromToml with = parse_string)]
321 pub unused_call: Option<Severity>,
322 #[toml(FromToml with = parse_string)]
326 pub unnecessary_function_call: Option<Severity>,
327 #[toml(FromToml with = parse_string)]
333 pub using_fallback_version: Option<Severity>,
334 #[toml(FromToml with = parse_string)]
338 pub misleading_declaration_order: Option<Severity>,
339 #[toml(FromToml with = parse_string)]
343 pub meaningless_lint_directive: Option<Severity>,
344 #[toml(FromToml with = parse_string)]
348 pub known_rules: Option<Severity>,
349 #[toml(FromToml with = parse_string)]
353 pub except_directive_valid: Option<Severity>,
354 #[toml(FromToml with = parse_string)]
358 pub command_section_indentation: Option<Severity>,
359 #[toml(FromToml with = parse_string)]
363 pub deprecated_object: Option<Severity>,
364 #[toml(FromToml with = parse_string)]
368 pub deprecated_placeholder: Option<Severity>,
369 #[toml(FromToml with = parse_string)]
373 pub deprecated_runtime_section: Option<Severity>,
374}
375
376impl Default for DiagnosticsConfig {
377 fn default() -> Self {
378 Self::new(rules())
379 }
380}
381
382impl DiagnosticsConfig {
383 pub fn new<T: AsRef<dyn Rule>>(rules: impl IntoIterator<Item = T>) -> Self {
385 let mut unused_import = None;
386 let mut unused_input = None;
387 let mut unused_declaration = None;
388 let mut unused_call = None;
389 let mut unnecessary_function_call = None;
390 let mut using_fallback_version = None;
391 let mut misleading_declaration_order = None;
392 let mut meaningless_lint_directive = None;
393 let mut known_rules = None;
394 let mut except_directive_valid = None;
395 let mut command_section_indentation = None;
396 let mut deprecated_object = None;
397 let mut deprecated_placeholder = None;
398 let mut deprecated_runtime_section = None;
399
400 for rule in rules {
401 let rule = rule.as_ref();
402 match rule.id() {
403 UnusedImportRule::ID => unused_import = Some(rule.severity()),
404 UnusedInputRule::ID => unused_input = Some(rule.severity()),
405 UnusedDeclarationRule::ID => unused_declaration = Some(rule.severity()),
406 UnusedCallRule::ID => unused_call = Some(rule.severity()),
407 UnnecessaryFunctionCall::ID => unnecessary_function_call = Some(rule.severity()),
408 UsingFallbackVersion::ID => using_fallback_version = Some(rule.severity()),
409 MisleadingDeclarationOrderRule::ID => {
410 misleading_declaration_order = Some(rule.severity())
411 }
412 MeaninglessLintDirective::ID => meaningless_lint_directive = Some(rule.severity()),
413 KnownRulesRule::ID => known_rules = Some(rule.severity()),
414 ExceptDirectiveValidRule::ID => except_directive_valid = Some(rule.severity()),
415 CommandSectionIndentationRule::ID => {
416 command_section_indentation = Some(rule.severity())
417 }
418 DeprecatedObjectRule::ID => deprecated_object = Some(rule.severity()),
419 DeprecatedPlaceholderRule::ID => deprecated_placeholder = Some(rule.severity()),
420 DeprecatedRuntimeSectionRule::ID => {
421 deprecated_runtime_section = Some(rule.severity())
422 }
423 unrecognized => {
424 warn!(unrecognized, "unrecognized rule");
425 if cfg!(test) {
426 panic!("unrecognized rule: {unrecognized}");
427 }
428 }
429 }
430 }
431
432 Self {
433 unused_import,
434 unused_input,
435 unused_declaration,
436 unused_call,
437 unnecessary_function_call,
438 using_fallback_version,
439 misleading_declaration_order,
440 meaningless_lint_directive,
441 known_rules,
442 except_directive_valid,
443 command_section_indentation,
444 deprecated_object,
445 deprecated_placeholder,
446 deprecated_runtime_section,
447 }
448 }
449
450 pub fn excepted_for_node(mut self, node: &SyntaxNode) -> Self {
453 let exceptions = node.rule_exceptions();
454
455 for exception in exceptions {
456 match &*exception.name {
457 UnusedImportRule::ID => self.unused_import = None,
458 UnusedInputRule::ID => self.unused_input = None,
459 UnusedDeclarationRule::ID => self.unused_declaration = None,
460 UnusedCallRule::ID => self.unused_call = None,
461 UnnecessaryFunctionCall::ID => self.unnecessary_function_call = None,
462 UsingFallbackVersion::ID => self.using_fallback_version = None,
463 MisleadingDeclarationOrderRule::ID => self.misleading_declaration_order = None,
464 MeaninglessLintDirective::ID => self.meaningless_lint_directive = None,
465 KnownRulesRule::ID => self.known_rules = None,
466 ExceptDirectiveValidRule::ID => self.except_directive_valid = None,
467 CommandSectionIndentationRule::ID => self.command_section_indentation = None,
468 DeprecatedObjectRule::ID => self.deprecated_object = None,
469 DeprecatedPlaceholderRule::ID => self.deprecated_placeholder = None,
470 DeprecatedRuntimeSectionRule::ID => self.deprecated_runtime_section = None,
471 _ => {}
472 }
473 }
474
475 self
476 }
477
478 pub fn except_all() -> Self {
480 Self {
481 unused_import: None,
482 unused_input: None,
483 unused_declaration: None,
484 unused_call: None,
485 unnecessary_function_call: None,
486 using_fallback_version: None,
487 misleading_declaration_order: None,
488 meaningless_lint_directive: None,
489 known_rules: None,
490 except_directive_valid: None,
491 command_section_indentation: None,
492 deprecated_object: None,
493 deprecated_placeholder: None,
494 deprecated_runtime_section: None,
495 }
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use super::*;
502
503 #[test_log::test]
504 fn custom_format_config_round_trip() {
505 let custom_format_config = FormatConfig::default().trailing_commas(false);
506 let analysis_config = Config::default().with_format_config(custom_format_config);
507 assert_eq!(analysis_config.format(), &custom_format_config);
508 }
509
510 #[test_log::test]
511 fn no_format_config_is_default() {
512 let default_format_config = FormatConfig::default();
513 let analysis_config = Config::default();
514 assert_eq!(analysis_config.format(), &default_format_config);
515 }
516}