weavatrix_rust/language/rust/
mod.rs1use super::{
2 DomainFact, FileFacts, ImportFact, Language, LanguageAdapter, ReferenceFact, SourceFile,
3 SymbolFact, SymbolLocator,
4};
5use crate::model::{Diagnostic, Result};
6use proc_macro2::Span;
7use syn::spanned::Spanned;
8use syn::visit::Visit;
9use weavatrix_graph::{EdgeKind, NodeKind};
10
11use endpoints::{attribute_routes, callable_name, route_call};
12use syntax::{attributes_mark_test, impl_owner, source_span, use_tree_targets};
13
14mod endpoints;
15mod syntax;
16
17#[derive(Debug, Clone, Copy)]
18pub struct RustAdapter;
19
20impl LanguageAdapter for RustAdapter {
21 fn language(&self) -> Language {
22 Language::Rust
23 }
24
25 fn extensions(&self) -> &'static [&'static str] {
26 &["rs"]
27 }
28
29 fn extractor(&self) -> &'static str {
30 "weavatrix.rust.syn"
31 }
32
33 fn parse(&self, source: SourceFile<'_>) -> Result<FileFacts> {
34 let syntax = match syn::parse_file(source.text) {
35 Ok(syntax) => syntax,
36 Err(error) => {
37 return Ok(FileFacts {
38 diagnostics: vec![Diagnostic {
39 code: "rust.syntax_error".into(),
40 message: error.to_string(),
41 span: Some(source_span(source.path, error.span())),
42 }],
43 ..FileFacts::default()
44 });
45 }
46 };
47
48 let mut collector = Collector {
49 path: source.path,
50 facts: FileFacts::default(),
51 owner: OwnerScope::default(),
52 test_context: false,
53 };
54 collector.visit_file(&syntax);
55 collector
56 .facts
57 .symbols
58 .sort_by(|left, right| left.span.cmp(&right.span));
59 collector
60 .facts
61 .references
62 .sort_by(|left, right| left.span.cmp(&right.span));
63 collector
64 .facts
65 .imports
66 .sort_by(|left, right| left.span.cmp(&right.span));
67 Ok(collector.facts)
68 }
69}
70
71struct Collector<'source> {
72 path: &'source str,
73 facts: FileFacts,
74 owner: OwnerScope,
75 test_context: bool,
76}
77
78#[derive(Clone, Default)]
79struct OwnerScope {
80 symbol: Option<SymbolLocator>,
81 type_name: Option<String>,
82}
83
84enum OwnerUpdate {
85 Symbol(SymbolLocator),
86 Type(String),
87}
88
89impl Collector<'_> {
90 fn add_symbol(&mut self, name: &syn::Ident, kind: NodeKind, span: Span) -> SymbolLocator {
91 let locator = SymbolLocator {
92 name: name.to_string(),
93 kind,
94 span: source_span(self.path, span),
95 };
96 self.facts.symbols.push(SymbolFact {
97 name: locator.name.clone(),
98 kind: locator.kind.clone(),
99 span: locator.span.clone(),
100 test_only: self.test_context,
101 owner: (locator.kind == NodeKind::Method)
102 .then(|| self.owner.type_name.clone())
103 .flatten(),
104 });
105 locator
106 }
107
108 fn with_owner(&mut self, update: OwnerUpdate, visit: impl FnOnce(&mut Self)) {
109 let previous = self.owner.clone();
110 match update {
111 OwnerUpdate::Symbol(owner) => self.owner.symbol = Some(owner),
112 OwnerUpdate::Type(owner) => self.owner.type_name = Some(owner),
113 }
114 visit(self);
115 self.owner = previous;
116 }
117
118 fn with_test_context(&mut self, attributes: &[syn::Attribute], visit: impl FnOnce(&mut Self)) {
119 let previous = self.test_context;
120 self.test_context |= attributes_mark_test(attributes);
121 visit(self);
122 self.test_context = previous;
123 }
124
125 fn add_reference(&mut self, name: String, span: Span) {
126 self.facts.references.push(ReferenceFact {
127 name,
128 kind: EdgeKind::Calls,
129 receiver: None,
130 qualified: false,
131 span: source_span(self.path, span),
132 owner: self.owner.symbol.clone(),
133 });
134 }
135
136 fn add_endpoint(&mut self, method: &str, path: &str, span: Span) {
137 self.facts.domains.push(DomainFact {
138 name: format!("{method} {path}"),
139 kind: NodeKind::Endpoint,
140 relation: EdgeKind::Exposes,
141 span: source_span(self.path, span),
142 owner: self.owner.symbol.clone(),
143 });
144 }
145
146 fn add_attribute_endpoints(&mut self, attributes: &[syn::Attribute]) {
147 for (method, path, span) in attribute_routes(attributes) {
148 self.add_endpoint(method, &path, span);
149 }
150 }
151}
152
153impl<'ast> Visit<'ast> for Collector<'_> {
154 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
155 self.with_test_context(&node.attrs, |collector| {
156 let owner =
157 collector.add_symbol(&node.sig.ident, NodeKind::Function, node.sig.ident.span());
158 collector.with_owner(OwnerUpdate::Symbol(owner), |collector| {
159 collector.add_attribute_endpoints(&node.attrs);
160 syn::visit::visit_item_fn(collector, node);
161 });
162 });
163 }
164
165 fn visit_impl_item_fn(&mut self, node: &'ast syn::ImplItemFn) {
166 self.with_test_context(&node.attrs, |collector| {
167 let owner =
168 collector.add_symbol(&node.sig.ident, NodeKind::Method, node.sig.ident.span());
169 collector.with_owner(OwnerUpdate::Symbol(owner), |collector| {
170 collector.add_attribute_endpoints(&node.attrs);
171 syn::visit::visit_impl_item_fn(collector, node);
172 });
173 });
174 }
175
176 fn visit_trait_item_fn(&mut self, node: &'ast syn::TraitItemFn) {
177 self.with_test_context(&node.attrs, |collector| {
178 let owner =
179 collector.add_symbol(&node.sig.ident, NodeKind::Method, node.sig.ident.span());
180 collector.with_owner(OwnerUpdate::Symbol(owner), |collector| {
181 syn::visit::visit_trait_item_fn(collector, node);
182 });
183 });
184 }
185
186 fn visit_item_struct(&mut self, node: &'ast syn::ItemStruct) {
187 self.with_test_context(&node.attrs, |collector| {
188 collector.add_symbol(&node.ident, NodeKind::Struct, node.ident.span());
189 syn::visit::visit_item_struct(collector, node);
190 });
191 }
192
193 fn visit_item_enum(&mut self, node: &'ast syn::ItemEnum) {
194 self.with_test_context(&node.attrs, |collector| {
195 collector.add_symbol(&node.ident, NodeKind::Enum, node.ident.span());
196 syn::visit::visit_item_enum(collector, node);
197 });
198 }
199
200 fn visit_item_trait(&mut self, node: &'ast syn::ItemTrait) {
201 self.with_test_context(&node.attrs, |collector| {
202 collector.add_symbol(&node.ident, NodeKind::Trait, node.ident.span());
203 collector.with_owner(OwnerUpdate::Type(node.ident.to_string()), |collector| {
204 syn::visit::visit_item_trait(collector, node);
205 });
206 });
207 }
208
209 fn visit_item_impl(&mut self, node: &'ast syn::ItemImpl) {
210 self.with_test_context(&node.attrs, |collector| {
211 if let Some(owner) = impl_owner(&node.self_ty) {
212 collector.with_owner(OwnerUpdate::Type(owner), |collector| {
213 syn::visit::visit_item_impl(collector, node);
214 });
215 } else {
216 syn::visit::visit_item_impl(collector, node);
217 }
218 });
219 }
220
221 fn visit_item_type(&mut self, node: &'ast syn::ItemType) {
222 self.with_test_context(&node.attrs, |collector| {
223 collector.add_symbol(&node.ident, NodeKind::TypeAlias, node.ident.span());
224 syn::visit::visit_item_type(collector, node);
225 });
226 }
227
228 fn visit_item_const(&mut self, node: &'ast syn::ItemConst) {
229 self.with_test_context(&node.attrs, |collector| {
230 collector.add_symbol(&node.ident, NodeKind::Constant, node.ident.span());
231 syn::visit::visit_item_const(collector, node);
232 });
233 }
234
235 fn visit_item_static(&mut self, node: &'ast syn::ItemStatic) {
236 self.with_test_context(&node.attrs, |collector| {
237 collector.add_symbol(&node.ident, NodeKind::Static, node.ident.span());
238 syn::visit::visit_item_static(collector, node);
239 });
240 }
241
242 fn visit_item_mod(&mut self, node: &'ast syn::ItemMod) {
243 self.with_test_context(&node.attrs, |collector| {
244 collector.add_symbol(&node.ident, NodeKind::Module, node.ident.span());
245 if node.content.is_none() {
246 collector.facts.imports.push(ImportFact::new(
250 format!("self::{}", node.ident),
251 source_span(collector.path, node.span()),
252 ));
253 }
254 syn::visit::visit_item_mod(collector, node);
255 });
256 }
257
258 fn visit_item_use(&mut self, node: &'ast syn::ItemUse) {
259 for target in use_tree_targets(&node.tree) {
260 let fact = ImportFact::new(target, source_span(self.path, node.span()));
261 if matches!(node.vis, syn::Visibility::Inherited) {
262 self.facts.imports.push(fact);
263 } else {
264 self.facts.reexports.push(fact);
265 }
266 }
267 syn::visit::visit_item_use(self, node);
268 }
269
270 fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) {
271 if let Some(name) = callable_name(&node.func) {
272 self.add_reference(name, node.span());
273 }
274 syn::visit::visit_expr_call(self, node);
275 }
276
277 fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) {
278 self.add_reference(node.method.to_string(), node.span());
279 if node.method == "route"
280 && let Some((method, path)) = route_call(node)
281 {
282 self.add_endpoint(method, &path, node.span());
283 }
284 syn::visit::visit_expr_method_call(self, node);
285 }
286}
287
288#[cfg(test)]
289mod tests;