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::ExceptDirectiveValidRule;
18use crate::Exceptable as _;
19use crate::FormatConfig;
20use crate::KnownRulesRule;
21use crate::MeaninglessLintDirective;
22use crate::MisleadingDeclarationOrderRule;
23use crate::Rule;
24use crate::UnnecessaryFunctionCall;
25use crate::UnusedCallRule;
26use crate::UnusedDeclarationRule;
27use crate::UnusedImportRule;
28use crate::UnusedInputRule;
29use crate::UsingFallbackVersion;
30use crate::rules;
31
32#[derive(Clone, PartialEq, Eq)]
37pub struct Config {
38 inner: Arc<ConfigInner>,
40}
41
42impl<'de> FromToml<'de> for Config {
43 fn from_toml(ctx: &mut Context<'de>, item: &Item<'de>) -> Result<Self, Failed> {
44 Ok(Self {
45 inner: ConfigInner::from_toml(ctx, item)?.into(),
46 })
47 }
48}
49
50impl std::fmt::Debug for Config {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 f.debug_struct("Config")
55 .field("diagnostics", &self.inner.diagnostics)
56 .field("fallback_version", &self.inner.fallback_version)
57 .finish()
58 }
59}
60
61impl Default for Config {
62 fn default() -> Self {
63 Self {
64 inner: Arc::new(ConfigInner {
65 diagnostics: Default::default(),
66 fallback_version: None,
67 format: FormatConfig::default(),
68 ignore_filename: None,
69 all_rules: Default::default(),
70 feature_flags: FeatureFlags::default(),
71 }),
72 }
73 }
74}
75
76impl Config {
77 pub fn diagnostics_config(&self) -> &DiagnosticsConfig {
79 &self.inner.diagnostics
80 }
81
82 pub fn fallback_version(&self) -> Option<SupportedVersion> {
85 self.inner.fallback_version
86 }
87
88 pub fn format(&self) -> &FormatConfig {
91 &self.inner.format
92 }
93
94 pub fn ignore_filename(&self) -> Option<&str> {
96 self.inner.ignore_filename.as_deref()
97 }
98
99 pub fn all_rules(&self) -> &[String] {
101 &self.inner.all_rules
102 }
103
104 pub fn feature_flags(&self) -> &FeatureFlags {
106 &self.inner.feature_flags
107 }
108
109 pub fn with_diagnostics_config(&self, diagnostics: DiagnosticsConfig) -> Self {
112 let mut inner = (*self.inner).clone();
113 inner.diagnostics = diagnostics;
114 Self {
115 inner: Arc::new(inner),
116 }
117 }
118
119 pub fn with_fallback_version(&self, fallback_version: Option<SupportedVersion>) -> Self {
149 let mut inner = (*self.inner).clone();
150 inner.fallback_version = fallback_version;
151 Self {
152 inner: Arc::new(inner),
153 }
154 }
155
156 pub fn with_format_config(&self, format: FormatConfig) -> Self {
159 let mut inner = (*self.inner).clone();
160 inner.format = format;
161 Self {
162 inner: Arc::new(inner),
163 }
164 }
165
166 pub fn with_ignore_filename(&self, filename: Option<String>) -> Self {
178 let mut inner = (*self.inner).clone();
179 inner.ignore_filename = filename;
180 Self {
181 inner: Arc::new(inner),
182 }
183 }
184
185 pub fn with_all_rules(&self, rules: Vec<String>) -> Self {
190 let mut inner = (*self.inner).clone();
191 inner.all_rules = rules;
192 Self {
193 inner: Arc::new(inner),
194 }
195 }
196
197 pub fn with_feature_flags(&self, feature_flags: FeatureFlags) -> Self {
200 let mut inner = (*self.inner).clone();
201 inner.feature_flags = feature_flags;
202 Self {
203 inner: Arc::new(inner),
204 }
205 }
206}
207
208#[derive(Clone, Debug, PartialEq, Eq, Toml)]
210struct ConfigInner {
211 #[toml(default, style = Header)]
213 diagnostics: DiagnosticsConfig,
214 #[toml(FromToml with = parse_string)]
216 fallback_version: Option<SupportedVersion>,
217 #[toml(default, style = Header)]
219 format: FormatConfig,
220 ignore_filename: Option<String>,
222 #[toml(default)]
224 all_rules: Vec<String>,
225 #[toml(default)]
227 feature_flags: FeatureFlags,
228}
229
230fn default_wdl_1_3() -> bool {
232 true
233}
234
235#[derive(Clone, Copy, Debug, PartialEq, Eq, Toml, JsonSchema)]
237pub struct FeatureFlags {
238 #[toml(default = true)]
243 #[schemars(default = "default_wdl_1_3")]
244 wdl_1_3: bool,
245 #[toml(default)]
250 #[schemars(default)]
251 wdl_1_4: bool,
252}
253
254impl Default for FeatureFlags {
255 fn default() -> Self {
256 Self {
257 wdl_1_3: true,
258 wdl_1_4: false,
259 }
260 }
261}
262
263impl FeatureFlags {
264 pub fn wdl_1_3(&self) -> bool {
269 self.wdl_1_3
270 }
271
272 #[deprecated(note = "WDL 1.3 is now enabled by default; this method is a no-op")]
274 pub fn with_wdl_1_3(self) -> Self {
275 self
276 }
277
278 pub fn wdl_1_4(&self) -> bool {
280 self.wdl_1_4
281 }
282
283 pub fn with_wdl_1_4(mut self) -> Self {
285 self.wdl_1_4 = true;
286 self
287 }
288}
289
290#[derive(Debug, Clone, Copy, PartialEq, Eq, Toml)]
297pub struct DiagnosticsConfig {
298 #[toml(FromToml with = parse_string)]
302 pub unused_import: Option<Severity>,
303 #[toml(FromToml with = parse_string)]
307 pub unused_input: Option<Severity>,
308 #[toml(FromToml with = parse_string)]
312 pub unused_declaration: Option<Severity>,
313 #[toml(FromToml with = parse_string)]
317 pub unused_call: Option<Severity>,
318 #[toml(FromToml with = parse_string)]
322 pub unnecessary_function_call: Option<Severity>,
323 #[toml(FromToml with = parse_string)]
329 pub using_fallback_version: Option<Severity>,
330 #[toml(FromToml with = parse_string)]
334 pub misleading_declaration_order: Option<Severity>,
335 #[toml(FromToml with = parse_string)]
339 pub meaningless_lint_directive: Option<Severity>,
340 #[toml(FromToml with = parse_string)]
344 pub known_rules: Option<Severity>,
345 #[toml(FromToml with = parse_string)]
349 pub except_directive_valid: Option<Severity>,
350}
351
352impl Default for DiagnosticsConfig {
353 fn default() -> Self {
354 Self::new(rules())
355 }
356}
357
358impl DiagnosticsConfig {
359 pub fn new<T: AsRef<dyn Rule>>(rules: impl IntoIterator<Item = T>) -> Self {
361 let mut unused_import = None;
362 let mut unused_input = None;
363 let mut unused_declaration = None;
364 let mut unused_call = None;
365 let mut unnecessary_function_call = None;
366 let mut using_fallback_version = None;
367 let mut misleading_declaration_order = None;
368 let mut meaningless_lint_directive = None;
369 let mut known_rules = None;
370 let mut except_directive_valid = None;
371
372 for rule in rules {
373 let rule = rule.as_ref();
374 match rule.id() {
375 UnusedImportRule::ID => unused_import = Some(rule.severity()),
376 UnusedInputRule::ID => unused_input = Some(rule.severity()),
377 UnusedDeclarationRule::ID => unused_declaration = Some(rule.severity()),
378 UnusedCallRule::ID => unused_call = Some(rule.severity()),
379 UnnecessaryFunctionCall::ID => unnecessary_function_call = Some(rule.severity()),
380 UsingFallbackVersion::ID => using_fallback_version = Some(rule.severity()),
381 MisleadingDeclarationOrderRule::ID => {
382 misleading_declaration_order = Some(rule.severity())
383 }
384 MeaninglessLintDirective::ID => meaningless_lint_directive = Some(rule.severity()),
385 KnownRulesRule::ID => known_rules = Some(rule.severity()),
386 ExceptDirectiveValidRule::ID => except_directive_valid = Some(rule.severity()),
387 unrecognized => {
388 warn!(unrecognized, "unrecognized rule");
389 if cfg!(test) {
390 panic!("unrecognized rule: {unrecognized}");
391 }
392 }
393 }
394 }
395
396 Self {
397 unused_import,
398 unused_input,
399 unused_declaration,
400 unused_call,
401 unnecessary_function_call,
402 using_fallback_version,
403 misleading_declaration_order,
404 meaningless_lint_directive,
405 known_rules,
406 except_directive_valid,
407 }
408 }
409
410 pub fn excepted_for_node(mut self, node: &SyntaxNode) -> Self {
413 let exceptions = node.rule_exceptions();
414
415 for exception in exceptions {
416 match &*exception.name {
417 UnusedImportRule::ID => self.unused_import = None,
418 UnusedInputRule::ID => self.unused_input = None,
419 UnusedDeclarationRule::ID => self.unused_declaration = None,
420 UnusedCallRule::ID => self.unused_call = None,
421 UnnecessaryFunctionCall::ID => self.unnecessary_function_call = None,
422 UsingFallbackVersion::ID => self.using_fallback_version = None,
423 MisleadingDeclarationOrderRule::ID => self.misleading_declaration_order = None,
424 MeaninglessLintDirective::ID => self.meaningless_lint_directive = None,
425 KnownRulesRule::ID => self.known_rules = None,
426 ExceptDirectiveValidRule::ID => self.except_directive_valid = None,
427 _ => {}
428 }
429 }
430
431 self
432 }
433
434 pub fn except_all() -> Self {
436 Self {
437 unused_import: None,
438 unused_input: None,
439 unused_declaration: None,
440 unused_call: None,
441 unnecessary_function_call: None,
442 using_fallback_version: None,
443 misleading_declaration_order: None,
444 meaningless_lint_directive: None,
445 known_rules: None,
446 except_directive_valid: None,
447 }
448 }
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454
455 #[test_log::test]
456 fn custom_format_config_round_trip() {
457 let custom_format_config = FormatConfig::default().trailing_commas(false);
458 let analysis_config = Config::default().with_format_config(custom_format_config);
459 assert_eq!(analysis_config.format(), &custom_format_config);
460 }
461
462 #[test_log::test]
463 fn no_format_config_is_default() {
464 let default_format_config = FormatConfig::default();
465 let analysis_config = Config::default();
466 assert_eq!(analysis_config.format(), &default_format_config);
467 }
468}