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