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 owner: Option<String>,
94}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
97pub struct SymbolLocator {
98 pub name: String,
99 pub kind: NodeKind,
100 pub span: SourceSpan,
101}
102
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct ReferenceFact {
105 pub name: String,
106 pub kind: EdgeKind,
107 pub receiver: Option<String>,
110 pub qualified: bool,
114 pub span: SourceSpan,
115 pub owner: Option<SymbolLocator>,
116}
117
118#[derive(Debug, Clone, PartialEq, Eq)]
119pub struct ImportBindingFact {
120 pub imported: String,
122 pub local: String,
124}
125
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub struct ImportFact {
128 pub target: String,
129 pub span: SourceSpan,
130 pub type_only: bool,
134 pub bindings: Vec<ImportBindingFact>,
136}
137
138impl ImportFact {
139 #[must_use]
140 pub fn new(target: String, span: SourceSpan) -> Self {
141 Self {
142 target,
143 span,
144 type_only: false,
145 bindings: Vec::new(),
146 }
147 }
148
149 #[must_use]
150 pub fn type_only(target: String, span: SourceSpan) -> Self {
151 Self {
152 target,
153 span,
154 type_only: true,
155 bindings: Vec::new(),
156 }
157 }
158
159 #[must_use]
160 pub fn with_bindings(mut self, bindings: Vec<ImportBindingFact>) -> Self {
161 self.bindings = bindings;
162 self
163 }
164}
165
166#[derive(Debug, Clone, PartialEq, Eq)]
168pub struct MountFact {
169 pub prefix: String,
171 pub target: String,
173}
174
175#[derive(Debug, Clone, PartialEq, Eq)]
176pub struct DomainFact {
177 pub name: String,
178 pub kind: NodeKind,
179 pub relation: EdgeKind,
180 pub span: SourceSpan,
181 pub owner: Option<SymbolLocator>,
182}
183
184#[derive(Debug, Default)]
185pub struct FileFacts {
186 pub symbols: Vec<SymbolFact>,
187 pub references: Vec<ReferenceFact>,
188 pub imports: Vec<ImportFact>,
189 pub domains: Vec<DomainFact>,
190 pub diagnostics: Vec<Diagnostic>,
191 pub mounts: Vec<MountFact>,
192 pub reexports: Vec<ImportFact>,
195}
196
197pub trait LanguageAdapter: Send + Sync {
198 fn language(&self) -> Language;
199 fn extensions(&self) -> &'static [&'static str];
200 fn extractor(&self) -> &'static str;
201 fn parse(&self, source: SourceFile<'_>) -> Result<FileFacts>;
208}
209
210pub struct LanguageRegistry {
211 adapters: Vec<Box<dyn LanguageAdapter>>,
212}
213
214impl Default for LanguageRegistry {
215 fn default() -> Self {
216 #[cfg(feature = "lang-rust")]
217 let mut adapters: Vec<Box<dyn LanguageAdapter>> = vec![Box::new(RustAdapter)];
218 #[cfg(not(feature = "lang-rust"))]
219 let mut adapters: Vec<Box<dyn LanguageAdapter>> = Vec::new();
220 adapters.extend([
221 Box::new(graphql::GraphqlAdapter) as Box<dyn LanguageAdapter>,
222 Box::new(protobuf::ProtobufAdapter) as Box<dyn LanguageAdapter>,
223 Box::new(json::JsonAdapter) as Box<dyn LanguageAdapter>,
224 Box::new(yaml::YamlAdapter) as Box<dyn LanguageAdapter>,
225 ]);
226 adapters.extend(
232 tokenized::TokenizedAdapter::defaults()
233 .filter(|adapter| {
237 !cfg!(feature = "lang-rust") || !adapter.extensions().contains(&"rs")
238 })
239 .map(|adapter| Box::new(adapter) as Box<dyn LanguageAdapter>),
240 );
241 Self { adapters }
242 }
243}
244
245impl LanguageRegistry {
246 pub fn extensions(&self) -> impl Iterator<Item = &'static str> + '_ {
247 self.adapters
248 .iter()
249 .flat_map(|adapter| adapter.extensions().iter().copied())
250 }
251
252 #[must_use]
253 pub fn adapter_for_extension(&self, extension: &str) -> Option<&dyn LanguageAdapter> {
254 self.adapters
255 .iter()
256 .find(|adapter| adapter.extensions().contains(&extension))
257 .map(AsRef::as_ref)
258 }
259
260 pub fn languages(&self) -> impl Iterator<Item = Language> + '_ {
261 self.adapters
262 .iter()
263 .map(|adapter| adapter.language())
264 .collect::<std::collections::BTreeSet<_>>()
265 .into_iter()
266 }
267}