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