Skip to main content

wdl_lint/rules/
call_input_keyword.rs

1//! A lint rule for unnecessary input keyword when WDL version is >= 1.2.
2
3use wdl_analysis::Diagnostics;
4use wdl_analysis::Example;
5use wdl_analysis::LabeledSnippet;
6use wdl_analysis::VisitReason;
7use wdl_analysis::Visitor;
8use wdl_ast::AstNode;
9use wdl_ast::Diagnostic;
10use wdl_ast::Span;
11use wdl_ast::SupportedVersion;
12use wdl_ast::SyntaxKind;
13use wdl_ast::v1::CallStatement;
14use wdl_ast::version::V1;
15
16use crate::Rule;
17use crate::Tag;
18use crate::TagSet;
19
20/// The identifier for this rule.
21const ID: &str = "CallInputKeyword";
22
23/// Creates a diagnostic for unnecessary input keyword.
24fn call_input_unnecessary(span: Span) -> Diagnostic {
25    Diagnostic::note("the `input:` keyword is unnecessary for WDL version 1.2 and later")
26        .with_rule(ID)
27        .with_highlight(span)
28        .with_fix("remove the `input:` keyword from the call statement")
29}
30
31/// Detects unnecessary use of the `input:` keyword in call statements.
32#[derive(Default, Debug, Clone, Copy)]
33pub struct CallInputKeywordRule {
34    /// The WDL version of the file is stored here
35    version: Option<SupportedVersion>,
36}
37
38impl Rule for CallInputKeywordRule {
39    fn id(&self) -> &'static str {
40        ID
41    }
42
43    fn description(&self) -> &'static str {
44        "Ensures that the `input:` keyword is not used in call statements when WDL version is 1.2 \
45         or later."
46    }
47
48    fn explanation(&self) -> &'static str {
49        "Starting with WDL version 1.2, the `input:` keyword in call statements is optional. This \
50         specification change allows call inputs to be specified directly within the braces \
51         without the `input:` keyword, resulting in a cleaner and more concise syntax. This rule \
52         encourages adoption of the newer syntax when using WDL 1.2 or later."
53    }
54
55    fn examples(&self) -> &'static [Example] {
56        &[Example {
57            negative: LabeledSnippet {
58                label: None,
59                snippet: r#"version 1.2
60
61workflow example {
62    # In versions prior to WDL v1.2, the `input:` keyword
63    # was necessary in `call` statements.
64    call say_hello { input:
65        name = "world",
66    }
67}
68"#,
69            },
70            revised: Some(LabeledSnippet {
71                label: None,
72                snippet: r#"version 1.2
73
74workflow example {
75    # This is correct for WDL v1.2 and later.
76    call say_hello {
77        name = "world",
78    }
79}
80"#,
81            }),
82        }]
83    }
84
85    fn tags(&self) -> TagSet {
86        TagSet::new(&[Tag::Deprecated, Tag::Style])
87    }
88
89    fn exceptable_nodes(&self) -> Option<&'static [SyntaxKind]> {
90        Some(&[
91            SyntaxKind::VersionStatementNode,
92            SyntaxKind::CallStatementNode,
93            SyntaxKind::WorkflowDefinitionNode,
94        ])
95    }
96
97    fn related_rules(&self) -> &'static [&'static str] {
98        &[]
99    }
100}
101
102impl Visitor for CallInputKeywordRule {
103    fn reset(&mut self) {
104        *self = Self::default();
105    }
106
107    fn document(
108        &mut self,
109        _diagnostics: &mut Diagnostics,
110        reason: VisitReason,
111        _doc: &wdl_analysis::Document,
112        version: SupportedVersion,
113    ) {
114        if reason == VisitReason::Enter {
115            self.version = Some(version);
116        }
117    }
118
119    fn call_statement(
120        &mut self,
121        diagnostics: &mut Diagnostics,
122        reason: VisitReason,
123        call: &CallStatement,
124    ) {
125        if reason == VisitReason::Exit {
126            return;
127        }
128
129        let version = self.version.expect("document should have a version");
130
131        if version <= SupportedVersion::V1(V1::One) {
132            return;
133        }
134
135        if let Some(input_keyword) = call
136            .inner()
137            .children_with_tokens()
138            .find(|c| c.kind() == SyntaxKind::InputKeyword)
139        {
140            diagnostics.exceptable_add(
141                call_input_unnecessary(input_keyword.text_range().into()),
142                call.inner(),
143                &self.exceptable_nodes(),
144            );
145        }
146    }
147}