1use crate::error::Result;
2use crate::snapshot::Diagnostic;
3use std::fmt::{Display, Formatter};
4use weavatrix_graph::{EdgeKind, NodeKind, SourceSpan};
5
6mod contract;
7mod graphql;
8mod json;
9mod protobuf;
10#[cfg(feature = "lang-rust")]
11mod rust;
12#[cfg(feature = "lang-rust")]
13mod rust_endpoint;
14pub mod tokenized;
15mod yaml;
16
17pub(crate) use contract::file_facts_have_transport_evidence;
18#[cfg(test)]
19pub(crate) use contract::may_contain_transport_marker;
20
21#[cfg(feature = "lang-rust")]
22pub use rust::RustAdapter;
23
24#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
25#[non_exhaustive]
26pub enum Language {
27 Rust,
28 Go,
29 C,
30 Cpp,
31 Bash,
32 Sql,
33 Kubernetes,
34 JavaScript,
35 TypeScript,
36 Graphql,
37 Protobuf,
38 Json,
39 Python,
40 Java,
41 CSharp,
42 Custom(String),
43}
44
45impl Language {
46 #[must_use]
47 pub fn as_str(&self) -> &str {
48 match self {
49 Self::Rust => "rust",
50 Self::Go => "go",
51 Self::C => "c",
52 Self::Cpp => "cpp",
53 Self::Bash => "bash",
54 Self::Sql => "sql",
55 Self::Kubernetes => "kubernetes",
56 Self::JavaScript => "javascript",
57 Self::TypeScript => "typescript",
58 Self::Graphql => "graphql",
59 Self::Protobuf => "protobuf",
60 Self::Json => "json",
61 Self::Python => "python",
62 Self::Java => "java",
63 Self::CSharp => "csharp",
64 Self::Custom(value) => value,
65 }
66 }
67}
68
69impl Display for Language {
70 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
71 formatter.write_str(self.as_str())
72 }
73}
74
75#[derive(Debug)]
76pub struct SourceFile<'a> {
77 pub path: &'a str,
78 pub text: &'a str,
79}
80
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct SymbolFact {
83 pub name: String,
84 pub kind: NodeKind,
85 pub span: SourceSpan,
86 pub test_only: bool,
89 pub owner: Option<String>,
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
98pub struct SymbolLocator {
99 pub name: String,
100 pub kind: NodeKind,
101 pub span: SourceSpan,
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct ReferenceFact {
106 pub name: String,
107 pub kind: EdgeKind,
108 pub receiver: Option<String>,
111 pub qualified: bool,
115 pub span: SourceSpan,
116 pub owner: Option<SymbolLocator>,
117}
118
119#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct ImportBindingFact {
121 pub imported: String,
123 pub local: String,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub struct ImportFact {
129 pub target: String,
130 pub span: SourceSpan,
131 pub type_only: bool,
135 pub bindings: Vec<ImportBindingFact>,
137}
138
139impl ImportFact {
140 #[must_use]
141 pub fn new(target: String, span: SourceSpan) -> Self {
142 Self {
143 target,
144 span,
145 type_only: false,
146 bindings: Vec::new(),
147 }
148 }
149
150 #[must_use]
151 pub fn type_only(target: String, span: SourceSpan) -> Self {
152 Self {
153 target,
154 span,
155 type_only: true,
156 bindings: Vec::new(),
157 }
158 }
159
160 #[must_use]
161 pub fn with_bindings(mut self, bindings: Vec<ImportBindingFact>) -> Self {
162 self.bindings = bindings;
163 self
164 }
165}
166
167#[derive(Debug, Clone, PartialEq, Eq)]
169pub struct MountFact {
170 pub prefix: String,
172 pub target: String,
174}
175
176#[derive(Debug, Clone, PartialEq, Eq)]
177pub struct DomainFact {
178 pub name: String,
179 pub kind: NodeKind,
180 pub relation: EdgeKind,
181 pub span: SourceSpan,
182 pub owner: Option<SymbolLocator>,
183}
184
185#[derive(Debug, Default)]
186pub struct FileFacts {
187 pub symbols: Vec<SymbolFact>,
188 pub references: Vec<ReferenceFact>,
189 pub imports: Vec<ImportFact>,
190 pub domains: Vec<DomainFact>,
191 pub diagnostics: Vec<Diagnostic>,
192 pub mounts: Vec<MountFact>,
193 pub reexports: Vec<ImportFact>,
196}
197
198pub trait LanguageAdapter: Send + Sync {
199 fn language(&self) -> Language;
200 fn extensions(&self) -> &'static [&'static str];
201 fn extractor(&self) -> &'static str;
202 fn parse(&self, source: SourceFile<'_>) -> Result<FileFacts>;
209}
210
211pub struct LanguageRegistry {
212 adapters: Vec<Box<dyn LanguageAdapter>>,
213}
214
215impl Default for LanguageRegistry {
216 fn default() -> Self {
217 #[cfg(feature = "lang-rust")]
218 let mut adapters: Vec<Box<dyn LanguageAdapter>> = vec![Box::new(RustAdapter)];
219 #[cfg(not(feature = "lang-rust"))]
220 let mut adapters: Vec<Box<dyn LanguageAdapter>> = Vec::new();
221 adapters.extend([
222 Box::new(graphql::GraphqlAdapter) as Box<dyn LanguageAdapter>,
223 Box::new(protobuf::ProtobufAdapter) as Box<dyn LanguageAdapter>,
224 Box::new(json::JsonAdapter) as Box<dyn LanguageAdapter>,
225 Box::new(yaml::YamlAdapter) as Box<dyn LanguageAdapter>,
226 ]);
227 adapters.extend(
233 tokenized::TokenizedAdapter::defaults()
234 .filter(|adapter| {
238 !cfg!(feature = "lang-rust") || !adapter.extensions().contains(&"rs")
239 })
240 .map(|adapter| Box::new(adapter) as Box<dyn LanguageAdapter>),
241 );
242 Self { adapters }
243 }
244}
245
246impl LanguageRegistry {
247 pub fn extensions(&self) -> impl Iterator<Item = &'static str> + '_ {
248 self.adapters
249 .iter()
250 .flat_map(|adapter| adapter.extensions().iter().copied())
251 }
252
253 #[must_use]
254 pub fn adapter_for_extension(&self, extension: &str) -> Option<&dyn LanguageAdapter> {
255 self.adapters
256 .iter()
257 .find(|adapter| adapter.extensions().contains(&extension))
258 .map(AsRef::as_ref)
259 }
260
261 pub fn languages(&self) -> impl Iterator<Item = Language> + '_ {
262 self.adapters
263 .iter()
264 .map(|adapter| adapter.language())
265 .collect::<std::collections::BTreeSet<_>>()
266 .into_iter()
267 }
268}