wdl_analysis/
rules.rs

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