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