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