wdl_analysis/
validation.rs

1//! Validator for WDL documents.
2
3use wdl_ast::Comment;
4use wdl_ast::Diagnostic;
5use wdl_ast::SupportedVersion;
6use wdl_ast::SyntaxElement;
7use wdl_ast::SyntaxKind;
8use wdl_ast::VersionStatement;
9use wdl_ast::Whitespace;
10use wdl_ast::v1;
11
12use crate::Config;
13use crate::SyntaxNodeExt;
14use crate::VisitReason;
15use crate::Visitor;
16use crate::document::Document;
17
18mod counts;
19mod env;
20mod exprs;
21mod imports;
22mod keys;
23mod numbers;
24mod requirements;
25mod strings;
26mod version;
27
28/// Represents a collection of validation diagnostics.
29///
30/// Validation visitors receive a diagnostics collection during
31/// visitation of the AST.
32#[derive(Debug, Default)]
33pub struct Diagnostics(pub(crate) Vec<Diagnostic>);
34
35impl Diagnostics {
36    /// Adds a diagnostic to the collection.
37    pub fn add(&mut self, diagnostic: Diagnostic) {
38        self.0.push(diagnostic);
39    }
40
41    /// Adds a diagnostic to the collection, unless the diagnostic is for an
42    /// element that has an exception for the given rule.
43    ///
44    /// If the diagnostic does not have a rule, the diagnostic is always added.
45    pub fn exceptable_add(
46        &mut self,
47        diagnostic: Diagnostic,
48        element: SyntaxElement,
49        exceptable_nodes: &Option<&'static [SyntaxKind]>,
50    ) {
51        if let Some(rule) = diagnostic.rule() {
52            for node in element.ancestors().filter(|node| {
53                exceptable_nodes
54                    .as_ref()
55                    .is_none_or(|nodes| nodes.contains(&node.kind()))
56            }) {
57                if node.is_rule_excepted(rule) {
58                    // Rule is currently excepted, don't add the diagnostic
59                    return;
60                }
61            }
62        }
63
64        self.add(diagnostic);
65    }
66
67    /// Extends the collection with another collection of diagnostics.
68    pub fn extend(&mut self, diagnostics: Diagnostics) {
69        self.0.extend(diagnostics.0);
70    }
71
72    /// Returns whether the collection is empty.
73    pub fn is_empty(&self) -> bool {
74        self.0.is_empty()
75    }
76
77    /// Sorts the diagnostics in the collection.
78    pub fn sort(&mut self) {
79        self.0.sort();
80    }
81}
82
83/// Implements an AST validator.
84///
85/// A validator operates on a set of AST visitors.
86///
87/// See the [validate](Self::validate) method to perform the validation.
88#[allow(missing_debug_implementations)]
89pub struct Validator {
90    /// The set of validation visitors.
91    visitors: Vec<Box<dyn Visitor>>,
92}
93
94impl Validator {
95    /// Creates a validator with an empty visitors set.
96    pub const fn empty() -> Self {
97        Self {
98            visitors: Vec::new(),
99        }
100    }
101
102    /// Adds a visitor to the validator.
103    pub fn add_visitor<V: Visitor + 'static>(&mut self, visitor: V) {
104        self.visitors.push(Box::new(visitor));
105    }
106
107    /// Adds multiple visitors to the validator.
108    pub fn add_visitors(&mut self, visitors: impl IntoIterator<Item = Box<dyn Visitor>>) {
109        self.visitors.extend(visitors)
110    }
111
112    /// Validates the given document and returns the validation errors upon
113    /// failure.
114    pub fn validate(
115        &mut self,
116        document: &Document,
117        config: &Config,
118    ) -> Result<(), Vec<Diagnostic>> {
119        let mut diagnostics = Diagnostics::default();
120        self.register(config);
121        document.visit(&mut diagnostics, self);
122
123        self.reset();
124
125        if diagnostics.is_empty() {
126            Ok(())
127        } else {
128            diagnostics.sort();
129            Err(diagnostics.0)
130        }
131    }
132}
133
134impl Default for Validator {
135    /// Creates a validator with the default validation visitors.
136    fn default() -> Self {
137        Self {
138            visitors: vec![
139                Box::new(strings::LiteralTextVisitor),
140                Box::<counts::CountingVisitor>::default(),
141                Box::<keys::UniqueKeysVisitor>::default(),
142                Box::<numbers::NumberVisitor>::default(),
143                Box::<version::VersionVisitor>::default(),
144                Box::<requirements::RequirementsVisitor>::default(),
145                Box::<exprs::ScopedExprVisitor>::default(),
146                Box::<imports::ImportsVisitor>::default(),
147                Box::<env::EnvVisitor>::default(),
148            ],
149        }
150    }
151}
152
153impl Visitor for Validator {
154    fn register(&mut self, config: &crate::Config) {
155        for visitor in self.visitors.iter_mut() {
156            visitor.register(config);
157        }
158    }
159
160    fn reset(&mut self) {
161        for visitor in self.visitors.iter_mut() {
162            visitor.reset();
163        }
164    }
165
166    fn document(
167        &mut self,
168        diagnostics: &mut Diagnostics,
169        reason: VisitReason,
170        doc: &Document,
171        version: SupportedVersion,
172    ) {
173        for visitor in self.visitors.iter_mut() {
174            visitor.document(diagnostics, reason, doc, version);
175        }
176    }
177
178    fn whitespace(&mut self, diagnostics: &mut Diagnostics, whitespace: &Whitespace) {
179        for visitor in self.visitors.iter_mut() {
180            visitor.whitespace(diagnostics, whitespace);
181        }
182    }
183
184    fn comment(&mut self, diagnostics: &mut Diagnostics, comment: &Comment) {
185        for visitor in self.visitors.iter_mut() {
186            visitor.comment(diagnostics, comment);
187        }
188    }
189
190    fn version_statement(
191        &mut self,
192        diagnostics: &mut Diagnostics,
193        reason: VisitReason,
194        stmt: &VersionStatement,
195    ) {
196        for visitor in self.visitors.iter_mut() {
197            visitor.version_statement(diagnostics, reason, stmt);
198        }
199    }
200
201    fn import_statement(
202        &mut self,
203        diagnostics: &mut Diagnostics,
204        reason: VisitReason,
205        stmt: &v1::ImportStatement,
206    ) {
207        for visitor in self.visitors.iter_mut() {
208            visitor.import_statement(diagnostics, reason, stmt);
209        }
210    }
211
212    fn struct_definition(
213        &mut self,
214        diagnostics: &mut Diagnostics,
215        reason: VisitReason,
216        def: &v1::StructDefinition,
217    ) {
218        for visitor in self.visitors.iter_mut() {
219            visitor.struct_definition(diagnostics, reason, def);
220        }
221    }
222
223    fn enum_definition(
224        &mut self,
225        diagnostics: &mut Diagnostics,
226        reason: VisitReason,
227        def: &v1::EnumDefinition,
228    ) {
229        for visitor in self.visitors.iter_mut() {
230            visitor.enum_definition(diagnostics, reason, def);
231        }
232    }
233
234    fn task_definition(
235        &mut self,
236        diagnostics: &mut Diagnostics,
237        reason: VisitReason,
238        task: &v1::TaskDefinition,
239    ) {
240        for visitor in self.visitors.iter_mut() {
241            visitor.task_definition(diagnostics, reason, task);
242        }
243    }
244
245    fn workflow_definition(
246        &mut self,
247        diagnostics: &mut Diagnostics,
248        reason: VisitReason,
249        workflow: &v1::WorkflowDefinition,
250    ) {
251        for visitor in self.visitors.iter_mut() {
252            visitor.workflow_definition(diagnostics, reason, workflow);
253        }
254    }
255
256    fn input_section(
257        &mut self,
258        diagnostics: &mut Diagnostics,
259        reason: VisitReason,
260        section: &v1::InputSection,
261    ) {
262        for visitor in self.visitors.iter_mut() {
263            visitor.input_section(diagnostics, reason, section);
264        }
265    }
266
267    fn output_section(
268        &mut self,
269        diagnostics: &mut Diagnostics,
270        reason: VisitReason,
271        section: &v1::OutputSection,
272    ) {
273        for visitor in self.visitors.iter_mut() {
274            visitor.output_section(diagnostics, reason, section);
275        }
276    }
277
278    fn command_section(
279        &mut self,
280        diagnostics: &mut Diagnostics,
281        reason: VisitReason,
282        section: &v1::CommandSection,
283    ) {
284        for visitor in self.visitors.iter_mut() {
285            visitor.command_section(diagnostics, reason, section);
286        }
287    }
288
289    fn command_text(&mut self, diagnostics: &mut Diagnostics, text: &v1::CommandText) {
290        for visitor in self.visitors.iter_mut() {
291            visitor.command_text(diagnostics, text);
292        }
293    }
294
295    fn requirements_section(
296        &mut self,
297        diagnostics: &mut Diagnostics,
298        reason: VisitReason,
299        section: &v1::RequirementsSection,
300    ) {
301        for visitor in self.visitors.iter_mut() {
302            visitor.requirements_section(diagnostics, reason, section);
303        }
304    }
305
306    fn task_hints_section(
307        &mut self,
308        diagnostics: &mut Diagnostics,
309        reason: VisitReason,
310        section: &v1::TaskHintsSection,
311    ) {
312        for visitor in self.visitors.iter_mut() {
313            visitor.task_hints_section(diagnostics, reason, section);
314        }
315    }
316
317    fn workflow_hints_section(
318        &mut self,
319        diagnostics: &mut Diagnostics,
320        reason: VisitReason,
321        section: &v1::WorkflowHintsSection,
322    ) {
323        for visitor in self.visitors.iter_mut() {
324            visitor.workflow_hints_section(diagnostics, reason, section);
325        }
326    }
327
328    fn runtime_section(
329        &mut self,
330        diagnostics: &mut Diagnostics,
331        reason: VisitReason,
332        section: &v1::RuntimeSection,
333    ) {
334        for visitor in self.visitors.iter_mut() {
335            visitor.runtime_section(diagnostics, reason, section);
336        }
337    }
338
339    fn runtime_item(
340        &mut self,
341        diagnostics: &mut Diagnostics,
342        reason: VisitReason,
343        item: &v1::RuntimeItem,
344    ) {
345        for visitor in self.visitors.iter_mut() {
346            visitor.runtime_item(diagnostics, reason, item);
347        }
348    }
349
350    fn metadata_section(
351        &mut self,
352        diagnostics: &mut Diagnostics,
353        reason: VisitReason,
354        section: &v1::MetadataSection,
355    ) {
356        for visitor in self.visitors.iter_mut() {
357            visitor.metadata_section(diagnostics, reason, section);
358        }
359    }
360
361    fn parameter_metadata_section(
362        &mut self,
363        diagnostics: &mut Diagnostics,
364        reason: VisitReason,
365        section: &v1::ParameterMetadataSection,
366    ) {
367        for visitor in self.visitors.iter_mut() {
368            visitor.parameter_metadata_section(diagnostics, reason, section);
369        }
370    }
371
372    fn metadata_object(
373        &mut self,
374        diagnostics: &mut Diagnostics,
375        reason: VisitReason,
376        object: &v1::MetadataObject,
377    ) {
378        for visitor in self.visitors.iter_mut() {
379            visitor.metadata_object(diagnostics, reason, object);
380        }
381    }
382
383    fn metadata_object_item(
384        &mut self,
385        diagnostics: &mut Diagnostics,
386        reason: VisitReason,
387        item: &v1::MetadataObjectItem,
388    ) {
389        for visitor in self.visitors.iter_mut() {
390            visitor.metadata_object_item(diagnostics, reason, item);
391        }
392    }
393
394    fn metadata_array(
395        &mut self,
396        diagnostics: &mut Diagnostics,
397        reason: VisitReason,
398        item: &v1::MetadataArray,
399    ) {
400        for visitor in self.visitors.iter_mut() {
401            visitor.metadata_array(diagnostics, reason, item);
402        }
403    }
404
405    fn unbound_decl(
406        &mut self,
407        diagnostics: &mut Diagnostics,
408        reason: VisitReason,
409        decl: &v1::UnboundDecl,
410    ) {
411        for visitor in self.visitors.iter_mut() {
412            visitor.unbound_decl(diagnostics, reason, decl);
413        }
414    }
415
416    fn bound_decl(
417        &mut self,
418        diagnostics: &mut Diagnostics,
419        reason: VisitReason,
420        decl: &v1::BoundDecl,
421    ) {
422        for visitor in self.visitors.iter_mut() {
423            visitor.bound_decl(diagnostics, reason, decl);
424        }
425    }
426
427    fn expr(&mut self, diagnostics: &mut Diagnostics, reason: VisitReason, expr: &v1::Expr) {
428        for visitor in self.visitors.iter_mut() {
429            visitor.expr(diagnostics, reason, expr);
430        }
431    }
432
433    fn string_text(&mut self, diagnostics: &mut Diagnostics, text: &v1::StringText) {
434        for visitor in self.visitors.iter_mut() {
435            visitor.string_text(diagnostics, text);
436        }
437    }
438
439    fn placeholder(
440        &mut self,
441        diagnostics: &mut Diagnostics,
442        reason: VisitReason,
443        placeholder: &v1::Placeholder,
444    ) {
445        for visitor in self.visitors.iter_mut() {
446            visitor.placeholder(diagnostics, reason, placeholder);
447        }
448    }
449
450    fn conditional_statement(
451        &mut self,
452        diagnostics: &mut Diagnostics,
453        reason: VisitReason,
454        stmt: &v1::ConditionalStatement,
455    ) {
456        for visitor in self.visitors.iter_mut() {
457            visitor.conditional_statement(diagnostics, reason, stmt);
458        }
459    }
460
461    fn scatter_statement(
462        &mut self,
463        diagnostics: &mut Diagnostics,
464        reason: VisitReason,
465        stmt: &v1::ScatterStatement,
466    ) {
467        for visitor in self.visitors.iter_mut() {
468            visitor.scatter_statement(diagnostics, reason, stmt);
469        }
470    }
471
472    fn call_statement(
473        &mut self,
474        diagnostics: &mut Diagnostics,
475        reason: VisitReason,
476        stmt: &v1::CallStatement,
477    ) {
478        for visitor in self.visitors.iter_mut() {
479            visitor.call_statement(diagnostics, reason, stmt);
480        }
481    }
482}