wdl_lint/rules/
call_input_keyword.rs1use 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
20const ID: &str = "CallInputKeyword";
22
23fn 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#[derive(Default, Debug, Clone, Copy)]
33pub struct CallInputKeywordRule {
34 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}