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
32mod 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 information about a name in a scope.
139#[derive(Debug, Clone)]
140pub struct Name {
141    /// The span of the name.
142    span: Span,
143    /// The type of the name.
144    ty: Type,
145}
146
147impl Name {
148    /// Gets the span of the name.
149    pub fn span(&self) -> Span {
150        self.span
151    }
152
153    /// Gets the type of the name.
154    pub fn ty(&self) -> &Type {
155        &self.ty
156    }
157}
158
159/// Represents an index of a scope in a collection of scopes.
160#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
161pub struct ScopeIndex(usize);
162
163/// Represents a scope in a WDL document.
164#[derive(Debug)]
165pub struct Scope {
166    /// The index of the parent scope.
167    ///
168    /// This is `None` for task and workflow scopes.
169    parent: Option<ScopeIndex>,
170    /// The span in the document where the names of the scope are visible.
171    span: Span,
172    /// The map of names in scope to their span and types.
173    names: IndexMap<String, Name>,
174}
175
176impl Scope {
177    /// Creates a new scope given the parent scope and span.
178    fn new(parent: Option<ScopeIndex>, span: Span) -> Self {
179        Self {
180            parent,
181            span,
182            names: Default::default(),
183        }
184    }
185
186    /// Inserts a name into the scope.
187    pub fn insert(&mut self, name: impl Into<String>, span: Span, ty: Type) {
188        self.names.insert(name.into(), Name { span, ty });
189    }
190}
191
192/// Represents a reference to a scope.
193#[derive(Debug, Clone, Copy)]
194pub struct ScopeRef<'a> {
195    /// The reference to the scopes collection.
196    scopes: &'a [Scope],
197    /// The index of the scope in the collection.
198    index: ScopeIndex,
199}
200
201impl<'a> ScopeRef<'a> {
202    /// Creates a new scope reference given the scope index.
203    fn new(scopes: &'a [Scope], index: ScopeIndex) -> Self {
204        Self { scopes, index }
205    }
206
207    /// Gets the span of the scope.
208    pub fn span(&self) -> Span {
209        self.scopes[self.index.0].span
210    }
211
212    /// Gets the parent scope.
213    ///
214    /// Returns `None` if there is no parent scope.
215    pub fn parent(&self) -> Option<Self> {
216        self.scopes[self.index.0].parent.map(|p| Self {
217            scopes: self.scopes,
218            index: p,
219        })
220    }
221
222    /// Gets all of the names available at this scope.
223    pub fn names(&self) -> impl Iterator<Item = (&str, &Name)> + use<'_> {
224        self.scopes[self.index.0]
225            .names
226            .iter()
227            .map(|(name, n)| (name.as_str(), n))
228    }
229
230    /// Gets a name local to this scope.
231    ///
232    /// Returns `None` if a name local to this scope was not found.
233    pub fn local(&self, name: &str) -> Option<&Name> {
234        self.scopes[self.index.0].names.get(name)
235    }
236
237    /// Lookups a name in the scope.
238    ///
239    /// Returns `None` if the name is not available in the scope.
240    pub fn lookup(&self, name: &str) -> Option<&Name> {
241        let mut current = Some(self.index);
242
243        while let Some(index) = current {
244            if let Some(name) = self.scopes[index.0].names.get(name) {
245                return Some(name);
246            }
247
248            current = self.scopes[index.0].parent;
249        }
250
251        None
252    }
253}
254
255/// Represents a mutable reference to a scope.
256#[derive(Debug)]
257struct ScopeRefMut<'a> {
258    /// The reference to all scopes.
259    scopes: &'a mut [Scope],
260    /// The index to the scope.
261    index: ScopeIndex,
262}
263
264impl<'a> ScopeRefMut<'a> {
265    /// Creates a new mutable scope reference given the scope index.
266    fn new(scopes: &'a mut [Scope], index: ScopeIndex) -> Self {
267        Self { scopes, index }
268    }
269
270    /// Lookups a name in the scope.
271    ///
272    /// Returns `None` if the name is not available in the scope.
273    pub fn lookup(&self, name: &str) -> Option<&Name> {
274        let mut current = Some(self.index);
275
276        while let Some(index) = current {
277            if let Some(name) = self.scopes[index.0].names.get(name) {
278                return Some(name);
279            }
280
281            current = self.scopes[index.0].parent;
282        }
283
284        None
285    }
286
287    /// Inserts a name into the scope.
288    pub fn insert(&mut self, name: impl Into<String>, span: Span, ty: Type) {
289        self.scopes[self.index.0]
290            .names
291            .insert(name.into(), Name { span, ty });
292    }
293
294    /// Converts the mutable scope reference to an immutable scope reference.
295    pub fn as_scope_ref(&'a self) -> ScopeRef<'a> {
296        ScopeRef {
297            scopes: self.scopes,
298            index: self.index,
299        }
300    }
301}
302
303/// A scope union takes the union of names within a number of given scopes and
304/// computes a set of common output names for a presumed parent scope. This is
305/// useful when calculating common elements from, for example, an `if`
306/// statement within a workflow.
307#[derive(Debug)]
308pub struct ScopeUnion<'a> {
309    /// The scope references to process.
310    scope_refs: Vec<(ScopeRef<'a>, bool)>,
311}
312
313impl<'a> ScopeUnion<'a> {
314    /// Creates a new scope union.
315    pub fn new() -> Self {
316        Self {
317            scope_refs: Vec::new(),
318        }
319    }
320
321    /// Adds a scope to the union.
322    pub fn insert(&mut self, scope_ref: ScopeRef<'a>, exhaustive: bool) {
323        self.scope_refs.push((scope_ref, exhaustive));
324    }
325
326    /// Resolves the scope union to names and types that should be accessible
327    /// from the parent scope.
328    ///
329    /// Returns an error if any issues are encountered during resolving.
330    pub fn resolve(self) -> Result<HashMap<String, Name>, Vec<Diagnostic>> {
331        let mut errors = Vec::new();
332        let mut ignored: HashSet<String> = HashSet::new();
333
334        // Gather all declaration names and reconcile types
335        let mut names: HashMap<String, Name> = HashMap::new();
336        for (scope_ref, _) in &self.scope_refs {
337            for (name, info) in scope_ref.names() {
338                if ignored.contains(name) {
339                    continue;
340                }
341
342                match names.entry(name.to_string()) {
343                    Entry::Vacant(entry) => {
344                        entry.insert(info.clone());
345                    }
346                    Entry::Occupied(mut entry) => {
347                        let Some(ty) = entry.get().ty.common_type(&info.ty) else {
348                            errors.push(no_common_type(
349                                &entry.get().ty,
350                                entry.get().span,
351                                &info.ty,
352                                info.span,
353                            ));
354                            names.remove(name);
355                            ignored.insert(name.to_string());
356                            continue;
357                        };
358
359                        entry.get_mut().ty = ty;
360                    }
361                }
362            }
363        }
364
365        // Mark types as optional if not present in all clauses
366        for (scope_ref, _) in &self.scope_refs {
367            for (name, info) in &mut names {
368                if ignored.contains(name) {
369                    continue;
370                }
371
372                // If this name is not in the current clause's scope, mark as optional
373                if scope_ref.local(name).is_none() {
374                    info.ty = info.ty.optional();
375                }
376            }
377        }
378
379        // If there's no `else` clause, mark all types as optional
380        let has_exhaustive = self.scope_refs.iter().any(|(_, exhaustive)| *exhaustive);
381        if !has_exhaustive {
382            for info in names.values_mut() {
383                info.ty = info.ty.optional();
384            }
385        }
386
387        if !errors.is_empty() {
388            return Err(errors);
389        }
390
391        Ok(names)
392    }
393}
394
395/// Represents a task or workflow input.
396#[derive(Debug, Clone, PartialEq, Eq)]
397pub struct Input {
398    /// The type of the input.
399    ty: Type,
400    /// Whether or not the input is required.
401    ///
402    /// A required input is one that has a non-optional type and no default
403    /// expression.
404    required: bool,
405}
406
407impl Input {
408    /// Gets the type of the input.
409    pub fn ty(&self) -> &Type {
410        &self.ty
411    }
412
413    /// Whether or not the input is required.
414    pub fn required(&self) -> bool {
415        self.required
416    }
417}
418
419/// Represents a task or workflow output.
420#[derive(Debug, Clone, PartialEq, Eq)]
421pub struct Output {
422    /// The type of the output.
423    ty: Type,
424    /// The span of the output name.
425    name_span: Span,
426}
427
428impl Output {
429    /// Creates a new output with the given type.
430    pub(crate) fn new(ty: Type, name_span: Span) -> Self {
431        Self { ty, name_span }
432    }
433
434    /// Gets the type of the output.
435    pub fn ty(&self) -> &Type {
436        &self.ty
437    }
438
439    /// Gets the span of output's name.
440    pub fn name_span(&self) -> Span {
441        self.name_span
442    }
443}
444
445/// Represents a task in a document.
446#[derive(Debug)]
447pub struct Task {
448    /// The span of the task name.
449    name_span: Span,
450    /// The name of the task.
451    name: String,
452    /// The scopes contained in the task.
453    ///
454    /// The first scope will always be the task's scope.
455    ///
456    /// The scopes will be in sorted order by span start.
457    scopes: Vec<Scope>,
458    /// The inputs of the task.
459    inputs: Arc<IndexMap<String, Input>>,
460    /// The outputs of the task.
461    outputs: Arc<IndexMap<String, Output>>,
462}
463
464impl Task {
465    /// Gets the name of the task.
466    pub fn name(&self) -> &str {
467        &self.name
468    }
469
470    /// Gets the span of the name.
471    pub fn name_span(&self) -> Span {
472        self.name_span
473    }
474
475    /// Gets the scope of the task.
476    pub fn scope(&self) -> ScopeRef<'_> {
477        ScopeRef::new(&self.scopes, ScopeIndex(0))
478    }
479
480    /// Gets the inputs of the task.
481    pub fn inputs(&self) -> &IndexMap<String, Input> {
482        &self.inputs
483    }
484
485    /// Gets the outputs of the task.
486    pub fn outputs(&self) -> &IndexMap<String, Output> {
487        &self.outputs
488    }
489}
490
491/// Represents a workflow in a document.
492#[derive(Debug)]
493pub struct Workflow {
494    /// The span of the workflow name.
495    name_span: Span,
496    /// The name of the workflow.
497    name: String,
498    /// The scopes contained in the workflow.
499    ///
500    /// The first scope will always be the workflow's scope.
501    ///
502    /// The scopes will be in sorted order by span start.
503    scopes: Vec<Scope>,
504    /// The inputs of the workflow.
505    inputs: Arc<IndexMap<String, Input>>,
506    /// The outputs of the workflow.
507    outputs: Arc<IndexMap<String, Output>>,
508    /// The calls made by the workflow.
509    calls: HashMap<String, CallType>,
510    /// Whether or not nested inputs are allowed for the workflow.
511    allows_nested_inputs: bool,
512}
513
514impl Workflow {
515    /// Gets the name of the workflow.
516    pub fn name(&self) -> &str {
517        &self.name
518    }
519
520    /// Gets the span of the name.
521    pub fn name_span(&self) -> Span {
522        self.name_span
523    }
524
525    /// Gets the scope of the workflow.
526    pub fn scope(&self) -> ScopeRef<'_> {
527        ScopeRef::new(&self.scopes, ScopeIndex(0))
528    }
529
530    /// Gets the inputs of the workflow.
531    pub fn inputs(&self) -> &IndexMap<String, Input> {
532        &self.inputs
533    }
534
535    /// Gets the outputs of the workflow.
536    pub fn outputs(&self) -> &IndexMap<String, Output> {
537        &self.outputs
538    }
539
540    /// Gets the calls made by the workflow.
541    pub fn calls(&self) -> &HashMap<String, CallType> {
542        &self.calls
543    }
544
545    /// Determines if the workflow allows nested inputs.
546    pub fn allows_nested_inputs(&self) -> bool {
547        self.allows_nested_inputs
548    }
549}
550
551/// Represents analysis data about a WDL document.
552#[derive(Debug)]
553struct DocumentData {
554    /// The configuration under which this document was analyzed.
555    config: Config,
556    /// The root CST node of the document.
557    ///
558    /// This is `None` when the document could not be parsed.
559    root: Option<GreenNode>,
560    /// The document identifier.
561    ///
562    /// The identifier changes every time the document is analyzed.
563    id: Arc<String>,
564    /// The URI of the analyzed document.
565    uri: Arc<Url>,
566    /// The version of the document.
567    version: Option<SupportedVersion>,
568    /// The namespaces in the document.
569    namespaces: IndexMap<String, Namespace>,
570    /// The tasks in the document.
571    tasks: IndexMap<String, Task>,
572    /// The singular workflow in the document.
573    workflow: Option<Workflow>,
574    /// The structs in the document.
575    structs: IndexMap<String, Struct>,
576    /// The diagnostics from parsing.
577    parse_diagnostics: Vec<Diagnostic>,
578    /// The diagnostics from analysis.
579    analysis_diagnostics: Vec<Diagnostic>,
580}
581
582impl DocumentData {
583    /// Constructs a new analysis document data.
584    fn new(
585        config: Config,
586        uri: Arc<Url>,
587        root: Option<GreenNode>,
588        version: Option<SupportedVersion>,
589        diagnostics: Vec<Diagnostic>,
590    ) -> Self {
591        Self {
592            config,
593            root,
594            id: Uuid::new_v4().to_string().into(),
595            uri,
596            version,
597            namespaces: Default::default(),
598            tasks: Default::default(),
599            workflow: Default::default(),
600            structs: Default::default(),
601            parse_diagnostics: diagnostics,
602            analysis_diagnostics: Default::default(),
603        }
604    }
605}
606
607/// Represents an analyzed WDL document.
608///
609/// This type is cheaply cloned.
610#[derive(Debug, Clone)]
611pub struct Document {
612    /// The document data for the document.
613    data: Arc<DocumentData>,
614}
615
616impl Document {
617    /// Creates a new default document from a URI.
618    pub(crate) fn default_from_uri(uri: Arc<Url>) -> Self {
619        Self {
620            data: Arc::new(DocumentData::new(
621                Default::default(),
622                uri,
623                None,
624                None,
625                Default::default(),
626            )),
627        }
628    }
629
630    /// Creates a new analyzed document from a document graph node.
631    pub(crate) fn from_graph_node(
632        config: &Config,
633        graph: &DocumentGraph,
634        index: NodeIndex,
635    ) -> Self {
636        let node = graph.get(index);
637
638        let (wdl_version, diagnostics) = match node.parse_state() {
639            ParseState::NotParsed => panic!("node should have been parsed"),
640            ParseState::Error(_) => return Self::default_from_uri(node.uri().clone()),
641            ParseState::Parsed {
642                wdl_version,
643                diagnostics,
644                ..
645            } => (wdl_version, diagnostics),
646        };
647
648        let root = node.root().expect("node should have been parsed");
649        let (config, wdl_version) = match (root.version_statement(), wdl_version) {
650            (Some(stmt), Some(wdl_version)) => (
651                config.with_diagnostics_config(
652                    config.diagnostics_config().excepted_for_node(stmt.inner()),
653                ),
654                *wdl_version,
655            ),
656            _ => {
657                // Don't process a document with a missing version statement or an unsupported
658                // version unless a fallback version is configured
659                return Self {
660                    data: Arc::new(DocumentData::new(
661                        config.clone(),
662                        node.uri().clone(),
663                        Some(root.inner().green().into()),
664                        None,
665                        diagnostics.to_vec(),
666                    )),
667                };
668            }
669        };
670
671        let mut data = DocumentData::new(
672            config.clone(),
673            node.uri().clone(),
674            Some(root.inner().green().into()),
675            Some(wdl_version),
676            diagnostics.to_vec(),
677        );
678        match root.ast_with_version_fallback(config.fallback_version()) {
679            Ast::Unsupported => {}
680            Ast::V1(ast) => v1::populate_document(&mut data, &config, graph, index, &ast),
681        }
682
683        // Check for unused imports
684        if let Some(severity) = config.diagnostics_config().unused_import {
685            let DocumentData {
686                namespaces,
687                analysis_diagnostics,
688                ..
689            } = &mut data;
690
691            analysis_diagnostics.extend(
692                namespaces
693                    .iter()
694                    .filter(|(_, ns)| !ns.used && !ns.excepted)
695                    .map(|(name, ns)| unused_import(name, ns.span()).with_severity(severity)),
696            );
697        }
698
699        Self {
700            data: Arc::new(data),
701        }
702    }
703
704    /// Gets the analysis configuration.
705    pub fn config(&self) -> &Config {
706        &self.data.config
707    }
708
709    /// Gets the root AST document node.
710    ///
711    /// # Panics
712    ///
713    /// Panics if the document was not parsed.
714    pub fn root(&self) -> wdl_ast::Document {
715        wdl_ast::Document::cast(SyntaxNode::new_root(
716            self.data.root.clone().expect("should have a root"),
717        ))
718        .expect("should cast")
719    }
720
721    /// Gets the identifier of the document.
722    ///
723    /// This value changes when a document is reanalyzed.
724    pub fn id(&self) -> &Arc<String> {
725        &self.data.id
726    }
727
728    /// Gets the URI of the document.
729    pub fn uri(&self) -> &Arc<Url> {
730        &self.data.uri
731    }
732
733    /// Gets the path to the document.
734    ///
735    /// If the scheme of the document's URI is not `file`, this will return the
736    /// URI as a string. Otherwise, this will attempt to return the path
737    /// relative to the current working directory, or the absolute path
738    /// failing that.
739    pub fn path(&self) -> Cow<'_, str> {
740        if let Ok(path) = self.data.uri.to_file_path() {
741            if let Some(path) = std::env::current_dir()
742                .ok()
743                .and_then(|cwd| path.strip_prefix(cwd).ok().and_then(Path::to_str))
744            {
745                return path.to_string().into();
746            }
747
748            if let Ok(path) = path.into_os_string().into_string() {
749                return path.into();
750            }
751        }
752
753        self.data.uri.as_str().into()
754    }
755
756    /// Gets the supported version of the document.
757    ///
758    /// Returns `None` if the document could not be parsed or contains an
759    /// unsupported version.
760    pub fn version(&self) -> Option<SupportedVersion> {
761        self.data.version
762    }
763
764    /// Gets the namespaces in the document.
765    pub fn namespaces(&self) -> impl Iterator<Item = (&str, &Namespace)> {
766        self.data.namespaces.iter().map(|(n, ns)| (n.as_str(), ns))
767    }
768
769    /// Gets a namespace in the document by name.
770    pub fn namespace(&self, name: &str) -> Option<&Namespace> {
771        self.data.namespaces.get(name)
772    }
773
774    /// Gets the tasks in the document.
775    pub fn tasks(&self) -> impl Iterator<Item = &Task> {
776        self.data.tasks.iter().map(|(_, t)| t)
777    }
778
779    /// Gets a task in the document by name.
780    pub fn task_by_name(&self, name: &str) -> Option<&Task> {
781        self.data.tasks.get(name)
782    }
783
784    /// Gets a workflow in the document.
785    ///
786    /// Returns `None` if the document did not contain a workflow.
787    pub fn workflow(&self) -> Option<&Workflow> {
788        self.data.workflow.as_ref()
789    }
790
791    /// Gets the structs in the document.
792    pub fn structs(&self) -> impl Iterator<Item = (&str, &Struct)> {
793        self.data.structs.iter().map(|(n, s)| (n.as_str(), s))
794    }
795
796    /// Gets a struct in the document by name.
797    pub fn struct_by_name(&self, name: &str) -> Option<&Struct> {
798        self.data.structs.get(name)
799    }
800
801    /// Gets the parse diagnostics for the document.
802    pub fn parse_diagnostics(&self) -> &[Diagnostic] {
803        &self.data.parse_diagnostics
804    }
805
806    /// Gets the analysis diagnostics for the document.
807    pub fn analysis_diagnostics(&self) -> &[Diagnostic] {
808        &self.data.analysis_diagnostics
809    }
810
811    /// Gets all diagnostics for the document (both from parsing and analysis).
812    pub fn diagnostics(&self) -> impl Iterator<Item = &Diagnostic> {
813        self.data
814            .parse_diagnostics
815            .iter()
816            .chain(self.data.analysis_diagnostics.iter())
817    }
818
819    /// Sorts the diagnostics for the document.
820    ///
821    /// # Panics
822    ///
823    /// Panics if there is more than one reference to the document.
824    pub fn sort_diagnostics(&mut self) -> Self {
825        let data = &mut self.data;
826        let inner = Arc::get_mut(data).expect("should only have one reference");
827        inner.parse_diagnostics.sort();
828        inner.analysis_diagnostics.sort();
829        Self { data: data.clone() }
830    }
831
832    /// Extends the analysis diagnostics for the document.
833    ///
834    /// # Panics
835    ///
836    /// Panics if there is more than one reference to the document.
837    pub fn extend_diagnostics(&mut self, diagnostics: Vec<Diagnostic>) -> Self {
838        let data = &mut self.data;
839        let inner = Arc::get_mut(data).expect("should only have one reference");
840        inner.analysis_diagnostics.extend(diagnostics);
841        Self { data: data.clone() }
842    }
843
844    /// Finds a scope based on a position within the document.
845    pub fn find_scope_by_position(&self, position: usize) -> Option<ScopeRef<'_>> {
846        /// Finds a scope within a collection of sorted scopes by position.
847        fn find_scope(scopes: &[Scope], position: usize) -> Option<ScopeRef<'_>> {
848            let mut index = match scopes.binary_search_by_key(&position, |s| s.span.start()) {
849                Ok(index) => index,
850                Err(index) => {
851                    // This indicates that we couldn't find a match and the match would go _before_
852                    // the first scope, so there is no containing scope.
853                    if index == 0 {
854                        return None;
855                    }
856
857                    index - 1
858                }
859            };
860
861            // We now have the index to start looking up the list of scopes
862            // We walk up the list to try to find a span that contains the position
863            loop {
864                let scope = &scopes[index];
865                if scope.span.contains(position) {
866                    return Some(ScopeRef::new(scopes, ScopeIndex(index)));
867                }
868
869                if index == 0 {
870                    return None;
871                }
872
873                index -= 1;
874            }
875        }
876
877        // Check to see if the position is contained in the workflow
878        if let Some(workflow) = &self.data.workflow
879            && workflow.scope().span().contains(position)
880        {
881            return find_scope(&workflow.scopes, position);
882        }
883
884        // Search for a task that might contain the position
885        let task = match self
886            .data
887            .tasks
888            .binary_search_by_key(&position, |_, t| t.scope().span().start())
889        {
890            Ok(index) => &self.data.tasks[index],
891            Err(index) => {
892                // This indicates that we couldn't find a match and the match would go _before_
893                // the first task, so there is no containing task.
894                if index == 0 {
895                    return None;
896                }
897
898                &self.data.tasks[index - 1]
899            }
900        };
901
902        if task.scope().span().contains(position) {
903            return find_scope(&task.scopes, position);
904        }
905
906        None
907    }
908
909    /// Determines if the document, or any documents transitively imported by
910    /// this document, has errors.
911    ///
912    /// Returns `true` if the document, or one of its transitive imports, has at
913    /// least one error diagnostic.
914    ///
915    /// Returns `false` if the document, and all of its transitive imports, have
916    /// no error diagnostics.
917    pub fn has_errors(&self) -> bool {
918        // Check this document for errors
919        if self.diagnostics().any(|d| d.severity() == Severity::Error) {
920            return true;
921        }
922
923        // Check every imported document for errors
924        for (_, ns) in self.namespaces() {
925            if ns.document.has_errors() {
926                return true;
927            }
928        }
929
930        false
931    }
932
933    /// Visits the document with a pre-order traversal using the provided
934    /// visitor to visit each element in the document.
935    pub fn visit<V: crate::Visitor>(&self, diagnostics: &mut crate::Diagnostics, visitor: &mut V) {
936        crate::visit(self, diagnostics, visitor)
937    }
938}