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