1use tree_sitter::Node;
4
5use super::{field_text, line, named_children, text, LangSpec, RawContract, Target};
6use crate::facts::{Args, Import, ImportItem, RefKind, TemplatePart, TypeSource, ValueExpr};
7use crate::model::SymbolKind;
8
9pub struct Rust;
10
11const WRAPPERS: &[&str] = &[
13 "Box", "Arc", "Rc", "Option", "Result", "RefCell", "Mutex", "RwLock", "Cow",
14];
15
16const VERBS: &[&str] = &["get", "post", "put", "patch", "delete", "head"];
17
18impl LangSpec for Rust {
19 fn receiver(&self) -> &'static str {
20 "self"
21 }
22
23 fn contracts(&self, node: Node, src: &[u8]) -> Vec<RawContract> {
24 match node.kind() {
25 "function_item" => attribute_route(node, src).into_iter().collect(),
27 "call_expression" => {
28 let Some(f) = node.child_by_field_name("function") else {
29 return Vec::new();
30 };
31 let Some(list) = node.child_by_field_name("arguments") else {
32 return Vec::new();
33 };
34 let args = named_children(list);
35 if f.kind() == "field_expression" && field_text(f, "field", src) == Some("route") {
37 if let [p, chain] = args.as_slice() {
38 if let Some(ValueExpr::Lit(path)) = value(*p, src) {
39 let mut out = Vec::new();
40 method_router(*chain, src, &mut out);
41 return out
42 .into_iter()
43 .map(|(verb, handler)| RawContract::Handle {
44 receiver: Vec::new(),
45 method: verb.to_uppercase(),
46 path: ValueExpr::Lit(path.clone()),
47 handler,
48 })
49 .collect();
50 }
51 }
52 return Vec::new();
53 }
54 let Some(mut p) = path(f, src) else {
56 return Vec::new();
57 };
58 let Some(method) = p.pop() else {
59 return Vec::new();
60 };
61 if !VERBS.contains(&method.as_str()) || args.is_empty() {
62 return Vec::new();
63 }
64 vec![RawContract::Op {
65 receiver: p,
66 method,
67 args: Args {
68 positional: args.iter().map(|n| value(*n, src)).collect(),
69 ..Default::default()
70 },
71 }]
72 }
73 _ => Vec::new(),
74 }
75 }
76
77 fn value(&self, node: Node, src: &[u8]) -> Option<(String, ValueExpr)> {
78 if !matches!(node.kind(), "const_item" | "static_item") {
79 return None;
80 }
81 Some((
82 field_text(node, "name", src)?.to_string(),
83 value(node.child_by_field_name("value")?, src)?,
84 ))
85 }
86
87 fn def(&self, node: Node, src: &[u8], _in_class: bool) -> Option<(SymbolKind, String)> {
88 let kind = match node.kind() {
89 "function_item" if in_impl_or_trait(node) => SymbolKind::Method,
90 "function_item" => SymbolKind::Function,
91 "function_signature_item" => SymbolKind::Method,
92 "struct_item" | "union_item" => SymbolKind::Struct,
93 "enum_item" => SymbolKind::Enum,
94 "enum_variant" => SymbolKind::Constant,
95 "trait_item" => SymbolKind::Trait,
96 "type_item" => SymbolKind::TypeAlias,
97 "mod_item" if node.child_by_field_name("body").is_some() => SymbolKind::Module,
99 _ => return None,
100 };
101 Some((kind, field_text(node, "name", src)?.to_string()))
102 }
103
104 fn owner(&self, def: Node, src: &[u8]) -> Option<String> {
105 if !matches!(def.kind(), "function_item" | "function_signature_item") {
106 return None;
107 }
108 let imp = def.parent()?.parent()?;
109 if imp.kind() != "impl_item" {
110 return None;
111 }
112 type_path(imp.child_by_field_name("type")?, src)?.pop()
113 }
114
115 fn owned_bases(&self, node: Node, src: &[u8]) -> Option<(String, RefKind, Vec<String>)> {
116 if node.kind() != "impl_item" {
117 return None;
118 }
119 let ty = type_path(node.child_by_field_name("type")?, src)?.pop()?;
120 let tr = type_path(node.child_by_field_name("trait")?, src)?;
121 Some((ty, RefKind::Implements, tr))
122 }
123
124 fn variable(&self, node: Node, src: &[u8]) -> Option<String> {
125 matches!(node.kind(), "const_item" | "static_item")
126 .then(|| field_text(node, "name", src).map(str::to_string))?
127 }
128
129 fn import(&self, node: Node, src: &[u8]) -> Option<Vec<Import>> {
130 if node.kind() != "use_declaration" {
131 return None;
132 }
133 let mut flat = Vec::new();
134 flatten_use(node.child_by_field_name("argument")?, &[], src, &mut flat);
135 Some(
136 flat.into_iter()
137 .map(|(module, item)| Import {
138 module: module.join("::"),
139 level: 0,
140 items: vec![item],
141 line: line(node),
142 file: None,
143 })
144 .collect(),
145 )
146 }
147
148 fn reference(&self, node: Node, src: &[u8]) -> Option<(RefKind, Vec<String>)> {
149 match node.kind() {
150 "call_expression" => Some((
151 RefKind::Call,
152 path(node.child_by_field_name("function")?, src)?,
153 )),
154 "struct_expression" => Some((
155 RefKind::New,
156 type_path(node.child_by_field_name("name")?, src)?,
157 )),
158 _ => None,
159 }
160 }
161
162 fn assignment(&self, node: Node, src: &[u8]) -> Option<(Target, TypeSource)> {
163 match node.kind() {
164 "let_declaration" => {
165 let pat = node.child_by_field_name("pattern")?;
166 if pat.kind() != "identifier" {
167 return None;
168 }
169 let target = Target::Var(text(pat, src).to_string());
170 if let Some(t) = node
171 .child_by_field_name("type")
172 .and_then(|t| type_path(t, src))
173 {
174 return Some((target, TypeSource::Annotation(t)));
175 }
176 Some((target, value_type(node.child_by_field_name("value")?, src)?))
177 }
178 "field_declaration" => Some((
179 Target::Attr(field_text(node, "name", src)?.to_string()),
180 TypeSource::Annotation(type_path(node.child_by_field_name("type")?, src)?),
181 )),
182 _ => None,
183 }
184 }
185
186 fn bases(&self, def: Node, src: &[u8]) -> Vec<(RefKind, Vec<String>)> {
187 def.child_by_field_name("bounds")
189 .filter(|_| def.kind() == "trait_item")
190 .map(|b| {
191 named_children(b)
192 .into_iter()
193 .filter_map(|t| type_path(t, src))
194 .map(|p| (RefKind::Inherits, p))
195 .collect()
196 })
197 .unwrap_or_default()
198 }
199
200 fn returns(&self, def: Node, src: &[u8]) -> Option<Vec<String>> {
201 type_path(def.child_by_field_name("return_type")?, src)
202 }
203
204 fn params(&self, def: Node, src: &[u8]) -> Vec<(String, Vec<String>, bool)> {
205 let Some(ps) = def.child_by_field_name("parameters") else {
206 return Vec::new();
207 };
208 named_children(ps)
209 .into_iter()
210 .filter(|p| p.kind() == "parameter")
211 .filter_map(|p| {
212 let pat = p.child_by_field_name("pattern")?;
213 (pat.kind() == "identifier").then_some(())?;
214 Some((
215 text(pat, src).to_string(),
216 type_path(p.child_by_field_name("type")?, src)?,
217 false,
218 ))
219 })
220 .collect()
221 }
222}
223
224fn in_impl_or_trait(node: Node) -> bool {
225 node.parent()
226 .and_then(|p| p.parent())
227 .is_some_and(|g| matches!(g.kind(), "impl_item" | "trait_item"))
228}
229
230fn flatten_use(
232 node: Node,
233 prefix: &[String],
234 src: &[u8],
235 out: &mut Vec<(Vec<String>, ImportItem)>,
236) {
237 let with = |segs: Vec<String>| -> Vec<String> {
238 let mut p = prefix.to_vec();
239 p.extend(segs);
240 p
241 };
242 match node.kind() {
243 "identifier" | "crate" | "super" => {
244 let name = text(node, src).to_string();
245 if prefix.is_empty() {
246 out.push((vec![name.clone()], ImportItem::Module { alias: Some(name) }));
247 } else {
248 out.push((prefix.to_vec(), ImportItem::Name { name, alias: None }));
249 }
250 }
251 "self" => out.push((
253 prefix.to_vec(),
254 ImportItem::Module {
255 alias: prefix.last().cloned(),
256 },
257 )),
258 "scoped_identifier" => {
259 if let Some(mut segs) = segments(node, src) {
260 let name = segs.pop().unwrap_or_default();
261 if name == "self" {
262 let alias = segs.last().cloned();
263 out.push((with(segs), ImportItem::Module { alias }));
264 } else {
265 out.push((with(segs), ImportItem::Name { name, alias: None }));
266 }
267 }
268 }
269 "use_as_clause" => {
270 let alias = field_text(node, "alias", src).map(str::to_string);
271 let Some(mut segs) = node
272 .child_by_field_name("path")
273 .and_then(|p| segments(p, src))
274 else {
275 return;
276 };
277 if segs.len() == 1 && prefix.is_empty() {
278 out.push((segs, ImportItem::Module { alias }));
279 } else {
280 let name = segs.pop().unwrap_or_default();
281 out.push((with(segs), ImportItem::Name { name, alias }));
282 }
283 }
284 "use_wildcard" => {
285 if let Some(segs) = named_children(node)
286 .into_iter()
287 .next()
288 .and_then(|p| segments(p, src))
289 {
290 out.push((with(segs), ImportItem::All));
291 }
292 }
293 "scoped_use_list" => {
294 let base = node
295 .child_by_field_name("path")
296 .and_then(|p| segments(p, src))
297 .unwrap_or_default();
298 if let Some(list) = node.child_by_field_name("list") {
299 let p = with(base);
300 for c in named_children(list) {
301 flatten_use(c, &p, src, out);
302 }
303 }
304 }
305 "use_list" => {
306 for c in named_children(node) {
307 flatten_use(c, prefix, src, out);
308 }
309 }
310 _ => {}
311 }
312}
313
314fn segments(node: Node, src: &[u8]) -> Option<Vec<String>> {
316 match node.kind() {
317 "identifier" | "crate" | "self" | "super" | "type_identifier" => {
318 Some(vec![text(node, src).to_string()])
319 }
320 "scoped_identifier" | "scoped_type_identifier" => {
321 let mut p = match node.child_by_field_name("path") {
322 Some(p) => segments(p, src)?,
323 None => Vec::new(),
324 };
325 p.push(field_text(node, "name", src)?.to_string());
326 Some(p)
327 }
328 _ => None,
329 }
330}
331
332fn attribute_route(func: Node, src: &[u8]) -> Option<RawContract> {
334 let mut prev = func.prev_named_sibling();
335 while let Some(a) = prev.filter(|p| matches!(p.kind(), "attribute_item" | "line_comment")) {
336 prev = a.prev_named_sibling();
337 let Some(attr) = named_children(a)
338 .into_iter()
339 .find(|c| c.kind() == "attribute")
340 else {
341 continue;
342 };
343 let kids = named_children(attr);
344 let verb = kids.first().and_then(|n| path(*n, src))?.pop()?;
345 if !VERBS.contains(&verb.as_str()) {
346 continue;
347 }
348 let lit = kids
349 .iter()
350 .find(|k| k.kind() == "token_tree")
351 .and_then(|t| {
352 named_children(*t)
353 .into_iter()
354 .find(|c| c.kind() == "string_literal")
355 })
356 .and_then(|s| value(s, src))?;
357 return Some(RawContract::Route {
358 handler: field_text(func, "name", src)?.to_string(),
359 receiver: Vec::new(),
360 method: verb.to_uppercase(),
361 path: lit,
362 client: false,
363 });
364 }
365 None
366}
367
368fn method_router(node: Node, src: &[u8], out: &mut Vec<(String, Vec<String>)>) {
370 if node.kind() != "call_expression" {
371 return;
372 }
373 let (Some(f), Some(list)) = (
374 node.child_by_field_name("function"),
375 node.child_by_field_name("arguments"),
376 ) else {
377 return;
378 };
379 let (verb, inner) = match f.kind() {
380 "field_expression" => (
381 field_text(f, "field", src).unwrap_or_default().to_string(),
382 f.child_by_field_name("value"),
383 ),
384 _ => (
385 path(f, src).and_then(|mut p| p.pop()).unwrap_or_default(),
386 None,
387 ),
388 };
389 if let Some(i) = inner {
390 method_router(i, src, out);
391 }
392 if VERBS.contains(&verb.as_str()) {
393 let handler = named_children(list)
394 .first()
395 .filter(|h| h.kind() != "closure_expression")
396 .and_then(|h| path(*h, src))
397 .unwrap_or_default();
398 out.push((verb, handler));
399 }
400}
401
402fn value(node: Node, src: &[u8]) -> Option<ValueExpr> {
404 match node.kind() {
405 "string_literal" | "raw_string_literal" => Some(ValueExpr::Lit(
406 named_children(node)
407 .iter()
408 .filter(|c| c.kind() == "string_content")
409 .map(|c| text(*c, src))
410 .collect(),
411 )),
412 "identifier" | "scoped_identifier" => path(node, src).map(ValueExpr::Name),
413 "reference_expression" => value(node.child_by_field_name("value")?, src),
414 "macro_invocation" if field_text(node, "macro", src) == Some("format") => {
415 let tt = named_children(node)
416 .into_iter()
417 .find(|c| c.kind() == "token_tree")?;
418 let kids = named_children(tt);
419 let fmt = value(*kids.first().filter(|k| k.kind() == "string_literal")?, src)?;
420 let ValueExpr::Lit(fmt) = fmt else {
421 return None;
422 };
423 let mut positional = kids[1..]
425 .iter()
426 .map(|k| (k.kind() == "identifier").then(|| text(*k, src).to_string()));
427 let mut parts = Vec::new();
428 let mut rest = fmt.as_str();
429 while let Some(open) = rest.find('{') {
430 parts.push(TemplatePart::Lit(rest[..open].to_string()));
431 let close = rest[open..].find('}').map_or(rest.len(), |c| open + c);
432 let name = rest[open + 1..close].split(':').next().unwrap_or("");
433 let expr = if name.is_empty() {
434 positional.next().flatten().map(|n| vec![n])
435 } else {
436 Some(vec![name.to_string()])
437 };
438 parts.push(TemplatePart::Expr(expr));
439 rest = rest.get(close + 1..).unwrap_or("");
440 }
441 parts.push(TemplatePart::Lit(rest.to_string()));
442 Some(ValueExpr::Template(parts))
443 }
444 _ => None,
445 }
446}
447
448fn path(node: Node, src: &[u8]) -> Option<Vec<String>> {
449 match node.kind() {
450 "identifier" | "self" | "crate" | "super" => Some(vec![text(node, src).to_string()]),
451 "scoped_identifier" => {
452 let mut p = segments(node, src)?;
453 if p.first().is_some_and(|s| s == "self") {
454 p[0] = "<self>".into();
455 }
456 Some(p)
457 }
458 "field_expression" => {
459 let mut p = path(node.child_by_field_name("value")?, src)?;
460 p.push(field_text(node, "field", src)?.to_string());
461 Some(p)
462 }
463 "generic_function" => path(node.child_by_field_name("function")?, src),
464 _ => None,
465 }
466}
467
468fn type_path(node: Node, src: &[u8]) -> Option<Vec<String>> {
469 match node.kind() {
470 "type_identifier" | "identifier" => Some(vec![text(node, src).to_string()]),
471 "scoped_type_identifier" | "scoped_identifier" => segments(node, src),
472 "reference_type" => type_path(node.child_by_field_name("type")?, src),
473 "generic_type" | "generic_type_with_turbofish" => {
474 let base = type_path(node.child_by_field_name("type")?, src)?;
475 if base.last().is_some_and(|b| WRAPPERS.contains(&b.as_str())) {
476 let args = node.child_by_field_name("type_arguments")?;
477 return named_children(args)
478 .into_iter()
479 .find_map(|a| type_path(a, src));
480 }
481 Some(base)
482 }
483 _ => None,
484 }
485}
486
487fn value_type(v: Node, src: &[u8]) -> Option<TypeSource> {
489 match v.kind() {
490 "call_expression" => Some(TypeSource::Construct(path(
491 v.child_by_field_name("function")?,
492 src,
493 )?)),
494 "struct_expression" => Some(TypeSource::Construct(type_path(
495 v.child_by_field_name("name")?,
496 src,
497 )?)),
498 "try_expression" | "await_expression" | "reference_expression" => {
499 value_type(named_children(v).into_iter().next()?, src)
500 }
501 "identifier" => Some(TypeSource::Alias {
502 name: text(v, src).to_string(),
503 scope: String::new(),
504 }),
505 _ => None,
506 }
507}