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 ScatterStatementNode,
365 CallStatementNode,
367 CallTargetNode,
369 CallAliasNode,
371 CallAfterNode,
373 CallInputItemNode,
375
376 MAX,
379}
380
381impl SyntaxKind {
382 pub fn is_symbolic(&self) -> bool {
388 matches!(
389 self,
390 Self::Abandoned | Self::Unknown | Self::Unparsed | Self::MAX
391 )
392 }
393
394 pub fn describe(&self) -> &'static str {
396 match self {
397 Self::Unknown => unreachable!(),
398 Self::Unparsed => unreachable!(),
399 Self::Whitespace => "whitespace",
400 Self::Comment => "comment",
401 Self::Version => "version",
402 Self::Float => "float",
403 Self::Integer => "integer",
404 Self::Ident => "identifier",
405 Self::SingleQuote => "single quote",
406 Self::DoubleQuote => "double quote",
407 Self::OpenHeredoc => "open heredoc",
408 Self::CloseHeredoc => "close heredoc",
409 Self::ArrayTypeKeyword => "`Array` type keyword",
410 Self::BooleanTypeKeyword => "`Boolean` type keyword",
411 Self::FileTypeKeyword => "`File` type keyword",
412 Self::FloatTypeKeyword => "`Float` type keyword",
413 Self::IntTypeKeyword => "`Int` type keyword",
414 Self::MapTypeKeyword => "`Map` type keyword",
415 Self::ObjectTypeKeyword => "`Object` type keyword",
416 Self::PairTypeKeyword => "`Pair` type keyword",
417 Self::StringTypeKeyword => "`String` type keyword",
418 Self::AfterKeyword => "`after` keyword",
419 Self::AliasKeyword => "`alias` keyword",
420 Self::AsKeyword => "`as` keyword",
421 Self::CallKeyword => "`call` keyword",
422 Self::CommandKeyword => "`command` keyword",
423 Self::ElseKeyword => "`else` keyword",
424 Self::EnvKeyword => "`env` keyword",
425 Self::FalseKeyword => "`false` keyword",
426 Self::IfKeyword => "`if` keyword",
427 Self::InKeyword => "`in` keyword",
428 Self::ImportKeyword => "`import` keyword",
429 Self::InputKeyword => "`input` keyword",
430 Self::MetaKeyword => "`meta` keyword",
431 Self::NoneKeyword => "`None` keyword",
432 Self::NullKeyword => "`null` keyword",
433 Self::ObjectKeyword => "`object` keyword",
434 Self::OutputKeyword => "`output` keyword",
435 Self::ParameterMetaKeyword => "`parameter_meta` keyword",
436 Self::RuntimeKeyword => "`runtime` keyword",
437 Self::ScatterKeyword => "`scatter` keyword",
438 Self::StructKeyword => "`struct` keyword",
439 Self::TaskKeyword => "`task` keyword",
440 Self::ThenKeyword => "`then` keyword",
441 Self::TrueKeyword => "`true` keyword",
442 Self::VersionKeyword => "`version` keyword",
443 Self::WorkflowKeyword => "`workflow` keyword",
444 Self::DirectoryTypeKeyword => "`Directory` type keyword",
445 Self::HintsKeyword => "`hints` keyword",
446 Self::RequirementsKeyword => "`requirements` keyword",
447 Self::OpenBrace => "`{` symbol",
448 Self::CloseBrace => "`}` symbol",
449 Self::OpenBracket => "`[` symbol",
450 Self::CloseBracket => "`]` symbol",
451 Self::Assignment => "`=` symbol",
452 Self::Colon => "`:` symbol",
453 Self::Comma => "`,` symbol",
454 Self::OpenParen => "`(` symbol",
455 Self::CloseParen => "`)` symbol",
456 Self::QuestionMark => "`?` symbol",
457 Self::Exclamation => "`!` symbol",
458 Self::Plus => "`+` symbol",
459 Self::Minus => "`-` symbol",
460 Self::LogicalOr => "`||` symbol",
461 Self::LogicalAnd => "`&&` symbol",
462 Self::Asterisk => "`*` symbol",
463 Self::Exponentiation => "`**` symbol",
464 Self::Slash => "`/` symbol",
465 Self::Percent => "`%` symbol",
466 Self::Equal => "`==` symbol",
467 Self::NotEqual => "`!=` symbol",
468 Self::LessEqual => "`<=` symbol",
469 Self::GreaterEqual => "`>=` symbol",
470 Self::Less => "`<` symbol",
471 Self::Greater => "`>` symbol",
472 Self::Dot => "`.` symbol",
473 Self::LiteralStringText => "literal string text",
474 Self::LiteralCommandText => "literal command text",
475 Self::PlaceholderOpen => "placeholder open",
476 Self::Abandoned => unreachable!(),
477 Self::RootNode => "root node",
478 Self::VersionStatementNode => "version statement",
479 Self::ImportStatementNode => "import statement",
480 Self::ImportAliasNode => "import alias",
481 Self::StructDefinitionNode => "struct definition",
482 Self::TaskDefinitionNode => "task definition",
483 Self::WorkflowDefinitionNode => "workflow definition",
484 Self::UnboundDeclNode => "declaration without assignment",
485 Self::BoundDeclNode => "declaration with assignment",
486 Self::InputSectionNode => "input section",
487 Self::OutputSectionNode => "output section",
488 Self::CommandSectionNode => "command section",
489 Self::RequirementsSectionNode => "requirements section",
490 Self::RequirementsItemNode => "requirements item",
491 Self::TaskHintsSectionNode | Self::WorkflowHintsSectionNode => "hints section",
492 Self::TaskHintsItemNode | Self::WorkflowHintsItemNode => "hints item",
493 Self::WorkflowHintsObjectNode => "literal object",
494 Self::WorkflowHintsObjectItemNode => "literal object item",
495 Self::WorkflowHintsArrayNode => "literal array",
496 Self::RuntimeSectionNode => "runtime section",
497 Self::RuntimeItemNode => "runtime item",
498 Self::PrimitiveTypeNode => "primitive type",
499 Self::MapTypeNode => "map type",
500 Self::ArrayTypeNode => "array type",
501 Self::PairTypeNode => "pair type",
502 Self::ObjectTypeNode => "object type",
503 Self::TypeRefNode => "type reference",
504 Self::MetadataSectionNode => "metadata section",
505 Self::ParameterMetadataSectionNode => "parameter metadata section",
506 Self::MetadataObjectItemNode => "metadata object item",
507 Self::MetadataObjectNode => "metadata object",
508 Self::MetadataArrayNode => "metadata array",
509 Self::LiteralIntegerNode => "literal integer",
510 Self::LiteralFloatNode => "literal float",
511 Self::LiteralBooleanNode => "literal boolean",
512 Self::LiteralNoneNode => "literal `None`",
513 Self::LiteralNullNode => "literal null",
514 Self::LiteralStringNode => "literal string",
515 Self::LiteralPairNode => "literal pair",
516 Self::LiteralArrayNode => "literal array",
517 Self::LiteralMapNode => "literal map",
518 Self::LiteralMapItemNode => "literal map item",
519 Self::LiteralObjectNode => "literal object",
520 Self::LiteralObjectItemNode => "literal object item",
521 Self::LiteralStructNode => "literal struct",
522 Self::LiteralStructItemNode => "literal struct item",
523 Self::LiteralHintsNode => "literal hints",
524 Self::LiteralHintsItemNode => "literal hints item",
525 Self::LiteralInputNode => "literal input",
526 Self::LiteralInputItemNode => "literal input item",
527 Self::LiteralOutputNode => "literal output",
528 Self::LiteralOutputItemNode => "literal output item",
529 Self::ParenthesizedExprNode => "parenthesized expression",
530 Self::NameRefExprNode => "name reference expression",
531 Self::IfExprNode => "`if` expression",
532 Self::LogicalNotExprNode => "logical not expression",
533 Self::NegationExprNode => "negation expression",
534 Self::LogicalOrExprNode => "logical OR expression",
535 Self::LogicalAndExprNode => "logical AND expression",
536 Self::EqualityExprNode => "equality expression",
537 Self::InequalityExprNode => "inequality expression",
538 Self::LessExprNode => "less than expression",
539 Self::LessEqualExprNode => "less than or equal to expression",
540 Self::GreaterExprNode => "greater than expression",
541 Self::GreaterEqualExprNode => "greater than or equal to expression",
542 Self::AdditionExprNode => "addition expression",
543 Self::SubtractionExprNode => "subtraction expression",
544 Self::MultiplicationExprNode => "multiplication expression",
545 Self::DivisionExprNode => "division expression",
546 Self::ModuloExprNode => "modulo expression",
547 Self::ExponentiationExprNode => "exponentiation expression",
548 Self::CallExprNode => "call expression",
549 Self::IndexExprNode => "index expression",
550 Self::AccessExprNode => "access expression",
551 Self::PlaceholderNode => "placeholder",
552 Self::PlaceholderSepOptionNode => "placeholder `sep` option",
553 Self::PlaceholderDefaultOptionNode => "placeholder `default` option",
554 Self::PlaceholderTrueFalseOptionNode => "placeholder `true`/`false` option",
555 Self::ConditionalStatementNode => "conditional statement",
556 Self::ScatterStatementNode => "scatter statement",
557 Self::CallStatementNode => "call statement",
558 Self::CallTargetNode => "call target",
559 Self::CallAliasNode => "call alias",
560 Self::CallAfterNode => "call `after` clause",
561 Self::CallInputItemNode => "call input item",
562 Self::MAX => unreachable!(),
563 }
564 }
565
566 pub fn is_trivia(&self) -> bool {
568 matches!(self, Self::Whitespace | Self::Comment)
569 }
570
571 pub fn is_keyword(&self) -> bool {
573 matches!(
574 self,
575 SyntaxKind::AfterKeyword
576 | SyntaxKind::AliasKeyword
577 | SyntaxKind::ArrayTypeKeyword
578 | SyntaxKind::AsKeyword
579 | SyntaxKind::BooleanTypeKeyword
580 | SyntaxKind::CallKeyword
581 | SyntaxKind::CommandKeyword
582 | SyntaxKind::DirectoryTypeKeyword
583 | SyntaxKind::ElseKeyword
584 | SyntaxKind::EnvKeyword
585 | SyntaxKind::FalseKeyword
586 | SyntaxKind::FileTypeKeyword
587 | SyntaxKind::FloatTypeKeyword
588 | SyntaxKind::HintsKeyword
589 | SyntaxKind::IfKeyword
590 | SyntaxKind::ImportKeyword
591 | SyntaxKind::InKeyword
592 | SyntaxKind::InputKeyword
593 | SyntaxKind::IntTypeKeyword
594 | SyntaxKind::MapTypeKeyword
595 | SyntaxKind::MetaKeyword
596 | SyntaxKind::NoneKeyword
597 | SyntaxKind::NullKeyword
598 | SyntaxKind::ObjectKeyword
599 | SyntaxKind::ObjectTypeKeyword
600 | SyntaxKind::OutputKeyword
601 | SyntaxKind::PairTypeKeyword
602 | SyntaxKind::ParameterMetaKeyword
603 | SyntaxKind::RequirementsKeyword
604 | SyntaxKind::RuntimeKeyword
605 | SyntaxKind::ScatterKeyword
606 | SyntaxKind::StringTypeKeyword
607 | SyntaxKind::StructKeyword
608 | SyntaxKind::TaskKeyword
609 | SyntaxKind::ThenKeyword
610 | SyntaxKind::TrueKeyword
611 | SyntaxKind::VersionKeyword
612 | SyntaxKind::WorkflowKeyword
613 )
614 }
615
616 pub fn is_operator(&self) -> bool {
618 matches!(
619 self,
620 SyntaxKind::Plus
621 | SyntaxKind::Minus
622 | SyntaxKind::Slash
623 | SyntaxKind::Percent
624 | SyntaxKind::Asterisk
625 | SyntaxKind::Exponentiation
626 | SyntaxKind::Equal
627 | SyntaxKind::NotEqual
628 | SyntaxKind::Less
629 | SyntaxKind::LessEqual
630 | SyntaxKind::Greater
631 | SyntaxKind::GreaterEqual
632 | SyntaxKind::LogicalAnd
633 | SyntaxKind::LogicalOr
634 | SyntaxKind::Exclamation
635 | SyntaxKind::Assignment
636 | SyntaxKind::QuestionMark
637 | SyntaxKind::Dot
638 | SyntaxKind::Colon
639 )
640 }
641}
642
643pub static ALL_SYNTAX_KIND: &[SyntaxKind] = SyntaxKind::VARIANTS;
645
646impl From<SyntaxKind> for rowan::SyntaxKind {
647 fn from(kind: SyntaxKind) -> Self {
648 rowan::SyntaxKind(kind as u16)
649 }
650}
651
652#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
654pub struct WorkflowDescriptionLanguage;
655
656impl rowan::Language for WorkflowDescriptionLanguage {
657 type Kind = SyntaxKind;
658
659 fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
660 assert!(raw.0 <= SyntaxKind::MAX as u16);
661 unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
662 }
663
664 fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
665 kind.into()
666 }
667}
668
669pub type SyntaxNode = rowan::SyntaxNode<WorkflowDescriptionLanguage>;
671pub type SyntaxToken = rowan::SyntaxToken<WorkflowDescriptionLanguage>;
673pub type SyntaxElement = rowan::SyntaxElement<WorkflowDescriptionLanguage>;
675pub type SyntaxNodeChildren = rowan::SyntaxNodeChildren<WorkflowDescriptionLanguage>;
677
678pub fn construct_tree(source: &str, mut events: Vec<Event>) -> SyntaxNode {
680 let mut builder = GreenNodeBuilder::default();
681 let mut ancestors = Vec::new();
682
683 for i in 0..events.len() {
684 match std::mem::replace(&mut events[i], Event::abandoned()) {
685 Event::NodeStarted {
686 kind,
687 forward_parent,
688 } => {
689 ancestors.push(kind);
692 let mut idx = i;
693 let mut fp: Option<usize> = forward_parent;
694 while let Some(distance) = fp {
695 idx += distance;
696 fp = match std::mem::replace(&mut events[idx], Event::abandoned()) {
697 Event::NodeStarted {
698 kind,
699 forward_parent,
700 } => {
701 ancestors.push(kind);
702 forward_parent
703 }
704 _ => unreachable!(),
705 };
706 }
707
708 for kind in ancestors.drain(..).rev() {
711 if kind != SyntaxKind::Abandoned {
712 builder.start_node(kind.into());
713 }
714 }
715 }
716 Event::NodeFinished => builder.finish_node(),
717 Event::Token { kind, span } => {
718 builder.token(kind.into(), &source[span.start()..span.end()])
719 }
720 }
721 }
722
723 SyntaxNode::new_root(builder.finish())
724}
725
726#[derive(Clone, PartialEq, Eq, Hash)]
728pub struct SyntaxTree(SyntaxNode);
729
730impl SyntaxTree {
731 pub fn parse(source: &str) -> (Self, Vec<Diagnostic>) {
750 let parser = Parser::new(Lexer::new(source));
751 let (events, mut diagnostics) = grammar::document(parser);
752 diagnostics.sort();
753 (Self(construct_tree(source, events)), diagnostics)
754 }
755
756 pub fn root(&self) -> &SyntaxNode {
758 &self.0
759 }
760
761 pub fn green(&self) -> Cow<'_, GreenNodeData> {
763 self.0.green()
764 }
765
766 pub fn into_syntax(self) -> SyntaxNode {
768 self.0
769 }
770}
771
772impl fmt::Display for SyntaxTree {
773 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
774 self.0.fmt(f)
775 }
776}
777
778impl fmt::Debug for SyntaxTree {
779 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
780 self.0.fmt(f)
781 }
782}
783
784pub trait SyntaxTokenExt {
786 fn preceding_trivia(&self) -> impl Iterator<Item = SyntaxToken>;
788
789 fn inline_comment(&self) -> Option<SyntaxToken>;
792}
793
794impl SyntaxTokenExt for SyntaxToken {
795 fn preceding_trivia(&self) -> impl Iterator<Item = SyntaxToken> {
796 let mut tokens = VecDeque::new();
797 let mut cur = self.prev_token();
798 while let Some(token) = cur {
799 cur = token.prev_token();
800 if !token.kind().is_trivia() {
802 break;
803 }
804 if token.kind() == SyntaxKind::Comment
806 && let Some(prev) = token.prev_token()
807 {
808 if prev.kind() == SyntaxKind::Whitespace {
809 let has_newlines = prev.text().chars().any(|c| c == '\n');
810 if !has_newlines && prev.prev_token().is_some() {
816 break;
817 }
818 } else {
819 break;
821 }
822 }
823 match token.kind() {
825 SyntaxKind::Whitespace
826 if token.text().chars().filter(|c| *c == '\n').count() > 1 =>
827 {
828 tokens.push_front(token);
829 }
830 SyntaxKind::Comment => {
831 tokens.push_front(token);
832 }
833 _ => {}
834 }
835 }
836 tokens.into_iter()
837 }
838
839 fn inline_comment(&self) -> Option<SyntaxToken> {
840 let mut next = self.next_token();
841 iter::from_fn(move || {
842 let cur = next.clone()?;
843 next = cur.next_token();
844 Some(cur)
845 })
846 .take_while(|t| {
847 if !t.kind().is_trivia() {
849 return false;
850 }
851 if t.kind() == SyntaxKind::Whitespace {
853 return !t.text().chars().any(|c| c == '\n');
854 }
855 true
856 })
857 .find(|t| t.kind() == SyntaxKind::Comment)
858 }
859}
860
861#[cfg(test)]
862mod tests {
863 use super::*;
864 use crate::SyntaxTree;
865
866 #[test]
867 fn preceding_comments() {
868 let (tree, diagnostics) = SyntaxTree::parse(
869 "version 1.2
870
871# This comment should not be included
872task foo {} # This comment should not be included
873
874# Some
875# comments
876# are
877# long
878
879# Others are short
880
881# and, yet another
882workflow foo {} # This should not be collected.
883
884# This comment should not be included either.",
885 );
886
887 assert!(diagnostics.is_empty());
888
889 let workflow = tree.root().last_child().unwrap();
890 assert_eq!(workflow.kind(), SyntaxKind::WorkflowDefinitionNode);
891 let token = workflow.first_token().unwrap();
892 let mut trivia = token.preceding_trivia();
893 assert_eq!(trivia.next().unwrap().text(), "\n\n");
894 assert_eq!(trivia.next().unwrap().text(), "# Some");
895 assert_eq!(trivia.next().unwrap().text(), "# comments");
896 assert_eq!(trivia.next().unwrap().text(), "# are");
897 assert_eq!(trivia.next().unwrap().text(), "# long");
898 assert_eq!(trivia.next().unwrap().text(), "\n \n");
899 assert_eq!(trivia.next().unwrap().text(), "# Others are short");
900 assert_eq!(trivia.next().unwrap().text(), "\n\n");
901 assert_eq!(trivia.next().unwrap().text(), "# and, yet another");
902 assert!(trivia.next().is_none());
903 }
904
905 #[test]
906 fn inline_comment() {
907 let (tree, diagnostics) = SyntaxTree::parse(
908 "version 1.2
909
910# This comment should not be included
911task foo {}
912
913# This should not be collected.
914workflow foo {} # Here is a comment that should be collected.
915
916# This comment should not be included either.",
917 );
918
919 assert!(diagnostics.is_empty());
920
921 let workflow = tree.root().last_child().unwrap();
922 assert_eq!(workflow.kind(), SyntaxKind::WorkflowDefinitionNode);
923 let comment = workflow.last_token().unwrap().inline_comment().unwrap();
924 assert_eq!(
925 comment.text(),
926 "# Here is a comment that should be collected."
927 );
928 }
929}