1use crate::model::{Diagnostic, Result};
2use std::fmt::{Display, Formatter};
3use weavatrix_graph::{EdgeKind, NodeKind, SourceSpan};
4
5mod contract;
6mod graphql;
7mod json;
8mod n8n;
9mod protobuf;
10#[cfg(feature = "lang-rust")]
11mod rust;
12pub mod tokenized;
13mod yaml;
14
15pub(crate) use contract::file_facts_have_transport_evidence;
16#[cfg(test)]
17pub(crate) use contract::may_contain_transport_marker;
18pub(crate) use n8n::DEFAULT_FILE_BYTES as N8N_DEFAULT_FILE_BYTES;
19pub(crate) use n8n::looks_promising as n8n_looks_promising;
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 Swift,
43 Custom(String),
44}
45
46impl Language {
47 #[must_use]
48 pub fn as_str(&self) -> &str {
49 match self {
50 Self::Rust => "rust",
51 Self::Go => "go",
52 Self::C => "c",
53 Self::Cpp => "cpp",
54 Self::Bash => "bash",
55 Self::Sql => "sql",
56 Self::Kubernetes => "kubernetes",
57 Self::JavaScript => "javascript",
58 Self::TypeScript => "typescript",
59 Self::Graphql => "graphql",
60 Self::Protobuf => "protobuf",
61 Self::Json => "json",
62 Self::Python => "python",
63 Self::Java => "java",
64 Self::CSharp => "csharp",
65 Self::Swift => "swift",
66 Self::Custom(value) => value,
67 }
68 }
69}
70
71impl Display for Language {
72 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
73 formatter.write_str(self.as_str())
74 }
75}
76
77#[derive(Debug)]
78pub struct SourceFile<'a> {
79 pub path: &'a str,
80 pub text: &'a str,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct SymbolFact {
85 pub name: String,
86 pub kind: NodeKind,
87 pub span: SourceSpan,
88 pub test_only: bool,
91 pub exported: bool,
93 pub source_fingerprint: Option<String>,
95 pub source_extent: Option<SourceSpan>,
97 pub owner: Option<String>,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct SymbolLocator {
107 pub name: String,
108 pub kind: NodeKind,
109 pub span: SourceSpan,
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
113pub struct ReferenceFact {
114 pub name: String,
115 pub kind: EdgeKind,
116 pub receiver: Option<String>,
119 pub qualified: bool,
123 pub span: SourceSpan,
124 pub owner: Option<SymbolLocator>,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub struct ImportBindingFact {
129 pub imported: String,
131 pub local: String,
133}
134
135#[derive(Debug, Clone, PartialEq, Eq)]
136pub struct ImportFact {
137 pub target: String,
138 pub span: SourceSpan,
139 pub type_only: bool,
143 pub bindings: Vec<ImportBindingFact>,
145}
146
147impl ImportFact {
148 #[must_use]
149 pub fn new(target: String, span: SourceSpan) -> Self {
150 Self {
151 target,
152 span,
153 type_only: false,
154 bindings: Vec::new(),
155 }
156 }
157
158 #[must_use]
159 pub fn type_only(target: String, span: SourceSpan) -> Self {
160 Self {
161 target,
162 span,
163 type_only: true,
164 bindings: Vec::new(),
165 }
166 }
167
168 #[must_use]
169 pub fn with_bindings(mut self, bindings: Vec<ImportBindingFact>) -> Self {
170 self.bindings = bindings;
171 self
172 }
173}
174
175#[derive(Debug, Clone, PartialEq, Eq)]
177pub struct MountFact {
178 pub prefix: String,
180 pub target: String,
182}
183
184#[derive(Debug, Clone, PartialEq, Eq)]
185pub struct DomainFact {
186 pub name: String,
187 pub kind: NodeKind,
188 pub relation: EdgeKind,
189 pub span: SourceSpan,
190 pub owner: Option<SymbolLocator>,
191}
192
193#[derive(Debug, Default)]
194pub struct FileFacts {
195 pub symbols: Vec<SymbolFact>,
196 pub references: Vec<ReferenceFact>,
197 pub imports: Vec<ImportFact>,
198 pub domains: Vec<DomainFact>,
199 pub diagnostics: Vec<Diagnostic>,
200 pub mounts: Vec<MountFact>,
201 pub reexports: Vec<ImportFact>,
204}
205
206pub trait LanguageAdapter: Send + Sync {
207 fn language(&self) -> Language;
208 fn extensions(&self) -> &'static [&'static str];
209 fn extractor(&self) -> &'static str;
210 fn parse(&self, source: SourceFile<'_>) -> Result<FileFacts>;
217}
218
219pub struct LanguageRegistry {
220 adapters: Vec<Box<dyn LanguageAdapter>>,
221}
222
223impl Default for LanguageRegistry {
224 fn default() -> Self {
225 #[cfg(feature = "lang-rust")]
226 let mut adapters: Vec<Box<dyn LanguageAdapter>> = vec![Box::new(RustAdapter)];
227 #[cfg(not(feature = "lang-rust"))]
228 let mut adapters: Vec<Box<dyn LanguageAdapter>> = Vec::new();
229 adapters.extend([
230 Box::new(graphql::GraphqlAdapter) as Box<dyn LanguageAdapter>,
231 Box::new(protobuf::ProtobufAdapter) as Box<dyn LanguageAdapter>,
232 Box::new(json::JsonAdapter) as Box<dyn LanguageAdapter>,
233 Box::new(yaml::YamlAdapter) as Box<dyn LanguageAdapter>,
234 ]);
235 adapters.extend(
241 tokenized::TokenizedAdapter::defaults()
242 .filter(|adapter| {
246 !cfg!(feature = "lang-rust") || !adapter.extensions().contains(&"rs")
247 })
248 .map(|adapter| Box::new(adapter) as Box<dyn LanguageAdapter>),
249 );
250 Self { adapters }
251 }
252}
253
254impl LanguageRegistry {
255 pub fn extensions(&self) -> impl Iterator<Item = &'static str> + '_ {
256 self.adapters
257 .iter()
258 .flat_map(|adapter| adapter.extensions().iter().copied())
259 }
260
261 #[must_use]
262 pub fn adapter_for_extension(&self, extension: &str) -> Option<&dyn LanguageAdapter> {
263 self.adapters
264 .iter()
265 .find(|adapter| adapter.extensions().contains(&extension))
266 .map(AsRef::as_ref)
267 }
268
269 pub fn languages(&self) -> impl Iterator<Item = Language> + '_ {
270 self.adapters
271 .iter()
272 .map(|adapter| adapter.language())
273 .collect::<std::collections::BTreeSet<_>>()
274 .into_iter()
275 }
276}