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