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