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