wdl_ast/v1/
import.rs

1//! V1 AST representation for import statements.
2
3use 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/// Represents an import statement.
24#[derive(Clone, Debug, PartialEq, Eq)]
25pub struct ImportStatement<N: TreeNode = SyntaxNode>(N);
26
27impl<N: TreeNode> ImportStatement<N> {
28    /// Gets the URI of the import statement.
29    pub fn uri(&self) -> LiteralString<N> {
30        self.child().expect("import should have a URI")
31    }
32
33    /// Gets the `import` keyword of the import statement.
34    pub fn keyword(&self) -> ImportKeyword<N::Token> {
35        self.token().expect("import should have a keyword")
36    }
37
38    /// Gets the explicit namespace of the import statement (i.e. the `as`
39    /// clause).
40    pub fn explicit_namespace(&self) -> Option<Ident<N::Token>> {
41        self.token()
42    }
43
44    /// Gets the aliased names of the import statement.
45    pub fn aliases(&self) -> impl Iterator<Item = ImportAlias<N>> + use<'_, N> {
46        self.children()
47    }
48
49    /// Gets the namespace of the import.
50    ///
51    /// If an explicit namespace was not present, this will determine the
52    /// namespace based on the URI.
53    ///
54    /// Returns `None` if the namespace could not be derived; this may occur
55    /// when the URI contains an interpolation or if the file stem of the
56    /// URI is not a valid WDL identifier.
57    ///
58    /// The returned span is either the span of the explicit namespace or the
59    /// span of the URI.
60    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        // Get just the file stem of the URI
66        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        // Check to see if the stem is a valid WDL identifier
84        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/// Represents an import alias.
112#[derive(Clone, Debug, PartialEq, Eq)]
113pub struct ImportAlias<N: TreeNode = SyntaxNode>(N);
114
115impl<N: TreeNode> ImportAlias<N> {
116    /// Gets the source and target names of the alias.
117    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    /// Gets the `alias` keyword of the alias.
129    pub fn alias_keyword(&self) -> AliasKeyword<N::Token> {
130        self.token().expect("alias should have an `alias` keyword")
131    }
132
133    /// Gets the `as` keyword of the alias.
134    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                // First import statement
195                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                // Second import statement
204                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                // Third import statement
210                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                // Fourth import statement
219                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}