1use std::ffi::OsStr;
4use std::path::Path;
5
6use rowan::NodeOrToken;
7use url::Url;
8use wdl_grammar::lexer::v1::Logos;
9use wdl_grammar::lexer::v1::Token;
10
11use super::AliasKeyword;
12use super::AsKeyword;
13use super::ImportKeyword;
14use super::LiteralString;
15use crate::AstNode;
16use crate::AstToken;
17use crate::Ident;
18use crate::Span;
19use crate::SyntaxKind;
20use crate::SyntaxNode;
21use crate::TreeNode;
22
23#[derive(Clone, Debug, PartialEq, Eq)]
25pub struct ImportStatement<N: TreeNode = SyntaxNode>(N);
26
27impl<N: TreeNode> ImportStatement<N> {
28 pub fn uri(&self) -> LiteralString<N> {
30 self.child().expect("import should have a URI")
31 }
32
33 pub fn keyword(&self) -> ImportKeyword<N::Token> {
35 self.token().expect("import should have a keyword")
36 }
37
38 pub fn explicit_namespace(&self) -> Option<Ident<N::Token>> {
41 self.token()
42 }
43
44 pub fn aliases(&self) -> impl Iterator<Item = ImportAlias<N>> + use<'_, N> {
46 self.children()
47 }
48
49 pub fn namespace(&self) -> Option<(String, Span)> {
61 if let Some(explicit) = self.explicit_namespace() {
62 return Some((explicit.text().to_string(), explicit.span()));
63 }
64
65 let uri = self.uri();
67 let text = uri.text()?;
68 let stem = match Url::parse(text.text()) {
69 Ok(url) => Path::new(
70 urlencoding::decode(url.path_segments()?.next_back()?)
71 .ok()?
72 .as_ref(),
73 )
74 .file_stem()
75 .and_then(OsStr::to_str)?
76 .to_string(),
77 Err(_) => Path::new(text.text())
78 .file_stem()
79 .and_then(OsStr::to_str)?
80 .to_string(),
81 };
82
83 let mut lexer = Token::lexer(&stem);
85 match lexer.next()?.ok()? {
86 Token::Ident if lexer.next().is_none() => {}
87 _ => return None,
88 }
89
90 Some((stem.to_string(), uri.span()))
91 }
92}
93
94impl<N: TreeNode> AstNode<N> for ImportStatement<N> {
95 fn can_cast(kind: SyntaxKind) -> bool {
96 kind == SyntaxKind::ImportStatementNode
97 }
98
99 fn cast(inner: N) -> Option<Self> {
100 match inner.kind() {
101 SyntaxKind::ImportStatementNode => Some(Self(inner)),
102 _ => None,
103 }
104 }
105
106 fn inner(&self) -> &N {
107 &self.0
108 }
109}
110
111#[derive(Clone, Debug, PartialEq, Eq)]
113pub struct ImportAlias<N: TreeNode = SyntaxNode>(N);
114
115impl<N: TreeNode> ImportAlias<N> {
116 pub fn names(&self) -> (Ident<N::Token>, Ident<N::Token>) {
118 let mut children = self.0.children_with_tokens().filter_map(|c| match c {
119 NodeOrToken::Node(_) => None,
120 NodeOrToken::Token(t) => Ident::cast(t),
121 });
122
123 let source = children.next().expect("expected a source identifier");
124 let target = children.next().expect("expected a target identifier");
125 (source, target)
126 }
127
128 pub fn alias_keyword(&self) -> AliasKeyword<N::Token> {
130 self.token().expect("alias should have an `alias` keyword")
131 }
132
133 pub fn as_keyword(&self) -> AsKeyword<N::Token> {
135 self.token().expect("alias should have an `as` keyword")
136 }
137}
138
139impl<N: TreeNode> AstNode<N> for ImportAlias<N> {
140 fn can_cast(kind: SyntaxKind) -> bool {
141 kind == SyntaxKind::ImportAliasNode
142 }
143
144 fn cast(inner: N) -> Option<Self> {
145 match inner.kind() {
146 SyntaxKind::ImportAliasNode => Some(Self(inner)),
147 _ => None,
148 }
149 }
150
151 fn inner(&self) -> &N {
152 &self.0
153 }
154}
155
156#[cfg(test)]
157mod test {
158 use pretty_assertions::assert_eq;
159
160 use super::*;
161 use crate::Ast;
162 use crate::Document;
163
164 #[test]
165 fn import_statements() {
166 let (document, diagnostics) = Document::parse(
167 r#"
168version 1.1
169
170import "foo.wdl"
171import "bar.wdl" as x
172import "baz.wdl" alias A as B alias C as D
173import "qux.wdl" as x alias A as B alias C as D
174"#,
175 );
176 assert!(diagnostics.is_empty());
177 match document.ast() {
178 Ast::V1(ast) => {
179 fn assert_aliases<N: TreeNode>(mut aliases: impl Iterator<Item = ImportAlias<N>>) {
180 let alias = aliases.next().unwrap();
181 let (to, from) = alias.names();
182 assert_eq!(to.text(), "A");
183 assert_eq!(from.text(), "B");
184 let alias = aliases.next().unwrap();
185 let (to, from) = alias.names();
186 assert_eq!(to.text(), "C");
187 assert_eq!(from.text(), "D");
188 assert!(aliases.next().is_none());
189 }
190
191 let imports: Vec<_> = ast.imports().collect();
192 assert_eq!(imports.len(), 4);
193
194 assert_eq!(imports[0].uri().text().unwrap().text(), "foo.wdl");
196 assert!(imports[0].explicit_namespace().is_none());
197 assert_eq!(
198 imports[0].namespace().map(|(n, _)| n).as_deref(),
199 Some("foo")
200 );
201 assert_eq!(imports[0].aliases().count(), 0);
202
203 assert_eq!(imports[1].uri().text().unwrap().text(), "bar.wdl");
205 assert_eq!(imports[1].explicit_namespace().unwrap().text(), "x");
206 assert_eq!(imports[1].namespace().map(|(n, _)| n).as_deref(), Some("x"));
207 assert_eq!(imports[1].aliases().count(), 0);
208
209 assert_eq!(imports[2].uri().text().unwrap().text(), "baz.wdl");
211 assert!(imports[2].explicit_namespace().is_none());
212 assert_eq!(
213 imports[2].namespace().map(|(n, _)| n).as_deref(),
214 Some("baz")
215 );
216 assert_aliases(imports[2].aliases());
217
218 assert_eq!(imports[3].uri().text().unwrap().text(), "qux.wdl");
220 assert_eq!(imports[3].explicit_namespace().unwrap().text(), "x");
221 assert_eq!(imports[3].namespace().map(|(n, _)| n).as_deref(), Some("x"));
222 assert_aliases(imports[3].aliases());
223 }
224 _ => panic!("expected a V1 AST"),
225 }
226 }
227}