1use std::sync::LazyLock;
4
5use wdl_ast::Severity;
6
7pub const UNUSED_IMPORT_RULE_ID: &str = "UnusedImport";
9
10pub const UNUSED_INPUT_RULE_ID: &str = "UnusedInput";
12
13pub const UNUSED_DECL_RULE_ID: &str = "UnusedDeclaration";
15
16pub const UNUSED_CALL_RULE_ID: &str = "UnusedCall";
18
19pub const UNNECESSARY_FUNCTION_CALL: &str = "UnnecessaryFunctionCall";
21
22pub const USING_FALLBACK_VERSION: &str = "UsingFallbackVersion";
24
25pub static ALL_RULE_IDS: LazyLock<Vec<String>> = LazyLock::new(|| {
27 let mut ids: Vec<String> = rules().iter().map(|r| r.id().to_string()).collect();
28 ids.sort();
29 ids
30});
31
32pub trait Rule: Send + Sync {
34 fn id(&self) -> &'static str;
39
40 fn description(&self) -> &'static str;
42
43 fn explanation(&self) -> &'static str;
45
46 fn deny(&mut self);
50
51 fn severity(&self) -> Severity;
53}
54
55pub fn rules() -> Vec<Box<dyn Rule>> {
57 let rules: Vec<Box<dyn Rule>> = vec![
58 Box::<UnusedImportRule>::default(),
59 Box::<UnusedInputRule>::default(),
60 Box::<UnusedDeclarationRule>::default(),
61 Box::<UnusedCallRule>::default(),
62 Box::<UnnecessaryFunctionCall>::default(),
63 Box::<UsingFallbackVersion>::default(),
64 ];
65
66 #[cfg(debug_assertions)]
68 {
69 use convert_case::Case;
70 use convert_case::Casing;
71 let mut set = std::collections::HashSet::new();
72 for r in rules.iter() {
73 if r.id().to_case(Case::Pascal) != r.id() {
74 panic!("analysis rule id `{id}` is not pascal case", id = r.id());
75 }
76
77 if !set.insert(r.id()) {
78 panic!("duplicate rule id `{id}`", id = r.id());
79 }
80 }
81 }
82
83 rules
84}
85
86#[derive(Debug, Clone, Copy)]
88pub struct UnusedImportRule(Severity);
89
90impl UnusedImportRule {
91 pub fn new() -> Self {
93 Self(Severity::Warning)
94 }
95}
96
97impl Default for UnusedImportRule {
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103impl Rule for UnusedImportRule {
104 fn id(&self) -> &'static str {
105 UNUSED_IMPORT_RULE_ID
106 }
107
108 fn description(&self) -> &'static str {
109 "Ensures that import namespaces are used in the importing document."
110 }
111
112 fn explanation(&self) -> &'static str {
113 "Imported WDL documents should be used in the document that imports them. Unused imports \
114 impact parsing and evaluation performance."
115 }
116
117 fn deny(&mut self) {
118 self.0 = Severity::Error;
119 }
120
121 fn severity(&self) -> Severity {
122 self.0
123 }
124}
125
126#[derive(Debug, Clone, Copy)]
128pub struct UnusedInputRule(Severity);
129
130impl UnusedInputRule {
131 pub fn new() -> Self {
133 Self(Severity::Warning)
134 }
135}
136
137impl Default for UnusedInputRule {
138 fn default() -> Self {
139 Self::new()
140 }
141}
142
143impl Rule for UnusedInputRule {
144 fn id(&self) -> &'static str {
145 UNUSED_INPUT_RULE_ID
146 }
147
148 fn description(&self) -> &'static str {
149 "Ensures that task or workspace inputs are used within the declaring task or workspace."
150 }
151
152 fn explanation(&self) -> &'static str {
153 "Unused inputs degrade evaluation performance and reduce the clarity of the code. Unused \
154 file inputs in tasks can also cause unnecessary file localizations."
155 }
156
157 fn deny(&mut self) {
158 self.0 = Severity::Error;
159 }
160
161 fn severity(&self) -> Severity {
162 self.0
163 }
164}
165
166#[derive(Debug, Clone, Copy)]
168pub struct UnusedDeclarationRule(Severity);
169
170impl UnusedDeclarationRule {
171 pub fn new() -> Self {
173 Self(Severity::Warning)
174 }
175}
176
177impl Default for UnusedDeclarationRule {
178 fn default() -> Self {
179 Self::new()
180 }
181}
182
183impl Rule for UnusedDeclarationRule {
184 fn id(&self) -> &'static str {
185 UNUSED_DECL_RULE_ID
186 }
187
188 fn description(&self) -> &'static str {
189 "Ensures that private declarations in tasks or workspaces are used within the declaring \
190 task or workspace."
191 }
192
193 fn explanation(&self) -> &'static str {
194 "Unused private declarations degrade evaluation performance and reduce the clarity of the \
195 code."
196 }
197
198 fn deny(&mut self) {
199 self.0 = Severity::Error;
200 }
201
202 fn severity(&self) -> Severity {
203 self.0
204 }
205}
206
207#[derive(Debug, Clone, Copy)]
209pub struct UnusedCallRule(Severity);
210
211impl UnusedCallRule {
212 pub fn new() -> Self {
214 Self(Severity::Warning)
215 }
216}
217
218impl Default for UnusedCallRule {
219 fn default() -> Self {
220 Self::new()
221 }
222}
223
224impl Rule for UnusedCallRule {
225 fn id(&self) -> &'static str {
226 UNUSED_CALL_RULE_ID
227 }
228
229 fn description(&self) -> &'static str {
230 "Ensures that outputs of a call statement are used in the declaring workflow."
231 }
232
233 fn explanation(&self) -> &'static str {
234 "Unused calls may cause unnecessary consumption of compute resources."
235 }
236
237 fn deny(&mut self) {
238 self.0 = Severity::Error;
239 }
240
241 fn severity(&self) -> Severity {
242 self.0
243 }
244}
245
246#[derive(Debug, Clone, Copy)]
248pub struct UnnecessaryFunctionCall(Severity);
249
250impl UnnecessaryFunctionCall {
251 pub fn new() -> Self {
253 Self(Severity::Warning)
254 }
255}
256
257impl Default for UnnecessaryFunctionCall {
258 fn default() -> Self {
259 Self::new()
260 }
261}
262
263impl Rule for UnnecessaryFunctionCall {
264 fn id(&self) -> &'static str {
265 UNNECESSARY_FUNCTION_CALL
266 }
267
268 fn description(&self) -> &'static str {
269 "Ensures that function calls are necessary."
270 }
271
272 fn explanation(&self) -> &'static str {
273 "Unnecessary function calls may impact evaluation performance."
274 }
275
276 fn deny(&mut self) {
277 self.0 = Severity::Error;
278 }
279
280 fn severity(&self) -> Severity {
281 self.0
282 }
283}
284
285#[derive(Debug, Clone, Copy)]
287pub struct UsingFallbackVersion(Severity);
288
289impl UsingFallbackVersion {
290 pub fn new() -> Self {
292 Self(Severity::Warning)
293 }
294}
295
296impl Default for UsingFallbackVersion {
297 fn default() -> Self {
298 Self::new()
299 }
300}
301
302impl Rule for UsingFallbackVersion {
303 fn id(&self) -> &'static str {
304 USING_FALLBACK_VERSION
305 }
306
307 fn description(&self) -> &'static str {
308 "Warns if interpretation of a document with an unsupported version falls back to a default."
309 }
310
311 fn explanation(&self) -> &'static str {
312 "A document with an unsupported version may have unpredictable behavior if interpreted as \
313 a different version."
314 }
315
316 fn deny(&mut self) {
317 self.0 = Severity::Error;
318 }
319
320 fn severity(&self) -> Severity {
321 self.0
322 }
323}