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