use crate::error::Result;
use crate::snapshot::Diagnostic;
use std::fmt::{Display, Formatter};
use weavatrix_graph::{EdgeKind, NodeKind, SourceSpan};
mod contract;
mod graphql;
mod json;
mod protobuf;
#[cfg(feature = "lang-rust")]
mod rust;
#[cfg(feature = "lang-rust")]
mod rust_endpoint;
pub mod tokenized;
mod yaml;
pub(crate) use contract::file_facts_have_transport_evidence;
#[cfg(test)]
pub(crate) use contract::may_contain_transport_marker;
#[cfg(feature = "lang-rust")]
pub use rust::RustAdapter;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum Language {
Rust,
Go,
C,
Cpp,
Bash,
Sql,
Kubernetes,
JavaScript,
TypeScript,
Graphql,
Protobuf,
Json,
Python,
Java,
CSharp,
Custom(String),
}
impl Language {
#[must_use]
pub fn as_str(&self) -> &str {
match self {
Self::Rust => "rust",
Self::Go => "go",
Self::C => "c",
Self::Cpp => "cpp",
Self::Bash => "bash",
Self::Sql => "sql",
Self::Kubernetes => "kubernetes",
Self::JavaScript => "javascript",
Self::TypeScript => "typescript",
Self::Graphql => "graphql",
Self::Protobuf => "protobuf",
Self::Json => "json",
Self::Python => "python",
Self::Java => "java",
Self::CSharp => "csharp",
Self::Custom(value) => value,
}
}
}
impl Display for Language {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Debug)]
pub struct SourceFile<'a> {
pub path: &'a str,
pub text: &'a str,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SymbolFact {
pub name: String,
pub kind: NodeKind,
pub span: SourceSpan,
pub test_only: bool,
pub owner: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SymbolLocator {
pub name: String,
pub kind: NodeKind,
pub span: SourceSpan,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReferenceFact {
pub name: String,
pub kind: EdgeKind,
pub receiver: Option<String>,
pub qualified: bool,
pub span: SourceSpan,
pub owner: Option<SymbolLocator>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ImportBindingFact {
pub imported: String,
pub local: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ImportFact {
pub target: String,
pub span: SourceSpan,
pub type_only: bool,
pub bindings: Vec<ImportBindingFact>,
}
impl ImportFact {
#[must_use]
pub fn new(target: String, span: SourceSpan) -> Self {
Self {
target,
span,
type_only: false,
bindings: Vec::new(),
}
}
#[must_use]
pub fn type_only(target: String, span: SourceSpan) -> Self {
Self {
target,
span,
type_only: true,
bindings: Vec::new(),
}
}
#[must_use]
pub fn with_bindings(mut self, bindings: Vec<ImportBindingFact>) -> Self {
self.bindings = bindings;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MountFact {
pub prefix: String,
pub target: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DomainFact {
pub name: String,
pub kind: NodeKind,
pub relation: EdgeKind,
pub span: SourceSpan,
pub owner: Option<SymbolLocator>,
}
#[derive(Debug, Default)]
pub struct FileFacts {
pub symbols: Vec<SymbolFact>,
pub references: Vec<ReferenceFact>,
pub imports: Vec<ImportFact>,
pub domains: Vec<DomainFact>,
pub diagnostics: Vec<Diagnostic>,
pub mounts: Vec<MountFact>,
pub reexports: Vec<ImportFact>,
}
pub trait LanguageAdapter: Send + Sync {
fn language(&self) -> Language;
fn extensions(&self) -> &'static [&'static str];
fn extractor(&self) -> &'static str;
fn parse(&self, source: SourceFile<'_>) -> Result<FileFacts>;
}
pub struct LanguageRegistry {
adapters: Vec<Box<dyn LanguageAdapter>>,
}
impl Default for LanguageRegistry {
fn default() -> Self {
#[cfg(feature = "lang-rust")]
let mut adapters: Vec<Box<dyn LanguageAdapter>> = vec![Box::new(RustAdapter)];
#[cfg(not(feature = "lang-rust"))]
let mut adapters: Vec<Box<dyn LanguageAdapter>> = Vec::new();
adapters.extend([
Box::new(graphql::GraphqlAdapter) as Box<dyn LanguageAdapter>,
Box::new(protobuf::ProtobufAdapter) as Box<dyn LanguageAdapter>,
Box::new(json::JsonAdapter) as Box<dyn LanguageAdapter>,
Box::new(yaml::YamlAdapter) as Box<dyn LanguageAdapter>,
]);
adapters.extend(
tokenized::TokenizedAdapter::defaults()
.filter(|adapter| {
!cfg!(feature = "lang-rust") || !adapter.extensions().contains(&"rs")
})
.map(|adapter| Box::new(adapter) as Box<dyn LanguageAdapter>),
);
Self { adapters }
}
}
impl LanguageRegistry {
pub fn extensions(&self) -> impl Iterator<Item = &'static str> + '_ {
self.adapters
.iter()
.flat_map(|adapter| adapter.extensions().iter().copied())
}
#[must_use]
pub fn adapter_for_extension(&self, extension: &str) -> Option<&dyn LanguageAdapter> {
self.adapters
.iter()
.find(|adapter| adapter.extensions().contains(&extension))
.map(AsRef::as_ref)
}
pub fn languages(&self) -> impl Iterator<Item = Language> + '_ {
self.adapters
.iter()
.map(|adapter| adapter.language())
.collect::<std::collections::BTreeSet<_>>()
.into_iter()
}
}