1#![warn(missing_docs)]
27#![warn(rust_2018_idioms)]
28#![warn(rust_2021_compatibility)]
29#![warn(missing_debug_implementations)]
30#![warn(clippy::missing_docs_in_private_items)]
31#![warn(rustdoc::broken_intra_doc_links)]
32
33use std::collections::HashSet;
34use std::fmt;
35use std::hash::Hash;
36use std::str::FromStr;
37
38pub use rowan::Direction;
39use rowan::NodeOrToken;
40use v1::CloseBrace;
41use v1::CloseHeredoc;
42use v1::OpenBrace;
43use v1::OpenHeredoc;
44pub use wdl_grammar::Diagnostic;
45pub use wdl_grammar::Label;
46pub use wdl_grammar::Severity;
47pub use wdl_grammar::Span;
48pub use wdl_grammar::SupportedVersion;
49pub use wdl_grammar::SyntaxElement;
50pub use wdl_grammar::SyntaxKind;
51pub use wdl_grammar::SyntaxNode;
52pub use wdl_grammar::SyntaxToken;
53pub use wdl_grammar::SyntaxTokenExt;
54pub use wdl_grammar::SyntaxTree;
55pub use wdl_grammar::WorkflowDescriptionLanguage;
56pub use wdl_grammar::lexer;
57pub use wdl_grammar::version;
58
59pub mod v1;
60
61mod element;
62
63pub use element::*;
64
65pub trait Documented<N: TreeNode>: AstNode<N> {
67 fn doc_comments(&self) -> Option<Vec<Comment<N::Token>>>;
74}
75
76pub fn doc_comments<N: TreeNode>(
78 preceding_trivia: impl IntoIterator<Item = N::Token>,
79) -> impl Iterator<Item = Comment<N::Token>> {
80 preceding_trivia
81 .into_iter()
82 .take_while(|token| {
83 token.kind() == SyntaxKind::Whitespace || token.kind() == SyntaxKind::Comment
84 })
85 .filter_map(|token| {
86 if token.kind() == SyntaxKind::Comment && token.text().starts_with(DOC_COMMENT_PREFIX) {
87 Some(Comment::<N::Token>::cast(token).expect("should be a comment"))
88 } else {
89 None
90 }
91 })
92}
93
94pub trait TreeNode: Clone + fmt::Debug + PartialEq + Eq + std::hash::Hash {
98 type Token: TreeToken;
100
101 fn parent(&self) -> Option<Self>;
105
106 fn kind(&self) -> SyntaxKind;
108
109 fn text(&self) -> impl fmt::Display;
113
114 fn span(&self) -> Span;
116
117 fn children(&self) -> impl Iterator<Item = Self>;
119
120 fn children_with_tokens(&self) -> impl Iterator<Item = NodeOrToken<Self, Self::Token>>;
122
123 fn first_token(&self) -> Option<Self::Token>;
125
126 fn last_token(&self) -> Option<Self::Token>;
128
129 fn descendants(&self) -> impl Iterator<Item = Self>;
131
132 fn ancestors(&self) -> impl Iterator<Item = Self>;
134}
135
136pub trait TreeToken: Clone + fmt::Debug + PartialEq + Eq + std::hash::Hash {
138 type Node: TreeNode;
140
141 fn parent(&self) -> Self::Node;
143
144 fn kind(&self) -> SyntaxKind;
146
147 fn text(&self) -> &str;
149
150 fn span(&self) -> Span;
152}
153
154pub trait AstNode<N: TreeNode>: Sized {
156 fn can_cast(kind: SyntaxKind) -> bool;
158
159 fn cast(inner: N) -> Option<Self>;
161
162 fn inner(&self) -> &N;
164
165 fn kind(&self) -> SyntaxKind {
167 self.inner().kind()
168 }
169
170 fn text<'a>(&'a self) -> impl fmt::Display
175 where
176 N: 'a,
177 {
178 self.inner().text()
179 }
180
181 fn span(&self) -> Span {
183 self.inner().span()
184 }
185
186 fn token<C>(&self) -> Option<C>
188 where
189 C: AstToken<N::Token>,
190 {
191 self.inner()
192 .children_with_tokens()
193 .filter_map(|e| e.into_token())
194 .find_map(|t| C::cast(t))
195 }
196
197 fn tokens<'a, C>(&'a self) -> impl Iterator<Item = C>
199 where
200 C: AstToken<N::Token>,
201 N: 'a,
202 {
203 self.inner()
204 .children_with_tokens()
205 .filter_map(|e| e.into_token().and_then(C::cast))
206 }
207
208 fn last_token<C>(&self) -> Option<C>
214 where
215 C: AstToken<N::Token>,
216 {
217 self.inner().last_token().and_then(C::cast)
218 }
219
220 fn child<C>(&self) -> Option<C>
222 where
223 C: AstNode<N>,
224 {
225 self.inner().children().find_map(C::cast)
226 }
227
228 fn children<'a, C>(&'a self) -> impl Iterator<Item = C>
230 where
231 C: AstNode<N>,
232 N: 'a,
233 {
234 self.inner().children().filter_map(C::cast)
235 }
236
237 fn parent<'a, P>(&self) -> Option<P>
242 where
243 P: AstNode<N>,
244 N: 'a,
245 {
246 P::cast(self.inner().parent()?)
247 }
248
249 fn scope_span<O, C>(&self, include_braces: bool) -> Option<Span>
255 where
256 O: AstToken<N::Token>,
257 C: AstToken<N::Token>,
258 {
259 let open = self.token::<O>()?.span();
260 let close = self.last_token::<C>()?.span();
261
262 let start = if include_braces {
263 open.start()
264 } else {
265 open.end()
266 };
267 Some(Span::new(start, close.end() - start))
268 }
269
270 fn braced_scope_span(&self, include_braces: bool) -> Option<Span> {
280 self.scope_span::<OpenBrace<N::Token>, CloseBrace<N::Token>>(include_braces)
281 }
282
283 fn heredoc_scope_span(&self, include_braces: bool) -> Option<Span> {
293 self.scope_span::<OpenHeredoc<N::Token>, CloseHeredoc<N::Token>>(include_braces)
294 }
295
296 fn descendants<'a, D>(&'a self) -> impl Iterator<Item = D>
299 where
300 D: AstNode<N>,
301 N: 'a,
302 {
303 self.inner().descendants().filter_map(|d| D::cast(d))
304 }
305}
306
307pub trait AstToken<T: TreeToken>: Sized {
309 fn can_cast(kind: SyntaxKind) -> bool;
311
312 fn cast(inner: T) -> Option<Self>;
314
315 fn inner(&self) -> &T;
317
318 fn kind(&self) -> SyntaxKind {
320 self.inner().kind()
321 }
322
323 fn text<'a>(&'a self) -> &'a str
325 where
326 T: 'a,
327 {
328 self.inner().text()
329 }
330
331 fn span(&self) -> Span {
333 self.inner().span()
334 }
335
336 fn parent<'a, P>(&self) -> Option<P>
340 where
341 P: AstNode<T::Node>,
342 T: 'a,
343 {
344 P::cast(self.inner().parent())
345 }
346}
347
348pub trait NewRoot<N: TreeNode>: Sized {
351 fn new_root(root: N) -> Self;
354}
355
356impl TreeNode for SyntaxNode {
357 type Token = SyntaxToken;
358
359 fn parent(&self) -> Option<SyntaxNode> {
360 self.parent()
361 }
362
363 fn kind(&self) -> SyntaxKind {
364 self.kind()
365 }
366
367 fn children(&self) -> impl Iterator<Item = Self> {
368 self.children()
369 }
370
371 fn children_with_tokens(&self) -> impl Iterator<Item = NodeOrToken<Self, Self::Token>> {
372 self.children_with_tokens()
373 }
374
375 fn text(&self) -> impl fmt::Display {
376 self.text()
377 }
378
379 fn span(&self) -> Span {
380 let range = self.text_range();
381 let start = usize::from(range.start());
382 Span::new(start, usize::from(range.end()) - start)
383 }
384
385 fn first_token(&self) -> Option<Self::Token> {
386 self.first_token()
387 }
388
389 fn last_token(&self) -> Option<Self::Token> {
390 self.last_token()
391 }
392
393 fn descendants(&self) -> impl Iterator<Item = Self> {
394 self.descendants()
395 }
396
397 fn ancestors(&self) -> impl Iterator<Item = Self> {
398 self.ancestors()
399 }
400}
401
402impl TreeToken for SyntaxToken {
403 type Node = SyntaxNode;
404
405 fn parent(&self) -> SyntaxNode {
406 self.parent().expect("token should have a parent")
407 }
408
409 fn kind(&self) -> SyntaxKind {
410 self.kind()
411 }
412
413 fn text(&self) -> &str {
414 self.text()
415 }
416
417 fn span(&self) -> Span {
418 let range = self.text_range();
419 let start = usize::from(range.start());
420 Span::new(start, usize::from(range.end()) - start)
421 }
422}
423
424#[derive(Clone, Debug, PartialEq, Eq)]
428pub enum Ast<N: TreeNode = SyntaxNode> {
429 Unsupported,
431 V1(v1::Ast<N>),
433}
434
435impl<N: TreeNode> Ast<N> {
436 pub fn as_v1(&self) -> Option<&v1::Ast<N>> {
440 match self {
441 Self::V1(ast) => Some(ast),
442 _ => None,
443 }
444 }
445
446 pub fn into_v1(self) -> Option<v1::Ast<N>> {
448 match self {
449 Self::V1(ast) => Some(ast),
450 _ => None,
451 }
452 }
453
454 pub fn unwrap_v1(self) -> v1::Ast<N> {
460 self.into_v1().expect("the AST is not a V1 AST")
461 }
462}
463
464#[derive(Clone, PartialEq, Eq, Hash)]
469pub struct Document<N: TreeNode = SyntaxNode>(N);
470
471impl<N: TreeNode> AstNode<N> for Document<N> {
472 fn can_cast(kind: SyntaxKind) -> bool {
473 kind == SyntaxKind::RootNode
474 }
475
476 fn cast(inner: N) -> Option<Self> {
477 if Self::can_cast(inner.kind()) {
478 Some(Self(inner))
479 } else {
480 None
481 }
482 }
483
484 fn inner(&self) -> &N {
485 &self.0
486 }
487}
488
489impl Documented<SyntaxNode> for Document<SyntaxNode> {
490 fn doc_comments(&self) -> Option<Vec<Comment<<SyntaxNode as TreeNode>::Token>>> {
491 let version_statement = self.child::<VersionStatement>()?;
492 let version_keyword = version_statement.keyword();
493 Some(doc_comments::<SyntaxNode>(version_keyword.inner().preceding_trivia()).collect())
494 }
495}
496
497impl Document {
498 pub fn parse(
558 source: &str,
559 fallback_version: Option<SupportedVersion>,
560 ) -> (Self, Vec<Diagnostic>) {
561 let (tree, diagnostics) = SyntaxTree::parse(source, fallback_version);
562 (
563 Document::cast(tree.into_syntax()).expect("document should cast"),
564 diagnostics,
565 )
566 }
567}
568
569impl<N: TreeNode> Document<N> {
570 pub fn version_statement(&self) -> Option<VersionStatement<N>> {
577 self.child()
578 }
579
580 pub fn ast(&self) -> Ast<N> {
582 self.ast_with_version_fallback(None)
583 }
584
585 pub fn ast_with_version_fallback(&self, fallback_version: Option<SupportedVersion>) -> Ast<N> {
602 let Some(stmt) = self.version_statement() else {
603 return Ast::Unsupported;
604 };
605 let Some(version) = stmt
608 .version()
609 .text()
610 .parse::<SupportedVersion>()
611 .ok()
612 .or(fallback_version)
613 else {
614 return Ast::Unsupported;
615 };
616 match version {
617 SupportedVersion::V1(_) => Ast::V1(v1::Ast(self.0.clone())),
618 _ => Ast::Unsupported,
619 }
620 }
621
622 pub fn morph<U: TreeNode + NewRoot<N>>(self) -> Document<U> {
625 Document(U::new_root(self.0))
626 }
627}
628
629impl fmt::Debug for Document {
630 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
631 self.0.fmt(f)
632 }
633}
634
635#[derive(Clone, Debug, PartialEq, Eq, Hash)]
637pub struct Whitespace<T: TreeToken = SyntaxToken>(T);
638
639impl<T: TreeToken> AstToken<T> for Whitespace<T> {
640 fn can_cast(kind: SyntaxKind) -> bool {
641 kind == SyntaxKind::Whitespace
642 }
643
644 fn cast(inner: T) -> Option<Self> {
645 match inner.kind() {
646 SyntaxKind::Whitespace => Some(Self(inner)),
647 _ => None,
648 }
649 }
650
651 fn inner(&self) -> &T {
652 &self.0
653 }
654}
655
656pub const DIRECTIVE_COMMENT_PREFIX: &str = "#@";
658pub const DIRECTIVE_DELIMITER: &str = ":";
660
661#[derive(Clone, Debug, PartialEq, Eq, Hash)]
663pub struct ExceptRule {
664 pub name: String,
666 pub span: Span,
668}
669
670impl ExceptRule {
671 pub fn target_node(&self, document: &Document) -> Option<SyntaxNode> {
673 let comment = document.inner().descendants_with_tokens().find_map(|d| {
674 let token = d.into_token()?;
675 let comment = Comment::cast(token)?;
676 if comment.kind() == CommentKind::Directive(DirectiveKind::Except)
677 && self.span.within(comment.span())
678 {
679 Some(comment)
680 } else {
681 None
682 }
683 });
684
685 comment.and_then(|c| {
686 c.inner()
687 .siblings_with_tokens(Direction::Next)
688 .find_map(|sibling| {
689 if let SyntaxElement::Node(node) = sibling {
690 Some(node)
691 } else {
692 None
693 }
694 })
695 })
696 }
697}
698
699#[derive(Debug, PartialEq, Eq)]
701pub enum Directive {
702 Except(HashSet<ExceptRule>),
704}
705
706impl Directive {
707 pub fn kind(&self) -> DirectiveKind {
709 match self {
710 Self::Except(_) => DirectiveKind::Except,
711 }
712 }
713
714 pub fn into_except(self) -> Option<HashSet<ExceptRule>> {
717 match self {
718 Self::Except(rules) => Some(rules),
719 }
720 }
721}
722
723#[derive(Copy, Clone, Debug, PartialEq, Eq)]
725pub enum CommentKind {
726 Line,
728 Directive(DirectiveKind),
731 Documentation,
733}
734
735#[derive(Copy, Clone, Debug, PartialEq, Eq)]
737pub enum DirectiveKind {
738 Except,
740}
741
742impl FromStr for DirectiveKind {
743 type Err = ();
744
745 fn from_str(s: &str) -> Result<Self, Self::Err> {
746 match s {
747 "except" => Ok(Self::Except),
748 _ => Err(()),
749 }
750 }
751}
752
753pub const DOC_COMMENT_PREFIX: &str = "##";
755
756#[derive(Debug, Clone, PartialEq, Eq, Hash)]
758pub struct Comment<T: TreeToken = SyntaxToken>(T);
759
760impl<T: TreeToken> AstToken<T> for Comment<T> {
761 fn can_cast(kind: SyntaxKind) -> bool {
762 kind == SyntaxKind::Comment
763 }
764
765 fn cast(inner: T) -> Option<Self> {
766 match inner.kind() {
767 SyntaxKind::Comment => Some(Self(inner)),
768 _ => None,
769 }
770 }
771
772 fn inner(&self) -> &T {
773 &self.0
774 }
775}
776
777fn split_directive(comment: &str) -> Option<(DirectiveKind, &str)> {
781 let s = comment.strip_prefix(DIRECTIVE_COMMENT_PREFIX)?;
782 let (directive, contents) = s.trim().split_once(DIRECTIVE_DELIMITER)?;
783 Some((
784 DirectiveKind::from_str(directive.trim_end()).ok()?,
785 contents,
786 ))
787}
788
789impl Comment {
790 pub fn directive(&self) -> Option<Directive> {
792 let text = self.text();
793 let mut offset = self.span().start();
794
795 let (kind, contents) = split_directive(text)?;
796 offset += text.len() - contents.len();
797
798 match kind {
799 DirectiveKind::Except => Some(Directive::Except(HashSet::from_iter(
800 contents.split(',').filter_map(|original_id| {
801 let trimmed = original_id.trim();
802 if trimmed.is_empty() {
803 return None;
804 }
805
806 let name = trimmed.to_string();
807 offset += original_id.len() - name.len();
808
809 let span = Span::new(offset, name.len());
810 offset += name.len() + 1; Some(ExceptRule { name, span })
813 }),
814 ))),
815 }
816 }
817
818 pub fn kind(&self) -> CommentKind {
820 let text = self.text();
821 if text.starts_with(DOC_COMMENT_PREFIX) {
822 return CommentKind::Documentation;
823 } else if let Some((kind, _)) = split_directive(text) {
824 return CommentKind::Directive(kind);
825 }
826
827 CommentKind::Line
828 }
829
830 pub fn is_inline_comment(&self) -> bool {
832 if let Some(prev) = self.inner().prev_sibling_or_token() {
835 if prev.kind() == SyntaxKind::Whitespace {
836 !prev
837 .into_token()
838 .expect("SyntaxKind::Whitespace is a token")
839 .text()
840 .contains('\n')
841 } else {
842 true
843 }
844 } else {
845 false
846 }
847 }
848}
849
850#[derive(Debug, Clone, PartialEq, Eq, Hash)]
852pub struct VersionStatement<N: TreeNode = SyntaxNode>(N);
853
854impl<N: TreeNode> VersionStatement<N> {
855 pub fn version(&self) -> Version<N::Token> {
857 self.token()
858 .expect("version statement must have a version token")
859 }
860
861 pub fn keyword(&self) -> v1::VersionKeyword<N::Token> {
863 self.token()
864 .expect("version statement must have a version keyword")
865 }
866}
867
868impl<N: TreeNode> AstNode<N> for VersionStatement<N> {
869 fn can_cast(kind: SyntaxKind) -> bool {
870 kind == SyntaxKind::VersionStatementNode
871 }
872
873 fn cast(inner: N) -> Option<Self> {
874 match inner.kind() {
875 SyntaxKind::VersionStatementNode => Some(Self(inner)),
876 _ => None,
877 }
878 }
879
880 fn inner(&self) -> &N {
881 &self.0
882 }
883}
884
885#[derive(Clone, Debug, PartialEq, Eq, Hash)]
887pub struct Version<T: TreeToken = SyntaxToken>(T);
888
889impl<T: TreeToken> AstToken<T> for Version<T> {
890 fn can_cast(kind: SyntaxKind) -> bool {
891 kind == SyntaxKind::Version
892 }
893
894 fn cast(inner: T) -> Option<Self> {
895 match inner.kind() {
896 SyntaxKind::Version => Some(Self(inner)),
897 _ => None,
898 }
899 }
900
901 fn inner(&self) -> &T {
902 &self.0
903 }
904}
905
906#[derive(Debug, Clone, PartialEq, Eq, Hash)]
908pub struct Ident<T: TreeToken = SyntaxToken>(T);
909
910impl<T: TreeToken> Ident<T> {
911 pub fn hashable(&self) -> TokenText<T> {
913 TokenText(self.0.clone())
914 }
915}
916
917impl<T: TreeToken> AstToken<T> for Ident<T> {
918 fn can_cast(kind: SyntaxKind) -> bool {
919 kind == SyntaxKind::Ident
920 }
921
922 fn cast(inner: T) -> Option<Self> {
923 match inner.kind() {
924 SyntaxKind::Ident => Some(Self(inner)),
925 _ => None,
926 }
927 }
928
929 fn inner(&self) -> &T {
930 &self.0
931 }
932}
933
934#[derive(Debug, Clone)]
943pub struct TokenText<T: TreeToken = SyntaxToken>(T);
944
945impl TokenText {
946 pub fn text(&self) -> &str {
948 self.0.text()
949 }
950
951 pub fn span(&self) -> Span {
953 self.0.span()
954 }
955}
956
957impl<T: TreeToken> PartialEq for TokenText<T> {
958 fn eq(&self, other: &Self) -> bool {
959 self.0.text() == other.0.text()
960 }
961}
962
963impl<T: TreeToken> Eq for TokenText<T> {}
964
965impl<T: TreeToken> std::hash::Hash for TokenText<T> {
966 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
967 self.0.text().hash(state);
968 }
969}
970
971impl<T: TreeToken> std::borrow::Borrow<str> for TokenText<T> {
972 fn borrow(&self) -> &str {
973 self.0.text()
974 }
975}