1use rowan::WalkEvent;
25use tracing::trace;
26use wdl_ast::AstNode;
27use wdl_ast::AstToken;
28use wdl_ast::Comment;
29use wdl_ast::SupportedVersion;
30use wdl_ast::SyntaxKind;
31use wdl_ast::SyntaxNode;
32use wdl_ast::VersionStatement;
33use wdl_ast::Whitespace;
34use wdl_ast::v1::BoundDecl;
35use wdl_ast::v1::CallStatement;
36use wdl_ast::v1::CommandSection;
37use wdl_ast::v1::CommandText;
38use wdl_ast::v1::ConditionalStatement;
39use wdl_ast::v1::EnumDefinition;
40use wdl_ast::v1::Expr;
41use wdl_ast::v1::ImportStatement;
42use wdl_ast::v1::InputSection;
43use wdl_ast::v1::MetadataArray;
44use wdl_ast::v1::MetadataObject;
45use wdl_ast::v1::MetadataObjectItem;
46use wdl_ast::v1::MetadataSection;
47use wdl_ast::v1::OutputSection;
48use wdl_ast::v1::ParameterMetadataSection;
49use wdl_ast::v1::Placeholder;
50use wdl_ast::v1::RequirementsSection;
51use wdl_ast::v1::RuntimeItem;
52use wdl_ast::v1::RuntimeSection;
53use wdl_ast::v1::ScatterStatement;
54use wdl_ast::v1::StringText;
55use wdl_ast::v1::StructDefinition;
56use wdl_ast::v1::TaskDefinition;
57use wdl_ast::v1::TaskHintsSection;
58use wdl_ast::v1::UnboundDecl;
59use wdl_ast::v1::WorkflowDefinition;
60use wdl_ast::v1::WorkflowHintsSection;
61
62use crate::Config;
63use crate::Diagnostics;
64use crate::document::Document as AnalysisDocument;
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
71pub enum VisitReason {
72 Enter,
74 Exit,
76}
77
78#[allow(unused_variables)]
84pub trait Visitor {
85 fn register(&mut self, config: &Config) {}
87
88 fn reset(&mut self);
93
94 fn document(
96 &mut self,
97 diagnostics: &mut Diagnostics,
98 reason: VisitReason,
99 doc: &AnalysisDocument,
100 version: SupportedVersion,
101 ) {
102 }
103
104 fn whitespace(&mut self, diagnostics: &mut Diagnostics, whitespace: &Whitespace) {}
106
107 fn comment(&mut self, diagnostics: &mut Diagnostics, comment: &Comment) {}
109
110 fn version_statement(
112 &mut self,
113 diagnostics: &mut Diagnostics,
114 reason: VisitReason,
115 stmt: &VersionStatement,
116 ) {
117 }
118
119 fn import_statement(
121 &mut self,
122 diagnostics: &mut Diagnostics,
123 reason: VisitReason,
124 stmt: &ImportStatement,
125 ) {
126 }
127
128 fn struct_definition(
130 &mut self,
131 diagnostics: &mut Diagnostics,
132 reason: VisitReason,
133 def: &StructDefinition,
134 ) {
135 }
136
137 fn enum_definition(
139 &mut self,
140 diagnostics: &mut Diagnostics,
141 reason: VisitReason,
142 def: &EnumDefinition,
143 ) {
144 }
145
146 fn task_definition(
148 &mut self,
149 diagnostics: &mut Diagnostics,
150 reason: VisitReason,
151 task: &TaskDefinition,
152 ) {
153 }
154
155 fn workflow_definition(
157 &mut self,
158 diagnostics: &mut Diagnostics,
159 reason: VisitReason,
160 workflow: &WorkflowDefinition,
161 ) {
162 }
163
164 fn input_section(
166 &mut self,
167 diagnostics: &mut Diagnostics,
168 reason: VisitReason,
169 section: &InputSection,
170 ) {
171 }
172
173 fn output_section(
175 &mut self,
176 diagnostics: &mut Diagnostics,
177 reason: VisitReason,
178 section: &OutputSection,
179 ) {
180 }
181
182 fn command_section(
184 &mut self,
185 diagnostics: &mut Diagnostics,
186 reason: VisitReason,
187 section: &CommandSection,
188 ) {
189 }
190
191 fn command_text(&mut self, diagnostics: &mut Diagnostics, text: &CommandText) {}
193
194 fn requirements_section(
196 &mut self,
197 diagnostics: &mut Diagnostics,
198 reason: VisitReason,
199 section: &RequirementsSection,
200 ) {
201 }
202
203 fn task_hints_section(
205 &mut self,
206 diagnostics: &mut Diagnostics,
207 reason: VisitReason,
208 section: &TaskHintsSection,
209 ) {
210 }
211
212 fn workflow_hints_section(
214 &mut self,
215 diagnostics: &mut Diagnostics,
216 reason: VisitReason,
217 section: &WorkflowHintsSection,
218 ) {
219 }
220
221 fn runtime_section(
223 &mut self,
224 diagnostics: &mut Diagnostics,
225 reason: VisitReason,
226 section: &RuntimeSection,
227 ) {
228 }
229
230 fn runtime_item(
232 &mut self,
233 diagnostics: &mut Diagnostics,
234 reason: VisitReason,
235 item: &RuntimeItem,
236 ) {
237 }
238
239 fn metadata_section(
241 &mut self,
242 diagnostics: &mut Diagnostics,
243 reason: VisitReason,
244 section: &MetadataSection,
245 ) {
246 }
247
248 fn parameter_metadata_section(
250 &mut self,
251 diagnostics: &mut Diagnostics,
252 reason: VisitReason,
253 section: &ParameterMetadataSection,
254 ) {
255 }
256
257 fn metadata_object(
259 &mut self,
260 diagnostics: &mut Diagnostics,
261 reason: VisitReason,
262 object: &MetadataObject,
263 ) {
264 }
265
266 fn metadata_object_item(
268 &mut self,
269 diagnostics: &mut Diagnostics,
270 reason: VisitReason,
271 item: &MetadataObjectItem,
272 ) {
273 }
274
275 fn metadata_array(
278 &mut self,
279 diagnostics: &mut Diagnostics,
280 reason: VisitReason,
281 item: &MetadataArray,
282 ) {
283 }
284
285 fn unbound_decl(
287 &mut self,
288 diagnostics: &mut Diagnostics,
289 reason: VisitReason,
290 decl: &UnboundDecl,
291 ) {
292 }
293
294 fn bound_decl(&mut self, diagnostics: &mut Diagnostics, reason: VisitReason, decl: &BoundDecl) {
296 }
297
298 fn expr(&mut self, diagnostics: &mut Diagnostics, reason: VisitReason, expr: &Expr) {}
300
301 fn string_text(&mut self, diagnostics: &mut Diagnostics, text: &StringText) {}
303
304 fn placeholder(
306 &mut self,
307 diagnostics: &mut Diagnostics,
308 reason: VisitReason,
309 placeholder: &Placeholder,
310 ) {
311 }
312
313 fn conditional_statement(
315 &mut self,
316 diagnostics: &mut Diagnostics,
317 reason: VisitReason,
318 stmt: &ConditionalStatement,
319 ) {
320 }
321
322 fn scatter_statement(
324 &mut self,
325 diagnostics: &mut Diagnostics,
326 reason: VisitReason,
327 stmt: &ScatterStatement,
328 ) {
329 }
330
331 fn call_statement(
333 &mut self,
334 diagnostics: &mut Diagnostics,
335 reason: VisitReason,
336 stmt: &CallStatement,
337 ) {
338 }
339}
340
341pub(crate) fn visit<V: Visitor>(
344 document: &AnalysisDocument,
345 diagnostics: &mut Diagnostics,
346 visitor: &mut V,
347) {
348 trace!(
349 uri = %document.uri(),
350 "beginning document traversal",
351 );
352 for event in document.root().inner().preorder_with_tokens() {
353 let (reason, element) = match event {
354 WalkEvent::Enter(node) => (VisitReason::Enter, node),
355 WalkEvent::Leave(node) => (VisitReason::Exit, node),
356 };
357 trace!(uri = %document.uri(), ?reason, element = ?element.kind());
358 match element.kind() {
359 SyntaxKind::RootNode => visitor.document(
360 diagnostics,
361 reason,
362 document,
363 document
364 .version()
365 .expect("visited document must have a version"),
366 ),
367 SyntaxKind::VersionStatementNode => visitor.version_statement(
368 diagnostics,
369 reason,
370 &VersionStatement::cast(element.into_node().unwrap()).expect("should cast"),
371 ),
372 SyntaxKind::ImportStatementNode => visitor.import_statement(
373 diagnostics,
374 reason,
375 &ImportStatement::cast(element.into_node().unwrap()).expect("should cast"),
376 ),
377 SyntaxKind::ImportAliasNode => {
378 }
380 SyntaxKind::StructDefinitionNode => visitor.struct_definition(
381 diagnostics,
382 reason,
383 &StructDefinition::cast(element.into_node().unwrap()).expect("should cast"),
384 ),
385 SyntaxKind::EnumDefinitionNode => visitor.enum_definition(
386 diagnostics,
387 reason,
388 &EnumDefinition::cast(element.into_node().unwrap()).expect("should cast"),
389 ),
390 SyntaxKind::TaskDefinitionNode => visitor.task_definition(
391 diagnostics,
392 reason,
393 &TaskDefinition::cast(element.into_node().unwrap()).expect("should cast"),
394 ),
395 SyntaxKind::WorkflowDefinitionNode => visitor.workflow_definition(
396 diagnostics,
397 reason,
398 &WorkflowDefinition::cast(element.into_node().unwrap()).expect("should cast"),
399 ),
400 SyntaxKind::UnboundDeclNode => visitor.unbound_decl(
401 diagnostics,
402 reason,
403 &UnboundDecl::cast(element.into_node().unwrap()).expect("should cast"),
404 ),
405 SyntaxKind::BoundDeclNode => visitor.bound_decl(
406 diagnostics,
407 reason,
408 &BoundDecl::cast(element.into_node().unwrap()).expect("should cast"),
409 ),
410 SyntaxKind::PrimitiveTypeNode
411 | SyntaxKind::MapTypeNode
412 | SyntaxKind::ArrayTypeNode
413 | SyntaxKind::PairTypeNode
414 | SyntaxKind::ObjectTypeNode
415 | SyntaxKind::TypeRefNode => {
416 }
418 SyntaxKind::InputSectionNode => visitor.input_section(
419 diagnostics,
420 reason,
421 &InputSection::cast(element.into_node().unwrap()).expect("should cast"),
422 ),
423 SyntaxKind::OutputSectionNode => visitor.output_section(
424 diagnostics,
425 reason,
426 &OutputSection::cast(element.into_node().unwrap()).expect("should cast"),
427 ),
428 SyntaxKind::CommandSectionNode => visitor.command_section(
429 diagnostics,
430 reason,
431 &CommandSection::cast(element.into_node().unwrap()).expect("should cast"),
432 ),
433 SyntaxKind::RequirementsSectionNode => visitor.requirements_section(
434 diagnostics,
435 reason,
436 &RequirementsSection::cast(element.into_node().unwrap()).expect("should cast"),
437 ),
438 SyntaxKind::TaskHintsSectionNode => visitor.task_hints_section(
439 diagnostics,
440 reason,
441 &TaskHintsSection::cast(element.into_node().unwrap()).expect("should cast"),
442 ),
443 SyntaxKind::WorkflowHintsSectionNode => visitor.workflow_hints_section(
444 diagnostics,
445 reason,
446 &WorkflowHintsSection::cast(element.into_node().unwrap()).expect("should cast"),
447 ),
448 SyntaxKind::TaskHintsItemNode | SyntaxKind::WorkflowHintsItemNode => {
449 }
451 SyntaxKind::RequirementsItemNode => {
452 }
454 SyntaxKind::RuntimeSectionNode => visitor.runtime_section(
455 diagnostics,
456 reason,
457 &RuntimeSection::cast(element.into_node().unwrap()).expect("should cast"),
458 ),
459 SyntaxKind::RuntimeItemNode => visitor.runtime_item(
460 diagnostics,
461 reason,
462 &RuntimeItem::cast(element.into_node().unwrap()).expect("should cast"),
463 ),
464 SyntaxKind::MetadataSectionNode => visitor.metadata_section(
465 diagnostics,
466 reason,
467 &MetadataSection::cast(element.into_node().unwrap()).expect("should cast"),
468 ),
469 SyntaxKind::ParameterMetadataSectionNode => visitor.parameter_metadata_section(
470 diagnostics,
471 reason,
472 &ParameterMetadataSection::cast(element.into_node().unwrap()).expect("should cast"),
473 ),
474 SyntaxKind::MetadataObjectNode => visitor.metadata_object(
475 diagnostics,
476 reason,
477 &MetadataObject::cast(element.into_node().unwrap()).expect("should cast"),
478 ),
479 SyntaxKind::MetadataObjectItemNode => visitor.metadata_object_item(
480 diagnostics,
481 reason,
482 &MetadataObjectItem::cast(element.into_node().unwrap()).expect("should cast"),
483 ),
484 SyntaxKind::MetadataArrayNode => visitor.metadata_array(
485 diagnostics,
486 reason,
487 &MetadataArray::cast(element.into_node().unwrap()).expect("should cast"),
488 ),
489 SyntaxKind::LiteralNullNode => {
490 }
492 k if Expr::<SyntaxNode>::can_cast(k) => {
493 visitor.expr(
494 diagnostics,
495 reason,
496 &Expr::cast(element.into_node().expect(
497 "any element that is able to be turned into an expr should be a node",
498 ))
499 .expect("expr should be built"),
500 )
501 }
502 SyntaxKind::LiteralMapItemNode
503 | SyntaxKind::LiteralObjectItemNode
504 | SyntaxKind::LiteralStructItemNode
505 | SyntaxKind::LiteralHintsItemNode
506 | SyntaxKind::LiteralInputItemNode
507 | SyntaxKind::LiteralOutputItemNode => {
508 }
510 k @ (SyntaxKind::LiteralIntegerNode
511 | SyntaxKind::LiteralFloatNode
512 | SyntaxKind::LiteralBooleanNode
513 | SyntaxKind::LiteralNoneNode
514 | SyntaxKind::LiteralStringNode
515 | SyntaxKind::LiteralPairNode
516 | SyntaxKind::LiteralArrayNode
517 | SyntaxKind::LiteralMapNode
518 | SyntaxKind::LiteralObjectNode
519 | SyntaxKind::LiteralStructNode
520 | SyntaxKind::LiteralHintsNode
521 | SyntaxKind::LiteralInputNode
522 | SyntaxKind::LiteralOutputNode
523 | SyntaxKind::ParenthesizedExprNode
524 | SyntaxKind::NameRefExprNode
525 | SyntaxKind::IfExprNode
526 | SyntaxKind::LogicalNotExprNode
527 | SyntaxKind::NegationExprNode
528 | SyntaxKind::LogicalOrExprNode
529 | SyntaxKind::LogicalAndExprNode
530 | SyntaxKind::EqualityExprNode
531 | SyntaxKind::InequalityExprNode
532 | SyntaxKind::LessExprNode
533 | SyntaxKind::LessEqualExprNode
534 | SyntaxKind::GreaterExprNode
535 | SyntaxKind::GreaterEqualExprNode
536 | SyntaxKind::AdditionExprNode
537 | SyntaxKind::SubtractionExprNode
538 | SyntaxKind::MultiplicationExprNode
539 | SyntaxKind::DivisionExprNode
540 | SyntaxKind::ModuloExprNode
541 | SyntaxKind::CallExprNode
542 | SyntaxKind::IndexExprNode
543 | SyntaxKind::AccessExprNode) => {
544 unreachable!("`{k:?}` should be handled by `Expr::can_cast`")
545 }
546 SyntaxKind::PlaceholderNode => visitor.placeholder(
547 diagnostics,
548 reason,
549 &Placeholder::cast(element.into_node().unwrap()).expect("should cast"),
550 ),
551 SyntaxKind::PlaceholderSepOptionNode
552 | SyntaxKind::PlaceholderDefaultOptionNode
553 | SyntaxKind::PlaceholderTrueFalseOptionNode => {
554 }
556 SyntaxKind::ConditionalStatementNode => visitor.conditional_statement(
557 diagnostics,
558 reason,
559 &ConditionalStatement::cast(element.into_node().unwrap()).expect("should cast"),
560 ),
561 SyntaxKind::ScatterStatementNode => visitor.scatter_statement(
562 diagnostics,
563 reason,
564 &ScatterStatement::cast(element.into_node().unwrap()).expect("should cast"),
565 ),
566 SyntaxKind::CallStatementNode => visitor.call_statement(
567 diagnostics,
568 reason,
569 &CallStatement::cast(element.into_node().unwrap()).expect("should cast"),
570 ),
571 SyntaxKind::CallTargetNode
572 | SyntaxKind::CallAliasNode
573 | SyntaxKind::CallAfterNode
574 | SyntaxKind::CallInputItemNode => {
575 }
577 SyntaxKind::Abandoned | SyntaxKind::MAX => {
578 unreachable!("node should not exist in the tree")
579 }
580 SyntaxKind::Whitespace if reason == VisitReason::Enter => visitor.whitespace(
581 diagnostics,
582 &Whitespace::cast(element.into_token().unwrap()).expect("should cast"),
583 ),
584 SyntaxKind::Comment if reason == VisitReason::Enter => visitor.comment(
585 diagnostics,
586 &Comment::cast(element.into_token().unwrap()).expect("should cast"),
587 ),
588 SyntaxKind::LiteralStringText if reason == VisitReason::Enter => visitor.string_text(
589 diagnostics,
590 &StringText::cast(element.into_token().unwrap()).expect("should cast"),
591 ),
592 SyntaxKind::LiteralCommandText if reason == VisitReason::Enter => visitor.command_text(
593 diagnostics,
594 &CommandText::cast(element.into_token().unwrap()).expect("should cast"),
595 ),
596 _ => {
597 }
599 }
600 }
601}