use std::collections::BTreeMap;
use std::path::Path;
use crate::diagnostics::{
DiagnosticCatalogEntry, LintDiagnostic, RototoRuleId, SemanticEntity, SourcePosition,
};
use crate::error::{Result, RototoError};
use crate::model::{CatalogLint, QualifierLint, VariableLint, WorkspaceDiff, WorkspaceLint};
mod builtins;
mod catalog_schema;
mod custom;
mod diff;
mod engine;
mod index;
pub(crate) mod input;
mod inspect;
mod output;
mod project;
mod references;
mod request_context;
mod runtime;
mod semantic_model;
mod source;
mod stages;
mod symbols;
mod syntax;
use index::*;
pub(crate) use input::{LintInput, OverlayDocument};
pub(crate) use inspect::inspect_snapshot;
use references::ReferenceIndex;
pub(crate) use request_context::RequestContextCompatibility;
pub(crate) use runtime::{
RuntimeCatalogQuery, RuntimeRule, RuntimeRuleSelection, RuntimeSelectedValue, RuntimeWorkspace,
compile_runtime_workspace, compile_runtime_workspace_from_snapshot,
};
pub use semantic_model::{
CatalogEntryModel, CatalogModel, DeclarationModel, LinterModel, LinterRuleModel,
ModelEntityRef, ModelField, ModelLocation, ModelReferenceVia, ModelValueField, PredicateModel,
QualifierModel, QualifierRequestContextModel, ReferenceModel, RequestContextEntryModel,
RequestContextModel, ResolveModel, RuleModel, ValueModel, VariableModel,
VariableRequestContextModel, WorkspaceSemanticModel,
};
pub(crate) use symbols::{
WorkspaceCompletionItem, WorkspaceCompletionItemKind, WorkspaceDefinition,
WorkspaceDocumentSymbol, WorkspaceDocumentSymbolKind, WorkspaceHover, WorkspaceReference,
};
const WORKSPACE_MANIFEST: &str = "rototo-workspace.toml";
pub async fn workspace_semantic_model(workspace_root: &Path) -> Result<WorkspaceSemanticModel> {
let snapshot = lint_workspace_snapshot(LintInput::new(workspace_root.to_path_buf())).await?;
Ok(snapshot.semantic_model())
}
pub async fn lint_workspace(workspace_root: &Path) -> Result<WorkspaceLint> {
lint_workspace_with_input(LintInput::new(workspace_root.to_path_buf())).await
}
pub async fn diff_workspaces(
before_root: &Path,
after_root: &Path,
context: Option<&serde_json::Value>,
) -> Result<WorkspaceDiff> {
diff::diff_workspaces(before_root, after_root, context).await
}
pub async fn lint_qualifier(workspace_root: &Path, id: &str) -> Result<QualifierLint> {
let lint = lint_workspace(workspace_root).await?;
let path = format!("qualifiers/{id}.toml");
if !lint.documents.iter().any(|document| document.path == path) {
return Err(RototoError::new(format!(
"qualifier not found: qualifier://{id}"
)));
}
Ok(QualifierLint {
root: lint.root,
id: id.to_owned(),
diagnostics: lint
.diagnostics
.into_iter()
.filter(|diagnostic| diagnostic_belongs_to_qualifier(diagnostic, id, &path))
.collect(),
})
}
pub async fn lint_variable(workspace_root: &Path, id: &str) -> Result<VariableLint> {
let lint = lint_workspace(workspace_root).await?;
let path = format!("variables/{id}.toml");
if !lint.documents.iter().any(|document| document.path == path) {
return Err(RototoError::new(format!(
"variable not found: variable://{id}"
)));
}
Ok(VariableLint {
root: lint.root,
id: id.to_owned(),
diagnostics: lint
.diagnostics
.into_iter()
.filter(|diagnostic| diagnostic_belongs_to_variable(diagnostic, id, &path))
.collect(),
})
}
pub async fn lint_catalog(workspace_root: &Path, id: &str) -> Result<CatalogLint> {
let lint = lint_workspace(workspace_root).await?;
let path = format!("catalogs/{id}.schema.json");
if !lint.documents.iter().any(|document| document.path == path) {
return Err(RototoError::new(format!(
"catalog not found: catalog://{id}"
)));
}
Ok(CatalogLint {
root: lint.root,
id: id.to_owned(),
diagnostics: lint
.diagnostics
.into_iter()
.filter(|diagnostic| diagnostic_belongs_to_catalog(diagnostic, id, &path))
.collect(),
})
}
fn diagnostic_belongs_to_qualifier(diagnostic: &LintDiagnostic, id: &str, path: &str) -> bool {
matches!(&diagnostic.target.entity, SemanticEntity::Qualifier { id: diagnostic_id } if diagnostic_id == id)
|| matches!(&diagnostic.target.entity, SemanticEntity::Predicate { qualifier, .. } if qualifier == id)
|| diagnostic.primary.path == path
}
fn diagnostic_belongs_to_variable(diagnostic: &LintDiagnostic, id: &str, path: &str) -> bool {
matches!(&diagnostic.target.entity, SemanticEntity::Variable { id: diagnostic_id } if diagnostic_id == id)
|| matches!(&diagnostic.target.entity, SemanticEntity::Value { variable, .. } if variable == id)
|| matches!(&diagnostic.target.entity, SemanticEntity::Rule { variable, .. } if variable == id)
|| diagnostic.primary.path == path
}
fn diagnostic_belongs_to_catalog(diagnostic: &LintDiagnostic, id: &str, path: &str) -> bool {
let entries_prefix = format!("catalogs/{id}-entries/");
matches!(&diagnostic.target.entity, SemanticEntity::Catalog { id: diagnostic_id } if diagnostic_id == id)
|| matches!(&diagnostic.target.entity, SemanticEntity::CatalogEntry { catalog, .. } if catalog == id)
|| diagnostic.primary.path == path
|| diagnostic.primary.path.starts_with(&entries_prefix)
}
pub(crate) async fn lint_workspace_with_input(input: LintInput) -> Result<WorkspaceLint> {
Ok(lint_workspace_snapshot(input).await?.lint)
}
pub(crate) async fn lint_workspace_snapshot(input: LintInput) -> Result<WorkspaceLintSnapshot> {
engine::lint_workspace_snapshot(input).await
}
pub(crate) struct WorkspaceLintSnapshot {
pub(crate) lint: WorkspaceLint,
index: SemanticIndex,
references: ReferenceIndex,
source_texts: BTreeMap<String, String>,
}
impl WorkspaceLintSnapshot {
pub(crate) fn diagnostic_catalog_entries(&self) -> Vec<DiagnosticCatalogEntry> {
let mut entries = RototoRuleId::iter()
.map(DiagnosticCatalogEntry::from_rototo)
.collect::<Vec<_>>();
entries.extend(
self.index
.custom_lints
.rules
.values()
.map(|rule| DiagnosticCatalogEntry::from_custom(&rule.definition)),
);
entries.sort_by(|left, right| left.rule.cmp(&right.rule));
entries
}
pub(crate) fn document_symbols(&self, path: &str) -> Vec<WorkspaceDocumentSymbol> {
symbols::document_symbols(&self.index, path)
}
pub(crate) fn completion_items(
&self,
path: &str,
position: SourcePosition,
) -> Vec<WorkspaceCompletionItem> {
symbols::completion_items(self, path, position)
}
pub(crate) fn hover(&self, path: &str, position: SourcePosition) -> Option<WorkspaceHover> {
symbols::hover(self, path, position)
}
pub(crate) fn definition(
&self,
path: &str,
position: SourcePosition,
) -> Option<WorkspaceDefinition> {
symbols::definition(self, path, position)
}
pub(crate) fn references(
&self,
path: &str,
position: SourcePosition,
include_declaration: bool,
) -> Vec<WorkspaceReference> {
symbols::references(self, path, position, include_declaration)
}
pub(crate) fn request_context_compatibility(&self) -> RequestContextCompatibility {
request_context::compatibility(self)
}
pub(crate) fn source_text(&self, path: &str) -> Option<&str> {
self.source_texts.get(path).map(String::as_str)
}
}