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 Ok(collector.facts)
64 }
65}
66
67struct Collector<'source> {
68 path: &'source str,
69 facts: FileFacts,
70 owner: OwnerScope,
71 test_context: bool,
72 module_scope: ModuleScope,
73}
74
75fn is_public(visibility: &syn::Visibility) -> bool {
76 matches!(visibility, syn::Visibility::Public(_))
77}
78
79impl<'ast> Visit<'ast> for Collector<'_> {
80 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
81 self.with_test_context(&node.attrs, |collector| {
82 let owner = collector.add_symbol(
83 &node.sig.ident,
84 NodeKind::Function,
85 node.span(),
86 is_public(&node.vis),
87 );
88 collector.with_owner(OwnerUpdate::Symbol(owner), |collector| {
89 collector.add_attribute_endpoints(&node.attrs);
90 syn::visit::visit_item_fn(collector, node);
91 });
92 });
93 }
94
95 fn visit_impl_item_fn(&mut self, node: &'ast syn::ImplItemFn) {
96 self.with_test_context(&node.attrs, |collector| {
97 let type_name = collector.owner.type_name.clone();
98 let owner = collector.add_symbol(
99 &node.sig.ident,
100 NodeKind::Method,
101 node.span(),
102 is_public(&node.vis),
103 );
104 collector.with_owner(OwnerUpdate::Symbol(owner), |collector| {
105 if let Some(type_name) = type_name {
106 collector.add_reference(
107 type_name,
108 EdgeKind::References,
109 false,
110 node.sig.ident.span(),
111 );
112 }
113 collector.add_attribute_endpoints(&node.attrs);
114 syn::visit::visit_impl_item_fn(collector, node);
115 });
116 });
117 }
118
119 fn visit_trait_item_fn(&mut self, node: &'ast syn::TraitItemFn) {
120 self.with_test_context(&node.attrs, |collector| {
121 let owner = collector.add_symbol(&node.sig.ident, NodeKind::Method, node.span(), false);
122 collector.with_owner(OwnerUpdate::Symbol(owner), |collector| {
123 syn::visit::visit_trait_item_fn(collector, node);
124 });
125 });
126 }
127
128 fn visit_item_struct(&mut self, node: &'ast syn::ItemStruct) {
129 self.with_test_context(&node.attrs, |collector| {
130 collector.add_symbol(
131 &node.ident,
132 NodeKind::Struct,
133 node.span(),
134 is_public(&node.vis),
135 );
136 syn::visit::visit_item_struct(collector, node);
137 });
138 }
139
140 fn visit_item_enum(&mut self, node: &'ast syn::ItemEnum) {
141 self.with_test_context(&node.attrs, |collector| {
142 collector.add_symbol(
143 &node.ident,
144 NodeKind::Enum,
145 node.span(),
146 is_public(&node.vis),
147 );
148 syn::visit::visit_item_enum(collector, node);
149 });
150 }
151
152 fn visit_item_trait(&mut self, node: &'ast syn::ItemTrait) {
153 self.with_test_context(&node.attrs, |collector| {
154 collector.add_symbol(
155 &node.ident,
156 NodeKind::Trait,
157 node.span(),
158 is_public(&node.vis),
159 );
160 collector.with_owner(OwnerUpdate::Type(node.ident.to_string()), |collector| {
161 syn::visit::visit_item_trait(collector, node);
162 });
163 });
164 }
165
166 fn visit_item_impl(&mut self, node: &'ast syn::ItemImpl) {
167 self.with_test_context(&node.attrs, |collector| {
168 if let Some(owner) = impl_owner(&node.self_ty) {
169 collector.with_owner(OwnerUpdate::Type(owner), |collector| {
170 syn::visit::visit_item_impl(collector, node);
171 });
172 } else {
173 syn::visit::visit_item_impl(collector, node);
174 }
175 });
176 }
177
178 fn visit_item_type(&mut self, node: &'ast syn::ItemType) {
179 self.with_test_context(&node.attrs, |collector| {
180 collector.add_symbol(
181 &node.ident,
182 NodeKind::TypeAlias,
183 node.span(),
184 is_public(&node.vis),
185 );
186 syn::visit::visit_item_type(collector, node);
187 });
188 }
189
190 fn visit_item_const(&mut self, node: &'ast syn::ItemConst) {
191 self.with_test_context(&node.attrs, |collector| {
192 collector.add_symbol(
193 &node.ident,
194 NodeKind::Constant,
195 node.span(),
196 is_public(&node.vis),
197 );
198 syn::visit::visit_item_const(collector, node);
199 });
200 }
201
202 fn visit_item_static(&mut self, node: &'ast syn::ItemStatic) {
203 self.with_test_context(&node.attrs, |collector| {
204 collector.add_symbol(
205 &node.ident,
206 NodeKind::Static,
207 node.span(),
208 is_public(&node.vis),
209 );
210 syn::visit::visit_item_static(collector, node);
211 });
212 }
213
214 fn visit_item_mod(&mut self, node: &'ast syn::ItemMod) {
215 self.with_test_context(&node.attrs, |collector| {
216 collector.add_symbol(
217 &node.ident,
218 NodeKind::Module,
219 node.span(),
220 is_public(&node.vis),
221 );
222 if node.content.is_some() {
223 collector.module_scope.enter(node.ident.to_string());
224 } else {
225 let target = collector
227 .module_scope
228 .declared_target(&node.ident.to_string());
229 collector.facts.imports.push(ImportFact::new(
230 target,
231 source_span(collector.path, node.span()),
232 ));
233 }
234 syn::visit::visit_item_mod(collector, node);
235 if node.content.is_some() {
236 collector.module_scope.leave();
237 }
238 });
239 }
240
241 fn visit_item_use(&mut self, node: &'ast syn::ItemUse) {
242 for target in use_tree_targets(&node.tree) {
243 let target = self.module_scope.target(&target);
244 let fact = ImportFact::new(target, source_span(self.path, node.span()));
245 if matches!(node.vis, syn::Visibility::Inherited) {
246 self.facts.imports.push(fact);
247 } else {
248 self.facts.reexports.push(fact);
249 }
250 }
251 syn::visit::visit_item_use(self, node);
252 }
253
254 fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) {
255 self.collect_call(node);
256 syn::visit::visit_expr_call(self, node);
257 }
258
259 fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) {
260 self.collect_method_call(node);
261 syn::visit::visit_expr_method_call(self, node);
262 }
263
264 fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
265 macro_calls::for_each_standard_argument(node, |argument| self.visit_expr(argument));
266 syn::visit::visit_expr_macro(self, node);
267 }
268
269 fn visit_type_path(&mut self, node: &'ast syn::TypePath) {
270 if node.qself.is_none()
272 && node.path.segments.len() == 1
273 && let Some(segment) = node.path.segments.last()
274 {
275 self.add_reference(
276 segment.ident.to_string(),
277 EdgeKind::References,
278 false,
279 node.span(),
280 );
281 }
282 syn::visit::visit_type_path(self, node);
283 }
284}
285#[cfg(test)]
286mod tests;