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