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 Cpp,
21 Kotlin,
22 Swift,
23 Php,
24}
25
26impl Lang {
27 pub fn from_path(path: &str) -> Option<Lang> {
28 let ext = path.rsplit('.').next()?;
29 Some(match ext {
30 "rs" => Lang::Rust,
31 "ts" | "mts" | "cts" => Lang::TypeScript,
32 "tsx" | "jsx" | "js" | "mjs" | "cjs" => Lang::Tsx,
33 "py" | "pyi" => Lang::Python,
34 "go" => Lang::Go,
35 "java" => Lang::Java,
36 "rb" | "rake" => Lang::Ruby,
37 "c" => Lang::C,
38 "cpp" | "cc" | "cxx" | "hpp" | "hxx" | "h" => Lang::Cpp,
43 "kt" | "kts" => Lang::Kotlin,
44 "swift" => Lang::Swift,
45 "php" => Lang::Php,
46 "cs" | "csx" => Lang::CSharp,
47 "sql" => Lang::Sql,
48 _ => return None,
49 })
50 }
51
52 pub fn name(&self) -> &'static str {
53 match self {
54 Lang::Rust => "rust",
55 Lang::TypeScript | Lang::Tsx => "typescript",
56 Lang::Python => "python",
57 Lang::Go => "go",
58 Lang::Java => "java",
59 Lang::Ruby => "ruby",
60 Lang::C => "c",
61 Lang::CSharp => "csharp",
62 Lang::Sql => "sql",
63 Lang::Cpp => "cpp",
64 Lang::Kotlin => "kotlin",
65 Lang::Swift => "swift",
66 Lang::Php => "php",
67 }
68 }
69
70 pub fn language(&self) -> tree_sitter::Language {
71 match self {
72 Lang::Rust => tree_sitter_rust::LANGUAGE.into(),
73 Lang::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
74 Lang::Tsx => tree_sitter_typescript::LANGUAGE_TSX.into(),
75 Lang::Python => tree_sitter_python::LANGUAGE.into(),
76 Lang::Go => tree_sitter_go::LANGUAGE.into(),
77 Lang::Java => tree_sitter_java::LANGUAGE.into(),
78 Lang::Ruby => tree_sitter_ruby::LANGUAGE.into(),
79 Lang::C => tree_sitter_c::LANGUAGE.into(),
80 Lang::CSharp => tree_sitter_c_sharp::LANGUAGE.into(),
81 Lang::Sql => tree_sitter_sequel_tsql::LANGUAGE.into(),
82 Lang::Cpp => tree_sitter_cpp::LANGUAGE.into(),
83 Lang::Kotlin => tree_sitter_kotlin_ng::LANGUAGE.into(),
84 Lang::Swift => tree_sitter_swift::LANGUAGE.into(),
85 Lang::Php => tree_sitter_php::LANGUAGE_PHP.into(),
86 }
87 }
88
89 pub fn separator(&self) -> String {
90 "::".into()
91 }
92
93 pub fn definition(&self, node: Node, src: &str) -> Option<(String, &'static str)> {
96 let text = |n: Node| src[n.byte_range()].to_string();
97 match self {
98 Lang::Rust => match node.kind() {
99 "function_item" => Some((text(node.child_by_field_name("name")?), "function")),
100 "struct_item" => Some((text(node.child_by_field_name("name")?), "struct")),
101 "enum_item" => Some((text(node.child_by_field_name("name")?), "enum")),
102 "trait_item" => Some((text(node.child_by_field_name("name")?), "trait")),
103 "union_item" => Some((text(node.child_by_field_name("name")?), "struct")),
104 _ => None,
105 },
106 Lang::TypeScript | Lang::Tsx => match node.kind() {
107 "function_declaration" | "generator_function_declaration" => {
108 Some((text(node.child_by_field_name("name")?), "function"))
109 }
110 "class_declaration" => Some((text(node.child_by_field_name("name")?), "class")),
111 "method_definition" => {
112 let name = text(node.child_by_field_name("name")?);
113 if name == "constructor" {
114 return None;
115 }
116 Some((name, "method"))
117 }
118 "interface_declaration" => {
119 Some((text(node.child_by_field_name("name")?), "interface"))
120 }
121 "enum_declaration" => Some((text(node.child_by_field_name("name")?), "enum")),
122 "type_alias_declaration" => Some((text(node.child_by_field_name("name")?), "type")),
123 "variable_declarator" => {
125 let value = node.child_by_field_name("value")?;
126 if matches!(value.kind(), "arrow_function" | "function_expression") {
127 let name = node.child_by_field_name("name")?;
128 if name.kind() == "identifier" {
129 return Some((text(name), "function"));
130 }
131 }
132 None
133 }
134 _ => None,
135 },
136 Lang::Python => match node.kind() {
137 "function_definition" => {
138 Some((text(node.child_by_field_name("name")?), "function"))
139 }
140 "class_definition" => Some((text(node.child_by_field_name("name")?), "class")),
141 _ => None,
142 },
143 Lang::Go => match node.kind() {
144 "function_declaration" => {
145 Some((text(node.child_by_field_name("name")?), "function"))
146 }
147 "method_declaration" => {
148 let name = text(node.child_by_field_name("name")?);
149 let recv = node
150 .child_by_field_name("receiver")
151 .and_then(|r| receiver_type(r, src));
152 Some((
153 match recv {
154 Some(t) => format!("{t}::{name}"),
155 None => name,
156 },
157 "method",
158 ))
159 }
160 "type_spec" => {
161 let name = text(node.child_by_field_name("name")?);
162 let kind = match node.child_by_field_name("type").map(|t| t.kind()) {
163 Some("struct_type") => "struct",
164 Some("interface_type") => "interface",
165 _ => "type",
166 };
167 Some((name, kind))
168 }
169 _ => None,
170 },
171 Lang::Java => match node.kind() {
172 "class_declaration" => Some((text(node.child_by_field_name("name")?), "class")),
173 "interface_declaration" => {
174 Some((text(node.child_by_field_name("name")?), "interface"))
175 }
176 "enum_declaration" => Some((text(node.child_by_field_name("name")?), "enum")),
177 "record_declaration" => Some((text(node.child_by_field_name("name")?), "class")),
178 "method_declaration" => Some((text(node.child_by_field_name("name")?), "method")),
179 "constructor_declaration" => {
180 Some((text(node.child_by_field_name("name")?), "method"))
181 }
182 _ => None,
183 },
184 Lang::Ruby => match node.kind() {
185 "class" => Some((const_name(node.child_by_field_name("name")?, src), "class")),
186 "module" => Some((const_name(node.child_by_field_name("name")?, src), "module")),
187 "method" => Some((text(node.child_by_field_name("name")?), "method")),
188 "singleton_method" => Some((text(node.child_by_field_name("name")?), "method")),
190 _ => None,
191 },
192 Lang::C => match node.kind() {
193 "function_definition" => Some((
194 c_declarator_name(node.child_by_field_name("declarator")?, src)?,
195 "function",
196 )),
197 "struct_specifier" => Some((text(node.child_by_field_name("name")?), "struct")),
198 "union_specifier" => Some((text(node.child_by_field_name("name")?), "struct")),
199 "enum_specifier" => Some((text(node.child_by_field_name("name")?), "enum")),
200 _ => None,
201 },
202 Lang::CSharp => match node.kind() {
203 "namespace_declaration" | "file_scoped_namespace_declaration" => {
206 Some((text(node.child_by_field_name("name")?), "namespace"))
207 }
208 "class_declaration" => Some((text(node.child_by_field_name("name")?), "class")),
209 "interface_declaration" => {
210 Some((text(node.child_by_field_name("name")?), "interface"))
211 }
212 "struct_declaration" => Some((text(node.child_by_field_name("name")?), "struct")),
213 "enum_declaration" => Some((text(node.child_by_field_name("name")?), "enum")),
214 "record_declaration" => Some((text(node.child_by_field_name("name")?), "class")),
216 "record_struct_declaration" => {
217 Some((text(node.child_by_field_name("name")?), "struct"))
218 }
219 "method_declaration" => Some((text(node.child_by_field_name("name")?), "method")),
220 "property_declaration" => {
221 Some((text(node.child_by_field_name("name")?), "property"))
222 }
223 "constructor_declaration" => {
225 Some((text(node.child_by_field_name("name")?), "constructor"))
226 }
227 _ => None,
228 },
229 Lang::Sql => match node.kind() {
230 "create_table" => sql_def_name(node, src).map(|n| (n, "table")),
231 "create_view" => sql_def_name(node, src).map(|n| (n, "view")),
232 "create_function" => sql_def_name(node, src).map(|n| (n, "function")),
233 "create_procedure" => sql_def_name(node, src).map(|n| (n, "procedure")),
234 _ => None,
235 },
236 Lang::Cpp => match node.kind() {
237 "function_definition" => {
243 let name = c_declarator_name(node.child_by_field_name("declarator")?, src)?;
244 let kind = if name.contains("::") {
245 "method"
246 } else {
247 "function"
248 };
249 Some((name, kind))
250 }
251 "class_specifier" => Some((text(node.child_by_field_name("name")?), "class")),
252 "struct_specifier" => Some((text(node.child_by_field_name("name")?), "struct")),
253 "union_specifier" => Some((text(node.child_by_field_name("name")?), "struct")),
254 "enum_specifier" => Some((text(node.child_by_field_name("name")?), "enum")),
255 "namespace_definition" => {
256 Some((text(node.child_by_field_name("name")?), "namespace"))
257 }
258 _ => None,
259 },
260 Lang::Kotlin => match node.kind() {
264 "class_declaration" => {
265 let name = text(node.child_by_field_name("name")?);
266 let mut cursor = node.walk();
267 let kind = if node
268 .children(&mut cursor)
269 .any(|c| c.kind() == "enum_class_body")
270 {
271 "enum"
272 } else {
273 "class"
274 };
275 Some((name, kind))
276 }
277 "object_declaration" => Some((text(node.child_by_field_name("name")?), "object")),
278 "function_declaration" => {
279 Some((text(node.child_by_field_name("name")?), "function"))
280 }
281 _ => None,
282 },
283 Lang::Swift => match node.kind() {
292 "class_declaration" => {
293 let kind = match node
294 .child_by_field_name("declaration_kind")
295 .map(text)
296 .as_deref()
297 {
298 Some("struct") => "struct",
299 Some("enum") => "enum",
300 _ => "class",
301 };
302 Some((text(node.child_by_field_name("name")?), kind))
303 }
304 "protocol_declaration" => {
305 Some((text(node.child_by_field_name("name")?), "interface"))
306 }
307 "function_declaration" | "protocol_function_declaration" => {
308 Some((text(node.child_by_field_name("name")?), "function"))
309 }
310 "init_declaration" => Some(("init".to_string(), "constructor")),
312 _ => None,
313 },
314 Lang::Php => match node.kind() {
315 "function_definition" => {
316 Some((text(node.child_by_field_name("name")?), "function"))
317 }
318 "method_declaration" => Some((text(node.child_by_field_name("name")?), "method")),
319 "class_declaration" => Some((text(node.child_by_field_name("name")?), "class")),
320 "interface_declaration" => {
321 Some((text(node.child_by_field_name("name")?), "interface"))
322 }
323 "trait_declaration" => Some((text(node.child_by_field_name("name")?), "trait")),
324 "enum_declaration" => Some((text(node.child_by_field_name("name")?), "enum")),
325 "namespace_definition" => {
326 Some((text(node.child_by_field_name("name")?), "namespace"))
327 }
328 _ => None,
329 },
330 }
331 }
332
333 pub fn scope_only(&self, node: Node, src: &str) -> Option<String> {
335 match self {
336 Lang::Rust => match node.kind() {
337 "impl_item" => {
339 let ty = node.child_by_field_name("type")?;
340 Some(base_type_name(ty, src))
341 }
342 "mod_item" => Some(src[node.child_by_field_name("name")?.byte_range()].to_string()),
343 _ => None,
344 },
345 _ => None,
346 }
347 }
348
349 pub fn body_field(&self) -> Option<&'static str> {
351 Some("body")
354 }
355
356 pub fn call(&self, node: Node, src: &str) -> Option<String> {
358 let text = |n: Node| src[n.byte_range()].to_string();
359 match self {
360 Lang::Rust => {
361 if node.kind() != "call_expression" {
362 return None;
363 }
364 let f = node.child_by_field_name("function")?;
365 match f.kind() {
366 "identifier" => Some(text(f)),
367 "field_expression" => f.child_by_field_name("field").map(text),
368 "scoped_identifier" => f.child_by_field_name("name").map(text),
369 "generic_function" => {
370 let inner = f.child_by_field_name("function")?;
371 match inner.kind() {
372 "identifier" => Some(text(inner)),
373 "scoped_identifier" => inner.child_by_field_name("name").map(text),
374 _ => None,
375 }
376 }
377 _ => None,
378 }
379 }
380 Lang::TypeScript | Lang::Tsx => {
381 if node.kind() != "call_expression" {
382 return None;
383 }
384 let f = node.child_by_field_name("function")?;
385 match f.kind() {
386 "identifier" => Some(text(f)),
387 "member_expression" => f.child_by_field_name("property").map(text),
388 _ => None,
389 }
390 }
391 Lang::Python => {
392 if node.kind() != "call" {
393 return None;
394 }
395 let f = node.child_by_field_name("function")?;
396 match f.kind() {
397 "identifier" => Some(text(f)),
398 "attribute" => f.child_by_field_name("attribute").map(text),
399 _ => None,
400 }
401 }
402 Lang::Go => {
403 if node.kind() != "call_expression" {
404 return None;
405 }
406 let f = node.child_by_field_name("function")?;
407 match f.kind() {
408 "identifier" => Some(text(f)),
409 "selector_expression" => f.child_by_field_name("field").map(text),
410 _ => None,
411 }
412 }
413 Lang::Java => {
414 if node.kind() != "method_invocation" {
415 return None;
416 }
417 node.child_by_field_name("name").map(text)
418 }
419 Lang::Ruby => {
420 if node.kind() != "call" {
422 return None;
423 }
424 node.child_by_field_name("method").map(text)
425 }
426 Lang::C => {
427 if node.kind() != "call_expression" {
428 return None;
429 }
430 let f = node.child_by_field_name("function")?;
431 match f.kind() {
432 "identifier" => Some(text(f)),
433 _ => None,
434 }
435 }
436 Lang::CSharp => {
437 if node.kind() != "invocation_expression" {
438 return None;
439 }
440 let f = node.child_by_field_name("function")?;
441 match f.kind() {
442 "identifier" => Some(text(f)),
443 "member_access_expression" => f.child_by_field_name("name").map(text),
445 _ => None,
446 }
447 }
448 Lang::Sql => {
449 if node.kind() != "object_reference" {
455 return None;
456 }
457 match node.parent()?.kind() {
458 "relation" | "from" | "column_definition" => {
459 Some(text(node.child_by_field_name("name").unwrap_or(node)))
460 }
461 _ => None,
462 }
463 }
464 Lang::Cpp => {
465 if node.kind() != "call_expression" {
466 return None;
467 }
468 let f = node.child_by_field_name("function")?;
469 match f.kind() {
470 "identifier" | "field_identifier" => Some(text(f)),
471 "field_expression" => f.child_by_field_name("field").map(text),
473 "qualified_identifier" => f.child_by_field_name("name").map(text),
475 _ => None,
476 }
477 }
478 Lang::Kotlin => {
484 if node.kind() != "call_expression" {
485 return None;
486 }
487 let callee = node.named_child(0)?;
488 match callee.kind() {
489 "identifier" => Some(text(callee)),
490 "navigation_expression" => {
491 let n = u32::try_from(callee.named_child_count())
492 .ok()?
493 .checked_sub(1)?;
494 let last = callee.named_child(n)?;
495 (last.kind() == "identifier").then(|| text(last))
496 }
497 _ => None,
498 }
499 }
500 Lang::Swift => {
501 if node.kind() != "call_expression" {
502 return None;
503 }
504 let callee = node.named_child(0)?;
505 match callee.kind() {
506 "simple_identifier" => Some(text(callee)),
507 "navigation_expression" => {
511 let suffix = callee.child_by_field_name("suffix")?;
512 suffix.child_by_field_name("suffix").map(text)
513 }
514 _ => None,
515 }
516 }
517 Lang::Php => match node.kind() {
518 "function_call_expression" => node.child_by_field_name("function").map(text),
519 "member_call_expression" => node.child_by_field_name("name").map(text),
521 "scoped_call_expression" => node.child_by_field_name("name").map(text),
523 _ => None,
524 },
525 }
526 }
527
528 pub fn imports(&self, node: Node, src: &str, out: &mut Vec<ImportRef>) {
530 let text = |n: Node| src[n.byte_range()].to_string();
531 match self {
532 Lang::Rust => {
533 if node.kind() == "use_declaration" {
534 if let Some(arg) = node.child_by_field_name("argument") {
535 rust_use_tree(arg, src, "", out);
536 }
537 }
538 }
539 Lang::TypeScript | Lang::Tsx => {
540 if node.kind() != "import_statement" {
541 return;
542 }
543 let Some(source) = node
544 .child_by_field_name("source")
545 .map(|s| text(s).trim_matches(['"', '\'']).to_string())
546 else {
547 return;
548 };
549 let mut cursor = node.walk();
550 for child in node.children(&mut cursor) {
551 if child.kind() != "import_clause" {
552 continue;
553 }
554 let mut c2 = child.walk();
555 for part in child.children(&mut c2) {
556 match part.kind() {
557 "identifier" => out.push(ImportRef {
558 local: text(part),
559 source: source.clone(),
560 }),
561 "named_imports" => {
562 let mut c3 = part.walk();
563 for spec in part.children(&mut c3) {
564 if spec.kind() != "import_specifier" {
565 continue;
566 }
567 let local = spec
568 .child_by_field_name("alias")
569 .or_else(|| spec.child_by_field_name("name"))
570 .map(text);
571 if let Some(local) = local {
572 out.push(ImportRef {
573 local,
574 source: source.clone(),
575 });
576 }
577 }
578 }
579 "namespace_import" => {
580 let mut c3 = part.walk();
582 for id in part.children(&mut c3) {
583 if id.kind() == "identifier" {
584 out.push(ImportRef {
585 local: text(id),
586 source: source.clone(),
587 });
588 }
589 }
590 }
591 _ => {}
592 }
593 }
594 }
595 }
596 Lang::Python => match node.kind() {
597 "import_statement" => {
598 let mut cursor = node.walk();
599 for child in node.children(&mut cursor) {
600 match child.kind() {
601 "dotted_name" => out.push(ImportRef {
602 local: text(child)
603 .rsplit('.')
604 .next()
605 .unwrap_or_default()
606 .to_string(),
607 source: text(child),
608 }),
609 "aliased_import" => {
610 let name = child.child_by_field_name("name").map(text);
611 let alias = child.child_by_field_name("alias").map(text);
612 if let (Some(name), Some(alias)) = (name, alias) {
613 out.push(ImportRef {
614 local: alias,
615 source: name,
616 });
617 }
618 }
619 _ => {}
620 }
621 }
622 }
623 "import_from_statement" => {
624 let Some(module) = node.child_by_field_name("module_name").map(text) else {
625 return;
626 };
627 let mut cursor = node.walk();
628 let mut past_import = false;
629 for child in node.children(&mut cursor) {
630 if child.kind() == "import" {
631 past_import = true;
632 continue;
633 }
634 if !past_import {
635 continue;
636 }
637 match child.kind() {
638 "dotted_name" => out.push(ImportRef {
639 local: text(child),
640 source: module.clone(),
641 }),
642 "aliased_import" => {
643 if let Some(alias) = child.child_by_field_name("alias").map(text) {
644 out.push(ImportRef {
645 local: alias,
646 source: module.clone(),
647 });
648 }
649 }
650 _ => {}
651 }
652 }
653 }
654 _ => {}
655 },
656 Lang::Go => {
657 if node.kind() != "import_spec" {
658 return;
659 }
660 let Some(path) = node
661 .child_by_field_name("path")
662 .map(|p| text(p).trim_matches('"').to_string())
663 else {
664 return;
665 };
666 let local = node
667 .child_by_field_name("name")
668 .map(text)
669 .unwrap_or_else(|| path.rsplit('/').next().unwrap_or(&path).to_string());
670 out.push(ImportRef {
671 local,
672 source: path,
673 });
674 }
675 Lang::Java => {
676 if node.kind() != "import_declaration" {
678 return;
679 }
680 let mut cursor = node.walk();
681 let Some(scoped) = node
682 .children(&mut cursor)
683 .find(|c| c.kind() == "scoped_identifier")
684 else {
685 return;
686 };
687 let source = text(scoped);
688 let local = source.rsplit('.').next().unwrap_or(&source).to_string();
689 out.push(ImportRef { local, source });
690 }
691 Lang::C | Lang::Cpp => {
694 if node.kind() != "preproc_include" {
696 return;
697 }
698 let Some(path_node) = node.child_by_field_name("path") else {
699 return;
700 };
701 let source = text(path_node).trim_matches(['"', '<', '>']).to_string();
702 let local = source
703 .rsplit('/')
704 .next()
705 .unwrap_or(&source)
706 .trim_end_matches(".h")
707 .to_string();
708 out.push(ImportRef { local, source });
709 }
710 Lang::CSharp => {
711 if node.kind() != "using_directive" {
714 return;
715 }
716 let mut cursor = node.walk();
717 let names: Vec<String> = node
718 .children(&mut cursor)
719 .filter(|c| {
720 matches!(
721 c.kind(),
722 "identifier" | "qualified_name" | "alias_qualified_name"
723 )
724 })
725 .map(text)
726 .collect();
727 match names.as_slice() {
728 [alias, source, ..] => out.push(ImportRef {
730 local: alias.clone(),
731 source: source.clone(),
732 }),
733 [source] => out.push(ImportRef {
735 local: source.rsplit('.').next().unwrap_or(source).to_string(),
736 source: source.clone(),
737 }),
738 [] => {}
739 }
740 }
741 Lang::Ruby => {}
744 Lang::Sql => {}
746 Lang::Kotlin => {
747 if node.kind() != "import" {
748 return;
749 }
750 let mut cursor = node.walk();
751 let children: Vec<Node> = node.children(&mut cursor).collect();
752 if children.iter().any(|c| c.kind() == "*") {
753 return; }
755 let Some(path) = children.iter().find(|c| c.kind() == "qualified_identifier")
756 else {
757 return;
758 };
759 let source = text(*path);
760 let local = children
761 .iter()
762 .find(|c| c.kind() == "identifier")
763 .map(|c| text(*c))
764 .unwrap_or_else(|| source.rsplit('.').next().unwrap_or(&source).to_string());
765 out.push(ImportRef { local, source });
766 }
767 Lang::Swift => {
768 if node.kind() != "import_declaration" {
769 return;
770 }
771 let mut cursor = node.walk();
772 let Some(path) = node
773 .children(&mut cursor)
774 .find(|c| c.kind() == "identifier")
775 else {
776 return;
777 };
778 let source = text(path);
779 let local = source.rsplit('.').next().unwrap_or(&source).to_string();
780 out.push(ImportRef { local, source });
781 }
782 Lang::Php => match node.kind() {
783 "namespace_use_clause" => {
784 let mut cursor = node.walk();
785 let Some(path) = node
786 .children(&mut cursor)
787 .find(|c| matches!(c.kind(), "name" | "qualified_name"))
788 else {
789 return;
790 };
791 let source = text(path);
792 let local = node
793 .child_by_field_name("alias")
794 .map(text)
795 .unwrap_or_else(|| {
796 source.rsplit('\\').next().unwrap_or(&source).to_string()
797 });
798 out.push(ImportRef { local, source });
799 }
800 "require_expression"
801 | "require_once_expression"
802 | "include_expression"
803 | "include_once_expression" => {
804 let Some(expr) = node.named_child(0) else {
805 return;
806 };
807 if expr.kind() != "string" {
808 return;
809 }
810 let source = text(expr).trim_matches(['\'', '"']).to_string();
811 let local = source
812 .rsplit('/')
813 .next()
814 .unwrap_or(&source)
815 .trim_end_matches(".php")
816 .to_string();
817 out.push(ImportRef { local, source });
818 }
819 _ => {}
820 },
821 }
822 }
823
824 pub fn doc_comment(&self, node: Node, src: &str) -> Option<String> {
826 match self {
827 Lang::Python => {
828 let body = node.child_by_field_name("body")?;
830 let first = body.named_child(0)?;
831 if first.kind() != "expression_statement" {
832 return None;
833 }
834 let s = first.named_child(0)?;
835 if s.kind() != "string" {
836 return None;
837 }
838 let raw = &src[s.byte_range()];
839 let cleaned = raw
840 .trim_start_matches(['r', 'b', 'f', 'u', 'R', 'B', 'F', 'U'])
841 .trim_matches(['"', '\''])
842 .trim();
843 Some(cleaned.lines().next().unwrap_or("").trim().to_string())
844 .filter(|s| !s.is_empty())
845 }
846 Lang::Rust
847 | Lang::Go
848 | Lang::TypeScript
849 | Lang::Tsx
850 | Lang::Java
851 | Lang::C
852 | Lang::CSharp
853 | Lang::Ruby
854 | Lang::Sql
855 | Lang::Cpp
856 | Lang::Kotlin
857 | Lang::Swift
858 | Lang::Php => {
859 let mut anchor = node;
865 if matches!(self, Lang::Sql) {
866 while let Some(p) = anchor.parent() {
867 if p.kind() == "statement" {
868 anchor = p;
869 } else {
870 break;
871 }
872 }
873 }
874 let mut lines: Vec<String> = Vec::new();
875 let mut expect_row = anchor.start_position().row;
876 let mut prev = anchor.prev_sibling();
877 while let Some(p) = prev {
878 if !p.kind().contains("comment")
879 || expect_row.saturating_sub(p.end_position().row) > 1
880 || src[p.byte_range()].starts_with("//!")
881 {
882 break;
883 }
884 lines.push(src[p.byte_range()].to_string());
885 expect_row = p.start_position().row;
886 prev = p.prev_sibling();
887 }
888 if lines.is_empty() {
889 return None;
890 }
891 lines.reverse();
892 let cleaned: Vec<String> = lines
893 .iter()
894 .flat_map(|c| c.lines())
895 .map(|l| {
896 l.trim()
897 .trim_start_matches("///")
898 .trim_start_matches("//!")
899 .trim_start_matches("//")
900 .trim_start_matches("--") .trim_start_matches("/**")
902 .trim_start_matches("/*")
903 .trim_end_matches("*/")
904 .trim_start_matches('*')
905 .trim_start_matches('#') .trim()
907 .to_string()
908 })
909 .filter(|l| !l.is_empty())
910 .collect();
911 if cleaned.is_empty() {
912 None
913 } else {
914 Some(cleaned.join(" ").chars().take(300).collect())
915 }
916 }
917 }
918 }
919}
920
921fn sql_def_name(node: Node, src: &str) -> Option<String> {
925 let mut cursor = node.walk();
926 let obj = node
927 .children(&mut cursor)
928 .find(|c| c.kind() == "object_reference")?;
929 let name = obj.child_by_field_name("name").unwrap_or(obj);
930 Some(src[name.byte_range()].to_string())
931}
932
933fn base_type_name(ty: Node, src: &str) -> String {
935 match ty.kind() {
936 "generic_type" => ty
937 .child_by_field_name("type")
938 .map(|t| src[t.byte_range()].to_string())
939 .unwrap_or_else(|| src[ty.byte_range()].to_string()),
940 _ => src[ty.byte_range()].to_string(),
941 }
942}
943
944fn const_name(node: Node, src: &str) -> String {
947 let full = src[node.byte_range()].to_string();
948 full.rsplit("::").next().unwrap_or(&full).to_string()
949}
950
951fn c_declarator_name(node: Node, src: &str) -> Option<String> {
954 match node.kind() {
955 "identifier" | "field_identifier" => Some(src[node.byte_range()].to_string()),
962 "function_declarator" | "pointer_declarator" | "parenthesized_declarator" => {
963 c_declarator_name(node.child_by_field_name("declarator")?, src)
964 }
965 "qualified_identifier" => {
971 let name = c_declarator_name(node.child_by_field_name("name")?, src)?;
972 match node.child_by_field_name("scope") {
973 Some(scope) => Some(format!("{}::{name}", &src[scope.byte_range()])),
974 None => Some(name),
975 }
976 }
977 _ => {
978 let mut cursor = node.walk();
980 for child in node.children(&mut cursor) {
981 if let Some(name) = c_declarator_name(child, src) {
982 return Some(name);
983 }
984 }
985 None
986 }
987 }
988}
989
990fn receiver_type(receiver: Node, src: &str) -> Option<String> {
992 let mut cursor = receiver.walk();
993 for child in receiver.children(&mut cursor) {
994 if child.kind() == "parameter_declaration" {
995 let ty = child.child_by_field_name("type")?;
996 let base = match ty.kind() {
997 "pointer_type" => ty.named_child(0)?,
998 _ => ty,
999 };
1000 return Some(src[base.byte_range()].to_string());
1001 }
1002 }
1003 None
1004}
1005
1006fn rust_use_tree(node: Node, src: &str, prefix: &str, out: &mut Vec<ImportRef>) {
1008 let text = |n: Node| src[n.byte_range()].to_string();
1009 let join = |prefix: &str, seg: &str| {
1010 if prefix.is_empty() {
1011 seg.to_string()
1012 } else {
1013 format!("{prefix}::{seg}")
1014 }
1015 };
1016 match node.kind() {
1017 "identifier" | "crate" | "self" | "super" => {
1018 let seg = text(node);
1019 out.push(ImportRef {
1020 local: seg.clone(),
1021 source: join(prefix, &seg),
1022 });
1023 }
1024 "scoped_identifier" => {
1025 let full = join(prefix, &text(node));
1026 let local = node
1027 .child_by_field_name("name")
1028 .map(text)
1029 .unwrap_or_default();
1030 if !local.is_empty() {
1031 out.push(ImportRef {
1032 local,
1033 source: full,
1034 });
1035 }
1036 }
1037 "use_as_clause" => {
1038 let alias = node.child_by_field_name("alias").map(text);
1039 let path = node.child_by_field_name("path").map(text);
1040 if let (Some(alias), Some(path)) = (alias, path) {
1041 out.push(ImportRef {
1042 local: alias,
1043 source: join(prefix, &path),
1044 });
1045 }
1046 }
1047 "scoped_use_list" => {
1048 let new_prefix = node
1049 .child_by_field_name("path")
1050 .map(|p| join(prefix, &text(p)))
1051 .unwrap_or_else(|| prefix.to_string());
1052 if let Some(list) = node.child_by_field_name("list") {
1053 let mut cursor = list.walk();
1054 for child in list.named_children(&mut cursor) {
1055 rust_use_tree(child, src, &new_prefix, out);
1056 }
1057 }
1058 }
1059 "use_list" => {
1060 let mut cursor = node.walk();
1061 for child in node.named_children(&mut cursor) {
1062 rust_use_tree(child, src, prefix, out);
1063 }
1064 }
1065 _ => {}
1067 }
1068}