1use std::ffi::OsStr;
4use std::path::Path;
5
6use rowan::NodeOrToken;
7use url::Url;
8use wdl_grammar::lexer::v1::is_ident;
9
10use super::AliasKeyword;
11use super::AsKeyword;
12use super::Asterisk;
13use super::FromKeyword;
14use super::ImportKeyword;
15use super::LiteralString;
16use crate::AstNode;
17use crate::AstToken;
18use crate::Ident;
19use crate::Span;
20use crate::SyntaxKind;
21use crate::SyntaxNode;
22use crate::TreeNode;
23use crate::TreeToken;
24
25#[derive(Clone, Debug, PartialEq, Eq)]
43pub struct ImportStatement<N: TreeNode = SyntaxNode>(N);
44
45#[derive(Clone, Debug, PartialEq, Eq)]
50pub enum ImportSource<N: TreeNode = SyntaxNode> {
51 Uri(LiteralString<N>),
53 ModulePath(SymbolicModulePath<N>),
55}
56
57impl<N: TreeNode> ImportSource<N> {
58 pub fn span(&self) -> Span {
60 match self {
61 Self::Uri(uri) => uri.span(),
62 Self::ModulePath(path) => path.span(),
63 }
64 }
65}
66
67#[derive(Copy, Clone, Debug, PartialEq, Eq)]
72pub enum ImportForm {
73 Namespace,
83 Wildcard,
91 Selected,
99}
100
101impl<N: TreeNode> ImportStatement<N> {
102 pub fn keyword(&self) -> ImportKeyword<N::Token> {
104 self.token()
105 .expect("`ImportStatement` should have an `ImportKeyword`")
106 }
107
108 pub fn form(&self) -> ImportForm {
110 if self.wildcard().is_some() {
111 ImportForm::Wildcard
112 } else if self.members().is_some() {
113 ImportForm::Selected
114 } else {
115 ImportForm::Namespace
116 }
117 }
118
119 pub fn source(&self) -> ImportSource<N> {
122 if let Some(uri) = self.child::<LiteralString<N>>() {
123 return ImportSource::Uri(uri);
124 }
125 if let Some(path) = self.child::<SymbolicModulePath<N>>() {
126 return ImportSource::ModulePath(path);
127 }
128 unreachable!(
129 "a well-formed `ImportStatementNode` always has a `LiteralString` or \
130 `SymbolicModulePath` child"
131 )
132 }
133
134 pub fn members(&self) -> Option<ImportMembers<N>> {
136 self.child()
137 }
138
139 pub fn wildcard(&self) -> Option<Asterisk<N::Token>> {
141 self.token()
142 }
143
144 pub fn from_keyword(&self) -> Option<FromKeyword<N::Token>> {
146 self.token()
147 }
148
149 pub fn explicit_namespace(&self) -> Option<Ident<N::Token>> {
156 if self.form() != ImportForm::Namespace {
157 return None;
158 }
159 let mut tokens = self.0.children_with_tokens().filter_map(|c| c.into_token());
160 while let Some(t) = tokens.next() {
161 if t.kind() == SyntaxKind::AsKeyword {
162 return tokens.find_map(Ident::cast);
163 }
164 }
165 None
166 }
167
168 pub fn aliases(&self) -> impl Iterator<Item = ImportAlias<N>> + use<'_, N> {
170 self.children()
171 }
172
173 pub fn namespace(&self) -> Option<(String, Span)> {
183 if self.form() != ImportForm::Namespace {
184 return None;
185 }
186
187 if let Some(explicit) = self.explicit_namespace() {
188 return Some((explicit.text().to_string(), explicit.span()));
189 }
190
191 match self.source() {
192 ImportSource::Uri(uri) => {
193 let text = uri.text()?;
194 let stem = match Url::parse(text.text()) {
195 Ok(url) => Path::new(
196 urlencoding::decode(url.path_segments()?.next_back()?)
197 .ok()?
198 .as_ref(),
199 )
200 .file_stem()
201 .and_then(OsStr::to_str)?
202 .to_string(),
203 Err(_) => Path::new(text.text())
204 .file_stem()
205 .and_then(OsStr::to_str)?
206 .to_string(),
207 };
208 if !is_ident(&stem) {
209 return None;
210 }
211 Some((stem, uri.span()))
212 }
213 ImportSource::ModulePath(path) => {
214 let last = path.components().last()?;
215 Some((last.text().to_string(), last.span()))
216 }
217 }
218 }
219}
220
221impl<N: TreeNode> AstNode<N> for ImportStatement<N> {
222 fn can_cast(kind: SyntaxKind) -> bool {
223 kind == SyntaxKind::ImportStatementNode
224 }
225
226 fn cast(inner: N) -> Option<Self> {
227 match inner.kind() {
228 SyntaxKind::ImportStatementNode => Some(Self(inner)),
229 _ => None,
230 }
231 }
232
233 fn inner(&self) -> &N {
234 &self.0
235 }
236}
237
238#[derive(Clone, Debug, PartialEq, Eq)]
242pub struct SymbolicModulePath<N: TreeNode = SyntaxNode>(N);
243
244impl<N: TreeNode> SymbolicModulePath<N> {
245 pub fn components(&self) -> impl Iterator<Item = Ident<N::Token>> + use<'_, N> {
247 self.tokens()
248 }
249
250 pub fn text(&self) -> String {
252 let mut out = String::new();
253 let mut first = true;
254 for c in self.components() {
255 if !first {
256 out.push('/');
257 }
258 out.push_str(c.text());
259 first = false;
260 }
261 out
262 }
263}
264
265impl<N: TreeNode> AstNode<N> for SymbolicModulePath<N> {
266 fn can_cast(kind: SyntaxKind) -> bool {
267 kind == SyntaxKind::SymbolicModulePathNode
268 }
269
270 fn cast(inner: N) -> Option<Self> {
271 match inner.kind() {
272 SyntaxKind::SymbolicModulePathNode => Some(Self(inner)),
273 _ => None,
274 }
275 }
276
277 fn inner(&self) -> &N {
278 &self.0
279 }
280}
281
282#[derive(Clone, Debug, PartialEq, Eq)]
287pub struct ImportMembers<N: TreeNode = SyntaxNode>(N);
288
289impl<N: TreeNode> ImportMembers<N> {
290 pub fn members(&self) -> impl Iterator<Item = ImportMember<N>> + use<'_, N> {
292 self.children()
293 }
294}
295
296impl<N: TreeNode> AstNode<N> for ImportMembers<N> {
297 fn can_cast(kind: SyntaxKind) -> bool {
298 kind == SyntaxKind::ImportMembersNode
299 }
300
301 fn cast(inner: N) -> Option<Self> {
302 match inner.kind() {
303 SyntaxKind::ImportMembersNode => Some(Self(inner)),
304 _ => None,
305 }
306 }
307
308 fn inner(&self) -> &N {
309 &self.0
310 }
311}
312
313#[derive(Clone, Debug, PartialEq, Eq)]
318pub struct ImportMember<N: TreeNode = SyntaxNode>(N);
319
320impl<N: TreeNode> ImportMember<N> {
321 pub fn name(&self) -> Ident<N::Token> {
323 self.idents()
324 .next()
325 .expect("member should have a name identifier")
326 }
327
328 pub fn alias(&self) -> Option<Ident<N::Token>> {
330 self.idents().nth(1)
331 }
332
333 fn idents(&self) -> impl Iterator<Item = Ident<N::Token>> + use<'_, N> {
335 self.tokens()
336 }
337}
338
339impl<N: TreeNode> AstNode<N> for ImportMember<N> {
340 fn can_cast(kind: SyntaxKind) -> bool {
341 kind == SyntaxKind::ImportMemberNode
342 }
343
344 fn cast(inner: N) -> Option<Self> {
345 match inner.kind() {
346 SyntaxKind::ImportMemberNode => Some(Self(inner)),
347 _ => None,
348 }
349 }
350
351 fn inner(&self) -> &N {
352 &self.0
353 }
354}
355
356#[derive(Clone, Debug, PartialEq, Eq)]
358pub struct ImportAlias<N: TreeNode = SyntaxNode>(N);
359
360impl<N: TreeNode> ImportAlias<N> {
361 pub fn names(&self) -> (Ident<N::Token>, Ident<N::Token>) {
363 let mut children = self.0.children_with_tokens().filter_map(|c| match c {
364 NodeOrToken::Node(_) => None,
365 NodeOrToken::Token(t) => Ident::cast(t),
366 });
367
368 let source = children.next().expect("expected a source identifier");
369 let target = children.next().expect("expected a target identifier");
370 (source, target)
371 }
372
373 pub fn alias_keyword(&self) -> AliasKeyword<N::Token> {
375 self.token().expect("alias should have an `alias` keyword")
376 }
377
378 pub fn as_keyword(&self) -> AsKeyword<N::Token> {
380 self.token().expect("alias should have an `as` keyword")
381 }
382}
383
384impl<N: TreeNode> AstNode<N> for ImportAlias<N> {
385 fn can_cast(kind: SyntaxKind) -> bool {
386 kind == SyntaxKind::ImportAliasNode
387 }
388
389 fn cast(inner: N) -> Option<Self> {
390 match inner.kind() {
391 SyntaxKind::ImportAliasNode => Some(Self(inner)),
392 _ => None,
393 }
394 }
395
396 fn inner(&self) -> &N {
397 &self.0
398 }
399}
400
401#[cfg(test)]
402mod test {
403 use pretty_assertions::assert_eq;
404
405 use super::*;
406 use crate::Ast;
407 use crate::Document;
408
409 #[test]
410 fn quoted_imports() {
411 let (document, diagnostics) = Document::parse(
412 r#"
413version 1.1
414
415import "foo.wdl"
416import "bar.wdl" as x
417import "baz.wdl" alias A as B alias C as D
418import "qux.wdl" as x alias A as B alias C as D
419"#,
420 None,
421 );
422 assert!(diagnostics.is_empty());
423 let Ast::V1(ast) = document.ast() else {
424 panic!("expected a V1 AST");
425 };
426
427 fn assert_aliases<N: TreeNode>(mut aliases: impl Iterator<Item = ImportAlias<N>>) {
428 let alias = aliases.next().unwrap();
429 let (to, from) = alias.names();
430 assert_eq!(to.text(), "A");
431 assert_eq!(from.text(), "B");
432 let alias = aliases.next().unwrap();
433 let (to, from) = alias.names();
434 assert_eq!(to.text(), "C");
435 assert_eq!(from.text(), "D");
436 assert!(aliases.next().is_none());
437 }
438
439 let imports: Vec<_> = ast.imports().collect();
440 assert_eq!(imports.len(), 4);
441
442 for import in &imports {
443 assert_eq!(import.form(), ImportForm::Namespace);
444 assert!(matches!(import.source(), ImportSource::Uri(_)));
445 assert!(import.wildcard().is_none());
446 assert!(import.members().is_none());
447 }
448
449 assert_eq!(uri_text(&imports[0]), "foo.wdl");
450 assert!(imports[0].explicit_namespace().is_none());
451 assert_eq!(
452 imports[0].namespace().map(|(n, _)| n).as_deref(),
453 Some("foo"),
454 );
455 assert_eq!(imports[0].aliases().count(), 0);
456
457 assert_eq!(uri_text(&imports[1]), "bar.wdl");
458 assert_eq!(imports[1].explicit_namespace().unwrap().text(), "x");
459 assert_eq!(imports[1].namespace().map(|(n, _)| n).as_deref(), Some("x"),);
460 assert_eq!(imports[1].aliases().count(), 0);
461
462 assert_eq!(uri_text(&imports[2]), "baz.wdl");
463 assert!(imports[2].explicit_namespace().is_none());
464 assert_eq!(
465 imports[2].namespace().map(|(n, _)| n).as_deref(),
466 Some("baz"),
467 );
468 assert_aliases(imports[2].aliases());
469
470 assert_eq!(uri_text(&imports[3]), "qux.wdl");
471 assert_eq!(imports[3].explicit_namespace().unwrap().text(), "x");
472 assert_eq!(imports[3].namespace().map(|(n, _)| n).as_deref(), Some("x"),);
473 assert_aliases(imports[3].aliases());
474 }
475
476 fn uri_text(import: &ImportStatement) -> String {
477 match import.source() {
478 ImportSource::Uri(uri) => uri.text().unwrap().text().to_string(),
479 ImportSource::ModulePath(_) => panic!("expected a quoted URI source"),
480 }
481 }
482
483 fn module_path_text(import: &ImportStatement) -> String {
484 match import.source() {
485 ImportSource::ModulePath(path) => path.text(),
486 ImportSource::Uri(_) => panic!("expected a symbolic module path source"),
487 }
488 }
489
490 #[test]
491 fn symbolic_imports() {
492 let (document, diagnostics) = Document::parse(
493 r#"
494version 1.4
495
496import openwdl/csvkit
497import openwdl/csvkit as csv
498import * from openwdl/csvkit
499import { sort } from openwdl/csvkit
500import { CsvSort, CsvSortStable as Stable } from "local.wdl"
501"#,
502 None,
503 );
504 assert!(diagnostics.is_empty(), "diagnostics: {diagnostics:#?}");
505 let Ast::V1(ast) = document.ast() else {
506 panic!("expected a V1 AST");
507 };
508
509 let imports: Vec<_> = ast.imports().collect();
510 assert_eq!(imports.len(), 5);
511
512 assert_eq!(imports[0].form(), ImportForm::Namespace);
514 assert_eq!(imports[0].keyword().text(), "import");
515 assert_eq!(module_path_text(&imports[0]), "openwdl/csvkit");
516 assert!(imports[0].wildcard().is_none());
517 assert!(imports[0].from_keyword().is_none());
518 assert!(imports[0].members().is_none());
519 assert!(imports[0].explicit_namespace().is_none());
520 assert_eq!(imports[0].aliases().count(), 0);
521 assert_eq!(
522 imports[0].namespace().map(|(n, _)| n).as_deref(),
523 Some("csvkit"),
524 );
525
526 assert_eq!(imports[1].form(), ImportForm::Namespace);
528 assert_eq!(imports[1].keyword().text(), "import");
529 assert_eq!(module_path_text(&imports[1]), "openwdl/csvkit");
530 assert!(imports[1].wildcard().is_none());
531 assert!(imports[1].from_keyword().is_none());
532 assert!(imports[1].members().is_none());
533 assert_eq!(imports[1].explicit_namespace().unwrap().text(), "csv");
534 assert_eq!(imports[1].aliases().count(), 0);
535 assert_eq!(
536 imports[1].namespace().map(|(n, _)| n).as_deref(),
537 Some("csv"),
538 );
539
540 assert_eq!(imports[2].form(), ImportForm::Wildcard);
542 assert_eq!(imports[2].keyword().text(), "import");
543 assert_eq!(module_path_text(&imports[2]), "openwdl/csvkit");
544 assert!(imports[2].wildcard().is_some());
545 assert_eq!(imports[2].from_keyword().unwrap().text(), "from");
546 assert!(imports[2].members().is_none());
547 assert!(imports[2].explicit_namespace().is_none());
548 assert_eq!(imports[2].aliases().count(), 0);
549 assert!(imports[2].namespace().is_none());
550
551 assert_eq!(imports[3].form(), ImportForm::Selected);
554 assert_eq!(imports[3].keyword().text(), "import");
555 assert_eq!(module_path_text(&imports[3]), "openwdl/csvkit");
556 assert!(imports[3].wildcard().is_none());
557 assert_eq!(imports[3].from_keyword().unwrap().text(), "from");
558 assert!(imports[3].explicit_namespace().is_none());
559 assert_eq!(imports[3].aliases().count(), 0);
560 assert!(imports[3].namespace().is_none());
561 let members: Vec<_> = imports[3].members().unwrap().members().collect();
562 assert_eq!(members.len(), 1);
563 assert_eq!(members[0].name().text(), "sort");
564 assert!(members[0].alias().is_none());
565
566 assert_eq!(imports[4].form(), ImportForm::Selected);
569 assert_eq!(imports[4].keyword().text(), "import");
570 assert_eq!(uri_text(&imports[4]), "local.wdl");
571 assert!(imports[4].wildcard().is_none());
572 assert_eq!(imports[4].from_keyword().unwrap().text(), "from");
573 assert!(imports[4].explicit_namespace().is_none());
574 assert_eq!(imports[4].aliases().count(), 0);
575 assert!(imports[4].namespace().is_none());
576 let members: Vec<_> = imports[4].members().unwrap().members().collect();
577 assert_eq!(members.len(), 2);
578 assert_eq!(members[0].name().text(), "CsvSort");
579 assert!(members[0].alias().is_none());
580 assert_eq!(members[1].name().text(), "CsvSortStable");
581 assert_eq!(members[1].alias().unwrap().text(), "Stable");
582 }
583}