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