1pub mod dive;
4
5use std::borrow::Cow;
6use std::collections::VecDeque;
7use std::fmt;
8use std::iter;
9
10use rowan::GreenNodeBuilder;
11use rowan::GreenNodeData;
12use strum::VariantArray;
13
14use super::Diagnostic;
15use super::grammar;
16use super::lexer::Lexer;
17use super::parser::Event;
18use crate::parser::Parser;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, VariantArray)]
29#[repr(u16)]
30pub enum SyntaxKind {
31 Unknown,
33 Unparsed,
37 Whitespace,
39 Comment,
41 Version,
43 Float,
45 Integer,
47 Ident,
49 SingleQuote,
51 DoubleQuote,
53 OpenHeredoc,
55 CloseHeredoc,
57 ArrayTypeKeyword,
59 BooleanTypeKeyword,
61 FileTypeKeyword,
63 FloatTypeKeyword,
65 IntTypeKeyword,
67 MapTypeKeyword,
69 ObjectTypeKeyword,
71 PairTypeKeyword,
73 StringTypeKeyword,
75 AfterKeyword,
77 AliasKeyword,
79 AsKeyword,
81 CallKeyword,
83 CommandKeyword,
85 ElseKeyword,
87 EnvKeyword,
89 FalseKeyword,
91 IfKeyword,
93 InKeyword,
95 ImportKeyword,
97 InputKeyword,
99 MetaKeyword,
101 NoneKeyword,
103 NullKeyword,
105 ObjectKeyword,
107 OutputKeyword,
109 ParameterMetaKeyword,
111 RuntimeKeyword,
113 ScatterKeyword,
115 StructKeyword,
117 TaskKeyword,
119 ThenKeyword,
121 TrueKeyword,
123 VersionKeyword,
125 WorkflowKeyword,
127 DirectoryTypeKeyword,
129 HintsKeyword,
131 RequirementsKeyword,
133 OpenBrace,
135 CloseBrace,
137 OpenBracket,
139 CloseBracket,
141 Assignment,
143 Colon,
145 Comma,
147 OpenParen,
149 CloseParen,
151 QuestionMark,
153 Exclamation,
155 Plus,
157 Minus,
159 LogicalOr,
161 LogicalAnd,
163 Asterisk,
165 Exponentiation,
167 Slash,
169 Percent,
171 Equal,
173 NotEqual,
175 LessEqual,
177 GreaterEqual,
179 Less,
181 Greater,
183 Dot,
185 LiteralStringText,
187 LiteralCommandText,
189 PlaceholderOpen,
191
192 #[doc(hidden)]
200 Abandoned,
201 RootNode,
203 VersionStatementNode,
205 ImportStatementNode,
207 ImportAliasNode,
209 StructDefinitionNode,
211 TaskDefinitionNode,
213 WorkflowDefinitionNode,
215 UnboundDeclNode,
217 BoundDeclNode,
219 InputSectionNode,
221 OutputSectionNode,
223 CommandSectionNode,
225 RequirementsSectionNode,
227 RequirementsItemNode,
229 TaskHintsSectionNode,
231 WorkflowHintsSectionNode,
233 TaskHintsItemNode,
235 WorkflowHintsItemNode,
237 WorkflowHintsObjectNode,
239 WorkflowHintsObjectItemNode,
241 WorkflowHintsArrayNode,
243 RuntimeSectionNode,
245 RuntimeItemNode,
247 PrimitiveTypeNode,
249 MapTypeNode,
251 ArrayTypeNode,
253 PairTypeNode,
255 ObjectTypeNode,
257 TypeRefNode,
259 MetadataSectionNode,
261 ParameterMetadataSectionNode,
263 MetadataObjectItemNode,
265 MetadataObjectNode,
267 MetadataArrayNode,
269 LiteralIntegerNode,
271 LiteralFloatNode,
273 LiteralBooleanNode,
275 LiteralNoneNode,
277 LiteralNullNode,
279 LiteralStringNode,
281 LiteralPairNode,
283 LiteralArrayNode,
285 LiteralMapNode,
287 LiteralMapItemNode,
289 LiteralObjectNode,
291 LiteralObjectItemNode,
293 LiteralStructNode,
295 LiteralStructItemNode,
297 LiteralHintsNode,
299 LiteralHintsItemNode,
301 LiteralInputNode,
303 LiteralInputItemNode,
305 LiteralOutputNode,
307 LiteralOutputItemNode,
309 ParenthesizedExprNode,
311 NameRefExprNode,
313 IfExprNode,
315 LogicalNotExprNode,
317 NegationExprNode,
319 LogicalOrExprNode,
321 LogicalAndExprNode,
323 EqualityExprNode,
325 InequalityExprNode,
327 LessExprNode,
329 LessEqualExprNode,
331 GreaterExprNode,
333 GreaterEqualExprNode,
335 AdditionExprNode,
337 SubtractionExprNode,
339 MultiplicationExprNode,
341 DivisionExprNode,
343 ModuloExprNode,
345 ExponentiationExprNode,
347 CallExprNode,
349 IndexExprNode,
351 AccessExprNode,
353 PlaceholderNode,
355 PlaceholderSepOptionNode,
357 PlaceholderDefaultOptionNode,
359 PlaceholderTrueFalseOptionNode,
361 ConditionalStatementNode,
363 ConditionalStatementClauseNode,
365 ScatterStatementNode,
367 CallStatementNode,
369 CallTargetNode,
371 CallAliasNode,
373 CallAfterNode,
375 CallInputItemNode,
377
378 MAX,
381}
382
383impl SyntaxKind {
384 pub fn is_symbolic(&self) -> bool {
390 matches!(
391 self,
392 Self::Abandoned | Self::Unknown | Self::Unparsed | Self::MAX
393 )
394 }
395
396 pub fn describe(&self) -> &'static str {
398 match self {
399 Self::Unknown => unreachable!(),
400 Self::Unparsed => unreachable!(),
401 Self::Whitespace => "whitespace",
402 Self::Comment => "comment",
403 Self::Version => "version",
404 Self::Float => "float",
405 Self::Integer => "integer",
406 Self::Ident => "identifier",
407 Self::SingleQuote => "single quote",
408 Self::DoubleQuote => "double quote",
409 Self::OpenHeredoc => "open heredoc",
410 Self::CloseHeredoc => "close heredoc",
411 Self::ArrayTypeKeyword => "`Array` type keyword",
412 Self::BooleanTypeKeyword => "`Boolean` type keyword",
413 Self::FileTypeKeyword => "`File` type keyword",
414 Self::FloatTypeKeyword => "`Float` type keyword",
415 Self::IntTypeKeyword => "`Int` type keyword",
416 Self::MapTypeKeyword => "`Map` type keyword",
417 Self::ObjectTypeKeyword => "`Object` type keyword",
418 Self::PairTypeKeyword => "`Pair` type keyword",
419 Self::StringTypeKeyword => "`String` type keyword",
420 Self::AfterKeyword => "`after` keyword",
421 Self::AliasKeyword => "`alias` keyword",
422 Self::AsKeyword => "`as` keyword",
423 Self::CallKeyword => "`call` keyword",
424 Self::CommandKeyword => "`command` keyword",
425 Self::ElseKeyword => "`else` keyword",
426 Self::EnvKeyword => "`env` keyword",
427 Self::FalseKeyword => "`false` keyword",
428 Self::IfKeyword => "`if` keyword",
429 Self::InKeyword => "`in` keyword",
430 Self::ImportKeyword => "`import` keyword",
431 Self::InputKeyword => "`input` keyword",
432 Self::MetaKeyword => "`meta` keyword",
433 Self::NoneKeyword => "`None` keyword",
434 Self::NullKeyword => "`null` keyword",
435 Self::ObjectKeyword => "`object` keyword",
436 Self::OutputKeyword => "`output` keyword",
437 Self::ParameterMetaKeyword => "`parameter_meta` keyword",
438 Self::RuntimeKeyword => "`runtime` keyword",
439 Self::ScatterKeyword => "`scatter` keyword",
440 Self::StructKeyword => "`struct` keyword",
441 Self::TaskKeyword => "`task` keyword",
442 Self::ThenKeyword => "`then` keyword",
443 Self::TrueKeyword => "`true` keyword",
444 Self::VersionKeyword => "`version` keyword",
445 Self::WorkflowKeyword => "`workflow` keyword",
446 Self::DirectoryTypeKeyword => "`Directory` type keyword",
447 Self::HintsKeyword => "`hints` keyword",
448 Self::RequirementsKeyword => "`requirements` keyword",
449 Self::OpenBrace => "`{` symbol",
450 Self::CloseBrace => "`}` symbol",
451 Self::OpenBracket => "`[` symbol",
452 Self::CloseBracket => "`]` symbol",
453 Self::Assignment => "`=` symbol",
454 Self::Colon => "`:` symbol",
455 Self::Comma => "`,` symbol",
456 Self::OpenParen => "`(` symbol",
457 Self::CloseParen => "`)` symbol",
458 Self::QuestionMark => "`?` symbol",
459 Self::Exclamation => "`!` symbol",
460 Self::Plus => "`+` symbol",
461 Self::Minus => "`-` symbol",
462 Self::LogicalOr => "`||` symbol",
463 Self::LogicalAnd => "`&&` symbol",
464 Self::Asterisk => "`*` symbol",
465 Self::Exponentiation => "`**` symbol",
466 Self::Slash => "`/` symbol",
467 Self::Percent => "`%` symbol",
468 Self::Equal => "`==` symbol",
469 Self::NotEqual => "`!=` symbol",
470 Self::LessEqual => "`<=` symbol",
471 Self::GreaterEqual => "`>=` symbol",
472 Self::Less => "`<` symbol",
473 Self::Greater => "`>` symbol",
474 Self::Dot => "`.` symbol",
475 Self::LiteralStringText => "literal string text",
476 Self::LiteralCommandText => "literal command text",
477 Self::PlaceholderOpen => "placeholder open",
478 Self::Abandoned => unreachable!(),
479 Self::RootNode => "root node",
480 Self::VersionStatementNode => "version statement",
481 Self::ImportStatementNode => "import statement",
482 Self::ImportAliasNode => "import alias",
483 Self::StructDefinitionNode => "struct definition",
484 Self::TaskDefinitionNode => "task definition",
485 Self::WorkflowDefinitionNode => "workflow definition",
486 Self::UnboundDeclNode => "declaration without assignment",
487 Self::BoundDeclNode => "declaration with assignment",
488 Self::InputSectionNode => "input section",
489 Self::OutputSectionNode => "output section",
490 Self::CommandSectionNode => "command section",
491 Self::RequirementsSectionNode => "requirements section",
492 Self::RequirementsItemNode => "requirements item",
493 Self::TaskHintsSectionNode | Self::WorkflowHintsSectionNode => "hints section",
494 Self::TaskHintsItemNode | Self::WorkflowHintsItemNode => "hints item",
495 Self::WorkflowHintsObjectNode => "literal object",
496 Self::WorkflowHintsObjectItemNode => "literal object item",
497 Self::WorkflowHintsArrayNode => "literal array",
498 Self::RuntimeSectionNode => "runtime section",
499 Self::RuntimeItemNode => "runtime item",
500 Self::PrimitiveTypeNode => "primitive type",
501 Self::MapTypeNode => "map type",
502 Self::ArrayTypeNode => "array type",
503 Self::PairTypeNode => "pair type",
504 Self::ObjectTypeNode => "object type",
505 Self::TypeRefNode => "type reference",
506 Self::MetadataSectionNode => "metadata section",
507 Self::ParameterMetadataSectionNode => "parameter metadata section",
508 Self::MetadataObjectItemNode => "metadata object item",
509 Self::MetadataObjectNode => "metadata object",
510 Self::MetadataArrayNode => "metadata array",
511 Self::LiteralIntegerNode => "literal integer",
512 Self::LiteralFloatNode => "literal float",
513 Self::LiteralBooleanNode => "literal boolean",
514 Self::LiteralNoneNode => "literal `None`",
515 Self::LiteralNullNode => "literal null",
516 Self::LiteralStringNode => "literal string",
517 Self::LiteralPairNode => "literal pair",
518 Self::LiteralArrayNode => "literal array",
519 Self::LiteralMapNode => "literal map",
520 Self::LiteralMapItemNode => "literal map item",
521 Self::LiteralObjectNode => "literal object",
522 Self::LiteralObjectItemNode => "literal object item",
523 Self::LiteralStructNode => "literal struct",
524 Self::LiteralStructItemNode => "literal struct item",
525 Self::LiteralHintsNode => "literal hints",
526 Self::LiteralHintsItemNode => "literal hints item",
527 Self::LiteralInputNode => "literal input",
528 Self::LiteralInputItemNode => "literal input item",
529 Self::LiteralOutputNode => "literal output",
530 Self::LiteralOutputItemNode => "literal output item",
531 Self::ParenthesizedExprNode => "parenthesized expression",
532 Self::NameRefExprNode => "name reference expression",
533 Self::IfExprNode => "`if` expression",
534 Self::LogicalNotExprNode => "logical not expression",
535 Self::NegationExprNode => "negation expression",
536 Self::LogicalOrExprNode => "logical OR expression",
537 Self::LogicalAndExprNode => "logical AND expression",
538 Self::EqualityExprNode => "equality expression",
539 Self::InequalityExprNode => "inequality expression",
540 Self::LessExprNode => "less than expression",
541 Self::LessEqualExprNode => "less than or equal to expression",
542 Self::GreaterExprNode => "greater than expression",
543 Self::GreaterEqualExprNode => "greater than or equal to expression",
544 Self::AdditionExprNode => "addition expression",
545 Self::SubtractionExprNode => "subtraction expression",
546 Self::MultiplicationExprNode => "multiplication expression",
547 Self::DivisionExprNode => "division expression",
548 Self::ModuloExprNode => "modulo expression",
549 Self::ExponentiationExprNode => "exponentiation expression",
550 Self::CallExprNode => "call expression",
551 Self::IndexExprNode => "index expression",
552 Self::AccessExprNode => "access expression",
553 Self::PlaceholderNode => "placeholder",
554 Self::PlaceholderSepOptionNode => "placeholder `sep` option",
555 Self::PlaceholderDefaultOptionNode => "placeholder `default` option",
556 Self::PlaceholderTrueFalseOptionNode => "placeholder `true`/`false` option",
557 Self::ConditionalStatementNode => "conditional statement",
558 Self::ConditionalStatementClauseNode => "conditional statement clause",
559 Self::ScatterStatementNode => "scatter statement",
560 Self::CallStatementNode => "call statement",
561 Self::CallTargetNode => "call target",
562 Self::CallAliasNode => "call alias",
563 Self::CallAfterNode => "call `after` clause",
564 Self::CallInputItemNode => "call input item",
565 Self::MAX => unreachable!(),
566 }
567 }
568
569 pub fn is_trivia(&self) -> bool {
571 matches!(self, Self::Whitespace | Self::Comment)
572 }
573
574 pub fn is_keyword(&self) -> bool {
576 matches!(
577 self,
578 SyntaxKind::AfterKeyword
579 | SyntaxKind::AliasKeyword
580 | SyntaxKind::ArrayTypeKeyword
581 | SyntaxKind::AsKeyword
582 | SyntaxKind::BooleanTypeKeyword
583 | SyntaxKind::CallKeyword
584 | SyntaxKind::CommandKeyword
585 | SyntaxKind::DirectoryTypeKeyword
586 | SyntaxKind::ElseKeyword
587 | SyntaxKind::EnvKeyword
588 | SyntaxKind::FalseKeyword
589 | SyntaxKind::FileTypeKeyword
590 | SyntaxKind::FloatTypeKeyword
591 | SyntaxKind::HintsKeyword
592 | SyntaxKind::IfKeyword
593 | SyntaxKind::ImportKeyword
594 | SyntaxKind::InKeyword
595 | SyntaxKind::InputKeyword
596 | SyntaxKind::IntTypeKeyword
597 | SyntaxKind::MapTypeKeyword
598 | SyntaxKind::MetaKeyword
599 | SyntaxKind::NoneKeyword
600 | SyntaxKind::NullKeyword
601 | SyntaxKind::ObjectKeyword
602 | SyntaxKind::ObjectTypeKeyword
603 | SyntaxKind::OutputKeyword
604 | SyntaxKind::PairTypeKeyword
605 | SyntaxKind::ParameterMetaKeyword
606 | SyntaxKind::RequirementsKeyword
607 | SyntaxKind::RuntimeKeyword
608 | SyntaxKind::ScatterKeyword
609 | SyntaxKind::StringTypeKeyword
610 | SyntaxKind::StructKeyword
611 | SyntaxKind::TaskKeyword
612 | SyntaxKind::ThenKeyword
613 | SyntaxKind::TrueKeyword
614 | SyntaxKind::VersionKeyword
615 | SyntaxKind::WorkflowKeyword
616 )
617 }
618
619 pub fn is_operator(&self) -> bool {
621 matches!(
622 self,
623 SyntaxKind::Plus
624 | SyntaxKind::Minus
625 | SyntaxKind::Slash
626 | SyntaxKind::Percent
627 | SyntaxKind::Asterisk
628 | SyntaxKind::Exponentiation
629 | SyntaxKind::Equal
630 | SyntaxKind::NotEqual
631 | SyntaxKind::Less
632 | SyntaxKind::LessEqual
633 | SyntaxKind::Greater
634 | SyntaxKind::GreaterEqual
635 | SyntaxKind::LogicalAnd
636 | SyntaxKind::LogicalOr
637 | SyntaxKind::Exclamation
638 | SyntaxKind::Assignment
639 | SyntaxKind::QuestionMark
640 | SyntaxKind::Dot
641 | SyntaxKind::Colon
642 )
643 }
644}
645
646pub static ALL_SYNTAX_KIND: &[SyntaxKind] = SyntaxKind::VARIANTS;
648
649impl From<SyntaxKind> for rowan::SyntaxKind {
650 fn from(kind: SyntaxKind) -> Self {
651 rowan::SyntaxKind(kind as u16)
652 }
653}
654
655#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
657pub struct WorkflowDescriptionLanguage;
658
659impl rowan::Language for WorkflowDescriptionLanguage {
660 type Kind = SyntaxKind;
661
662 fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
663 assert!(raw.0 <= SyntaxKind::MAX as u16);
664 unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
665 }
666
667 fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
668 kind.into()
669 }
670}
671
672pub type SyntaxNode = rowan::SyntaxNode<WorkflowDescriptionLanguage>;
674pub type SyntaxToken = rowan::SyntaxToken<WorkflowDescriptionLanguage>;
676pub type SyntaxElement = rowan::SyntaxElement<WorkflowDescriptionLanguage>;
678pub type SyntaxNodeChildren = rowan::SyntaxNodeChildren<WorkflowDescriptionLanguage>;
680
681pub fn construct_tree(source: &str, mut events: Vec<Event>) -> SyntaxNode {
683 let mut builder = GreenNodeBuilder::default();
684 let mut ancestors = Vec::new();
685
686 for i in 0..events.len() {
687 match std::mem::replace(&mut events[i], Event::abandoned()) {
688 Event::NodeStarted {
689 kind,
690 forward_parent,
691 } => {
692 ancestors.push(kind);
695 let mut idx = i;
696 let mut fp: Option<usize> = forward_parent;
697 while let Some(distance) = fp {
698 idx += distance;
699 fp = match std::mem::replace(&mut events[idx], Event::abandoned()) {
700 Event::NodeStarted {
701 kind,
702 forward_parent,
703 } => {
704 ancestors.push(kind);
705 forward_parent
706 }
707 _ => unreachable!(),
708 };
709 }
710
711 for kind in ancestors.drain(..).rev() {
714 if kind != SyntaxKind::Abandoned {
715 builder.start_node(kind.into());
716 }
717 }
718 }
719 Event::NodeFinished => builder.finish_node(),
720 Event::Token { kind, span } => {
721 builder.token(kind.into(), &source[span.start()..span.end()])
722 }
723 }
724 }
725
726 SyntaxNode::new_root(builder.finish())
727}
728
729#[derive(Clone, PartialEq, Eq, Hash)]
731pub struct SyntaxTree(SyntaxNode);
732
733impl SyntaxTree {
734 pub fn parse(source: &str) -> (Self, Vec<Diagnostic>) {
753 let parser = Parser::new(Lexer::new(source));
754 let (events, mut diagnostics) = grammar::document(parser);
755 diagnostics.sort();
756 (Self(construct_tree(source, events)), diagnostics)
757 }
758
759 pub fn root(&self) -> &SyntaxNode {
761 &self.0
762 }
763
764 pub fn green(&self) -> Cow<'_, GreenNodeData> {
766 self.0.green()
767 }
768
769 pub fn into_syntax(self) -> SyntaxNode {
771 self.0
772 }
773}
774
775impl fmt::Display for SyntaxTree {
776 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
777 self.0.fmt(f)
778 }
779}
780
781impl fmt::Debug for SyntaxTree {
782 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
783 self.0.fmt(f)
784 }
785}
786
787pub trait SyntaxTokenExt {
789 fn preceding_trivia(&self) -> impl Iterator<Item = SyntaxToken>;
791
792 fn inline_comment(&self) -> Option<SyntaxToken>;
795}
796
797impl SyntaxTokenExt for SyntaxToken {
798 fn preceding_trivia(&self) -> impl Iterator<Item = SyntaxToken> {
799 let mut tokens = VecDeque::new();
800 let mut cur = self.prev_token();
801 while let Some(token) = cur {
802 cur = token.prev_token();
803 if !token.kind().is_trivia() {
805 break;
806 }
807 if token.kind() == SyntaxKind::Comment
809 && let Some(prev) = token.prev_token()
810 {
811 if prev.kind() == SyntaxKind::Whitespace {
812 let has_newlines = prev.text().chars().any(|c| c == '\n');
813 if !has_newlines && prev.prev_token().is_some() {
819 break;
820 }
821 } else {
822 break;
824 }
825 }
826 match token.kind() {
828 SyntaxKind::Whitespace
829 if token.text().chars().filter(|c| *c == '\n').count() > 1 =>
830 {
831 tokens.push_front(token);
832 }
833 SyntaxKind::Comment => {
834 tokens.push_front(token);
835 }
836 _ => {}
837 }
838 }
839 tokens.into_iter()
840 }
841
842 fn inline_comment(&self) -> Option<SyntaxToken> {
843 let mut next = self.next_token();
844 iter::from_fn(move || {
845 let cur = next.clone()?;
846 next = cur.next_token();
847 Some(cur)
848 })
849 .take_while(|t| {
850 if !t.kind().is_trivia() {
852 return false;
853 }
854 if t.kind() == SyntaxKind::Whitespace {
856 return !t.text().chars().any(|c| c == '\n');
857 }
858 true
859 })
860 .find(|t| t.kind() == SyntaxKind::Comment)
861 }
862}
863
864#[cfg(test)]
865mod tests {
866 use super::*;
867 use crate::SyntaxTree;
868
869 #[test]
870 fn preceding_comments() {
871 let (tree, diagnostics) = SyntaxTree::parse(
872 "version 1.2
873
874# This comment should not be included
875task foo {} # This comment should not be included
876
877# Some
878# comments
879# are
880# long
881
882# Others are short
883
884# and, yet another
885workflow foo {} # This should not be collected.
886
887# This comment should not be included either.",
888 );
889
890 assert!(diagnostics.is_empty());
891
892 let workflow = tree.root().last_child().unwrap();
893 assert_eq!(workflow.kind(), SyntaxKind::WorkflowDefinitionNode);
894 let token = workflow.first_token().unwrap();
895 let mut trivia = token.preceding_trivia();
896 assert_eq!(trivia.next().unwrap().text(), "\n\n");
897 assert_eq!(trivia.next().unwrap().text(), "# Some");
898 assert_eq!(trivia.next().unwrap().text(), "# comments");
899 assert_eq!(trivia.next().unwrap().text(), "# are");
900 assert_eq!(trivia.next().unwrap().text(), "# long");
901 assert_eq!(trivia.next().unwrap().text(), "\n \n");
902 assert_eq!(trivia.next().unwrap().text(), "# Others are short");
903 assert_eq!(trivia.next().unwrap().text(), "\n\n");
904 assert_eq!(trivia.next().unwrap().text(), "# and, yet another");
905 assert!(trivia.next().is_none());
906 }
907
908 #[test]
909 fn inline_comment() {
910 let (tree, diagnostics) = SyntaxTree::parse(
911 "version 1.2
912
913# This comment should not be included
914task foo {}
915
916# This should not be collected.
917workflow foo {} # Here is a comment that should be collected.
918
919# This comment should not be included either.",
920 );
921
922 assert!(diagnostics.is_empty());
923
924 let workflow = tree.root().last_child().unwrap();
925 assert_eq!(workflow.kind(), SyntaxKind::WorkflowDefinitionNode);
926 let comment = workflow.last_token().unwrap().inline_comment().unwrap();
927 assert_eq!(
928 comment.text(),
929 "# Here is a comment that should be collected."
930 );
931 }
932}