wdl_analysis/document.rs
1//! Representation of analyzed WDL documents.
2
3use std::borrow::Cow;
4use std::collections::HashMap;
5use std::collections::HashSet;
6use std::collections::hash_map::Entry;
7use std::path::Path;
8use std::sync::Arc;
9
10use arrayvec::ArrayString;
11use indexmap::IndexMap;
12use indexmap::IndexSet;
13use petgraph::graph::NodeIndex;
14use rowan::GreenNode;
15use rowan::TextRange;
16use rowan::TextSize;
17use url::Url;
18use uuid::Uuid;
19use wdl_ast::Ast;
20use wdl_ast::AstNode;
21use wdl_ast::Diagnostic;
22use wdl_ast::Severity;
23use wdl_ast::Span;
24use wdl_ast::SupportedVersion;
25use wdl_ast::SyntaxNode;
26
27use crate::Diagnostics;
28use crate::config::Config;
29use crate::diagnostics::Context;
30use crate::diagnostics::no_common_type;
31use crate::graph::DocumentGraph;
32use crate::graph::ParseState;
33use crate::types::CallType;
34use crate::types::Optional;
35use crate::types::Type;
36
37pub mod v1;
38
39/// The `task` variable name available in task command sections and outputs in
40/// WDL 1.2.
41pub const TASK_VAR_NAME: &str = "task";
42
43/// A successfully resolved namespace introduced by an import.
44#[derive(Debug)]
45pub struct Namespace {
46 /// The span of the import that introduced the namespace.
47 pub(crate) span: Span,
48 /// The URI of the imported document that introduced the namespace.
49 source: Arc<Url>,
50 /// The namespace's document.
51 document: Document,
52 /// Whether or not the namespace is used (i.e. referenced) in the document.
53 pub(crate) used: bool,
54}
55
56impl Namespace {
57 /// Gets the span of the import that introduced the namespace.
58 pub fn span(&self) -> Span {
59 self.span
60 }
61
62 /// Gets the URI of the imported document that introduced the namespace.
63 pub fn source(&self) -> &Arc<Url> {
64 &self.source
65 }
66
67 /// Gets the imported document.
68 pub fn document(&self) -> &Document {
69 &self.document
70 }
71}
72
73/// Represents a struct in a document.
74#[derive(Debug, Clone)]
75pub struct Struct {
76 /// The name of the struct.
77 name: String,
78 /// The span that introduced the struct.
79 ///
80 /// This is either the name of a struct definition (local) or an import's
81 /// URI or alias (imported).
82 name_span: Span,
83 /// The offset of the CST node from the start of the document.
84 ///
85 /// This is used to adjust diagnostics resulting from traversing the struct
86 /// node as if it were the root of the CST.
87 offset: usize,
88 /// Stores the CST node of the struct.
89 ///
90 /// This is used to calculate type equivalence for imports.
91 node: rowan::GreenNode,
92 /// The source of this struct.
93 ///
94 /// `Some(uri)` means the struct was imported (by a namespaced, wildcard, or
95 /// selective import) from the document at `uri`. Imported structs carry
96 /// their resolved type from that document, so they are not type-checked
97 /// again here, and `uri` locates the original definition for
98 /// go-to-definition.
99 ///
100 /// `None` means the struct is defined locally in the containing document.
101 source: Option<Arc<Url>>,
102 /// The type of the struct.
103 ///
104 /// Initially this is `None` until a type check occurs.
105 ty: Option<Type>,
106}
107
108impl Struct {
109 /// Gets the name of the struct.
110 pub fn name(&self) -> &str {
111 &self.name
112 }
113
114 /// Gets the span of the name.
115 pub fn name_span(&self) -> Span {
116 self.name_span
117 }
118
119 /// Gets the offset of the struct
120 pub fn offset(&self) -> usize {
121 self.offset
122 }
123
124 /// Gets the node of the struct.
125 pub fn node(&self) -> &rowan::GreenNode {
126 &self.node
127 }
128
129 /// Gets the URI of the document this struct was imported from.
130 ///
131 /// Returns `Some(uri)` for an imported struct (whether namespaced,
132 /// wildcard, or selective), where `uri` is the document the struct was
133 /// imported from. Returns `None` for a struct defined locally in the
134 /// containing document.
135 pub fn source(&self) -> Option<&Arc<Url>> {
136 self.source.as_ref()
137 }
138
139 /// Gets the type of the struct.
140 ///
141 /// A value of `None` indicates that the type could not be determined for
142 /// the struct; this may happen if the struct definition is recursive.
143 pub fn ty(&self) -> Option<&Type> {
144 self.ty.as_ref()
145 }
146}
147
148/// Represents an enum in a document.
149#[derive(Debug, Clone)]
150pub struct Enum {
151 /// The name of the enum.
152 name: String,
153 /// The span that introduced the enum.
154 ///
155 /// This is either the name of an enum definition (local) or an import's
156 /// URI or alias (imported).
157 name_span: Span,
158 /// The offset of the CST node from the start of the document.
159 ///
160 /// This is used to adjust diagnostics resulting from traversing the enum
161 /// node as if it were the root of the CST.
162 offset: usize,
163 /// Stores the CST node of the enum.
164 ///
165 /// This is used to calculate type equivalence for imports and can be
166 /// reconstructed into an AST node to access choice expressions.
167 node: rowan::GreenNode,
168 /// The source of this enum.
169 ///
170 /// `Some(uri)` means the enum was imported (by a namespaced, wildcard, or
171 /// selective import) from the document at `uri`. Imported enums carry their
172 /// resolved type from that document, so they are not type-checked again
173 /// here, and `uri` locates the original definition for go-to-definition.
174 ///
175 /// `None` means the enum is defined locally in the containing document.
176 source: Option<Arc<Url>>,
177 /// The type of the enum.
178 ///
179 /// Initially this is `None` until a type check/coercion occurs.
180 ty: Option<Type>,
181}
182
183impl Enum {
184 /// Gets the name of the enum.
185 pub fn name(&self) -> &str {
186 &self.name
187 }
188
189 /// Gets the span of the name.
190 pub fn name_span(&self) -> Span {
191 self.name_span
192 }
193
194 /// Gets the offset of the enum.
195 pub fn offset(&self) -> usize {
196 self.offset
197 }
198
199 /// Gets the green node of the enum.
200 pub fn node(&self) -> &rowan::GreenNode {
201 &self.node
202 }
203
204 /// Reconstructs the AST definition from the stored green node.
205 ///
206 /// This provides access to choice expressions and other AST details.
207 pub fn definition(&self) -> wdl_ast::v1::EnumDefinition {
208 wdl_ast::v1::EnumDefinition::cast(wdl_ast::SyntaxNode::new_root(self.node.clone()))
209 .expect("stored node should be a valid enum definition")
210 }
211
212 /// Gets the URI of the document this enum was imported from.
213 ///
214 /// Returns `Some(uri)` for an imported enum (whether namespaced, wildcard,
215 /// or selective), where `uri` is the document the enum was imported from.
216 /// Returns `None` for an enum defined locally in the containing document.
217 pub fn source(&self) -> Option<&Arc<Url>> {
218 self.source.as_ref()
219 }
220
221 /// Gets the type of the enum.
222 pub fn ty(&self) -> Option<&Type> {
223 self.ty.as_ref()
224 }
225}
226
227/// Represents information about a name in a scope.
228#[derive(Debug, Clone)]
229pub struct Name {
230 /// The span of the name.
231 span: Span,
232 /// The type of the name.
233 ty: Type,
234}
235
236impl Name {
237 /// Gets the span of the name.
238 pub fn span(&self) -> Span {
239 self.span
240 }
241
242 /// Gets the type of the name.
243 pub fn ty(&self) -> &Type {
244 &self.ty
245 }
246}
247
248/// Represents an index of a scope in a collection of scopes.
249#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
250pub struct ScopeIndex(usize);
251
252/// Represents a scope in a WDL document.
253#[derive(Debug)]
254pub struct Scope {
255 /// The index of the parent scope.
256 ///
257 /// This is `None` for task and workflow scopes.
258 parent: Option<ScopeIndex>,
259 /// The span in the document where the names of the scope are visible.
260 span: Span,
261 /// The map of names in scope to their span and types.
262 names: IndexMap<String, Name>,
263}
264
265impl Scope {
266 /// Creates a new scope given the parent scope and span.
267 fn new(parent: Option<ScopeIndex>, span: Span) -> Self {
268 Self {
269 parent,
270 span,
271 names: Default::default(),
272 }
273 }
274
275 /// Inserts a name into the scope.
276 pub fn insert(&mut self, name: impl Into<String>, span: Span, ty: Type) {
277 self.names.insert(name.into(), Name { span, ty });
278 }
279}
280
281/// Represents a reference to a scope.
282#[derive(Debug, Clone, Copy)]
283pub struct ScopeRef<'a> {
284 /// The reference to the scopes collection.
285 scopes: &'a [Scope],
286 /// The index of the scope in the collection.
287 index: ScopeIndex,
288}
289
290impl<'a> ScopeRef<'a> {
291 /// Creates a new scope reference given the scope index.
292 fn new(scopes: &'a [Scope], index: ScopeIndex) -> Self {
293 Self { scopes, index }
294 }
295
296 /// Gets the span of the scope.
297 pub fn span(&self) -> Span {
298 self.scopes[self.index.0].span
299 }
300
301 /// Gets the parent scope.
302 ///
303 /// Returns `None` if there is no parent scope.
304 pub fn parent(&self) -> Option<Self> {
305 self.scopes[self.index.0].parent.map(|p| Self {
306 scopes: self.scopes,
307 index: p,
308 })
309 }
310
311 /// Gets all of the names available at this scope.
312 pub fn names(&self) -> impl Iterator<Item = (&str, &Name)> + use<'_> {
313 self.scopes[self.index.0]
314 .names
315 .iter()
316 .map(|(name, n)| (name.as_str(), n))
317 }
318
319 /// Gets a name local to this scope.
320 ///
321 /// Returns `None` if a name local to this scope was not found.
322 pub fn local(&self, name: &str) -> Option<&Name> {
323 self.scopes[self.index.0].names.get(name)
324 }
325
326 /// Lookups a name in the scope.
327 ///
328 /// Returns `None` if the name is not available in the scope.
329 pub fn lookup(&self, name: &str) -> Option<&Name> {
330 let mut current = Some(self.index);
331
332 while let Some(index) = current {
333 if let Some(name) = self.scopes[index.0].names.get(name) {
334 return Some(name);
335 }
336
337 current = self.scopes[index.0].parent;
338 }
339
340 None
341 }
342}
343
344/// Represents a mutable reference to a scope.
345#[derive(Debug)]
346struct ScopeRefMut<'a> {
347 /// The reference to all scopes.
348 scopes: &'a mut [Scope],
349 /// The index to the scope.
350 index: ScopeIndex,
351}
352
353impl<'a> ScopeRefMut<'a> {
354 /// Creates a new mutable scope reference given the scope index.
355 fn new(scopes: &'a mut [Scope], index: ScopeIndex) -> Self {
356 Self { scopes, index }
357 }
358
359 /// Lookups a name in the scope.
360 ///
361 /// Returns `None` if the name is not available in the scope.
362 pub fn lookup(&self, name: &str) -> Option<&Name> {
363 let mut current = Some(self.index);
364
365 while let Some(index) = current {
366 if let Some(name) = self.scopes[index.0].names.get(name) {
367 return Some(name);
368 }
369
370 current = self.scopes[index.0].parent;
371 }
372
373 None
374 }
375
376 /// Inserts a name into the scope.
377 pub fn insert(&mut self, name: impl Into<String>, span: Span, ty: Type) {
378 self.scopes[self.index.0]
379 .names
380 .insert(name.into(), Name { span, ty });
381 }
382
383 /// Converts the mutable scope reference to an immutable scope reference.
384 pub fn as_scope_ref(&'a self) -> ScopeRef<'a> {
385 ScopeRef {
386 scopes: self.scopes,
387 index: self.index,
388 }
389 }
390}
391
392/// A scope union takes the union of names within a number of given scopes and
393/// computes a set of common output names for a presumed parent scope. This is
394/// useful when calculating common elements from, for example, an `if`
395/// statement within a workflow.
396#[derive(Debug)]
397pub struct ScopeUnion<'a> {
398 /// The scope references to process.
399 scope_refs: Vec<(ScopeRef<'a>, bool)>,
400}
401
402impl<'a> ScopeUnion<'a> {
403 /// Creates a new scope union.
404 pub fn new() -> Self {
405 Self {
406 scope_refs: Vec::new(),
407 }
408 }
409
410 /// Adds a scope to the union.
411 pub fn insert(&mut self, scope_ref: ScopeRef<'a>, exhaustive: bool) {
412 self.scope_refs.push((scope_ref, exhaustive));
413 }
414
415 /// Resolves the scope union to names and types that should be accessible
416 /// from the parent scope.
417 ///
418 /// Returns an error if any issues are encountered during resolving.
419 pub fn resolve(self) -> Result<HashMap<String, Name>, Vec<Diagnostic>> {
420 let mut errors = Vec::new();
421 let mut ignored: HashSet<String> = HashSet::new();
422
423 // Gather all declaration names and reconcile types
424 let mut names: HashMap<String, Name> = HashMap::new();
425 for (scope_ref, _) in &self.scope_refs {
426 for (name, info) in scope_ref.names() {
427 if ignored.contains(name) {
428 continue;
429 }
430
431 match names.entry(name.to_string()) {
432 Entry::Vacant(entry) => {
433 entry.insert(info.clone());
434 }
435 Entry::Occupied(mut entry) => {
436 let Some(ty) = entry.get().ty.common_type(&info.ty) else {
437 errors.push(no_common_type(
438 &entry.get().ty,
439 entry.get().span,
440 &info.ty,
441 info.span,
442 ));
443 names.remove(name);
444 ignored.insert(name.to_string());
445 continue;
446 };
447
448 entry.get_mut().ty = ty;
449 }
450 }
451 }
452 }
453
454 // Mark types as optional if not present in all clauses
455 for (scope_ref, _) in &self.scope_refs {
456 for (name, info) in &mut names {
457 if ignored.contains(name) {
458 continue;
459 }
460
461 // If this name is not in the current clause's scope, mark as optional
462 if scope_ref.local(name).is_none() {
463 info.ty = info.ty.optional();
464 }
465 }
466 }
467
468 // If there's no `else` clause, mark all types as optional
469 let has_exhaustive = self.scope_refs.iter().any(|(_, exhaustive)| *exhaustive);
470 if !has_exhaustive {
471 for info in names.values_mut() {
472 info.ty = info.ty.optional();
473 }
474 }
475
476 if !errors.is_empty() {
477 return Err(errors);
478 }
479
480 Ok(names)
481 }
482}
483
484/// Represents a task or workflow input.
485#[derive(Debug, Clone, PartialEq, Eq)]
486pub struct Input {
487 /// The type of the input.
488 ty: Type,
489 /// Whether or not the input is required.
490 ///
491 /// A required input is one that has a non-optional type and no default
492 /// expression.
493 required: bool,
494}
495
496impl Input {
497 /// Gets the type of the input.
498 pub fn ty(&self) -> &Type {
499 &self.ty
500 }
501
502 /// Whether or not the input is required.
503 pub fn required(&self) -> bool {
504 self.required
505 }
506}
507
508/// Represents a task or workflow output.
509#[derive(Debug, Clone, PartialEq, Eq)]
510pub struct Output {
511 /// The type of the output.
512 ty: Type,
513 /// The span of the output name.
514 name_span: Span,
515}
516
517impl Output {
518 /// Creates a new output with the given type.
519 pub(crate) fn new(ty: Type, name_span: Span) -> Self {
520 Self { ty, name_span }
521 }
522
523 /// Gets the type of the output.
524 pub fn ty(&self) -> &Type {
525 &self.ty
526 }
527
528 /// Gets the span of output's name.
529 pub fn name_span(&self) -> Span {
530 self.name_span
531 }
532}
533
534/// Represents a task in a document.
535#[derive(Debug)]
536pub struct Task {
537 /// The span of the task name.
538 name_span: Span,
539 /// The name of the task.
540 name: String,
541 /// The span of the task definition.
542 span: Span,
543 /// The scopes contained in the task.
544 ///
545 /// The first scope will always be the task's scope.
546 ///
547 /// The scopes will be in sorted order by span start.
548 scopes: Vec<Scope>,
549 /// The inputs of the task.
550 inputs: Arc<IndexMap<String, Input>>,
551 /// The outputs of the task.
552 outputs: Arc<IndexMap<String, Output>>,
553}
554
555impl Task {
556 /// Gets the name of the task.
557 pub fn name(&self) -> &str {
558 &self.name
559 }
560
561 /// Gets the span of the name.
562 pub fn name_span(&self) -> Span {
563 self.name_span
564 }
565
566 /// Gets the span of the workflow definition.
567 pub fn span(&self) -> Span {
568 self.span
569 }
570
571 /// Gets the scope of the task.
572 pub fn scope(&self) -> ScopeRef<'_> {
573 ScopeRef::new(&self.scopes, ScopeIndex(0))
574 }
575
576 /// Gets the inputs of the task.
577 pub fn inputs(&self) -> &IndexMap<String, Input> {
578 &self.inputs
579 }
580
581 /// Gets the outputs of the task.
582 pub fn outputs(&self) -> &IndexMap<String, Output> {
583 &self.outputs
584 }
585}
586
587/// Represents a workflow in a document.
588#[derive(Debug)]
589pub struct Workflow {
590 /// The span of the workflow name.
591 name_span: Span,
592 /// The name of the workflow.
593 name: String,
594 /// The span of the workflow definition.
595 span: Span,
596 /// The scopes contained in the workflow.
597 ///
598 /// The first scope will always be the workflow's scope.
599 ///
600 /// The scopes will be in sorted order by span start.
601 scopes: Vec<Scope>,
602 /// The inputs of the workflow.
603 inputs: Arc<IndexMap<String, Input>>,
604 /// The outputs of the workflow.
605 outputs: Arc<IndexMap<String, Output>>,
606 /// The calls made by the workflow.
607 calls: HashMap<String, CallType>,
608 /// Whether or not nested inputs are allowed for the workflow.
609 allows_nested_inputs: bool,
610}
611
612impl Workflow {
613 /// Gets the name of the workflow.
614 pub fn name(&self) -> &str {
615 &self.name
616 }
617
618 /// Gets the span of the name.
619 pub fn name_span(&self) -> Span {
620 self.name_span
621 }
622
623 /// Gets the span of the workflow definition.
624 pub fn span(&self) -> Span {
625 self.span
626 }
627
628 /// Gets the scope of the workflow.
629 pub fn scope(&self) -> ScopeRef<'_> {
630 ScopeRef::new(&self.scopes, ScopeIndex(0))
631 }
632
633 /// Gets the inputs of the workflow.
634 pub fn inputs(&self) -> &IndexMap<String, Input> {
635 &self.inputs
636 }
637
638 /// Gets the outputs of the workflow.
639 pub fn outputs(&self) -> &IndexMap<String, Output> {
640 &self.outputs
641 }
642
643 /// Gets the calls made by the workflow.
644 pub fn calls(&self) -> &HashMap<String, CallType> {
645 &self.calls
646 }
647
648 /// Determines if the workflow allows nested inputs.
649 pub fn allows_nested_inputs(&self) -> bool {
650 self.allows_nested_inputs
651 }
652}
653
654/// A task imported into scope by a wildcard or selected-member import.
655#[derive(Debug)]
656pub(crate) struct ImportedTask {
657 /// The task name in the source document.
658 pub name: String,
659 /// The span of the import statement that introduced this task.
660 pub span: Span,
661 /// The source URI the task came from.
662 pub source: Arc<Url>,
663 /// The inputs of the task.
664 pub inputs: Arc<IndexMap<String, Input>>,
665 /// The outputs of the task.
666 pub outputs: Arc<IndexMap<String, Output>>,
667}
668
669/// A workflow imported into scope by a wildcard or selected-member import.
670#[derive(Debug)]
671pub(crate) struct ImportedWorkflow {
672 /// The workflow name in the source document.
673 pub name: String,
674 /// The span of the import statement.
675 pub span: Span,
676 /// The source URI.
677 pub source: Arc<Url>,
678 /// The inputs of the workflow.
679 pub inputs: Arc<IndexMap<String, Input>>,
680 /// The outputs of the workflow.
681 pub outputs: Arc<IndexMap<String, Output>>,
682}
683
684/// A callable item.
685#[derive(Debug)]
686pub enum Callable<'a> {
687 /// A workflow.
688 Workflow(&'a Workflow),
689 /// A task.
690 Task(&'a Task),
691}
692
693impl Callable<'_> {
694 /// Get the name of this callable.
695 pub fn name(&self) -> &str {
696 match self {
697 Callable::Workflow(w) => w.name(),
698 Callable::Task(t) => t.name(),
699 }
700 }
701
702 /// Get the [`Span`] of the callable's name.
703 pub fn name_span(&self) -> Span {
704 match self {
705 Callable::Workflow(w) => w.name_span(),
706 Callable::Task(t) => t.name_span(),
707 }
708 }
709
710 /// Get the [`Span`] of the callable's full definition.
711 pub fn span(&self) -> Span {
712 match self {
713 Callable::Workflow(w) => w.span(),
714 Callable::Task(t) => t.span(),
715 }
716 }
717
718 /// Get the inputs of the callable.
719 pub fn inputs(&self) -> &IndexMap<String, Input> {
720 match self {
721 Callable::Workflow(w) => w.inputs(),
722 Callable::Task(t) => t.inputs(),
723 }
724 }
725}
726
727/// Represents analysis data about a WDL document.
728#[derive(Debug)]
729pub(crate) struct DocumentData {
730 /// The configuration under which this document was analyzed.
731 config: Config,
732 /// The root CST node of the document.
733 ///
734 /// This is `None` when the document could not be parsed.
735 root: Option<GreenNode>,
736 /// The document identifier.
737 ///
738 /// The identifier changes every time the document is analyzed.
739 id: Arc<String>,
740 /// The URI of the analyzed document.
741 uri: Arc<Url>,
742 /// The version of the document.
743 version: Option<SupportedVersion>,
744 /// The successfully resolved namespaces in the document, keyed by name.
745 namespaces: IndexMap<String, Namespace>,
746 /// The names of imports that failed to resolve, keyed by name, each with
747 /// the span of the failing import. Kept so that downstream references to
748 /// the imported name (e.g., `import spellbook` followed by
749 /// `call spellbook.fireball`) don't produce cascading "unknown namespace"
750 /// diagnostics.
751 failed_imports: IndexMap<String, Span>,
752 /// The tasks in the document.
753 tasks: IndexMap<String, Task>,
754 /// The singular workflow in the document.
755 workflow: Option<Workflow>,
756 /// The structs in the document.
757 structs: IndexMap<String, Struct>,
758 /// The enums in the document.
759 enums: IndexMap<String, Enum>,
760 /// Tasks imported via wildcard or selected-member imports.
761 imported_tasks: IndexMap<String, ImportedTask>,
762 /// Workflows imported via wildcard or selected-member imports.
763 imported_workflows: IndexMap<String, ImportedWorkflow>,
764 /// Selected task or workflow imports that failed to resolve.
765 failed_selected_imports: IndexSet<String>,
766 /// The diagnostics from parsing.
767 parse_diagnostics: Vec<Diagnostic>,
768 /// The diagnostics from analysis.
769 analysis_diagnostics: Diagnostics,
770}
771
772impl DocumentData {
773 /// Constructs a new analysis document data.
774 fn new(
775 config: Config,
776 uri: Arc<Url>,
777 root: Option<GreenNode>,
778 version: Option<SupportedVersion>,
779 diagnostics: Vec<Diagnostic>,
780 ) -> Self {
781 Self {
782 config,
783 root,
784 id: Uuid::new_v4().to_string().into(),
785 uri,
786 version,
787 namespaces: Default::default(),
788 failed_imports: Default::default(),
789 tasks: Default::default(),
790 workflow: Default::default(),
791 structs: Default::default(),
792 enums: Default::default(),
793 imported_tasks: Default::default(),
794 imported_workflows: Default::default(),
795 failed_selected_imports: Default::default(),
796 parse_diagnostics: diagnostics,
797 analysis_diagnostics: Default::default(),
798 }
799 }
800
801 /// Gets the context of the given name.
802 ///
803 /// The name may be for a namespace, task, workflow, struct, or enum.
804 ///
805 /// Returns `None` if there is no context for the given name.
806 pub fn context(&self, name: &str) -> Option<Context> {
807 // Look through the various data structures for the name
808 if let Some(ns) = self.namespaces.get(name) {
809 Some(Context::Namespace(ns.span))
810 } else if let Some(span) = self.failed_imports.get(name) {
811 Some(Context::Namespace(*span))
812 } else if let Some(task) = self.tasks.get(name) {
813 Some(Context::Task(task.name_span()))
814 } else if let Some(wf) = &self.workflow
815 && wf.name == name
816 {
817 Some(Context::Workflow(wf.name_span()))
818 } else if let Some(s) = self.structs.get(name) {
819 Some(Context::Struct(s.name_span()))
820 } else {
821 // Finally, check the enums and failing that return `None`
822 self.enums.get(name).map(|e| Context::Enum(e.name_span()))
823 }
824 }
825}
826
827/// Represents an analyzed WDL document.
828///
829/// This type is cheaply cloned.
830#[derive(Debug, Clone)]
831pub struct Document {
832 /// The document data for the document.
833 data: Arc<DocumentData>,
834}
835
836impl Document {
837 /// Creates a new default document from a URI.
838 pub(crate) fn default_from_uri(uri: Arc<Url>) -> Self {
839 Self {
840 data: Arc::new(DocumentData::new(
841 Default::default(),
842 uri,
843 None,
844 None,
845 Default::default(),
846 )),
847 }
848 }
849
850 /// Creates a new analyzed document from a document graph node.
851 pub(crate) fn from_graph_node(
852 config: &Config,
853 graph: &DocumentGraph,
854 index: NodeIndex,
855 ) -> Self {
856 let node = graph.get(index);
857
858 let (wdl_version, diagnostics) = match node.parse_state() {
859 ParseState::NotParsed => panic!("node should have been parsed"),
860 ParseState::Error(_) => return Self::default_from_uri(node.uri().clone()),
861 ParseState::Parsed {
862 wdl_version,
863 diagnostics,
864 ..
865 } => (wdl_version, diagnostics),
866 };
867
868 let root = node.root().expect("node should have been parsed");
869 let (config, wdl_version) = match (root.version_statement(), wdl_version) {
870 (Some(stmt), Some(wdl_version)) => (
871 config.with_diagnostics_config(
872 config.diagnostics_config().excepted_for_node(stmt.inner()),
873 ),
874 *wdl_version,
875 ),
876 _ => {
877 // Don't process a document with a missing version statement or an unsupported
878 // version unless a fallback version is configured
879 return Self {
880 data: Arc::new(DocumentData::new(
881 config.clone(),
882 node.uri().clone(),
883 Some(root.inner().green().into()),
884 None,
885 diagnostics.to_vec(),
886 )),
887 };
888 }
889 };
890
891 let mut data = DocumentData::new(
892 config.clone(),
893 node.uri().clone(),
894 Some(root.inner().green().into()),
895 Some(wdl_version),
896 diagnostics.to_vec(),
897 );
898 match root.ast_with_version_fallback(config.fallback_version()) {
899 Ast::Unsupported => {}
900 Ast::V1(ast) => v1::populate_document(&mut data, &config, graph, index, &ast),
901 }
902
903 Self {
904 data: Arc::new(data),
905 }
906 }
907
908 /// Gets the analysis configuration.
909 pub fn config(&self) -> &Config {
910 &self.data.config
911 }
912
913 /// Gets the root AST document node.
914 ///
915 /// # Panics
916 ///
917 /// Panics if the document was not parsed.
918 pub fn root(&self) -> wdl_ast::Document {
919 wdl_ast::Document::cast(SyntaxNode::new_root(
920 self.data.root.clone().expect("should have a root"),
921 ))
922 .expect("should cast")
923 }
924
925 /// Gets the identifier of the document.
926 ///
927 /// This value changes when a document is reanalyzed.
928 pub fn id(&self) -> &Arc<String> {
929 &self.data.id
930 }
931
932 /// Gets the URI of the document.
933 pub fn uri(&self) -> &Arc<Url> {
934 &self.data.uri
935 }
936
937 /// Gets the path to the document.
938 ///
939 /// If the scheme of the document's URI is not `file`, this will return the
940 /// URI as a string. Otherwise, this will attempt to return the path
941 /// relative to the current working directory, or the absolute path
942 /// failing that.
943 pub fn path(&self) -> Cow<'_, str> {
944 if let Ok(path) = self.data.uri.to_file_path() {
945 if let Some(path) = std::env::current_dir()
946 .ok()
947 .and_then(|cwd| path.strip_prefix(cwd).ok().and_then(Path::to_str))
948 {
949 return path.to_string().into();
950 }
951
952 if let Ok(path) = path.into_os_string().into_string() {
953 return path.into();
954 }
955 }
956
957 self.data.uri.as_str().into()
958 }
959
960 /// Computes the `blake3` hash of the document's source text over the
961 /// given span and returns the hex form.
962 ///
963 /// Uses `rowan::SyntaxText::for_each_chunk` so the span's text is never
964 /// materialized as a `String`.
965 ///
966 /// Returns `None` if `span` falls outside the document's source text.
967 pub fn hash_span(&self, span: Span) -> Option<ArrayString<64>> {
968 let text = self.root().inner().text();
969 let text_len = usize::from(text.len());
970 if span.end() > text_len {
971 return None;
972 }
973 let range = TextRange::new(
974 TextSize::new(span.start() as u32),
975 TextSize::new(span.end() as u32),
976 );
977 let slice = text.slice(range);
978 let mut hasher = blake3::Hasher::new();
979 slice.for_each_chunk(|chunk| {
980 hasher.update(chunk.as_bytes());
981 });
982 Some(hasher.finalize().to_hex())
983 }
984
985 /// Gets the supported version of the document.
986 ///
987 /// Returns `None` if the document could not be parsed or contains an
988 /// unsupported version.
989 pub fn version(&self) -> Option<SupportedVersion> {
990 self.data.version
991 }
992
993 /// Gets the successfully resolved namespaces in the document.
994 pub fn namespaces(&self) -> impl Iterator<Item = (&str, &Namespace)> {
995 self.data.namespaces.iter().map(|(n, ns)| (n.as_str(), ns))
996 }
997
998 /// Gets a successfully resolved namespace in the document by name.
999 pub fn namespace(&self, name: &str) -> Option<&Namespace> {
1000 self.data.namespaces.get(name)
1001 }
1002
1003 /// Gets the tasks in the document.
1004 pub fn tasks(&self) -> impl Iterator<Item = &Task> {
1005 self.data.tasks.iter().map(|(_, t)| t)
1006 }
1007
1008 /// Gets a task in the document by name.
1009 pub fn task_by_name(&self, name: &str) -> Option<&Task> {
1010 self.data.tasks.get(name)
1011 }
1012
1013 /// Gets an imported task in the document by local name.
1014 pub(crate) fn imported_task_by_name(&self, name: &str) -> Option<&ImportedTask> {
1015 self.data.imported_tasks.get(name)
1016 }
1017
1018 /// Gets a workflow in the document.
1019 ///
1020 /// Returns `None` if the document did not contain a workflow.
1021 pub fn workflow(&self) -> Option<&Workflow> {
1022 self.data.workflow.as_ref()
1023 }
1024
1025 /// Gets an imported workflow in the document by local name.
1026 pub(crate) fn imported_workflow_by_name(&self, name: &str) -> Option<&ImportedWorkflow> {
1027 self.data.imported_workflows.get(name)
1028 }
1029
1030 /// Gets a [`Callable`] in the document by name.
1031 ///
1032 /// Returns `None` if the document did not contain a callable definition
1033 /// with the given name.
1034 pub fn callable_by_name(&self, name: &str) -> Option<Callable<'_>> {
1035 if let Some(workflow) = self.workflow()
1036 && workflow.name() == name
1037 {
1038 return Some(Callable::Workflow(workflow));
1039 }
1040
1041 if let Some(task) = self.task_by_name(name) {
1042 return Some(Callable::Task(task));
1043 }
1044
1045 None
1046 }
1047
1048 /// Get all callable targets in the document.
1049 pub fn callables(&self) -> impl Iterator<Item = Callable<'_>> {
1050 self.workflow()
1051 .map(Callable::Workflow)
1052 .into_iter()
1053 .chain(self.tasks().map(Callable::Task))
1054 }
1055
1056 /// Gets the structs in the document.
1057 pub fn structs(&self) -> impl Iterator<Item = (&str, &Struct)> {
1058 self.data.structs.iter().map(|(n, s)| (n.as_str(), s))
1059 }
1060
1061 /// Gets a struct in the document by name.
1062 pub fn struct_by_name(&self, name: &str) -> Option<&Struct> {
1063 self.data.structs.get(name)
1064 }
1065
1066 /// Gets the enums in the document.
1067 pub fn enums(&self) -> impl Iterator<Item = (&str, &Enum)> {
1068 self.data.enums.iter().map(|(n, e)| (n.as_str(), e))
1069 }
1070
1071 /// Gets an enum in the document by name.
1072 pub fn enum_by_name(&self, name: &str) -> Option<&Enum> {
1073 self.data.enums.get(name)
1074 }
1075
1076 /// Gets the custom type by name.
1077 pub fn get_custom_type(&self, name: &str) -> Option<Type> {
1078 if let Some(s) = self.struct_by_name(name) {
1079 return s.ty().cloned();
1080 }
1081
1082 if let Some(s) = self.enum_by_name(name) {
1083 return s.ty().cloned();
1084 }
1085
1086 None
1087 }
1088
1089 /// Gets a cache key for an enum choice lookup.
1090 pub fn get_choice_cache_key(
1091 &self,
1092 name: &str,
1093 choice: &str,
1094 ) -> Option<crate::types::EnumChoiceCacheKey> {
1095 let (enum_index, _, r#enum) = self.data.enums.get_full(name)?;
1096 let enum_ty = r#enum.ty()?.as_enum()?;
1097 let choice_index = enum_ty.choices().iter().position(|v| v == choice)?;
1098 Some(crate::types::EnumChoiceCacheKey::new(
1099 enum_index,
1100 choice_index,
1101 ))
1102 }
1103
1104 /// Gets the parse diagnostics for the document.
1105 pub fn parse_diagnostics(&self) -> &[Diagnostic] {
1106 &self.data.parse_diagnostics
1107 }
1108
1109 /// Gets the analysis diagnostics for the document.
1110 pub fn analysis_diagnostics(&self) -> &Diagnostics {
1111 &self.data.analysis_diagnostics
1112 }
1113
1114 /// Gets all diagnostics for the document (both from parsing and analysis).
1115 pub fn diagnostics(&self) -> impl Iterator<Item = &Diagnostic> {
1116 self.data
1117 .parse_diagnostics
1118 .iter()
1119 .chain(self.data.analysis_diagnostics.diagnostics.iter())
1120 }
1121
1122 /// Sorts the diagnostics for the document.
1123 ///
1124 /// # Panics
1125 ///
1126 /// Panics if there is more than one reference to the document.
1127 pub fn sort_diagnostics(&mut self) -> Self {
1128 let data = &mut self.data;
1129 let inner = Arc::get_mut(data).expect("should only have one reference");
1130 inner.parse_diagnostics.sort();
1131 inner.analysis_diagnostics.sort();
1132 Self { data: data.clone() }
1133 }
1134
1135 /// Extends the analysis diagnostics for the document.
1136 ///
1137 /// # Panics
1138 ///
1139 /// Panics if there is more than one reference to the document.
1140 pub fn extend_diagnostics(&mut self, diagnostics: Diagnostics) -> Self {
1141 let data = &mut self.data;
1142 let inner = Arc::get_mut(data).expect("should only have one reference");
1143 inner.analysis_diagnostics.extend(diagnostics.diagnostics);
1144 Self { data: data.clone() }
1145 }
1146
1147 /// Finds a scope based on a position within the document.
1148 pub fn find_scope_by_position(&self, position: usize) -> Option<ScopeRef<'_>> {
1149 /// Finds a scope within a collection of sorted scopes by position.
1150 fn find_scope(scopes: &[Scope], position: usize) -> Option<ScopeRef<'_>> {
1151 let mut index = match scopes.binary_search_by_key(&position, |s| s.span.start()) {
1152 Ok(index) => index,
1153 Err(index) => {
1154 // This indicates that we couldn't find a match and the match would go _before_
1155 // the first scope, so there is no containing scope.
1156 if index == 0 {
1157 return None;
1158 }
1159
1160 index - 1
1161 }
1162 };
1163
1164 // We now have the index to start looking up the list of scopes
1165 // We walk up the list to try to find a span that contains the position
1166 loop {
1167 let scope = &scopes[index];
1168 if scope.span.contains(position) {
1169 return Some(ScopeRef::new(scopes, ScopeIndex(index)));
1170 }
1171
1172 if index == 0 {
1173 return None;
1174 }
1175
1176 index -= 1;
1177 }
1178 }
1179
1180 // Check to see if the position is contained in the workflow
1181 if let Some(workflow) = &self.data.workflow
1182 && workflow.scope().span().contains(position)
1183 {
1184 return find_scope(&workflow.scopes, position);
1185 }
1186
1187 // Search for a task that might contain the position
1188 let task = match self
1189 .data
1190 .tasks
1191 .binary_search_by_key(&position, |_, t| t.scope().span().start())
1192 {
1193 Ok(index) => &self.data.tasks[index],
1194 Err(index) => {
1195 // This indicates that we couldn't find a match and the match would go _before_
1196 // the first task, so there is no containing task.
1197 if index == 0 {
1198 return None;
1199 }
1200
1201 &self.data.tasks[index - 1]
1202 }
1203 };
1204
1205 if task.scope().span().contains(position) {
1206 return find_scope(&task.scopes, position);
1207 }
1208
1209 None
1210 }
1211
1212 /// Determines if the document, or any documents transitively imported by
1213 /// this document, has errors.
1214 ///
1215 /// Returns `true` if the document, or one of its transitive imports, has at
1216 /// least one error diagnostic.
1217 ///
1218 /// Returns `false` if the document, and all of its transitive imports, have
1219 /// no error diagnostics.
1220 pub fn has_errors(&self) -> bool {
1221 // Check this document for errors
1222 if self.diagnostics().any(|d| d.severity() == Severity::Error) {
1223 return true;
1224 }
1225
1226 // Check every imported document for errors
1227 for (_, ns) in self.namespaces() {
1228 if ns.document().has_errors() {
1229 return true;
1230 }
1231 }
1232
1233 false
1234 }
1235
1236 /// Visits the document with a pre-order traversal using the provided
1237 /// visitor to visit each element in the document.
1238 pub fn visit<V: crate::Visitor>(&self, diagnostics: &mut crate::Diagnostics, visitor: &mut V) {
1239 crate::visit(self, diagnostics, visitor)
1240 }
1241}