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 Custom(String),
40}
41
42impl Language {
43 #[must_use]
44 pub fn as_str(&self) -> &str {
45 match self {
46 Self::Rust => "rust",
47 Self::Go => "go",
48 Self::C => "c",
49 Self::Cpp => "cpp",
50 Self::Bash => "bash",
51 Self::Sql => "sql",
52 Self::Kubernetes => "kubernetes",
53 Self::JavaScript => "javascript",
54 Self::TypeScript => "typescript",
55 Self::Graphql => "graphql",
56 Self::Protobuf => "protobuf",
57 Self::Json => "json",
58 Self::Python => "python",
59 Self::Java => "java",
60 Self::CSharp => "csharp",
61 Self::Custom(value) => value,
62 }
63 }
64}
65
66impl Display for Language {
67 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
68 formatter.write_str(self.as_str())
69 }
70}
71
72#[derive(Debug)]
73pub struct SourceFile<'a> {
74 pub path: &'a str,
75 pub text: &'a str,
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct SymbolFact {
80 pub name: String,
81 pub kind: NodeKind,
82 pub span: SourceSpan,
83 pub test_only: bool,
86 pub owner: Option<String>,
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
95pub struct SymbolLocator {
96 pub name: String,
97 pub kind: NodeKind,
98 pub span: SourceSpan,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct ReferenceFact {
103 pub name: String,
104 pub kind: EdgeKind,
105 pub receiver: Option<String>,
108 pub qualified: bool,
112 pub span: SourceSpan,
113 pub owner: Option<SymbolLocator>,
114}
115
116#[derive(Debug, Clone, PartialEq, Eq)]
117pub struct ImportBindingFact {
118 pub imported: String,
120 pub local: String,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub struct ImportFact {
126 pub target: String,
127 pub span: SourceSpan,
128 pub type_only: bool,
132 pub bindings: Vec<ImportBindingFact>,
134}
135
136impl ImportFact {
137 #[must_use]
138 pub fn new(target: String, span: SourceSpan) -> Self {
139 Self {
140 target,
141 span,
142 type_only: false,
143 bindings: Vec::new(),
144 }
145 }
146
147 #[must_use]
148 pub fn type_only(target: String, span: SourceSpan) -> Self {
149 Self {
150 target,
151 span,
152 type_only: true,
153 bindings: Vec::new(),
154 }
155 }
156
157 #[must_use]
158 pub fn with_bindings(mut self, bindings: Vec<ImportBindingFact>) -> Self {
159 self.bindings = bindings;
160 self
161 }
162}
163
164#[derive(Debug, Clone, PartialEq, Eq)]
166pub struct MountFact {
167 pub prefix: String,
169 pub target: String,
171}
172
173#[derive(Debug, Clone, PartialEq, Eq)]
174pub struct DomainFact {
175 pub name: String,
176 pub kind: NodeKind,
177 pub relation: EdgeKind,
178 pub span: SourceSpan,
179 pub owner: Option<SymbolLocator>,
180}
181
182#[derive(Debug, Default)]
183pub struct FileFacts {
184 pub symbols: Vec<SymbolFact>,
185 pub references: Vec<ReferenceFact>,
186 pub imports: Vec<ImportFact>,
187 pub domains: Vec<DomainFact>,
188 pub diagnostics: Vec<Diagnostic>,
189 pub mounts: Vec<MountFact>,
190 pub reexports: Vec<ImportFact>,
193}
194
195pub trait LanguageAdapter: Send + Sync {
196 fn language(&self) -> Language;
197 fn extensions(&self) -> &'static [&'static str];
198 fn extractor(&self) -> &'static str;
199 fn parse(&self, source: SourceFile<'_>) -> Result<FileFacts>;
206}
207
208pub struct LanguageRegistry {
209 adapters: Vec<Box<dyn LanguageAdapter>>,
210}
211
212impl Default for LanguageRegistry {
213 fn default() -> Self {
214 #[cfg(feature = "lang-rust")]
215 let mut adapters: Vec<Box<dyn LanguageAdapter>> = vec![Box::new(RustAdapter)];
216 #[cfg(not(feature = "lang-rust"))]
217 let mut adapters: Vec<Box<dyn LanguageAdapter>> = Vec::new();
218 adapters.extend([
219 Box::new(graphql::GraphqlAdapter) as Box<dyn LanguageAdapter>,
220 Box::new(protobuf::ProtobufAdapter) as Box<dyn LanguageAdapter>,
221 Box::new(json::JsonAdapter) as Box<dyn LanguageAdapter>,
222 Box::new(yaml::YamlAdapter) as Box<dyn LanguageAdapter>,
223 ]);
224 adapters.extend(
230 tokenized::TokenizedAdapter::defaults()
231 .filter(|adapter| {
235 !cfg!(feature = "lang-rust") || !adapter.extensions().contains(&"rs")
236 })
237 .map(|adapter| Box::new(adapter) as Box<dyn LanguageAdapter>),
238 );
239 Self { adapters }
240 }
241}
242
243impl LanguageRegistry {
244 pub fn extensions(&self) -> impl Iterator<Item = &'static str> + '_ {
245 self.adapters
246 .iter()
247 .flat_map(|adapter| adapter.extensions().iter().copied())
248 }
249
250 #[must_use]
251 pub fn adapter_for_extension(&self, extension: &str) -> Option<&dyn LanguageAdapter> {
252 self.adapters
253 .iter()
254 .find(|adapter| adapter.extensions().contains(&extension))
255 .map(AsRef::as_ref)
256 }
257
258 pub fn languages(&self) -> impl Iterator<Item = Language> + '_ {
259 self.adapters
260 .iter()
261 .map(|adapter| adapter.language())
262 .collect::<std::collections::BTreeSet<_>>()
263 .into_iter()
264 }
265}