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