Skip to main content

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}