1use tree_sitter::Node;
5
6use crate::extract::ImportRef;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Lang {
10 Rust,
11 TypeScript,
12 Tsx,
13 Python,
14 Go,
15 Java,
16 Ruby,
17 C,
18 CSharp,
19 Sql,
20}
21
22impl Lang {
23 pub fn from_path(path: &str) -> Option<Lang> {
24 let ext = path.rsplit('.').next()?;
25 Some(match ext {
26 "rs" => Lang::Rust,
27 "ts" | "mts" | "cts" => Lang::TypeScript,
28 "tsx" | "jsx" | "js" | "mjs" | "cjs" => Lang::Tsx,
29 "py" | "pyi" => Lang::Python,
30 "go" => Lang::Go,
31 "java" => Lang::Java,
32 "rb" | "rake" => Lang::Ruby,
33 "c" | "h" => Lang::C,
34 "cs" | "csx" => Lang::CSharp,
35 "sql" => Lang::Sql,
36 _ => return None,
37 })
38 }
39
40 pub fn name(&self) -> &'static str {
41 match self {
42 Lang::Rust => "rust",
43 Lang::TypeScript | Lang::Tsx => "typescript",
44 Lang::Python => "python",
45 Lang::Go => "go",
46 Lang::Java => "java",
47 Lang::Ruby => "ruby",
48 Lang::C => "c",
49 Lang::CSharp => "csharp",
50 Lang::Sql => "sql",
51 }
52 }
53
54 pub fn language(&self) -> tree_sitter::Language {
55 match self {
56 Lang::Rust => tree_sitter_rust::LANGUAGE.into(),
57 Lang::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
58 Lang::Tsx => tree_sitter_typescript::LANGUAGE_TSX.into(),
59 Lang::Python => tree_sitter_python::LANGUAGE.into(),
60 Lang::Go => tree_sitter_go::LANGUAGE.into(),
61 Lang::Java => tree_sitter_java::LANGUAGE.into(),
62 Lang::Ruby => tree_sitter_ruby::LANGUAGE.into(),
63 Lang::C => tree_sitter_c::LANGUAGE.into(),
64 Lang::CSharp => tree_sitter_c_sharp::LANGUAGE.into(),
65 Lang::Sql => tree_sitter_sequel_tsql::LANGUAGE.into(),
66 }
67 }
68
69 pub fn separator(&self) -> String {
70 "::".into()
71 }
72
73 pub fn definition(&self, node: Node, src: &str) -> Option<(String, &'static str)> {
76 let text = |n: Node| src[n.byte_range()].to_string();
77 match self {
78 Lang::Rust => match node.kind() {
79 "function_item" => Some((text(node.child_by_field_name("name")?), "function")),
80 "struct_item" => Some((text(node.child_by_field_name("name")?), "struct")),
81 "enum_item" => Some((text(node.child_by_field_name("name")?), "enum")),
82 "trait_item" => Some((text(node.child_by_field_name("name")?), "trait")),
83 "union_item" => Some((text(node.child_by_field_name("name")?), "struct")),
84 _ => None,
85 },
86 Lang::TypeScript | Lang::Tsx => match node.kind() {
87 "function_declaration" | "generator_function_declaration" => {
88 Some((text(node.child_by_field_name("name")?), "function"))
89 }
90 "class_declaration" => Some((text(node.child_by_field_name("name")?), "class")),
91 "method_definition" => {
92 let name = text(node.child_by_field_name("name")?);
93 if name == "constructor" {
94 return None;
95 }
96 Some((name, "method"))
97 }
98 "interface_declaration" => {
99 Some((text(node.child_by_field_name("name")?), "interface"))
100 }
101 "enum_declaration" => Some((text(node.child_by_field_name("name")?), "enum")),
102 "type_alias_declaration" => Some((text(node.child_by_field_name("name")?), "type")),
103 "variable_declarator" => {
105 let value = node.child_by_field_name("value")?;
106 if matches!(value.kind(), "arrow_function" | "function_expression") {
107 let name = node.child_by_field_name("name")?;
108 if name.kind() == "identifier" {
109 return Some((text(name), "function"));
110 }
111 }
112 None
113 }
114 _ => None,
115 },
116 Lang::Python => match node.kind() {
117 "function_definition" => {
118 Some((text(node.child_by_field_name("name")?), "function"))
119 }
120 "class_definition" => Some((text(node.child_by_field_name("name")?), "class")),
121 _ => None,
122 },
123 Lang::Go => match node.kind() {
124 "function_declaration" => {
125 Some((text(node.child_by_field_name("name")?), "function"))
126 }
127 "method_declaration" => {
128 let name = text(node.child_by_field_name("name")?);
129 let recv = node
130 .child_by_field_name("receiver")
131 .and_then(|r| receiver_type(r, src));
132 Some((
133 match recv {
134 Some(t) => format!("{t}::{name}"),
135 None => name,
136 },
137 "method",
138 ))
139 }
140 "type_spec" => {
141 let name = text(node.child_by_field_name("name")?);
142 let kind = match node.child_by_field_name("type").map(|t| t.kind()) {
143 Some("struct_type") => "struct",
144 Some("interface_type") => "interface",
145 _ => "type",
146 };
147 Some((name, kind))
148 }
149 _ => None,
150 },
151 Lang::Java => match node.kind() {
152 "class_declaration" => Some((text(node.child_by_field_name("name")?), "class")),
153 "interface_declaration" => {
154 Some((text(node.child_by_field_name("name")?), "interface"))
155 }
156 "enum_declaration" => Some((text(node.child_by_field_name("name")?), "enum")),
157 "record_declaration" => Some((text(node.child_by_field_name("name")?), "class")),
158 "method_declaration" => Some((text(node.child_by_field_name("name")?), "method")),
159 "constructor_declaration" => {
160 Some((text(node.child_by_field_name("name")?), "method"))
161 }
162 _ => None,
163 },
164 Lang::Ruby => match node.kind() {
165 "class" => Some((const_name(node.child_by_field_name("name")?, src), "class")),
166 "module" => Some((const_name(node.child_by_field_name("name")?, src), "module")),
167 "method" => Some((text(node.child_by_field_name("name")?), "method")),
168 "singleton_method" => Some((text(node.child_by_field_name("name")?), "method")),
170 _ => None,
171 },
172 Lang::C => match node.kind() {
173 "function_definition" => Some((
174 c_declarator_name(node.child_by_field_name("declarator")?, src)?,
175 "function",
176 )),
177 "struct_specifier" => Some((text(node.child_by_field_name("name")?), "struct")),
178 "union_specifier" => Some((text(node.child_by_field_name("name")?), "struct")),
179 "enum_specifier" => Some((text(node.child_by_field_name("name")?), "enum")),
180 _ => None,
181 },
182 Lang::CSharp => match node.kind() {
183 "namespace_declaration" | "file_scoped_namespace_declaration" => {
186 Some((text(node.child_by_field_name("name")?), "namespace"))
187 }
188 "class_declaration" => Some((text(node.child_by_field_name("name")?), "class")),
189 "interface_declaration" => {
190 Some((text(node.child_by_field_name("name")?), "interface"))
191 }
192 "struct_declaration" => Some((text(node.child_by_field_name("name")?), "struct")),
193 "enum_declaration" => Some((text(node.child_by_field_name("name")?), "enum")),
194 "record_declaration" => Some((text(node.child_by_field_name("name")?), "class")),
196 "record_struct_declaration" => {
197 Some((text(node.child_by_field_name("name")?), "struct"))
198 }
199 "method_declaration" => Some((text(node.child_by_field_name("name")?), "method")),
200 "property_declaration" => {
201 Some((text(node.child_by_field_name("name")?), "property"))
202 }
203 "constructor_declaration" => {
205 Some((text(node.child_by_field_name("name")?), "constructor"))
206 }
207 _ => None,
208 },
209 Lang::Sql => match node.kind() {
210 "create_table" => sql_def_name(node, src).map(|n| (n, "table")),
211 "create_view" => sql_def_name(node, src).map(|n| (n, "view")),
212 "create_function" => sql_def_name(node, src).map(|n| (n, "function")),
213 "create_procedure" => sql_def_name(node, src).map(|n| (n, "procedure")),
214 _ => None,
215 },
216 }
217 }
218
219 pub fn scope_only(&self, node: Node, src: &str) -> Option<String> {
221 match self {
222 Lang::Rust => match node.kind() {
223 "impl_item" => {
225 let ty = node.child_by_field_name("type")?;
226 Some(base_type_name(ty, src))
227 }
228 "mod_item" => Some(src[node.child_by_field_name("name")?.byte_range()].to_string()),
229 _ => None,
230 },
231 _ => None,
232 }
233 }
234
235 pub fn body_field(&self) -> Option<&'static str> {
237 Some("body")
240 }
241
242 pub fn call(&self, node: Node, src: &str) -> Option<String> {
244 let text = |n: Node| src[n.byte_range()].to_string();
245 match self {
246 Lang::Rust => {
247 if node.kind() != "call_expression" {
248 return None;
249 }
250 let f = node.child_by_field_name("function")?;
251 match f.kind() {
252 "identifier" => Some(text(f)),
253 "field_expression" => f.child_by_field_name("field").map(text),
254 "scoped_identifier" => f.child_by_field_name("name").map(text),
255 "generic_function" => {
256 let inner = f.child_by_field_name("function")?;
257 match inner.kind() {
258 "identifier" => Some(text(inner)),
259 "scoped_identifier" => inner.child_by_field_name("name").map(text),
260 _ => None,
261 }
262 }
263 _ => None,
264 }
265 }
266 Lang::TypeScript | Lang::Tsx => {
267 if node.kind() != "call_expression" {
268 return None;
269 }
270 let f = node.child_by_field_name("function")?;
271 match f.kind() {
272 "identifier" => Some(text(f)),
273 "member_expression" => f.child_by_field_name("property").map(text),
274 _ => None,
275 }
276 }
277 Lang::Python => {
278 if node.kind() != "call" {
279 return None;
280 }
281 let f = node.child_by_field_name("function")?;
282 match f.kind() {
283 "identifier" => Some(text(f)),
284 "attribute" => f.child_by_field_name("attribute").map(text),
285 _ => None,
286 }
287 }
288 Lang::Go => {
289 if node.kind() != "call_expression" {
290 return None;
291 }
292 let f = node.child_by_field_name("function")?;
293 match f.kind() {
294 "identifier" => Some(text(f)),
295 "selector_expression" => f.child_by_field_name("field").map(text),
296 _ => None,
297 }
298 }
299 Lang::Java => {
300 if node.kind() != "method_invocation" {
301 return None;
302 }
303 node.child_by_field_name("name").map(text)
304 }
305 Lang::Ruby => {
306 if node.kind() != "call" {
308 return None;
309 }
310 node.child_by_field_name("method").map(text)
311 }
312 Lang::C => {
313 if node.kind() != "call_expression" {
314 return None;
315 }
316 let f = node.child_by_field_name("function")?;
317 match f.kind() {
318 "identifier" => Some(text(f)),
319 _ => None,
320 }
321 }
322 Lang::CSharp => {
323 if node.kind() != "invocation_expression" {
324 return None;
325 }
326 let f = node.child_by_field_name("function")?;
327 match f.kind() {
328 "identifier" => Some(text(f)),
329 "member_access_expression" => f.child_by_field_name("name").map(text),
331 _ => None,
332 }
333 }
334 Lang::Sql => {
335 if node.kind() != "object_reference" {
341 return None;
342 }
343 match node.parent()?.kind() {
344 "relation" | "from" | "column_definition" => {
345 Some(text(node.child_by_field_name("name").unwrap_or(node)))
346 }
347 _ => None,
348 }
349 }
350 }
351 }
352
353 pub fn imports(&self, node: Node, src: &str, out: &mut Vec<ImportRef>) {
355 let text = |n: Node| src[n.byte_range()].to_string();
356 match self {
357 Lang::Rust => {
358 if node.kind() == "use_declaration" {
359 if let Some(arg) = node.child_by_field_name("argument") {
360 rust_use_tree(arg, src, "", out);
361 }
362 }
363 }
364 Lang::TypeScript | Lang::Tsx => {
365 if node.kind() != "import_statement" {
366 return;
367 }
368 let Some(source) = node
369 .child_by_field_name("source")
370 .map(|s| text(s).trim_matches(['"', '\'']).to_string())
371 else {
372 return;
373 };
374 let mut cursor = node.walk();
375 for child in node.children(&mut cursor) {
376 if child.kind() != "import_clause" {
377 continue;
378 }
379 let mut c2 = child.walk();
380 for part in child.children(&mut c2) {
381 match part.kind() {
382 "identifier" => out.push(ImportRef {
383 local: text(part),
384 source: source.clone(),
385 }),
386 "named_imports" => {
387 let mut c3 = part.walk();
388 for spec in part.children(&mut c3) {
389 if spec.kind() != "import_specifier" {
390 continue;
391 }
392 let local = spec
393 .child_by_field_name("alias")
394 .or_else(|| spec.child_by_field_name("name"))
395 .map(text);
396 if let Some(local) = local {
397 out.push(ImportRef {
398 local,
399 source: source.clone(),
400 });
401 }
402 }
403 }
404 "namespace_import" => {
405 let mut c3 = part.walk();
407 for id in part.children(&mut c3) {
408 if id.kind() == "identifier" {
409 out.push(ImportRef {
410 local: text(id),
411 source: source.clone(),
412 });
413 }
414 }
415 }
416 _ => {}
417 }
418 }
419 }
420 }
421 Lang::Python => match node.kind() {
422 "import_statement" => {
423 let mut cursor = node.walk();
424 for child in node.children(&mut cursor) {
425 match child.kind() {
426 "dotted_name" => out.push(ImportRef {
427 local: text(child)
428 .rsplit('.')
429 .next()
430 .unwrap_or_default()
431 .to_string(),
432 source: text(child),
433 }),
434 "aliased_import" => {
435 let name = child.child_by_field_name("name").map(text);
436 let alias = child.child_by_field_name("alias").map(text);
437 if let (Some(name), Some(alias)) = (name, alias) {
438 out.push(ImportRef {
439 local: alias,
440 source: name,
441 });
442 }
443 }
444 _ => {}
445 }
446 }
447 }
448 "import_from_statement" => {
449 let Some(module) = node.child_by_field_name("module_name").map(text) else {
450 return;
451 };
452 let mut cursor = node.walk();
453 let mut past_import = false;
454 for child in node.children(&mut cursor) {
455 if child.kind() == "import" {
456 past_import = true;
457 continue;
458 }
459 if !past_import {
460 continue;
461 }
462 match child.kind() {
463 "dotted_name" => out.push(ImportRef {
464 local: text(child),
465 source: module.clone(),
466 }),
467 "aliased_import" => {
468 if let Some(alias) = child.child_by_field_name("alias").map(text) {
469 out.push(ImportRef {
470 local: alias,
471 source: module.clone(),
472 });
473 }
474 }
475 _ => {}
476 }
477 }
478 }
479 _ => {}
480 },
481 Lang::Go => {
482 if node.kind() != "import_spec" {
483 return;
484 }
485 let Some(path) = node
486 .child_by_field_name("path")
487 .map(|p| text(p).trim_matches('"').to_string())
488 else {
489 return;
490 };
491 let local = node
492 .child_by_field_name("name")
493 .map(text)
494 .unwrap_or_else(|| path.rsplit('/').next().unwrap_or(&path).to_string());
495 out.push(ImportRef {
496 local,
497 source: path,
498 });
499 }
500 Lang::Java => {
501 if node.kind() != "import_declaration" {
503 return;
504 }
505 let mut cursor = node.walk();
506 let Some(scoped) = node
507 .children(&mut cursor)
508 .find(|c| c.kind() == "scoped_identifier")
509 else {
510 return;
511 };
512 let source = text(scoped);
513 let local = source.rsplit('.').next().unwrap_or(&source).to_string();
514 out.push(ImportRef { local, source });
515 }
516 Lang::C => {
517 if node.kind() != "preproc_include" {
519 return;
520 }
521 let Some(path_node) = node.child_by_field_name("path") else {
522 return;
523 };
524 let source = text(path_node).trim_matches(['"', '<', '>']).to_string();
525 let local = source
526 .rsplit('/')
527 .next()
528 .unwrap_or(&source)
529 .trim_end_matches(".h")
530 .to_string();
531 out.push(ImportRef { local, source });
532 }
533 Lang::CSharp => {
534 if node.kind() != "using_directive" {
537 return;
538 }
539 let mut cursor = node.walk();
540 let names: Vec<String> = node
541 .children(&mut cursor)
542 .filter(|c| {
543 matches!(
544 c.kind(),
545 "identifier" | "qualified_name" | "alias_qualified_name"
546 )
547 })
548 .map(text)
549 .collect();
550 match names.as_slice() {
551 [alias, source, ..] => out.push(ImportRef {
553 local: alias.clone(),
554 source: source.clone(),
555 }),
556 [source] => out.push(ImportRef {
558 local: source.rsplit('.').next().unwrap_or(source).to_string(),
559 source: source.clone(),
560 }),
561 [] => {}
562 }
563 }
564 Lang::Ruby => {}
567 Lang::Sql => {}
569 }
570 }
571
572 pub fn doc_comment(&self, node: Node, src: &str) -> Option<String> {
574 match self {
575 Lang::Python => {
576 let body = node.child_by_field_name("body")?;
578 let first = body.named_child(0)?;
579 if first.kind() != "expression_statement" {
580 return None;
581 }
582 let s = first.named_child(0)?;
583 if s.kind() != "string" {
584 return None;
585 }
586 let raw = &src[s.byte_range()];
587 let cleaned = raw
588 .trim_start_matches(['r', 'b', 'f', 'u', 'R', 'B', 'F', 'U'])
589 .trim_matches(['"', '\''])
590 .trim();
591 Some(cleaned.lines().next().unwrap_or("").trim().to_string())
592 .filter(|s| !s.is_empty())
593 }
594 Lang::Rust
595 | Lang::Go
596 | Lang::TypeScript
597 | Lang::Tsx
598 | Lang::Java
599 | Lang::C
600 | Lang::CSharp
601 | Lang::Ruby
602 | Lang::Sql => {
603 let mut anchor = node;
609 if matches!(self, Lang::Sql) {
610 while let Some(p) = anchor.parent() {
611 if p.kind() == "statement" {
612 anchor = p;
613 } else {
614 break;
615 }
616 }
617 }
618 let mut lines: Vec<String> = Vec::new();
619 let mut expect_row = anchor.start_position().row;
620 let mut prev = anchor.prev_sibling();
621 while let Some(p) = prev {
622 if !p.kind().contains("comment")
623 || expect_row.saturating_sub(p.end_position().row) > 1
624 || src[p.byte_range()].starts_with("//!")
625 {
626 break;
627 }
628 lines.push(src[p.byte_range()].to_string());
629 expect_row = p.start_position().row;
630 prev = p.prev_sibling();
631 }
632 if lines.is_empty() {
633 return None;
634 }
635 lines.reverse();
636 let cleaned: Vec<String> = lines
637 .iter()
638 .flat_map(|c| c.lines())
639 .map(|l| {
640 l.trim()
641 .trim_start_matches("///")
642 .trim_start_matches("//!")
643 .trim_start_matches("//")
644 .trim_start_matches("--") .trim_start_matches("/**")
646 .trim_start_matches("/*")
647 .trim_end_matches("*/")
648 .trim_start_matches('*')
649 .trim_start_matches('#') .trim()
651 .to_string()
652 })
653 .filter(|l| !l.is_empty())
654 .collect();
655 if cleaned.is_empty() {
656 None
657 } else {
658 Some(cleaned.join(" ").chars().take(300).collect())
659 }
660 }
661 }
662 }
663}
664
665fn sql_def_name(node: Node, src: &str) -> Option<String> {
669 let mut cursor = node.walk();
670 let obj = node
671 .children(&mut cursor)
672 .find(|c| c.kind() == "object_reference")?;
673 let name = obj.child_by_field_name("name").unwrap_or(obj);
674 Some(src[name.byte_range()].to_string())
675}
676
677fn base_type_name(ty: Node, src: &str) -> String {
679 match ty.kind() {
680 "generic_type" => ty
681 .child_by_field_name("type")
682 .map(|t| src[t.byte_range()].to_string())
683 .unwrap_or_else(|| src[ty.byte_range()].to_string()),
684 _ => src[ty.byte_range()].to_string(),
685 }
686}
687
688fn const_name(node: Node, src: &str) -> String {
691 let full = src[node.byte_range()].to_string();
692 full.rsplit("::").next().unwrap_or(&full).to_string()
693}
694
695fn c_declarator_name(node: Node, src: &str) -> Option<String> {
698 match node.kind() {
699 "identifier" => Some(src[node.byte_range()].to_string()),
700 "function_declarator" | "pointer_declarator" | "parenthesized_declarator" => {
701 c_declarator_name(node.child_by_field_name("declarator")?, src)
702 }
703 _ => {
704 let mut cursor = node.walk();
706 for child in node.children(&mut cursor) {
707 if let Some(name) = c_declarator_name(child, src) {
708 return Some(name);
709 }
710 }
711 None
712 }
713 }
714}
715
716fn receiver_type(receiver: Node, src: &str) -> Option<String> {
718 let mut cursor = receiver.walk();
719 for child in receiver.children(&mut cursor) {
720 if child.kind() == "parameter_declaration" {
721 let ty = child.child_by_field_name("type")?;
722 let base = match ty.kind() {
723 "pointer_type" => ty.named_child(0)?,
724 _ => ty,
725 };
726 return Some(src[base.byte_range()].to_string());
727 }
728 }
729 None
730}
731
732fn rust_use_tree(node: Node, src: &str, prefix: &str, out: &mut Vec<ImportRef>) {
734 let text = |n: Node| src[n.byte_range()].to_string();
735 let join = |prefix: &str, seg: &str| {
736 if prefix.is_empty() {
737 seg.to_string()
738 } else {
739 format!("{prefix}::{seg}")
740 }
741 };
742 match node.kind() {
743 "identifier" | "crate" | "self" | "super" => {
744 let seg = text(node);
745 out.push(ImportRef {
746 local: seg.clone(),
747 source: join(prefix, &seg),
748 });
749 }
750 "scoped_identifier" => {
751 let full = join(prefix, &text(node));
752 let local = node
753 .child_by_field_name("name")
754 .map(text)
755 .unwrap_or_default();
756 if !local.is_empty() {
757 out.push(ImportRef {
758 local,
759 source: full,
760 });
761 }
762 }
763 "use_as_clause" => {
764 let alias = node.child_by_field_name("alias").map(text);
765 let path = node.child_by_field_name("path").map(text);
766 if let (Some(alias), Some(path)) = (alias, path) {
767 out.push(ImportRef {
768 local: alias,
769 source: join(prefix, &path),
770 });
771 }
772 }
773 "scoped_use_list" => {
774 let new_prefix = node
775 .child_by_field_name("path")
776 .map(|p| join(prefix, &text(p)))
777 .unwrap_or_else(|| prefix.to_string());
778 if let Some(list) = node.child_by_field_name("list") {
779 let mut cursor = list.walk();
780 for child in list.named_children(&mut cursor) {
781 rust_use_tree(child, src, &new_prefix, out);
782 }
783 }
784 }
785 "use_list" => {
786 let mut cursor = node.walk();
787 for child in node.named_children(&mut cursor) {
788 rust_use_tree(child, src, prefix, out);
789 }
790 }
791 _ => {}
793 }
794}