wdl-analysis 0.19.1

Analysis of Workflow Description Language (WDL) documents.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
//! Implementation of analysis rules.

use std::sync::LazyLock;

use wdl_ast::Severity;

/// The rule identifier for unused import warnings.
pub const UNUSED_IMPORT_RULE_ID: &str = "UnusedImport";

/// The rule identifier for unused input warnings.
pub const UNUSED_INPUT_RULE_ID: &str = "UnusedInput";

/// The rule identifier for unused declaration warnings.
pub const UNUSED_DECL_RULE_ID: &str = "UnusedDeclaration";

/// The rule identifier for unused call warnings.
pub const UNUSED_CALL_RULE_ID: &str = "UnusedCall";

/// The rule identifier for unnecessary function call warnings.
pub const UNNECESSARY_FUNCTION_CALL: &str = "UnnecessaryFunctionCall";

/// The rule identifier for unsupported version fallback warnings.
pub const USING_FALLBACK_VERSION: &str = "UsingFallbackVersion";

/// All rule IDs sorted alphabetically.
pub static ALL_RULE_IDS: LazyLock<Vec<String>> = LazyLock::new(|| {
    let mut ids: Vec<String> = rules().iter().map(|r| r.id().to_string()).collect();
    ids.sort();
    ids
});

/// A trait implemented by analysis rules.
pub trait Rule: Send + Sync {
    /// The unique identifier for the rule.
    ///
    /// The identifier is required to be pascal case and it is the identifier by
    /// which a rule is excepted or denied.
    fn id(&self) -> &'static str;

    /// A short, single sentence description of the rule.
    fn description(&self) -> &'static str;

    /// Get the long-form explanation of the rule.
    fn explanation(&self) -> &'static str;

    /// Get a list of examples that would trigger this rule.
    fn examples(&self) -> &'static [&'static str];

    /// Denies the rule.
    ///
    /// Denying the rule treats any diagnostics it emits as an error.
    fn deny(&mut self);

    /// Gets the severity of the rule.
    fn severity(&self) -> Severity;
}

/// Gets the list of all analysis rules.
pub fn rules() -> Vec<Box<dyn Rule>> {
    let rules: Vec<Box<dyn Rule>> = vec![
        Box::<UnusedImportRule>::default(),
        Box::<UnusedInputRule>::default(),
        Box::<UnusedDeclarationRule>::default(),
        Box::<UnusedCallRule>::default(),
        Box::<UnnecessaryFunctionCall>::default(),
        Box::<UsingFallbackVersion>::default(),
    ];

    // Ensure all the rule ids are unique and pascal case
    #[cfg(debug_assertions)]
    {
        use convert_case::Case;
        use convert_case::Casing;
        let mut set = std::collections::HashSet::new();
        for r in rules.iter() {
            if r.id().to_case(Case::Pascal) != r.id() {
                panic!("analysis rule id `{id}` is not pascal case", id = r.id());
            }

            if !set.insert(r.id()) {
                panic!("duplicate rule id `{id}`", id = r.id());
            }
        }
    }

    rules
}

/// Represents the unused import rule.
#[derive(Debug, Clone, Copy)]
pub struct UnusedImportRule(Severity);

impl UnusedImportRule {
    /// Creates a new unused import rule.
    pub fn new() -> Self {
        Self(Severity::Warning)
    }
}

impl Default for UnusedImportRule {
    fn default() -> Self {
        Self::new()
    }
}

impl Rule for UnusedImportRule {
    fn id(&self) -> &'static str {
        UNUSED_IMPORT_RULE_ID
    }

    fn description(&self) -> &'static str {
        "Ensures that import namespaces are used in the importing document."
    }

    fn explanation(&self) -> &'static str {
        "Imported WDL documents should be used in the document that imports them. Unused imports \
         impact parsing and evaluation performance."
    }

    fn examples(&self) -> &'static [&'static str] {
        &[r#"```wdl
version 1.2

import "example2.wdl"

workflow example {
    meta {}

    output {}
}
```"#]
    }

    fn deny(&mut self) {
        self.0 = Severity::Error;
    }

    fn severity(&self) -> Severity {
        self.0
    }
}

/// Represents the unused input rule.
#[derive(Debug, Clone, Copy)]
pub struct UnusedInputRule(Severity);

impl UnusedInputRule {
    /// Creates a new unused input rule.
    pub fn new() -> Self {
        Self(Severity::Warning)
    }
}

impl Default for UnusedInputRule {
    fn default() -> Self {
        Self::new()
    }
}

impl Rule for UnusedInputRule {
    fn id(&self) -> &'static str {
        UNUSED_INPUT_RULE_ID
    }

    fn description(&self) -> &'static str {
        "Ensures that task or workspace inputs are used within the declaring task or workspace."
    }

    fn explanation(&self) -> &'static str {
        "Unused inputs degrade evaluation performance and reduce the clarity of the code. Unused \
         file inputs in tasks can also cause unnecessary file localizations."
    }

    fn examples(&self) -> &'static [&'static str] {
        &[r#"```wdl
version 1.2

workflow example {
    meta {}

    input {
        String unused
    }

    output {}
}
```"#]
    }

    fn deny(&mut self) {
        self.0 = Severity::Error;
    }

    fn severity(&self) -> Severity {
        self.0
    }
}

/// Represents the unused declaration rule.
#[derive(Debug, Clone, Copy)]
pub struct UnusedDeclarationRule(Severity);

impl UnusedDeclarationRule {
    /// Creates a new unused declaration rule.
    pub fn new() -> Self {
        Self(Severity::Warning)
    }
}

impl Default for UnusedDeclarationRule {
    fn default() -> Self {
        Self::new()
    }
}

impl Rule for UnusedDeclarationRule {
    fn id(&self) -> &'static str {
        UNUSED_DECL_RULE_ID
    }

    fn description(&self) -> &'static str {
        "Ensures that private declarations in tasks or workspaces are used within the declaring \
         task or workspace."
    }

    fn explanation(&self) -> &'static str {
        "Unused private declarations degrade evaluation performance and reduce the clarity of the \
         code."
    }

    fn examples(&self) -> &'static [&'static str] {
        &[r#"```wdl
version 1.2

workflow example {
    meta {}

    String unused = "this will produce a warning"

    output {}
}
```"#]
    }

    fn deny(&mut self) {
        self.0 = Severity::Error;
    }

    fn severity(&self) -> Severity {
        self.0
    }
}

/// Represents the unused call rule.
#[derive(Debug, Clone, Copy)]
pub struct UnusedCallRule(Severity);

impl UnusedCallRule {
    /// Creates a new unused call rule.
    pub fn new() -> Self {
        Self(Severity::Warning)
    }
}

impl Default for UnusedCallRule {
    fn default() -> Self {
        Self::new()
    }
}

impl Rule for UnusedCallRule {
    fn id(&self) -> &'static str {
        UNUSED_CALL_RULE_ID
    }

    fn description(&self) -> &'static str {
        "Ensures that outputs of a call statement are used in the declaring workflow."
    }

    fn explanation(&self) -> &'static str {
        "Unused calls may cause unnecessary consumption of compute resources."
    }

    fn examples(&self) -> &'static [&'static str] {
        &[r#"```wdl
version 1.2

workflow example {
    meta {}

    # The output of `do_work` is never used
    call do_work

    output {}
}

task do_work {
    command <<<>>>

    output {
        Int x = 0
    }
}
```"#]
    }

    fn deny(&mut self) {
        self.0 = Severity::Error;
    }

    fn severity(&self) -> Severity {
        self.0
    }
}

/// Represents the unnecessary call rule.
#[derive(Debug, Clone, Copy)]
pub struct UnnecessaryFunctionCall(Severity);

impl UnnecessaryFunctionCall {
    /// Creates a new unnecessary function call rule.
    pub fn new() -> Self {
        Self(Severity::Warning)
    }
}

impl Default for UnnecessaryFunctionCall {
    fn default() -> Self {
        Self::new()
    }
}

impl Rule for UnnecessaryFunctionCall {
    fn id(&self) -> &'static str {
        UNNECESSARY_FUNCTION_CALL
    }

    fn description(&self) -> &'static str {
        "Ensures that function calls are necessary."
    }

    fn explanation(&self) -> &'static str {
        "Unnecessary function calls may impact evaluation performance."
    }

    fn examples(&self) -> &'static [&'static str] {
        &[r#"```wdl
version 1.2

workflow example {
    meta {}

    # Calls to `defined` on values that are statically
    # known to be non-None are unnecessary.
    Boolean exists = defined("hello")

    output {}
}
```"#]
    }

    fn deny(&mut self) {
        self.0 = Severity::Error;
    }

    fn severity(&self) -> Severity {
        self.0
    }
}

/// Represents the using fallback version rule.
#[derive(Debug, Clone, Copy)]
pub struct UsingFallbackVersion(Severity);

impl UsingFallbackVersion {
    /// Creates a new using fallback version rule.
    pub fn new() -> Self {
        Self(Severity::Warning)
    }
}

impl Default for UsingFallbackVersion {
    fn default() -> Self {
        Self::new()
    }
}

impl Rule for UsingFallbackVersion {
    fn id(&self) -> &'static str {
        USING_FALLBACK_VERSION
    }

    fn description(&self) -> &'static str {
        "Warns if interpretation of a document with an unsupported version falls back to a default."
    }

    fn explanation(&self) -> &'static str {
        "A document with an unsupported version may have unpredictable behavior if interpreted as \
         a different version."
    }

    fn examples(&self) -> &'static [&'static str] {
        &[r#"```wdl
# Not a valid version. If a fallback version is configured,
# the document will be interpreted as that version.
version development

workflow example {
    meta {}

    output {}
}
```"#]
    }

    fn deny(&mut self) {
        self.0 = Severity::Error;
    }

    fn severity(&self) -> Severity {
        self.0
    }
}