1use crate::model::{Diagnostic, Result};
2use std::fmt::{Display, Formatter};
3use weavatrix_graph::{EdgeKind, NodeKind, SourceSpan};
4
5mod agent;
6mod contract;
7mod dify;
8mod graphql;
9mod json;
10mod mermaid;
11mod n8n;
12mod protobuf;
13#[cfg(feature = "lang-rust")]
14mod rust;
15pub mod tokenized;
16mod yaml;
17mod yaml_doc;
18
19pub(crate) use agent::looks_promising as agent_looks_promising;
20pub(crate) use contract::file_facts_have_transport_evidence;
21#[cfg(test)]
22pub(crate) use contract::may_contain_transport_marker;
23pub(crate) use dify::DEFAULT_FILE_BYTES as DIFY_DEFAULT_FILE_BYTES;
24pub(crate) use dify::looks_promising as dify_looks_promising;
25pub(crate) use dify::secret_label as dify_secret_label;
26pub(crate) use mermaid::looks_promising as mermaid_looks_promising;
27pub(crate) use n8n::{
28 DEFAULT_FILE_BYTES as N8N_DEFAULT_FILE_BYTES, looks_promising as n8n_looks_promising,
29};
30
31#[cfg(feature = "lang-rust")]
32pub use rust::RustAdapter;
33
34#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
35#[non_exhaustive]
36pub enum Language {
37 Rust,
38 Go,
39 C,
40 Cpp,
41 Bash,
42 Sql,
43 Kubernetes,
44 JavaScript,
45 TypeScript,
46 Graphql,
47 Protobuf,
48 Json,
49 Python,
50 Java,
51 CSharp,
52 Swift,
53 Custom(String),
54}
55
56impl Language {
57 #[must_use]
58 pub fn as_str(&self) -> &str {
59 match self {
60 Self::Rust => "rust",
61 Self::Go => "go",
62 Self::C => "c",
63 Self::Cpp => "cpp",
64 Self::Bash => "bash",
65 Self::Sql => "sql",
66 Self::Kubernetes => "kubernetes",
67 Self::JavaScript => "javascript",
68 Self::TypeScript => "typescript",
69 Self::Graphql => "graphql",
70 Self::Protobuf => "protobuf",
71 Self::Json => "json",
72 Self::Python => "python",
73 Self::Java => "java",
74 Self::CSharp => "csharp",
75 Self::Swift => "swift",
76 Self::Custom(value) => value,
77 }
78 }
79}
80
81impl Display for Language {
82 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
83 formatter.write_str(self.as_str())
84 }
85}
86
87#[derive(Debug)]
88pub struct SourceFile<'a> {
89 pub path: &'a str,
90 pub text: &'a str,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct SymbolFact {
95 pub name: String,
96 pub kind: NodeKind,
97 pub span: SourceSpan,
98 pub test_only: bool,
101 pub exported: bool,
103 pub source_fingerprint: Option<String>,
105 pub source_extent: Option<SourceSpan>,
107 pub owner: Option<String>,
113}
114
115#[derive(Debug, Clone, PartialEq, Eq)]
116pub struct SymbolLocator {
117 pub name: String,
118 pub kind: NodeKind,
119 pub span: SourceSpan,
120}
121
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct ReferenceFact {
124 pub name: String,
125 pub kind: EdgeKind,
126 pub receiver: Option<String>,
129 pub qualified: bool,
133 pub span: SourceSpan,
134 pub owner: Option<SymbolLocator>,
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct ImportBindingFact {
139 pub imported: String,
141 pub local: String,
143}
144
145#[derive(Debug, Clone, PartialEq, Eq)]
146pub struct ImportFact {
147 pub target: String,
148 pub span: SourceSpan,
149 pub type_only: bool,
153 pub bindings: Vec<ImportBindingFact>,
155}
156
157impl ImportFact {
158 #[must_use]
159 pub fn new(target: String, span: SourceSpan) -> Self {
160 Self {
161 target,
162 span,
163 type_only: false,
164 bindings: Vec::new(),
165 }
166 }
167
168 #[must_use]
169 pub fn type_only(target: String, span: SourceSpan) -> Self {
170 Self {
171 target,
172 span,
173 type_only: true,
174 bindings: Vec::new(),
175 }
176 }
177
178 #[must_use]
179 pub fn with_bindings(mut self, bindings: Vec<ImportBindingFact>) -> Self {
180 self.bindings = bindings;
181 self
182 }
183}
184
185#[derive(Debug, Clone, PartialEq, Eq)]
187pub struct MountFact {
188 pub prefix: String,
190 pub target: String,
192}
193
194#[derive(Debug, Clone, PartialEq, Eq)]
195pub struct DomainFact {
196 pub name: String,
197 pub kind: NodeKind,
198 pub relation: EdgeKind,
199 pub span: SourceSpan,
200 pub owner: Option<SymbolLocator>,
201}
202
203#[derive(Debug, Clone, PartialEq, Eq)]
206pub struct BoundEdgeFact {
207 pub from: SymbolLocator,
208 pub to: SymbolLocator,
209 pub kind: EdgeKind,
210 pub span: SourceSpan,
211 pub detail: String,
212}
213
214#[derive(Debug, Default)]
215pub struct FileFacts {
216 pub symbols: Vec<SymbolFact>,
217 pub references: Vec<ReferenceFact>,
218 pub imports: Vec<ImportFact>,
219 pub domains: Vec<DomainFact>,
220 pub bound_edges: Vec<BoundEdgeFact>,
221 pub diagnostics: Vec<Diagnostic>,
222 pub mounts: Vec<MountFact>,
223 pub reexports: Vec<ImportFact>,
226}
227
228pub trait LanguageAdapter: Send + Sync {
229 fn language(&self) -> Language;
230 fn extensions(&self) -> &'static [&'static str];
231 fn extractor(&self) -> &'static str;
232 fn parse(&self, source: SourceFile<'_>) -> Result<FileFacts>;
239}
240
241pub struct LanguageRegistry {
242 adapters: Vec<Box<dyn LanguageAdapter>>,
243}
244
245impl Default for LanguageRegistry {
246 fn default() -> Self {
247 #[cfg(feature = "lang-rust")]
248 let mut adapters: Vec<Box<dyn LanguageAdapter>> = vec![Box::new(RustAdapter)];
249 #[cfg(not(feature = "lang-rust"))]
250 let mut adapters: Vec<Box<dyn LanguageAdapter>> = Vec::new();
251 adapters.extend([
252 Box::new(graphql::GraphqlAdapter) as Box<dyn LanguageAdapter>,
253 Box::new(protobuf::ProtobufAdapter) as Box<dyn LanguageAdapter>,
254 Box::new(json::JsonAdapter) as Box<dyn LanguageAdapter>,
255 Box::new(yaml::YamlAdapter) as Box<dyn LanguageAdapter>,
256 Box::new(mermaid::MermaidAdapter) as Box<dyn LanguageAdapter>,
257 ]);
258 adapters.extend(
264 tokenized::TokenizedAdapter::defaults()
265 .filter(|adapter| {
269 !cfg!(feature = "lang-rust") || !adapter.extensions().contains(&"rs")
270 })
271 .map(|adapter| Box::new(adapter) as Box<dyn LanguageAdapter>),
272 );
273 Self { adapters }
274 }
275}
276
277impl LanguageRegistry {
278 pub fn extensions(&self) -> impl Iterator<Item = &'static str> + '_ {
279 self.adapters
280 .iter()
281 .flat_map(|adapter| adapter.extensions().iter().copied())
282 }
283
284 #[must_use]
285 pub fn adapter_for_extension(&self, extension: &str) -> Option<&dyn LanguageAdapter> {
286 self.adapters
287 .iter()
288 .find(|adapter| adapter.extensions().contains(&extension))
289 .map(AsRef::as_ref)
290 }
291
292 pub fn languages(&self) -> impl Iterator<Item = Language> + '_ {
293 self.adapters
294 .iter()
295 .map(|adapter| adapter.language())
296 .collect::<std::collections::BTreeSet<_>>()
297 .into_iter()
298 }
299}