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