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