use std::borrow::Cow;
use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::hash_map::Entry;
use std::path::Path;
use std::sync::Arc;
use arrayvec::ArrayString;
use indexmap::IndexMap;
use indexmap::IndexSet;
use petgraph::graph::NodeIndex;
use rowan::GreenNode;
use rowan::TextRange;
use rowan::TextSize;
use url::Url;
use uuid::Uuid;
use wdl_ast::Ast;
use wdl_ast::AstNode;
use wdl_ast::AstToken;
use wdl_ast::Diagnostic;
use wdl_ast::Severity;
use wdl_ast::Span;
use wdl_ast::SupportedVersion;
use wdl_ast::SyntaxNode;
use crate::AnalysisCache;
use crate::Diagnostics;
use crate::EnumRef;
use crate::StructRef;
use crate::TaskRef;
use crate::WorkflowRef;
use crate::config::Config;
use crate::diagnostics::Context;
use crate::diagnostics::no_common_type;
use crate::graph::DocumentGraph;
use crate::graph::ParseState;
use crate::types::CallType;
use crate::types::EnumChoiceCacheKey;
use crate::types::Optional;
use crate::types::Type;
pub mod cache;
pub mod v1;
pub const TASK_VAR_NAME: &str = "task";
#[derive(Debug, Clone, PartialEq)]
pub struct Namespace {
name: String,
pub(crate) span: Span,
source: Arc<Url>,
document: Document,
pub(crate) used: bool,
pub(in crate::document) imported_structs: IndexMap<String, ImportedStruct>,
pub(in crate::document) imported_enums: IndexMap<String, ImportedEnum>,
}
impl Namespace {
pub fn name(&self) -> &str {
&self.name
}
pub fn span(&self) -> Span {
self.span
}
pub fn source(&self) -> Arc<Url> {
self.source.clone()
}
pub fn document(&self) -> &Document {
&self.document
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Struct {
name: String,
pub(in crate::document) name_span: Span,
offset: usize,
node: rowan::GreenNode,
ty: Option<Type>,
}
impl Struct {
pub fn name(&self) -> &str {
&self.name
}
pub fn name_span(&self) -> Span {
self.name_span
}
pub fn offset(&self) -> usize {
self.offset
}
pub fn node(&self) -> &rowan::GreenNode {
&self.node
}
pub fn definition(&self) -> wdl_ast::v1::StructDefinition {
wdl_ast::v1::StructDefinition::cast(wdl_ast::SyntaxNode::new_root(self.node.clone()))
.expect("stored node should be a valid struct definition")
}
pub fn ty(&self) -> Option<&Type> {
self.ty.as_ref()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Enum {
name: String,
pub(in crate::document) name_span: Span,
offset: usize,
node: rowan::GreenNode,
ty: Option<Type>,
}
impl Enum {
pub fn name(&self) -> &str {
&self.name
}
pub fn name_span(&self) -> Span {
self.name_span
}
pub fn offset(&self) -> usize {
self.offset
}
pub fn node(&self) -> &rowan::GreenNode {
&self.node
}
pub fn definition(&self) -> wdl_ast::v1::EnumDefinition {
wdl_ast::v1::EnumDefinition::cast(wdl_ast::SyntaxNode::new_root(self.node.clone()))
.expect("stored node should be a valid enum definition")
}
pub fn ty(&self) -> Option<&Type> {
self.ty.as_ref()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Name {
pub(in crate::document) span: Span,
ty: Type,
}
impl Name {
pub fn span(&self) -> Span {
self.span
}
pub fn ty(&self) -> &Type {
&self.ty
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ScopeIndex(usize);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Scope {
parent: Option<ScopeIndex>,
pub(in crate::document) span: Span,
pub(in crate::document) names: IndexMap<String, Name>,
}
impl Scope {
fn new(parent: Option<ScopeIndex>, span: Span) -> Self {
Self {
parent,
span,
names: Default::default(),
}
}
pub fn insert(&mut self, name: impl Into<String>, span: Span, ty: Type) {
self.names.insert(name.into(), Name { span, ty });
}
}
#[derive(Debug, Clone, Copy)]
pub struct ScopeRef<'a> {
scopes: &'a [Scope],
index: ScopeIndex,
}
impl<'a> ScopeRef<'a> {
fn new(scopes: &'a [Scope], index: ScopeIndex) -> Self {
Self { scopes, index }
}
pub fn span(&self) -> Span {
self.scopes[self.index.0].span
}
pub fn parent(&self) -> Option<Self> {
self.scopes[self.index.0].parent.map(|p| Self {
scopes: self.scopes,
index: p,
})
}
pub fn names(&self) -> impl Iterator<Item = (&str, &Name)> + use<'_> {
self.scopes[self.index.0]
.names
.iter()
.map(|(name, n)| (name.as_str(), n))
}
pub fn local(&self, name: &str) -> Option<&Name> {
self.scopes[self.index.0].names.get(name)
}
pub fn lookup(&self, name: &str) -> Option<&Name> {
let mut current = Some(self.index);
while let Some(index) = current {
if let Some(name) = self.scopes[index.0].names.get(name) {
return Some(name);
}
current = self.scopes[index.0].parent;
}
None
}
}
#[derive(Debug)]
struct ScopeRefMut<'a> {
scopes: &'a mut [Scope],
index: ScopeIndex,
}
impl<'a> ScopeRefMut<'a> {
fn new(scopes: &'a mut [Scope], index: ScopeIndex) -> Self {
Self { scopes, index }
}
pub fn lookup(&self, name: &str) -> Option<&Name> {
let mut current = Some(self.index);
while let Some(index) = current {
if let Some(name) = self.scopes[index.0].names.get(name) {
return Some(name);
}
current = self.scopes[index.0].parent;
}
None
}
pub fn insert(&mut self, name: impl Into<String>, span: Span, ty: Type) {
self.scopes[self.index.0]
.names
.insert(name.into(), Name { span, ty });
}
pub fn as_scope_ref(&'a self) -> ScopeRef<'a> {
ScopeRef {
scopes: self.scopes,
index: self.index,
}
}
}
#[derive(Debug)]
pub struct ScopeUnion<'a> {
scope_refs: Vec<(ScopeRef<'a>, bool)>,
}
impl<'a> ScopeUnion<'a> {
pub fn new() -> Self {
Self {
scope_refs: Vec::new(),
}
}
pub fn insert(&mut self, scope_ref: ScopeRef<'a>, exhaustive: bool) {
self.scope_refs.push((scope_ref, exhaustive));
}
pub fn resolve(self) -> Result<HashMap<String, Name>, Vec<Diagnostic>> {
let mut errors = Vec::new();
let mut ignored: HashSet<String> = HashSet::new();
let mut names: HashMap<String, Name> = HashMap::new();
for (scope_ref, _) in &self.scope_refs {
for (name, info) in scope_ref.names() {
if ignored.contains(name) {
continue;
}
match names.entry(name.to_string()) {
Entry::Vacant(entry) => {
entry.insert(info.clone());
}
Entry::Occupied(mut entry) => {
let Some(ty) = entry.get().ty.common_type(&info.ty) else {
errors.push(no_common_type(
&entry.get().ty,
entry.get().span,
&info.ty,
info.span,
));
names.remove(name);
ignored.insert(name.to_string());
continue;
};
entry.get_mut().ty = ty;
}
}
}
}
for (scope_ref, _) in &self.scope_refs {
for (name, info) in &mut names {
if ignored.contains(name) {
continue;
}
if scope_ref.local(name).is_none() {
info.ty = info.ty.optional();
}
}
}
let has_exhaustive = self.scope_refs.iter().any(|(_, exhaustive)| *exhaustive);
if !has_exhaustive {
for info in names.values_mut() {
info.ty = info.ty.optional();
}
}
if !errors.is_empty() {
return Err(errors);
}
Ok(names)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Input {
ty: Type,
required: bool,
}
impl Input {
pub fn ty(&self) -> &Type {
&self.ty
}
pub fn required(&self) -> bool {
self.required
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Output {
ty: Type,
pub(in crate::document) name_span: Span,
}
impl Output {
pub(crate) fn new(ty: Type, name_span: Span) -> Self {
Self { ty, name_span }
}
pub fn ty(&self) -> &Type {
&self.ty
}
pub fn name_span(&self) -> Span {
self.name_span
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Task {
pub(in crate::document) name_span: Span,
pub(in crate::document) name: String,
pub(in crate::document) span: Span,
pub(in crate::document) scopes: Vec<Scope>,
pub(in crate::document) inputs: Arc<IndexMap<String, Input>>,
pub(in crate::document) outputs: Arc<IndexMap<String, Output>>,
}
impl Task {
pub fn name(&self) -> &str {
&self.name
}
pub fn name_span(&self) -> Span {
self.name_span
}
pub fn span(&self) -> Span {
self.span
}
pub fn scope(&self) -> ScopeRef<'_> {
ScopeRef::new(&self.scopes, ScopeIndex(0))
}
pub fn inputs(&self) -> &IndexMap<String, Input> {
&self.inputs
}
pub fn outputs(&self) -> &IndexMap<String, Output> {
&self.outputs
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Workflow {
pub(in crate::document) name_span: Span,
pub(in crate::document) name: String,
pub(in crate::document) span: Span,
pub(in crate::document) scopes: Vec<Scope>,
pub(in crate::document) inputs: Arc<IndexMap<String, Input>>,
pub(in crate::document) outputs: Arc<IndexMap<String, Output>>,
pub(in crate::document) calls: HashMap<String, CallType>,
pub(in crate::document) allows_nested_inputs: bool,
}
impl Workflow {
pub fn name(&self) -> &str {
&self.name
}
pub fn name_span(&self) -> Span {
self.name_span
}
pub fn span(&self) -> Span {
self.span
}
pub fn scope(&self) -> ScopeRef<'_> {
ScopeRef::new(&self.scopes, ScopeIndex(0))
}
pub fn inputs(&self) -> &IndexMap<String, Input> {
&self.inputs
}
pub fn outputs(&self) -> &IndexMap<String, Output> {
&self.outputs
}
pub fn calls(&self) -> &HashMap<String, CallType> {
&self.calls
}
pub fn allows_nested_inputs(&self) -> bool {
self.allows_nested_inputs
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ImportedStruct {
pub local_name: String,
offset: usize,
node: rowan::GreenNode,
pub span: Span,
pub document: Document,
ty: Option<Type>,
}
impl ImportedStruct {
pub fn node(&self) -> &rowan::GreenNode {
&self.node
}
pub fn offset(&self) -> usize {
self.offset
}
pub fn source(&self) -> Arc<Url> {
self.document.uri()
}
pub fn definition(&self) -> wdl_ast::v1::StructDefinition {
wdl_ast::v1::StructDefinition::cast(wdl_ast::SyntaxNode::new_root(self.node.clone()))
.expect("stored node should be a valid struct definition")
}
pub fn ty(&self) -> Option<&Type> {
self.ty.as_ref()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ImportedEnum {
pub local_name: String,
offset: usize,
node: rowan::GreenNode,
pub span: Span,
pub document: Document,
ty: Option<Type>,
}
impl ImportedEnum {
pub fn node(&self) -> &rowan::GreenNode {
&self.node
}
pub fn offset(&self) -> usize {
self.offset
}
pub fn source(&self) -> Arc<Url> {
self.document.uri()
}
pub fn definition(&self) -> wdl_ast::v1::EnumDefinition {
wdl_ast::v1::EnumDefinition::cast(wdl_ast::SyntaxNode::new_root(self.node.clone()))
.expect("stored node should be a valid enum definition")
}
pub fn ty(&self) -> Option<&Type> {
self.ty.as_ref()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ImportedTask {
pub local_name: String,
pub name: String,
pub span: Span,
pub document: Document,
pub inputs: Arc<IndexMap<String, Input>>,
pub outputs: Arc<IndexMap<String, Output>>,
}
impl ImportedTask {
pub fn name(&self) -> &str {
&self.name
}
pub fn document(&self) -> &Document {
&self.document
}
pub(crate) fn source(&self) -> Arc<Url> {
self.document.uri()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ImportedWorkflow {
pub local_name: String,
pub name: String,
pub span: Span,
pub document: Document,
pub inputs: Arc<IndexMap<String, Input>>,
pub outputs: Arc<IndexMap<String, Output>>,
}
impl ImportedWorkflow {
pub fn name(&self) -> &str {
&self.name
}
pub fn document(&self) -> &Document {
&self.document
}
pub(crate) fn source(&self) -> Arc<Url> {
self.document.uri()
}
}
#[derive(Copy, Clone, Debug)]
pub enum Callable<'a> {
Workflow(WorkflowRef<'a>),
Task(TaskRef<'a>),
}
impl Callable<'_> {
pub fn name(&self) -> &str {
match self {
Callable::Workflow(w) => w.name(),
Callable::Task(t) => t.name(),
}
}
pub fn name_span(&self) -> Span {
match self {
Callable::Workflow(w) => w.name_span(),
Callable::Task(t) => t.name_span(),
}
}
pub fn is_workflow(&self) -> bool {
matches!(self, Callable::Workflow(_))
}
pub fn is_task(&self) -> bool {
matches!(self, Callable::Task(_))
}
pub fn inputs(&self) -> Arc<IndexMap<String, Input>> {
match self {
Callable::Workflow(w) => w.inputs(),
Callable::Task(t) => t.inputs(),
}
}
pub fn outputs(&self) -> Arc<IndexMap<String, Output>> {
match self {
Callable::Workflow(w) => w.outputs(),
Callable::Task(t) => t.outputs(),
}
}
}
#[derive(Debug)]
pub(crate) struct DocumentData {
config: Config,
root: Option<GreenNode>,
id: Arc<String>,
uri: Arc<Url>,
version: Option<SupportedVersion>,
failed_imports: IndexMap<String, Span>,
cache: Arc<AnalysisCache>,
failed_wildcard_import: bool,
failed_selected_imports: IndexSet<String>,
parse_diagnostics: Vec<Diagnostic>,
pub(crate) analysis_diagnostics: Diagnostics,
}
impl PartialEq for DocumentData {
fn eq(&self, other: &Self) -> bool {
let Self {
config,
root,
id: _,
uri,
version,
failed_imports,
cache,
failed_wildcard_import,
failed_selected_imports,
parse_diagnostics,
analysis_diagnostics,
} = self;
config == &other.config
&& root == &other.root
&& uri == &other.uri
&& version == &other.version
&& failed_imports == &other.failed_imports
&& cache == &other.cache
&& failed_wildcard_import == &other.failed_wildcard_import
&& failed_selected_imports == &other.failed_selected_imports
&& parse_diagnostics == &other.parse_diagnostics
&& analysis_diagnostics == &other.analysis_diagnostics
}
}
impl DocumentData {
fn new(
config: Config,
uri: Arc<Url>,
root: Option<GreenNode>,
version: Option<SupportedVersion>,
parse_diagnostics: Vec<Diagnostic>,
) -> Self {
Self {
config,
root,
id: Uuid::new_v4().to_string().into(),
uri,
version,
failed_imports: Default::default(),
cache: Default::default(), failed_wildcard_import: false,
failed_selected_imports: Default::default(),
parse_diagnostics,
analysis_diagnostics: Default::default(),
}
}
fn context(&self, cache: &AnalysisCache, name: &str) -> Option<Context> {
if let Some((_hash, ns)) = cache.namespace_by_name(name) {
Some(Context::Namespace(ns.span))
} else if let Some(span) = self.failed_imports.get(name) {
Some(Context::Namespace(*span))
} else if let Some((_idx, _hash, task)) = cache.local_task_by_name(name) {
Some(Context::Task(task.name_span()))
} else if let Some(wf) = cache.workflow().filter(|w| w.name() == name) {
Some(Context::Workflow(wf.name_span()))
} else if let Some((_idx, _hash, s)) = cache.local_struct_by_name(name) {
Some(Context::Struct(s.name_span()))
} else {
cache
.local_enum_by_name(name)
.map(|(_idx, _hash, e)| Context::Enum(e.name_span()))
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Document {
data: Arc<DocumentData>,
}
impl Document {
#[cfg(test)]
pub(crate) fn data(&self) -> &Arc<DocumentData> {
&self.data
}
}
impl Document {
pub(crate) fn default_from_uri(uri: Arc<Url>) -> Self {
Self {
data: Arc::new(DocumentData::new(
Default::default(),
uri,
None,
None,
Default::default(),
)),
}
}
pub(crate) fn from_graph_node(
config: &Config,
graph: &DocumentGraph,
index: NodeIndex,
existing_cache: Option<Arc<AnalysisCache>>,
) -> Self {
let node = graph.get(index);
let (wdl_version, parse_diagnostics, edits) = match node.parse_state() {
ParseState::NotParsed => panic!("node should have been parsed"),
ParseState::Error(_) => {
return Self::default_from_uri(node.uri().clone());
}
ParseState::Parsed {
wdl_version,
diagnostics,
edits,
..
} => (*wdl_version, diagnostics.clone(), edits.clone()),
};
let root = node.root().expect("node should have been parsed");
let config = if let Some(stmt) = root.version_statement() {
config.with_diagnostics_config(
config.diagnostics_config().excepted_for_node(stmt.inner()),
)
} else {
config.clone()
};
let mut data = DocumentData::new(
config.clone(),
node.uri().clone(),
Some(root.inner().green().to_owned()),
wdl_version,
parse_diagnostics,
);
let _ = node;
match root.ast_with_version_fallback(config.fallback_version()) {
Ast::Unsupported => {
}
Ast::V1(ast) => v1::populate_document(
&mut data,
existing_cache,
&config,
graph,
index,
&ast,
&edits,
),
};
Self {
data: Arc::new(data),
}
}
pub fn config(&self) -> &Config {
&self.data.config
}
pub fn root(&self) -> wdl_ast::Document {
wdl_ast::Document::cast(SyntaxNode::new_root(
self.data.root.clone().expect("should have a root"),
))
.expect("should cast")
}
pub fn id(&self) -> &Arc<String> {
&self.data.id
}
pub fn uri(&self) -> Arc<Url> {
self.data.uri.clone()
}
pub fn path(&self) -> Cow<'_, str> {
if let Ok(path) = self.data.uri.to_file_path() {
if let Some(path) = std::env::current_dir()
.ok()
.and_then(|cwd| path.strip_prefix(cwd).ok().and_then(Path::to_str))
{
return path.to_string().into();
}
if let Ok(path) = path.into_os_string().into_string() {
return path.into();
}
}
self.data.uri.as_str().into()
}
pub fn hash_span(&self, span: Span) -> Option<ArrayString<64>> {
let text = self.root().inner().text();
let text_len = usize::from(text.len());
if span.end() > text_len {
return None;
}
let range = TextRange::new(
TextSize::new(span.start() as u32),
TextSize::new(span.end() as u32),
);
let slice = text.slice(range);
let mut hasher = blake3::Hasher::new();
slice.for_each_chunk(|chunk| {
hasher.update(chunk.as_bytes());
});
Some(hasher.finalize().to_hex())
}
pub fn version(&self) -> Option<SupportedVersion> {
self.data.version
}
pub(crate) fn cache(&self) -> Arc<AnalysisCache> {
self.data.cache.clone()
}
pub fn namespaces(&self) -> impl Iterator<Item = &Namespace> {
self.data.cache.namespaces().map(|(_, ns)| ns)
}
pub fn namespace(&self, name: &str) -> Option<&Namespace> {
self.data.cache.namespace_by_name(name).map(|(_, ns)| ns)
}
pub fn tasks(&self) -> impl Iterator<Item = TaskRef<'_>> {
self.data.cache.tasks()
}
pub(crate) fn local_tasks(&self) -> impl Iterator<Item = &Task> {
self.data.cache.local_tasks().map(|(_, _, task)| task)
}
pub fn local_task_by_name(&self, name: &str) -> Option<&Task> {
self.data
.cache
.local_task_by_name(name)
.map(|(_idx, _hash, task)| task)
}
pub fn task_by_name(&self, name: &str) -> Option<TaskRef<'_>> {
self.data.cache.task_by_name(name).map(|(_hash, task)| task)
}
pub fn imported_task_by_name(&self, name: &str) -> Option<&ImportedTask> {
self.data.cache.imported_task_by_name(name).map(|(_, t)| t)
}
pub fn workflow(&self) -> Option<&Workflow> {
self.data.cache.workflow()
}
pub fn imported_workflow_by_name(&self, name: &str) -> Option<&ImportedWorkflow> {
self.data
.cache
.imported_workflow_by_name(name)
.map(|(_, w)| w)
}
pub fn workflow_by_name(&self, name: &str) -> Option<WorkflowRef<'_>> {
self.data.cache.workflow_by_name(name).map(|(_, w)| w)
}
pub fn callable_by_name(&self, name: &str) -> Option<Callable<'_>> {
if let Some(workflow) = self.workflow_by_name(name) {
return Some(Callable::Workflow(workflow));
}
if let Some(task) = self.task_by_name(name) {
return Some(Callable::Task(task));
}
None
}
pub fn callables(&self) -> impl Iterator<Item = Callable<'_>> {
self.local_callables()
.chain(
self.data
.cache
.imported_workflows()
.map(|(_hash, w)| Callable::Workflow(WorkflowRef::Imported(w))),
)
.chain(
self.data
.cache
.imported_tasks()
.map(|(_hash, t)| Callable::Task(TaskRef::Imported(t))),
)
}
pub fn local_callable_by_name(&self, name: &str) -> Option<Callable<'_>> {
if let Some(workflow) = self.workflow()
&& workflow.name == name
{
return Some(Callable::Workflow(WorkflowRef::Local(workflow)));
}
if let Some(task) = self.local_task_by_name(name) {
return Some(Callable::Task(TaskRef::Local(task)));
}
None
}
pub fn local_callables(&self) -> impl Iterator<Item = Callable<'_>> {
self.workflow()
.map(WorkflowRef::Local)
.map(Callable::Workflow)
.into_iter()
.chain(self.local_tasks().map(TaskRef::Local).map(Callable::Task))
}
pub fn structs(&self) -> impl Iterator<Item = StructRef<'_>> {
self.data.cache.structs()
}
pub fn local_struct_by_name(&self, name: &str) -> Option<&Struct> {
self.data
.cache
.local_struct_by_name(name)
.map(|(_idx, _hash, s)| s)
}
pub fn imported_struct_by_name(&self, name: &str) -> Option<&ImportedStruct> {
self.data
.cache
.imported_struct_by_name(name)
.map(|(_hash, s)| s)
}
pub fn struct_by_name(&self, name: &str) -> Option<StructRef<'_>> {
self.data.cache.struct_by_name(name).map(|(_hash, s)| s)
}
pub fn local_enums(&self) -> impl Iterator<Item = &Enum> {
self.data.cache.local_enums().map(|(_idx, _hash, e)| e)
}
pub fn local_enum_by_name(&self, name: &str) -> Option<&Enum> {
self.data
.cache
.local_enum_by_name(name)
.map(|(_idx, _hash, e)| e)
}
pub fn enums(&self) -> impl Iterator<Item = EnumRef<'_>> {
self.data.cache.enums()
}
pub fn imported_enum_by_name(&self, name: &str) -> Option<&ImportedEnum> {
self.data
.cache
.imported_enum_by_name(name)
.map(|(_hash, e)| e)
}
pub fn enum_by_name(&self, name: &str) -> Option<EnumRef<'_>> {
self.data.cache.enum_by_name(name).map(|(_hash, e)| e)
}
pub fn get_custom_type(&self, name: &str) -> Option<&Type> {
if let Some(s) = self.struct_by_name(name) {
return s.ty();
}
if let Some(e) = self.enum_by_name(name) {
return e.ty();
}
None
}
pub fn get_choice_cache_key(&self, name: &str, choice: &str) -> Option<EnumChoiceCacheKey> {
let (source_uri, enum_index, r#enum) =
if let Some((enum_index, _, r#enum)) = self.data.cache.local_enum_by_name(name) {
(self.data.uri.clone(), enum_index, r#enum)
} else {
let (_, imported) = self.data.cache.imported_enum_by_name(name)?;
let (enum_index, _, r#enum) = imported
.document
.data
.cache
.local_enum_by_name(imported.definition().name().text())?;
(imported.document.uri(), enum_index, r#enum)
};
let enum_ty = r#enum.ty()?.as_enum()?;
let choice_index = enum_ty.choices().iter().position(|v| v == choice)?;
Some(EnumChoiceCacheKey::new(
source_uri,
enum_index,
choice_index,
))
}
pub fn parse_diagnostics(&self) -> &[Diagnostic] {
&self.data.parse_diagnostics
}
pub fn analysis_diagnostics(&self) -> &Diagnostics {
&self.data.analysis_diagnostics
}
pub fn diagnostics(&self) -> impl Iterator<Item = &Diagnostic> {
self.data
.parse_diagnostics
.iter()
.chain(self.data.analysis_diagnostics.diagnostics.iter())
}
pub fn sort_diagnostics(&mut self) -> Self {
let data = &mut self.data;
let inner = Arc::get_mut(data).expect("should only have one reference");
inner.parse_diagnostics.sort();
inner.analysis_diagnostics.sort();
Self { data: data.clone() }
}
pub fn extend_diagnostics(&mut self, diagnostics: Diagnostics) -> Self {
let data = &mut self.data;
let inner = Arc::get_mut(data).expect("should only have one reference");
inner.analysis_diagnostics.extend(diagnostics.diagnostics);
Self { data: data.clone() }
}
pub fn find_scope_by_position(&self, position: usize) -> Option<ScopeRef<'_>> {
fn find_scope(scopes: &[Scope], position: usize) -> Option<ScopeRef<'_>> {
let mut index = match scopes.binary_search_by_key(&position, |s| s.span.start()) {
Ok(index) => index,
Err(index) => {
if index == 0 {
return None;
}
index - 1
}
};
loop {
let scope = &scopes[index];
if scope.span.contains(position) {
return Some(ScopeRef::new(scopes, ScopeIndex(index)));
}
if index == 0 {
return None;
}
index -= 1;
}
}
if let Some(workflow) = self.data.cache.workflow()
&& workflow.scope().span().contains(position)
{
return find_scope(&workflow.scopes, position);
}
let task = self
.data
.cache
.local_tasks()
.filter_map(|(_idx, _hash, t)| {
if t.scope().span().start() <= position {
Some(t)
} else {
None
}
})
.max_by_key(|t| t.scope().span().start())?;
if task.scope().span().contains(position) {
return find_scope(&task.scopes, position);
}
None
}
pub fn has_errors(&self) -> bool {
if self.diagnostics().any(|d| d.severity() == Severity::Error) {
return true;
}
for ns in self.namespaces() {
if ns.document().has_errors() {
return true;
}
}
false
}
pub fn visit<V: crate::Visitor>(&self, diagnostics: &mut crate::Diagnostics, visitor: &mut V) {
crate::visit(self, diagnostics, visitor)
}
}