1use super::language::Language;
11use crate::types::{SymbolKind, Visibility};
12use std::collections::HashSet;
13use tree_sitter::Node;
14
15fn safe_char_boundary(s: &str, mut index: usize) -> usize {
18 if index >= s.len() {
19 return s.len();
20 }
21 while index > 0 && !s.is_char_boundary(index) {
23 index -= 1;
24 }
25 index
26}
27
28pub fn extract_signature(node: Node<'_>, source_code: &str, language: Language) -> Option<String> {
30 let sig_node = match language {
31 Language::Python => {
32 if node.kind() == "function_definition" {
33 let start = node.start_byte();
34 let mut end = start;
35 for byte in &source_code.as_bytes()[start..] {
36 end += 1;
37 if *byte == b':' || *byte == b'\n' {
38 break;
39 }
40 }
41 let safe_start = safe_char_boundary(source_code, start);
43 let safe_end = safe_char_boundary(source_code, end);
44 return Some(
45 source_code[safe_start..safe_end]
46 .trim()
47 .to_owned()
48 .replace('\n', " "),
49 );
50 }
51 None
52 },
53 Language::JavaScript | Language::TypeScript => {
54 if node.kind().contains("function") || node.kind().contains("method") {
55 let start = node.start_byte();
56 let mut end = start;
57 let mut brace_count = 0;
58 for byte in &source_code.as_bytes()[start..] {
59 if *byte == b'{' {
60 brace_count += 1;
61 if brace_count == 1 {
62 break;
63 }
64 }
65 end += 1;
66 }
67 let safe_start = safe_char_boundary(source_code, start);
69 let safe_end = safe_char_boundary(source_code, end);
70 return Some(
71 source_code[safe_start..safe_end]
72 .trim()
73 .to_owned()
74 .replace('\n', " "),
75 );
76 }
77 None
78 },
79 Language::Rust => {
80 if node.kind() == "function_item" {
81 for child in node.children(&mut node.walk()) {
82 if child.kind() == "block" {
83 let start = node.start_byte();
84 let end = child.start_byte();
85 return Some(source_code[start..end].trim().to_owned().replace('\n', " "));
86 }
87 }
88 }
89 None
90 },
91 Language::Go => {
92 if node.kind() == "function_declaration" || node.kind() == "method_declaration" {
93 for child in node.children(&mut node.walk()) {
94 if child.kind() == "block" {
95 let start = node.start_byte();
96 let end = child.start_byte();
97 return Some(source_code[start..end].trim().to_owned().replace('\n', " "));
98 }
99 }
100 }
101 None
102 },
103 Language::Java => {
104 if node.kind() == "method_declaration" {
105 for child in node.children(&mut node.walk()) {
106 if child.kind() == "block" {
107 let start = node.start_byte();
108 let end = child.start_byte();
109 return Some(source_code[start..end].trim().to_owned().replace('\n', " "));
110 }
111 }
112 }
113 None
114 },
115 Language::C
116 | Language::Cpp
117 | Language::CSharp
118 | Language::Php
119 | Language::Kotlin
120 | Language::Swift
121 | Language::Scala
122 | Language::Dart => {
123 for child in node.children(&mut node.walk()) {
124 if child.kind() == "block"
125 || child.kind() == "compound_statement"
126 || child.kind() == "function_body"
127 {
128 let start = node.start_byte();
129 let end = child.start_byte();
130 return Some(source_code[start..end].trim().to_owned().replace('\n', " "));
131 }
132 }
133 None
134 },
135 Language::Ruby | Language::Lua => {
136 let start = node.start_byte();
137 let mut end = start;
138 for byte in &source_code.as_bytes()[start..] {
139 end += 1;
140 if *byte == b'\n' {
141 break;
142 }
143 }
144 Some(source_code[start..end].trim().to_owned())
145 },
146 Language::Bash => {
147 let start = node.start_byte();
148 let mut end = start;
149 for byte in &source_code.as_bytes()[start..] {
150 if *byte == b'{' {
151 break;
152 }
153 end += 1;
154 }
155 Some(source_code[start..end].trim().to_owned())
156 },
157 Language::Haskell
158 | Language::OCaml
159 | Language::FSharp
160 | Language::Elixir
161 | Language::Clojure
162 | Language::R
163 | Language::Hcl
164 | Language::Zig => {
165 let start = node.start_byte();
166 let mut end = start;
167 for byte in &source_code.as_bytes()[start..] {
168 end += 1;
169 if *byte == b'\n' || *byte == b'=' {
170 break;
171 }
172 }
173 Some(source_code[start..end].trim().to_owned())
174 },
175 };
176
177 sig_node.or_else(|| {
178 let start = node.start_byte();
179 let end = std::cmp::min(start + 200, source_code.len());
180 let safe_start = safe_char_boundary(source_code, start);
182 let safe_end = safe_char_boundary(source_code, end);
183 if safe_start >= safe_end {
184 return None;
185 }
186 let text = &source_code[safe_start..safe_end];
187 text.lines().next().map(|s| s.trim().to_owned())
188 })
189}
190
191pub fn extract_docstring(node: Node<'_>, source_code: &str, language: Language) -> Option<String> {
193 match language {
194 Language::Python => {
195 let mut cursor = node.walk();
196 for child in node.children(&mut cursor) {
197 if child.kind() == "block" {
198 for stmt in child.children(&mut child.walk()) {
199 if stmt.kind() == "expression_statement" {
200 for expr in stmt.children(&mut stmt.walk()) {
201 if expr.kind() == "string" {
202 if let Ok(text) = expr.utf8_text(source_code.as_bytes()) {
203 return Some(
204 text.trim_matches(|c| c == '"' || c == '\'')
205 .trim()
206 .to_owned(),
207 );
208 }
209 }
210 }
211 }
212 }
213 }
214 }
215 None
216 },
217 Language::JavaScript | Language::TypeScript => {
218 if let Some(prev_sibling) = node.prev_sibling() {
219 if prev_sibling.kind() == "comment" {
220 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
221 if text.starts_with("/**") {
222 return Some(clean_jsdoc(text));
223 }
224 }
225 }
226 }
227 None
228 },
229 Language::Rust => {
230 let start_byte = node.start_byte();
231 let safe_boundary = source_code.floor_char_boundary(start_byte);
233 let lines_before: Vec<_> = source_code[..safe_boundary]
234 .lines()
235 .rev()
236 .take_while(|line| line.trim().starts_with("///") || line.trim().is_empty())
237 .collect();
238
239 if !lines_before.is_empty() {
240 let doc: Vec<String> = lines_before
241 .into_iter()
242 .rev()
243 .filter_map(|line| {
244 let trimmed = line.trim();
245 trimmed.strip_prefix("///").map(|s| s.trim().to_owned())
246 })
247 .collect();
248
249 if !doc.is_empty() {
250 return Some(doc.join(" "));
251 }
252 }
253 None
254 },
255 Language::Go => {
256 if let Some(prev_sibling) = node.prev_sibling() {
257 if prev_sibling.kind() == "comment" {
258 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
259 return Some(text.trim_start_matches("//").trim().to_owned());
260 }
261 }
262 }
263 None
264 },
265 Language::Java => {
266 if let Some(prev_sibling) = node.prev_sibling() {
267 if prev_sibling.kind() == "block_comment" {
268 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
269 if text.starts_with("/**") {
270 return Some(clean_javadoc(text));
271 }
272 }
273 }
274 }
275 None
276 },
277 Language::C | Language::Cpp => {
278 if let Some(prev_sibling) = node.prev_sibling() {
279 if prev_sibling.kind() == "comment" {
280 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
281 if text.starts_with("/**") || text.starts_with("/*") {
282 return Some(clean_jsdoc(text));
283 }
284 return Some(text.trim_start_matches("//").trim().to_owned());
285 }
286 }
287 }
288 None
289 },
290 Language::CSharp => {
291 let start_byte = node.start_byte();
292 let safe_boundary = source_code.floor_char_boundary(start_byte);
294 let lines_before: Vec<_> = source_code[..safe_boundary]
295 .lines()
296 .rev()
297 .take_while(|line| line.trim().starts_with("///") || line.trim().is_empty())
298 .collect();
299
300 if !lines_before.is_empty() {
301 let doc: Vec<String> = lines_before
302 .into_iter()
303 .rev()
304 .filter_map(|line| {
305 let trimmed = line.trim();
306 trimmed.strip_prefix("///").map(|s| s.trim().to_owned())
307 })
308 .collect();
309
310 if !doc.is_empty() {
311 return Some(doc.join(" "));
312 }
313 }
314 None
315 },
316 Language::Ruby => {
317 if let Some(prev_sibling) = node.prev_sibling() {
318 if prev_sibling.kind() == "comment" {
319 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
320 return Some(text.trim_start_matches('#').trim().to_owned());
321 }
322 }
323 }
324 None
325 },
326 Language::Php | Language::Kotlin | Language::Swift | Language::Scala => {
327 if let Some(prev_sibling) = node.prev_sibling() {
328 let kind = prev_sibling.kind();
329 if kind == "comment" || kind == "multiline_comment" || kind == "block_comment" {
330 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
331 if text.starts_with("/**") {
332 return Some(clean_jsdoc(text));
333 }
334 }
335 }
336 }
337 None
338 },
339 Language::Bash => {
340 if let Some(prev_sibling) = node.prev_sibling() {
341 if prev_sibling.kind() == "comment" {
342 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
343 return Some(text.trim_start_matches('#').trim().to_owned());
344 }
345 }
346 }
347 None
348 },
349 Language::Haskell => {
350 if let Some(prev_sibling) = node.prev_sibling() {
351 if prev_sibling.kind() == "comment" {
352 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
353 let cleaned = text
354 .trim_start_matches("{-")
355 .trim_end_matches("-}")
356 .trim_start_matches("--")
357 .trim();
358 return Some(cleaned.to_owned());
359 }
360 }
361 }
362 None
363 },
364 Language::Elixir => {
365 if let Some(prev_sibling) = node.prev_sibling() {
366 if prev_sibling.kind() == "comment" {
367 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
368 return Some(text.trim_start_matches('#').trim().to_owned());
369 }
370 }
371 }
372 None
373 },
374 Language::Clojure => None,
375 Language::OCaml | Language::FSharp => {
376 if let Some(prev_sibling) = node.prev_sibling() {
377 if prev_sibling.kind() == "comment" {
378 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
379 let cleaned = text
380 .trim_start_matches("(**")
381 .trim_start_matches("(*")
382 .trim_end_matches("*)")
383 .trim();
384 return Some(cleaned.to_owned());
385 }
386 }
387 }
388 None
389 },
390 Language::Lua => {
391 if let Some(prev_sibling) = node.prev_sibling() {
392 if prev_sibling.kind() == "comment" {
393 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
394 let cleaned = text
395 .trim_start_matches("--[[")
396 .trim_end_matches("]]")
397 .trim_start_matches("--")
398 .trim();
399 return Some(cleaned.to_owned());
400 }
401 }
402 }
403 None
404 },
405 Language::R => {
406 if let Some(prev_sibling) = node.prev_sibling() {
407 if prev_sibling.kind() == "comment" {
408 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
409 return Some(text.trim_start_matches('#').trim().to_owned());
410 }
411 }
412 }
413 None
414 },
415 Language::Hcl => None,
416 Language::Zig | Language::Dart => {
418 let start_byte = node.start_byte();
419 let safe_boundary = source_code.floor_char_boundary(start_byte);
420 let lines_before: Vec<_> = source_code[..safe_boundary]
421 .lines()
422 .rev()
423 .take_while(|line| line.trim().starts_with("///") || line.trim().is_empty())
424 .collect();
425
426 if !lines_before.is_empty() {
427 let doc: Vec<String> = lines_before
428 .into_iter()
429 .rev()
430 .filter_map(|line| {
431 let trimmed = line.trim();
432 trimmed.strip_prefix("///").map(|s| s.trim().to_owned())
433 })
434 .collect();
435
436 if !doc.is_empty() {
437 return Some(doc.join(" "));
438 }
439 }
440 None
441 },
442 }
443}
444
445pub fn extract_parent(node: Node<'_>, source_code: &str) -> Option<String> {
447 let mut current = node.parent()?;
448
449 while let Some(parent) = current.parent() {
450 if ["class_definition", "class_declaration", "struct_item", "impl_item"]
451 .contains(&parent.kind())
452 {
453 for child in parent.children(&mut parent.walk()) {
454 if child.kind() == "identifier" || child.kind() == "type_identifier" {
455 if let Ok(name) = child.utf8_text(source_code.as_bytes()) {
456 return Some(name.to_owned());
457 }
458 }
459 }
460 }
461 current = parent;
462 }
463
464 None
465}
466
467pub fn extract_visibility(node: Node<'_>, source_code: &str, language: Language) -> Visibility {
469 match language {
470 Language::Python => {
471 if let Some(name_node) = node.child_by_field_name("name") {
472 if let Ok(name) = name_node.utf8_text(source_code.as_bytes()) {
473 if name.starts_with("__") && !name.ends_with("__") {
474 return Visibility::Private;
475 } else if name.starts_with('_') {
476 return Visibility::Protected;
477 }
478 }
479 }
480 Visibility::Public
481 },
482 Language::Rust => {
483 for child in node.children(&mut node.walk()) {
484 if child.kind() == "visibility_modifier" {
485 if let Ok(text) = child.utf8_text(source_code.as_bytes()) {
486 if text.contains("pub(crate)") || text.contains("pub(super)") {
487 return Visibility::Internal;
488 } else if text.starts_with("pub") {
489 return Visibility::Public;
490 }
491 }
492 }
493 }
494 Visibility::Private
495 },
496 Language::JavaScript | Language::TypeScript => {
497 for child in node.children(&mut node.walk()) {
498 let kind = child.kind();
499 if kind == "private" || kind == "accessibility_modifier" {
500 if let Ok(text) = child.utf8_text(source_code.as_bytes()) {
501 return match text {
502 "private" => Visibility::Private,
503 "protected" => Visibility::Protected,
504 _ => Visibility::Public,
505 };
506 }
507 }
508 }
509 if let Some(name_node) = node.child_by_field_name("name") {
510 if let Ok(name) = name_node.utf8_text(source_code.as_bytes()) {
511 if name.starts_with('#') {
512 return Visibility::Private;
513 }
514 }
515 }
516 Visibility::Public
517 },
518 Language::Go => {
519 if let Some(name_node) = node.child_by_field_name("name") {
520 if let Ok(name) = name_node.utf8_text(source_code.as_bytes()) {
521 if let Some(first_char) = name.chars().next() {
522 if first_char.is_lowercase() {
523 return Visibility::Private;
524 }
525 }
526 }
527 }
528 Visibility::Public
529 },
530 Language::Java => {
531 for child in node.children(&mut node.walk()) {
532 if child.kind() == "modifiers" {
533 if let Ok(text) = child.utf8_text(source_code.as_bytes()) {
534 if text.contains("private") {
535 return Visibility::Private;
536 } else if text.contains("protected") {
537 return Visibility::Protected;
538 } else if text.contains("public") {
539 return Visibility::Public;
540 }
541 }
542 }
543 }
544 Visibility::Internal
545 },
546 Language::C | Language::Cpp => {
547 for child in node.children(&mut node.walk()) {
548 if child.kind() == "storage_class_specifier" {
549 if let Ok(text) = child.utf8_text(source_code.as_bytes()) {
550 if text == "static" {
551 return Visibility::Private;
552 }
553 }
554 }
555 }
556 Visibility::Public
557 },
558 Language::CSharp | Language::Kotlin | Language::Swift | Language::Scala => {
559 for child in node.children(&mut node.walk()) {
560 let kind = child.kind();
561 if kind == "modifier" || kind == "modifiers" || kind == "visibility_modifier" {
562 if let Ok(text) = child.utf8_text(source_code.as_bytes()) {
563 if text.contains("private") {
564 return Visibility::Private;
565 } else if text.contains("protected") {
566 return Visibility::Protected;
567 } else if text.contains("internal") {
568 return Visibility::Internal;
569 } else if text.contains("public") {
570 return Visibility::Public;
571 }
572 }
573 }
574 }
575 Visibility::Internal
576 },
577 Language::Ruby => {
578 if let Some(name_node) = node.child_by_field_name("name") {
579 if let Ok(name) = name_node.utf8_text(source_code.as_bytes()) {
580 if name.starts_with('_') {
581 return Visibility::Private;
582 }
583 }
584 }
585 Visibility::Public
586 },
587 Language::Php => {
588 for child in node.children(&mut node.walk()) {
589 if child.kind() == "visibility_modifier" {
590 if let Ok(text) = child.utf8_text(source_code.as_bytes()) {
591 return match text {
592 "private" => Visibility::Private,
593 "protected" => Visibility::Protected,
594 "public" => Visibility::Public,
595 _ => Visibility::Public,
596 };
597 }
598 }
599 }
600 Visibility::Public
601 },
602 Language::Bash => Visibility::Public,
603 Language::Haskell
604 | Language::Elixir
605 | Language::Clojure
606 | Language::OCaml
607 | Language::FSharp
608 | Language::Lua
609 | Language::R
610 | Language::Hcl
611 | Language::Zig => Visibility::Public,
612 Language::Dart => {
613 if let Some(name_node) = node.child_by_field_name("name") {
615 if let Ok(name) = name_node.utf8_text(source_code.as_bytes()) {
616 if name.starts_with('_') {
617 return Visibility::Private;
618 }
619 }
620 }
621 Visibility::Public
622 },
623 }
624}
625
626pub fn extract_calls(node: Node<'_>, source_code: &str, language: Language) -> Vec<String> {
628 let mut calls = HashSet::new();
629
630 let body_node = find_body_node(node, language);
631 if let Some(body) = body_node {
632 collect_calls_recursive(body, source_code, language, &mut calls);
633 }
634
635 if calls.is_empty() {
636 collect_calls_recursive(node, source_code, language, &mut calls);
637 }
638
639 calls.into_iter().collect()
640}
641
642pub fn find_body_node(node: Node<'_>, language: Language) -> Option<Node<'_>> {
644 match language {
645 Language::Python => {
646 for child in node.children(&mut node.walk()) {
647 if child.kind() == "block" {
648 return Some(child);
649 }
650 }
651 },
652 Language::Rust => {
653 for child in node.children(&mut node.walk()) {
654 if child.kind() == "block" {
655 return Some(child);
656 }
657 }
658 },
659 Language::JavaScript | Language::TypeScript => {
660 for child in node.children(&mut node.walk()) {
661 let kind = child.kind();
662 if kind == "statement_block" {
663 return Some(child);
664 }
665 if kind == "arrow_function" {
666 if let Some(body) = find_body_node(child, language) {
667 return Some(body);
668 }
669 return Some(child);
670 }
671 }
672 if node.kind() == "arrow_function" {
673 for child in node.children(&mut node.walk()) {
674 let kind = child.kind();
675 if kind != "formal_parameters"
676 && kind != "identifier"
677 && kind != "=>"
678 && kind != "("
679 && kind != ")"
680 && kind != ","
681 {
682 return Some(child);
683 }
684 }
685 return Some(node);
686 }
687 },
688 Language::Go => {
689 for child in node.children(&mut node.walk()) {
690 if child.kind() == "block" {
691 return Some(child);
692 }
693 }
694 },
695 Language::Java => {
696 for child in node.children(&mut node.walk()) {
697 if child.kind() == "block" {
698 return Some(child);
699 }
700 }
701 },
702 Language::C | Language::Cpp => {
703 for child in node.children(&mut node.walk()) {
704 if child.kind() == "compound_statement" {
705 return Some(child);
706 }
707 }
708 },
709 Language::CSharp | Language::Php | Language::Kotlin | Language::Swift | Language::Scala => {
710 for child in node.children(&mut node.walk()) {
711 let kind = child.kind();
712 if kind == "block" || kind == "compound_statement" || kind == "function_body" {
713 return Some(child);
714 }
715 }
716 },
717 Language::Ruby => {
718 for child in node.children(&mut node.walk()) {
719 if child.kind() == "body_statement" || child.kind() == "do_block" {
720 return Some(child);
721 }
722 }
723 },
724 Language::Bash => {
725 for child in node.children(&mut node.walk()) {
726 if child.kind() == "compound_statement" {
727 return Some(child);
728 }
729 }
730 },
731 Language::Haskell
732 | Language::Elixir
733 | Language::Clojure
734 | Language::OCaml
735 | Language::FSharp
736 | Language::R
737 | Language::Hcl
738 | Language::Zig
739 | Language::Dart => {
740 return Some(node);
741 },
742 Language::Lua => {
743 for child in node.children(&mut node.walk()) {
744 if child.kind() == "block" {
745 return Some(child);
746 }
747 }
748 },
749 }
750 None
751}
752
753const MAX_RECURSION_DEPTH: usize = 1000;
756
757pub fn collect_calls_recursive(
761 node: Node<'_>,
762 source_code: &str,
763 language: Language,
764 calls: &mut HashSet<String>,
765) {
766 collect_calls_recursive_with_depth(node, source_code, language, calls, 0);
767}
768
769fn collect_calls_recursive_with_depth(
771 node: Node<'_>,
772 source_code: &str,
773 language: Language,
774 calls: &mut HashSet<String>,
775 depth: usize,
776) {
777 if depth >= MAX_RECURSION_DEPTH {
779 return;
780 }
781
782 let kind = node.kind();
783
784 let call_name = match language {
785 Language::Python => {
786 if kind == "call" {
787 node.child_by_field_name("function").and_then(|f| {
788 if f.kind() == "identifier" {
789 f.utf8_text(source_code.as_bytes()).ok().map(String::from)
790 } else if f.kind() == "attribute" {
791 f.child_by_field_name("attribute")
792 .and_then(|a| a.utf8_text(source_code.as_bytes()).ok())
793 .map(String::from)
794 } else {
795 None
796 }
797 })
798 } else {
799 None
800 }
801 },
802 Language::Rust => {
803 if kind == "call_expression" {
804 node.child_by_field_name("function").and_then(|f| {
805 if f.kind() == "identifier" {
806 f.utf8_text(source_code.as_bytes()).ok().map(String::from)
807 } else if f.kind() == "field_expression" {
808 f.child_by_field_name("field")
809 .and_then(|a| a.utf8_text(source_code.as_bytes()).ok())
810 .map(String::from)
811 } else if f.kind() == "scoped_identifier" {
812 f.utf8_text(source_code.as_bytes()).ok().map(String::from)
813 } else {
814 None
815 }
816 })
817 } else if kind == "macro_invocation" {
818 node.child_by_field_name("macro")
819 .and_then(|m| m.utf8_text(source_code.as_bytes()).ok())
820 .map(|s| format!("{}!", s))
821 } else {
822 None
823 }
824 },
825 Language::JavaScript | Language::TypeScript => {
826 if kind == "call_expression" {
827 node.child_by_field_name("function").and_then(|f| {
828 if f.kind() == "identifier" {
829 f.utf8_text(source_code.as_bytes()).ok().map(String::from)
830 } else if f.kind() == "member_expression" {
831 f.child_by_field_name("property")
832 .and_then(|p| p.utf8_text(source_code.as_bytes()).ok())
833 .map(String::from)
834 } else {
835 None
836 }
837 })
838 } else {
839 None
840 }
841 },
842 Language::Go => {
843 if kind == "call_expression" {
844 node.child_by_field_name("function").and_then(|f| {
845 if f.kind() == "identifier" {
846 f.utf8_text(source_code.as_bytes()).ok().map(String::from)
847 } else if f.kind() == "selector_expression" {
848 f.child_by_field_name("field")
849 .and_then(|a| a.utf8_text(source_code.as_bytes()).ok())
850 .map(String::from)
851 } else {
852 None
853 }
854 })
855 } else {
856 None
857 }
858 },
859 Language::Java => {
860 if kind == "method_invocation" {
861 node.child_by_field_name("name")
862 .and_then(|n| n.utf8_text(source_code.as_bytes()).ok())
863 .map(String::from)
864 } else {
865 None
866 }
867 },
868 Language::C | Language::Cpp => {
869 if kind == "call_expression" {
870 node.child_by_field_name("function").and_then(|f| {
871 if f.kind() == "identifier" {
872 f.utf8_text(source_code.as_bytes()).ok().map(String::from)
873 } else if f.kind() == "field_expression" {
874 f.child_by_field_name("field")
875 .and_then(|a| a.utf8_text(source_code.as_bytes()).ok())
876 .map(String::from)
877 } else {
878 None
879 }
880 })
881 } else {
882 None
883 }
884 },
885 Language::CSharp | Language::Php | Language::Kotlin | Language::Swift | Language::Scala => {
886 if kind == "invocation_expression" || kind == "call_expression" {
887 node.children(&mut node.walk())
888 .find(|child| child.kind() == "identifier" || child.kind() == "simple_name")
889 .and_then(|child| child.utf8_text(source_code.as_bytes()).ok())
890 .map(|s| s.to_owned())
891 } else {
892 None
893 }
894 },
895 Language::Ruby => {
896 if kind == "call" || kind == "method_call" {
897 node.child_by_field_name("method")
898 .and_then(|m| m.utf8_text(source_code.as_bytes()).ok())
899 .map(String::from)
900 } else {
901 None
902 }
903 },
904 Language::Bash => {
905 if kind == "command" {
906 node.child_by_field_name("name")
907 .and_then(|n| n.utf8_text(source_code.as_bytes()).ok())
908 .map(String::from)
909 } else {
910 None
911 }
912 },
913 Language::Zig => {
914 if kind == "call_expression" {
915 node.child_by_field_name("function").and_then(|f| {
916 if f.kind() == "identifier" {
917 f.utf8_text(source_code.as_bytes()).ok().map(String::from)
918 } else if f.kind() == "field_expression" {
919 f.child_by_field_name("member")
921 .and_then(|m| m.utf8_text(source_code.as_bytes()).ok())
922 .map(String::from)
923 } else {
924 None
925 }
926 })
927 } else {
928 None
929 }
930 },
931 Language::Dart => {
932 if kind == "expression_statement" {
936 let children: Vec<_> = node.children(&mut node.walk()).collect();
937 if children.len() >= 2 && children[0].kind() == "identifier" {
938 let has_call = children.iter().any(|c| {
939 c.kind() == "selector"
940 && c.child(0).map_or(false, |gc| gc.kind() == "argument_part")
941 });
942 if has_call {
943 let mut name = children[0]
946 .utf8_text(source_code.as_bytes())
947 .ok()
948 .map(String::from);
949 for child in &children[1..] {
950 if child.kind() == "selector" {
951 if let Some(gc) = child.child(0) {
952 if gc.kind() == "unconditional_assignable_selector" {
953 name = gc
955 .children(&mut gc.walk())
956 .find(|c| c.kind() == "identifier")
957 .and_then(|id| {
958 id.utf8_text(source_code.as_bytes()).ok()
959 })
960 .map(String::from);
961 }
962 }
963 }
964 }
965 name
966 } else {
967 None
968 }
969 } else {
970 None
971 }
972 } else {
973 None
974 }
975 },
976 Language::Haskell
977 | Language::Elixir
978 | Language::Clojure
979 | Language::OCaml
980 | Language::FSharp
981 | Language::Lua
982 | Language::R
983 | Language::Hcl => {
984 if kind == "function_call" || kind == "call" || kind == "application" {
985 node.children(&mut node.walk())
986 .find(|child| child.kind() == "identifier" || child.kind() == "variable")
987 .and_then(|child| child.utf8_text(source_code.as_bytes()).ok())
988 .map(|s| s.to_owned())
989 } else {
990 None
991 }
992 },
993 };
994
995 if let Some(name) = call_name {
996 if !is_builtin(&name, language) {
997 calls.insert(name);
998 }
999 }
1000
1001 for child in node.children(&mut node.walk()) {
1002 collect_calls_recursive_with_depth(child, source_code, language, calls, depth + 1);
1003 }
1004}
1005
1006pub fn is_builtin(name: &str, language: Language) -> bool {
1008 match language {
1009 Language::Python => {
1010 matches!(
1011 name,
1012 "print"
1013 | "len"
1014 | "range"
1015 | "str"
1016 | "int"
1017 | "float"
1018 | "list"
1019 | "dict"
1020 | "set"
1021 | "tuple"
1022 | "bool"
1023 | "type"
1024 | "isinstance"
1025 | "hasattr"
1026 | "getattr"
1027 | "setattr"
1028 | "super"
1029 | "iter"
1030 | "next"
1031 | "open"
1032 | "input"
1033 | "format"
1034 | "enumerate"
1035 | "zip"
1036 | "map"
1037 | "filter"
1038 | "sorted"
1039 | "reversed"
1040 | "sum"
1041 | "min"
1042 | "max"
1043 | "abs"
1044 | "round"
1045 | "ord"
1046 | "chr"
1047 | "hex"
1048 | "bin"
1049 | "oct"
1050 )
1051 },
1052 Language::JavaScript | Language::TypeScript => {
1053 matches!(
1054 name,
1055 "console"
1056 | "log"
1057 | "error"
1058 | "warn"
1059 | "parseInt"
1060 | "parseFloat"
1061 | "setTimeout"
1062 | "setInterval"
1063 | "clearTimeout"
1064 | "clearInterval"
1065 | "JSON"
1066 | "stringify"
1067 | "parse"
1068 | "toString"
1069 | "valueOf"
1070 | "push"
1071 | "pop"
1072 | "shift"
1073 | "unshift"
1074 | "slice"
1075 | "splice"
1076 | "map"
1077 | "filter"
1078 | "reduce"
1079 | "forEach"
1080 | "find"
1081 | "findIndex"
1082 | "includes"
1083 | "indexOf"
1084 | "join"
1085 | "split"
1086 | "replace"
1087 )
1088 },
1089 Language::Rust => {
1090 matches!(
1091 name,
1092 "println!"
1093 | "print!"
1094 | "eprintln!"
1095 | "eprint!"
1096 | "format!"
1097 | "vec!"
1098 | "panic!"
1099 | "assert!"
1100 | "assert_eq!"
1101 | "assert_ne!"
1102 | "debug!"
1103 | "info!"
1104 | "warn!"
1105 | "error!"
1106 | "trace!"
1107 | "unwrap"
1108 | "expect"
1109 | "ok"
1110 | "err"
1111 | "some"
1112 | "none"
1113 | "clone"
1114 | "to_string"
1115 | "into"
1116 | "from"
1117 | "default"
1118 | "iter"
1119 | "into_iter"
1120 | "collect"
1121 | "map"
1122 | "filter"
1123 )
1124 },
1125 Language::Go => {
1126 matches!(
1127 name,
1128 "fmt"
1129 | "Println"
1130 | "Printf"
1131 | "Sprintf"
1132 | "Errorf"
1133 | "make"
1134 | "new"
1135 | "len"
1136 | "cap"
1137 | "append"
1138 | "copy"
1139 | "delete"
1140 | "close"
1141 | "panic"
1142 | "recover"
1143 | "print"
1144 )
1145 },
1146 Language::Java => {
1147 matches!(
1148 name,
1149 "println"
1150 | "print"
1151 | "printf"
1152 | "toString"
1153 | "equals"
1154 | "hashCode"
1155 | "getClass"
1156 | "clone"
1157 | "notify"
1158 | "wait"
1159 | "get"
1160 | "set"
1161 | "add"
1162 | "remove"
1163 | "size"
1164 | "isEmpty"
1165 | "contains"
1166 | "iterator"
1167 | "valueOf"
1168 | "parseInt"
1169 )
1170 },
1171 Language::C | Language::Cpp => {
1172 matches!(
1173 name,
1174 "printf"
1175 | "scanf"
1176 | "malloc"
1177 | "free"
1178 | "memcpy"
1179 | "memset"
1180 | "strlen"
1181 | "strcpy"
1182 | "strcmp"
1183 | "strcat"
1184 | "sizeof"
1185 | "cout"
1186 | "cin"
1187 | "endl"
1188 | "cerr"
1189 | "clog"
1190 )
1191 },
1192 Language::CSharp => {
1193 matches!(
1194 name,
1195 "WriteLine"
1196 | "Write"
1197 | "ReadLine"
1198 | "ToString"
1199 | "Equals"
1200 | "GetHashCode"
1201 | "GetType"
1202 | "Add"
1203 | "Remove"
1204 | "Contains"
1205 | "Count"
1206 | "Clear"
1207 | "ToList"
1208 | "ToArray"
1209 )
1210 },
1211 Language::Ruby => {
1212 matches!(
1213 name,
1214 "puts"
1215 | "print"
1216 | "p"
1217 | "gets"
1218 | "each"
1219 | "map"
1220 | "select"
1221 | "reject"
1222 | "reduce"
1223 | "inject"
1224 | "find"
1225 | "any?"
1226 | "all?"
1227 | "include?"
1228 | "empty?"
1229 | "nil?"
1230 | "length"
1231 | "size"
1232 )
1233 },
1234 Language::Php => {
1235 matches!(
1236 name,
1237 "echo"
1238 | "print"
1239 | "var_dump"
1240 | "print_r"
1241 | "isset"
1242 | "empty"
1243 | "array"
1244 | "count"
1245 | "strlen"
1246 | "strpos"
1247 | "substr"
1248 | "explode"
1249 | "implode"
1250 | "json_encode"
1251 | "json_decode"
1252 )
1253 },
1254 Language::Kotlin => {
1255 matches!(
1256 name,
1257 "println"
1258 | "print"
1259 | "readLine"
1260 | "toString"
1261 | "equals"
1262 | "hashCode"
1263 | "map"
1264 | "filter"
1265 | "forEach"
1266 | "let"
1267 | "also"
1268 | "apply"
1269 | "run"
1270 | "with"
1271 | "listOf"
1272 | "mapOf"
1273 | "setOf"
1274 )
1275 },
1276 Language::Swift => {
1277 matches!(
1278 name,
1279 "print"
1280 | "debugPrint"
1281 | "dump"
1282 | "map"
1283 | "filter"
1284 | "reduce"
1285 | "forEach"
1286 | "contains"
1287 | "count"
1288 | "isEmpty"
1289 | "append"
1290 )
1291 },
1292 Language::Scala => {
1293 matches!(
1294 name,
1295 "println"
1296 | "print"
1297 | "map"
1298 | "filter"
1299 | "flatMap"
1300 | "foreach"
1301 | "reduce"
1302 | "fold"
1303 | "foldLeft"
1304 | "foldRight"
1305 | "collect"
1306 )
1307 },
1308 Language::Bash
1309 | Language::Haskell
1310 | Language::Elixir
1311 | Language::Clojure
1312 | Language::OCaml
1313 | Language::FSharp
1314 | Language::Lua
1315 | Language::R
1316 | Language::Hcl
1317 | Language::Zig
1318 | Language::Dart => false,
1319 }
1320}
1321
1322pub fn clean_jsdoc(text: &str) -> String {
1324 text.lines()
1325 .map(|line| {
1326 line.trim()
1327 .trim_start_matches("/**")
1328 .trim_start_matches("/*")
1329 .trim_start_matches('*')
1330 .trim_end_matches("*/")
1331 .trim()
1332 })
1333 .filter(|line| !line.is_empty())
1334 .collect::<Vec<_>>()
1335 .join(" ")
1336}
1337
1338pub fn clean_javadoc(text: &str) -> String {
1340 clean_jsdoc(text)
1341}
1342
1343pub fn extract_inheritance(
1345 node: Node<'_>,
1346 source_code: &str,
1347 language: Language,
1348) -> (Option<String>, Vec<String>) {
1349 let mut extends = None;
1350 let mut implements = Vec::new();
1351
1352 match language {
1353 Language::Python => {
1354 if node.kind() == "class_definition" {
1356 if let Some(args) = node.child_by_field_name("superclasses") {
1357 for child in args.children(&mut args.walk()) {
1358 if child.kind() == "identifier" || child.kind() == "attribute" {
1359 if let Ok(name) = child.utf8_text(source_code.as_bytes()) {
1360 if extends.is_none() {
1361 extends = Some(name.to_owned());
1362 } else {
1363 implements.push(name.to_owned());
1364 }
1365 }
1366 }
1367 }
1368 }
1369 }
1370 },
1371 Language::JavaScript | Language::TypeScript => {
1372 if node.kind() == "class_declaration" || node.kind() == "class" {
1374 for child in node.children(&mut node.walk()) {
1375 if child.kind() == "class_heritage" {
1376 for heritage in child.children(&mut child.walk()) {
1377 if heritage.kind() == "extends_clause" {
1378 for type_node in heritage.children(&mut heritage.walk()) {
1379 if type_node.kind() == "identifier"
1380 || type_node.kind() == "type_identifier"
1381 {
1382 if let Ok(name) =
1383 type_node.utf8_text(source_code.as_bytes())
1384 {
1385 extends = Some(name.to_owned());
1386 }
1387 }
1388 }
1389 } else if heritage.kind() == "implements_clause" {
1390 for type_node in heritage.children(&mut heritage.walk()) {
1391 if type_node.kind() == "identifier"
1392 || type_node.kind() == "type_identifier"
1393 {
1394 if let Ok(name) =
1395 type_node.utf8_text(source_code.as_bytes())
1396 {
1397 implements.push(name.to_owned());
1398 }
1399 }
1400 }
1401 }
1402 }
1403 }
1404 }
1405 }
1406 },
1407 Language::Rust => {
1408 if node.kind() == "impl_item" {
1411 let mut has_for = false;
1412 for child in node.children(&mut node.walk()) {
1413 if child.kind() == "for" {
1414 has_for = true;
1415 }
1416 if child.kind() == "type_identifier" || child.kind() == "generic_type" {
1417 if let Ok(name) = child.utf8_text(source_code.as_bytes()) {
1418 if has_for {
1419 } else {
1421 implements.push(name.to_owned());
1423 }
1424 }
1425 }
1426 }
1427 }
1428 },
1429 Language::Go => {
1430 if node.kind() == "type_declaration" {
1432 for child in node.children(&mut node.walk()) {
1433 if child.kind() == "type_spec" {
1434 for spec_child in child.children(&mut child.walk()) {
1435 if spec_child.kind() == "struct_type" {
1436 for field in spec_child.children(&mut spec_child.walk()) {
1437 if field.kind() == "field_declaration" {
1438 let has_name = field.child_by_field_name("name").is_some();
1440 if !has_name {
1441 if let Some(type_node) =
1442 field.child_by_field_name("type")
1443 {
1444 if let Ok(name) =
1445 type_node.utf8_text(source_code.as_bytes())
1446 {
1447 implements.push(name.to_owned());
1448 }
1449 }
1450 }
1451 }
1452 }
1453 }
1454 }
1455 }
1456 }
1457 }
1458 },
1459 Language::Java => {
1460 if node.kind() == "class_declaration" {
1462 for child in node.children(&mut node.walk()) {
1463 if child.kind() == "superclass" {
1464 for type_node in child.children(&mut child.walk()) {
1465 if type_node.kind() == "type_identifier" {
1466 if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1467 extends = Some(name.to_owned());
1468 }
1469 }
1470 }
1471 } else if child.kind() == "super_interfaces" {
1472 for type_list in child.children(&mut child.walk()) {
1473 if type_list.kind() == "type_list" {
1474 for type_node in type_list.children(&mut type_list.walk()) {
1475 if type_node.kind() == "type_identifier" {
1476 if let Ok(name) =
1477 type_node.utf8_text(source_code.as_bytes())
1478 {
1479 implements.push(name.to_owned());
1480 }
1481 }
1482 }
1483 }
1484 }
1485 }
1486 }
1487 }
1488 },
1489 Language::C | Language::Cpp => {
1490 if node.kind() == "class_specifier" || node.kind() == "struct_specifier" {
1492 for child in node.children(&mut node.walk()) {
1493 if child.kind() == "base_class_clause" {
1494 for base in child.children(&mut child.walk()) {
1495 if base.kind() == "type_identifier" {
1496 if let Ok(name) = base.utf8_text(source_code.as_bytes()) {
1497 if extends.is_none() {
1498 extends = Some(name.to_owned());
1499 } else {
1500 implements.push(name.to_owned());
1501 }
1502 }
1503 }
1504 }
1505 }
1506 }
1507 }
1508 },
1509 Language::CSharp => {
1510 if node.kind() == "class_declaration" {
1512 for child in node.children(&mut node.walk()) {
1513 if child.kind() == "base_list" {
1514 for base in child.children(&mut child.walk()) {
1515 if base.kind() == "identifier" || base.kind() == "generic_name" {
1516 if let Ok(name) = base.utf8_text(source_code.as_bytes()) {
1517 if name.starts_with('I') && name.len() > 1 {
1518 implements.push(name.to_owned());
1520 } else if extends.is_none() {
1521 extends = Some(name.to_owned());
1522 } else {
1523 implements.push(name.to_owned());
1524 }
1525 }
1526 }
1527 }
1528 }
1529 }
1530 }
1531 },
1532 Language::Ruby => {
1533 if node.kind() == "class" {
1535 for child in node.children(&mut node.walk()) {
1536 if child.kind() == "superclass" {
1537 for type_node in child.children(&mut child.walk()) {
1538 if type_node.kind() == "constant" {
1539 if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1540 extends = Some(name.to_owned());
1541 }
1542 }
1543 }
1544 }
1545 }
1546 }
1547 },
1548 Language::Php => {
1549 if node.kind() == "class_declaration" {
1551 for child in node.children(&mut node.walk()) {
1552 if child.kind() == "base_clause" {
1553 for type_node in child.children(&mut child.walk()) {
1554 if type_node.kind() == "name" {
1555 if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1556 extends = Some(name.to_owned());
1557 }
1558 }
1559 }
1560 } else if child.kind() == "class_interface_clause" {
1561 for type_node in child.children(&mut child.walk()) {
1562 if type_node.kind() == "name" {
1563 if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1564 implements.push(name.to_owned());
1565 }
1566 }
1567 }
1568 }
1569 }
1570 }
1571 },
1572 Language::Kotlin => {
1573 if node.kind() == "class_declaration" {
1575 for child in node.children(&mut node.walk()) {
1576 if child.kind() == "delegation_specifiers" {
1577 for spec in child.children(&mut child.walk()) {
1578 if spec.kind() == "delegation_specifier" {
1579 for type_node in spec.children(&mut spec.walk()) {
1580 if type_node.kind() == "user_type" {
1581 if let Ok(name) =
1582 type_node.utf8_text(source_code.as_bytes())
1583 {
1584 if extends.is_none() {
1585 extends = Some(name.to_owned());
1586 } else {
1587 implements.push(name.to_owned());
1588 }
1589 }
1590 }
1591 }
1592 }
1593 }
1594 }
1595 }
1596 }
1597 },
1598 Language::Swift => {
1599 if node.kind() == "class_declaration" {
1601 for child in node.children(&mut node.walk()) {
1602 if child.kind() == "type_inheritance_clause" {
1603 for type_node in child.children(&mut child.walk()) {
1604 if type_node.kind() == "type_identifier" {
1605 if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1606 if extends.is_none() {
1607 extends = Some(name.to_owned());
1608 } else {
1609 implements.push(name.to_owned());
1610 }
1611 }
1612 }
1613 }
1614 }
1615 }
1616 }
1617 },
1618 Language::Scala => {
1619 if node.kind() == "class_definition" {
1621 for child in node.children(&mut node.walk()) {
1622 if child.kind() == "extends_clause" {
1623 for type_node in child.children(&mut child.walk()) {
1624 if type_node.kind() == "type_identifier" {
1625 if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1626 if extends.is_none() {
1627 extends = Some(name.to_owned());
1628 } else {
1629 implements.push(name.to_owned());
1630 }
1631 }
1632 }
1633 }
1634 }
1635 }
1636 }
1637 },
1638 Language::Dart => {
1639 if node.kind() == "class_definition" {
1649 for child in node.children(&mut node.walk()) {
1650 if child.kind() == "superclass" {
1651 for sc_child in child.children(&mut child.walk()) {
1652 if sc_child.kind() == "type_identifier" {
1653 if let Ok(name) = sc_child.utf8_text(source_code.as_bytes()) {
1655 extends = Some(name.to_owned());
1656 }
1657 } else if sc_child.kind() == "mixins" {
1658 for mixin_type in sc_child.children(&mut sc_child.walk()) {
1660 if mixin_type.kind() == "type_identifier" {
1661 if let Ok(name) =
1662 mixin_type.utf8_text(source_code.as_bytes())
1663 {
1664 implements.push(name.to_owned());
1665 }
1666 }
1667 }
1668 }
1669 }
1670 } else if child.kind() == "interfaces" {
1671 for type_node in child.children(&mut child.walk()) {
1672 if type_node.kind() == "type_identifier" {
1673 if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1674 implements.push(name.to_owned());
1675 }
1676 }
1677 }
1678 }
1679 }
1680 }
1681 },
1682 Language::Bash
1683 | Language::Haskell
1684 | Language::Elixir
1685 | Language::Clojure
1686 | Language::OCaml
1687 | Language::FSharp
1688 | Language::Lua
1689 | Language::R
1690 | Language::Hcl
1691 | Language::Zig => {},
1692 }
1693
1694 (extends, implements)
1695}
1696
1697pub fn map_symbol_kind(capture_name: &str) -> SymbolKind {
1699 match capture_name {
1700 "function" => SymbolKind::Function,
1701 "class" => SymbolKind::Class,
1702 "method" => SymbolKind::Method,
1703 "struct" => SymbolKind::Struct,
1704 "enum" => SymbolKind::Enum,
1705 "interface" => SymbolKind::Interface,
1706 "trait" => SymbolKind::Trait,
1707 "constant" => SymbolKind::Constant,
1708 "module" => SymbolKind::Module,
1709 _ => SymbolKind::Function,
1710 }
1711}
1712
1713#[cfg(test)]
1714mod tests {
1715 use super::*;
1716
1717 #[test]
1722 fn test_safe_char_boundary_ascii() {
1723 let s = "hello world";
1724 assert_eq!(safe_char_boundary(s, 0), 0);
1725 assert_eq!(safe_char_boundary(s, 5), 5);
1726 assert_eq!(safe_char_boundary(s, 11), 11);
1727 }
1728
1729 #[test]
1730 fn test_safe_char_boundary_beyond_length() {
1731 let s = "hello";
1732 assert_eq!(safe_char_boundary(s, 100), 5);
1733 assert_eq!(safe_char_boundary(s, 5), 5);
1734 }
1735
1736 #[test]
1737 fn test_safe_char_boundary_empty_string() {
1738 let s = "";
1739 assert_eq!(safe_char_boundary(s, 0), 0);
1740 assert_eq!(safe_char_boundary(s, 10), 0);
1741 }
1742
1743 #[test]
1744 fn test_safe_char_boundary_multibyte_utf8() {
1745 let s = "䏿–‡";
1747 assert_eq!(safe_char_boundary(s, 0), 0);
1749 assert_eq!(safe_char_boundary(s, 1), 0);
1751 assert_eq!(safe_char_boundary(s, 2), 0);
1753 assert_eq!(safe_char_boundary(s, 3), 3);
1755 assert_eq!(safe_char_boundary(s, 4), 3);
1757 }
1758
1759 #[test]
1760 fn test_safe_char_boundary_emoji() {
1761 let s = "Hello 👋 World";
1763 assert_eq!(safe_char_boundary(s, 6), 6);
1765 assert_eq!(safe_char_boundary(s, 7), 6);
1767 assert_eq!(safe_char_boundary(s, 8), 6);
1768 assert_eq!(safe_char_boundary(s, 9), 6);
1769 assert_eq!(safe_char_boundary(s, 10), 10);
1771 }
1772
1773 #[test]
1774 fn test_safe_char_boundary_mixed_content() {
1775 let s = "aбв"; assert_eq!(safe_char_boundary(s, 0), 0);
1778 assert_eq!(safe_char_boundary(s, 1), 1); assert_eq!(safe_char_boundary(s, 2), 1); assert_eq!(safe_char_boundary(s, 3), 3); assert_eq!(safe_char_boundary(s, 4), 3); assert_eq!(safe_char_boundary(s, 5), 5); }
1784
1785 #[test]
1790 fn test_clean_jsdoc_simple() {
1791 let input = "/** This is a simple doc */";
1792 assert_eq!(clean_jsdoc(input), "This is a simple doc");
1793 }
1794
1795 #[test]
1796 fn test_clean_jsdoc_multiline() {
1797 let input = "/**\n * Line 1\n * Line 2\n */";
1798 let result = clean_jsdoc(input);
1799 assert!(result.contains("Line 1"));
1801 assert!(result.contains("Line 2"));
1802 }
1803
1804 #[test]
1805 fn test_clean_jsdoc_with_asterisks() {
1806 let input = "/**\n * First line\n * Second line\n * Third line\n */";
1807 let result = clean_jsdoc(input);
1808 assert!(result.contains("First line"));
1809 assert!(result.contains("Second line"));
1810 assert!(result.contains("Third line"));
1811 }
1812
1813 #[test]
1814 fn test_clean_jsdoc_empty() {
1815 let input = "/** */";
1816 assert_eq!(clean_jsdoc(input), "");
1817 }
1818
1819 #[test]
1820 fn test_clean_jsdoc_c_style_comment() {
1821 let input = "/* Regular C comment */";
1822 assert_eq!(clean_jsdoc(input), "Regular C comment");
1823 }
1824
1825 #[test]
1826 fn test_clean_jsdoc_with_tags() {
1827 let input = "/**\n * Description\n * @param x The x value\n * @returns Result\n */";
1828 let result = clean_jsdoc(input);
1829 assert!(result.contains("Description"));
1830 assert!(result.contains("@param x"));
1831 assert!(result.contains("@returns"));
1832 }
1833
1834 #[test]
1835 fn test_clean_jsdoc_whitespace_handling() {
1836 let input = "/** \n * Lots of spaces \n */";
1837 assert!(clean_jsdoc(input).contains("Lots of spaces"));
1838 }
1839
1840 #[test]
1845 fn test_clean_javadoc_simple() {
1846 let input = "/** JavaDoc comment */";
1847 assert_eq!(clean_javadoc(input), "JavaDoc comment");
1848 }
1849
1850 #[test]
1851 fn test_clean_javadoc_multiline() {
1852 let input = "/**\n * Method description.\n * @param name The name\n */";
1853 let result = clean_javadoc(input);
1854 assert!(result.contains("Method description"));
1855 assert!(result.contains("@param name"));
1856 }
1857
1858 #[test]
1863 fn test_map_symbol_kind_function() {
1864 assert_eq!(map_symbol_kind("function"), SymbolKind::Function);
1865 }
1866
1867 #[test]
1868 fn test_map_symbol_kind_class() {
1869 assert_eq!(map_symbol_kind("class"), SymbolKind::Class);
1870 }
1871
1872 #[test]
1873 fn test_map_symbol_kind_method() {
1874 assert_eq!(map_symbol_kind("method"), SymbolKind::Method);
1875 }
1876
1877 #[test]
1878 fn test_map_symbol_kind_struct() {
1879 assert_eq!(map_symbol_kind("struct"), SymbolKind::Struct);
1880 }
1881
1882 #[test]
1883 fn test_map_symbol_kind_enum() {
1884 assert_eq!(map_symbol_kind("enum"), SymbolKind::Enum);
1885 }
1886
1887 #[test]
1888 fn test_map_symbol_kind_interface() {
1889 assert_eq!(map_symbol_kind("interface"), SymbolKind::Interface);
1890 }
1891
1892 #[test]
1893 fn test_map_symbol_kind_trait() {
1894 assert_eq!(map_symbol_kind("trait"), SymbolKind::Trait);
1895 }
1896
1897 #[test]
1898 fn test_map_symbol_kind_unknown() {
1899 assert_eq!(map_symbol_kind("unknown"), SymbolKind::Function);
1901 assert_eq!(map_symbol_kind(""), SymbolKind::Function);
1902 assert_eq!(map_symbol_kind("random"), SymbolKind::Function);
1903 }
1904
1905 #[test]
1910 fn test_is_builtin_python_print() {
1911 assert!(is_builtin("print", Language::Python));
1912 assert!(is_builtin("len", Language::Python));
1913 assert!(is_builtin("range", Language::Python));
1914 assert!(is_builtin("str", Language::Python));
1915 assert!(is_builtin("int", Language::Python));
1916 assert!(is_builtin("float", Language::Python));
1917 assert!(is_builtin("list", Language::Python));
1918 assert!(is_builtin("dict", Language::Python));
1919 assert!(is_builtin("set", Language::Python));
1920 assert!(is_builtin("tuple", Language::Python));
1921 }
1922
1923 #[test]
1924 fn test_is_builtin_python_type_funcs() {
1925 assert!(is_builtin("bool", Language::Python));
1926 assert!(is_builtin("type", Language::Python));
1927 assert!(is_builtin("isinstance", Language::Python));
1928 assert!(is_builtin("hasattr", Language::Python));
1929 assert!(is_builtin("getattr", Language::Python));
1930 assert!(is_builtin("setattr", Language::Python));
1931 assert!(is_builtin("super", Language::Python));
1932 }
1933
1934 #[test]
1935 fn test_is_builtin_python_itertools() {
1936 assert!(is_builtin("iter", Language::Python));
1937 assert!(is_builtin("next", Language::Python));
1938 assert!(is_builtin("enumerate", Language::Python));
1939 assert!(is_builtin("zip", Language::Python));
1940 assert!(is_builtin("map", Language::Python));
1941 assert!(is_builtin("filter", Language::Python));
1942 assert!(is_builtin("sorted", Language::Python));
1943 assert!(is_builtin("reversed", Language::Python));
1944 }
1945
1946 #[test]
1947 fn test_is_builtin_python_math() {
1948 assert!(is_builtin("sum", Language::Python));
1949 assert!(is_builtin("min", Language::Python));
1950 assert!(is_builtin("max", Language::Python));
1951 assert!(is_builtin("abs", Language::Python));
1952 assert!(is_builtin("round", Language::Python));
1953 }
1954
1955 #[test]
1956 fn test_is_builtin_python_not_builtin() {
1957 assert!(!is_builtin("my_function", Language::Python));
1958 assert!(!is_builtin("custom_print", Language::Python));
1959 assert!(!is_builtin("calculate", Language::Python));
1960 }
1961
1962 #[test]
1967 fn test_is_builtin_js_console() {
1968 assert!(is_builtin("console", Language::JavaScript));
1969 assert!(is_builtin("log", Language::JavaScript));
1970 assert!(is_builtin("error", Language::JavaScript));
1971 assert!(is_builtin("warn", Language::JavaScript));
1972 }
1973
1974 #[test]
1975 fn test_is_builtin_js_parsing() {
1976 assert!(is_builtin("parseInt", Language::JavaScript));
1977 assert!(is_builtin("parseFloat", Language::JavaScript));
1978 assert!(is_builtin("JSON", Language::JavaScript));
1979 assert!(is_builtin("stringify", Language::JavaScript));
1980 assert!(is_builtin("parse", Language::JavaScript));
1981 }
1982
1983 #[test]
1984 fn test_is_builtin_js_timers() {
1985 assert!(is_builtin("setTimeout", Language::JavaScript));
1986 assert!(is_builtin("setInterval", Language::JavaScript));
1987 assert!(is_builtin("clearTimeout", Language::JavaScript));
1988 assert!(is_builtin("clearInterval", Language::JavaScript));
1989 }
1990
1991 #[test]
1992 fn test_is_builtin_js_array_methods() {
1993 assert!(is_builtin("push", Language::JavaScript));
1994 assert!(is_builtin("pop", Language::JavaScript));
1995 assert!(is_builtin("shift", Language::JavaScript));
1996 assert!(is_builtin("unshift", Language::JavaScript));
1997 assert!(is_builtin("slice", Language::JavaScript));
1998 assert!(is_builtin("splice", Language::JavaScript));
1999 assert!(is_builtin("map", Language::JavaScript));
2000 assert!(is_builtin("filter", Language::JavaScript));
2001 assert!(is_builtin("reduce", Language::JavaScript));
2002 assert!(is_builtin("forEach", Language::JavaScript));
2003 }
2004
2005 #[test]
2006 fn test_is_builtin_ts_same_as_js() {
2007 assert!(is_builtin("console", Language::TypeScript));
2008 assert!(is_builtin("map", Language::TypeScript));
2009 assert!(is_builtin("filter", Language::TypeScript));
2010 }
2011
2012 #[test]
2013 fn test_is_builtin_js_not_builtin() {
2014 assert!(!is_builtin("myFunction", Language::JavaScript));
2015 assert!(!is_builtin("customLog", Language::JavaScript));
2016 }
2017
2018 #[test]
2023 fn test_is_builtin_rust_macros() {
2024 assert!(is_builtin("println!", Language::Rust));
2025 assert!(is_builtin("print!", Language::Rust));
2026 assert!(is_builtin("eprintln!", Language::Rust));
2027 assert!(is_builtin("eprint!", Language::Rust));
2028 assert!(is_builtin("format!", Language::Rust));
2029 assert!(is_builtin("vec!", Language::Rust));
2030 assert!(is_builtin("panic!", Language::Rust));
2031 assert!(is_builtin("assert!", Language::Rust));
2032 assert!(is_builtin("assert_eq!", Language::Rust));
2033 assert!(is_builtin("assert_ne!", Language::Rust));
2034 }
2035
2036 #[test]
2037 fn test_is_builtin_rust_logging() {
2038 assert!(is_builtin("debug!", Language::Rust));
2039 assert!(is_builtin("info!", Language::Rust));
2040 assert!(is_builtin("warn!", Language::Rust));
2041 assert!(is_builtin("error!", Language::Rust));
2042 assert!(is_builtin("trace!", Language::Rust));
2043 }
2044
2045 #[test]
2046 fn test_is_builtin_rust_common_methods() {
2047 assert!(is_builtin("unwrap", Language::Rust));
2048 assert!(is_builtin("expect", Language::Rust));
2049 assert!(is_builtin("ok", Language::Rust));
2050 assert!(is_builtin("err", Language::Rust));
2051 assert!(is_builtin("some", Language::Rust));
2052 assert!(is_builtin("none", Language::Rust));
2053 assert!(is_builtin("clone", Language::Rust));
2054 assert!(is_builtin("to_string", Language::Rust));
2055 assert!(is_builtin("into", Language::Rust));
2056 assert!(is_builtin("from", Language::Rust));
2057 assert!(is_builtin("default", Language::Rust));
2058 }
2059
2060 #[test]
2061 fn test_is_builtin_rust_iterators() {
2062 assert!(is_builtin("iter", Language::Rust));
2063 assert!(is_builtin("into_iter", Language::Rust));
2064 assert!(is_builtin("collect", Language::Rust));
2065 assert!(is_builtin("map", Language::Rust));
2066 assert!(is_builtin("filter", Language::Rust));
2067 }
2068
2069 #[test]
2070 fn test_is_builtin_rust_not_builtin() {
2071 assert!(!is_builtin("my_function", Language::Rust));
2072 assert!(!is_builtin("process_data", Language::Rust));
2073 }
2074
2075 #[test]
2080 fn test_is_builtin_go_fmt() {
2081 assert!(is_builtin("fmt", Language::Go));
2082 assert!(is_builtin("Println", Language::Go));
2083 assert!(is_builtin("Printf", Language::Go));
2084 assert!(is_builtin("Sprintf", Language::Go));
2085 assert!(is_builtin("Errorf", Language::Go));
2086 }
2087
2088 #[test]
2089 fn test_is_builtin_go_memory() {
2090 assert!(is_builtin("make", Language::Go));
2091 assert!(is_builtin("new", Language::Go));
2092 assert!(is_builtin("len", Language::Go));
2093 assert!(is_builtin("cap", Language::Go));
2094 assert!(is_builtin("append", Language::Go));
2095 assert!(is_builtin("copy", Language::Go));
2096 assert!(is_builtin("delete", Language::Go));
2097 }
2098
2099 #[test]
2100 fn test_is_builtin_go_control() {
2101 assert!(is_builtin("close", Language::Go));
2102 assert!(is_builtin("panic", Language::Go));
2103 assert!(is_builtin("recover", Language::Go));
2104 assert!(is_builtin("print", Language::Go));
2105 }
2106
2107 #[test]
2108 fn test_is_builtin_go_not_builtin() {
2109 assert!(!is_builtin("ProcessData", Language::Go));
2110 assert!(!is_builtin("handleRequest", Language::Go));
2111 }
2112
2113 #[test]
2118 fn test_is_builtin_java_io() {
2119 assert!(is_builtin("println", Language::Java));
2120 assert!(is_builtin("print", Language::Java));
2121 assert!(is_builtin("printf", Language::Java));
2122 }
2123
2124 #[test]
2125 fn test_is_builtin_java_object() {
2126 assert!(is_builtin("toString", Language::Java));
2127 assert!(is_builtin("equals", Language::Java));
2128 assert!(is_builtin("hashCode", Language::Java));
2129 assert!(is_builtin("getClass", Language::Java));
2130 assert!(is_builtin("clone", Language::Java));
2131 assert!(is_builtin("notify", Language::Java));
2132 assert!(is_builtin("wait", Language::Java));
2133 }
2134
2135 #[test]
2136 fn test_is_builtin_java_collections() {
2137 assert!(is_builtin("get", Language::Java));
2138 assert!(is_builtin("set", Language::Java));
2139 assert!(is_builtin("add", Language::Java));
2140 assert!(is_builtin("remove", Language::Java));
2141 assert!(is_builtin("size", Language::Java));
2142 assert!(is_builtin("isEmpty", Language::Java));
2143 assert!(is_builtin("contains", Language::Java));
2144 assert!(is_builtin("iterator", Language::Java));
2145 }
2146
2147 #[test]
2148 fn test_is_builtin_java_not_builtin() {
2149 assert!(!is_builtin("processData", Language::Java));
2150 assert!(!is_builtin("calculateTotal", Language::Java));
2151 }
2152
2153 #[test]
2158 fn test_is_builtin_c_io() {
2159 assert!(is_builtin("printf", Language::C));
2160 assert!(is_builtin("scanf", Language::C));
2161 }
2162
2163 #[test]
2164 fn test_is_builtin_c_memory() {
2165 assert!(is_builtin("malloc", Language::C));
2166 assert!(is_builtin("free", Language::C));
2167 assert!(is_builtin("memcpy", Language::C));
2168 assert!(is_builtin("memset", Language::C));
2169 }
2170
2171 #[test]
2172 fn test_is_builtin_c_string() {
2173 assert!(is_builtin("strlen", Language::C));
2174 assert!(is_builtin("strcpy", Language::C));
2175 assert!(is_builtin("strcmp", Language::C));
2176 assert!(is_builtin("strcat", Language::C));
2177 }
2178
2179 #[test]
2180 fn test_is_builtin_cpp_streams() {
2181 assert!(is_builtin("cout", Language::Cpp));
2182 assert!(is_builtin("cin", Language::Cpp));
2183 assert!(is_builtin("endl", Language::Cpp));
2184 assert!(is_builtin("cerr", Language::Cpp));
2185 assert!(is_builtin("clog", Language::Cpp));
2186 }
2187
2188 #[test]
2189 fn test_is_builtin_c_not_builtin() {
2190 assert!(!is_builtin("process_data", Language::C));
2191 assert!(!is_builtin("custom_malloc", Language::C));
2192 }
2193
2194 #[test]
2199 fn test_is_builtin_csharp_console() {
2200 assert!(is_builtin("WriteLine", Language::CSharp));
2201 assert!(is_builtin("Write", Language::CSharp));
2202 assert!(is_builtin("ReadLine", Language::CSharp));
2203 }
2204
2205 #[test]
2206 fn test_is_builtin_csharp_object() {
2207 assert!(is_builtin("ToString", Language::CSharp));
2208 assert!(is_builtin("Equals", Language::CSharp));
2209 assert!(is_builtin("GetHashCode", Language::CSharp));
2210 assert!(is_builtin("GetType", Language::CSharp));
2211 }
2212
2213 #[test]
2214 fn test_is_builtin_csharp_collections() {
2215 assert!(is_builtin("Add", Language::CSharp));
2216 assert!(is_builtin("Remove", Language::CSharp));
2217 assert!(is_builtin("Contains", Language::CSharp));
2218 assert!(is_builtin("Count", Language::CSharp));
2219 assert!(is_builtin("Clear", Language::CSharp));
2220 assert!(is_builtin("ToList", Language::CSharp));
2221 assert!(is_builtin("ToArray", Language::CSharp));
2222 }
2223
2224 #[test]
2229 fn test_is_builtin_ruby_io() {
2230 assert!(is_builtin("puts", Language::Ruby));
2231 assert!(is_builtin("print", Language::Ruby));
2232 assert!(is_builtin("p", Language::Ruby));
2233 assert!(is_builtin("gets", Language::Ruby));
2234 }
2235
2236 #[test]
2237 fn test_is_builtin_ruby_enumerable() {
2238 assert!(is_builtin("each", Language::Ruby));
2239 assert!(is_builtin("map", Language::Ruby));
2240 assert!(is_builtin("select", Language::Ruby));
2241 assert!(is_builtin("reject", Language::Ruby));
2242 assert!(is_builtin("reduce", Language::Ruby));
2243 assert!(is_builtin("inject", Language::Ruby));
2244 assert!(is_builtin("find", Language::Ruby));
2245 }
2246
2247 #[test]
2248 fn test_is_builtin_ruby_predicates() {
2249 assert!(is_builtin("any?", Language::Ruby));
2250 assert!(is_builtin("all?", Language::Ruby));
2251 assert!(is_builtin("include?", Language::Ruby));
2252 assert!(is_builtin("empty?", Language::Ruby));
2253 assert!(is_builtin("nil?", Language::Ruby));
2254 }
2255
2256 #[test]
2261 fn test_is_builtin_php_io() {
2262 assert!(is_builtin("echo", Language::Php));
2263 assert!(is_builtin("print", Language::Php));
2264 assert!(is_builtin("var_dump", Language::Php));
2265 assert!(is_builtin("print_r", Language::Php));
2266 }
2267
2268 #[test]
2269 fn test_is_builtin_php_checks() {
2270 assert!(is_builtin("isset", Language::Php));
2271 assert!(is_builtin("empty", Language::Php));
2272 }
2273
2274 #[test]
2275 fn test_is_builtin_php_array_string() {
2276 assert!(is_builtin("array", Language::Php));
2277 assert!(is_builtin("count", Language::Php));
2278 assert!(is_builtin("strlen", Language::Php));
2279 assert!(is_builtin("strpos", Language::Php));
2280 assert!(is_builtin("substr", Language::Php));
2281 assert!(is_builtin("explode", Language::Php));
2282 assert!(is_builtin("implode", Language::Php));
2283 assert!(is_builtin("json_encode", Language::Php));
2284 assert!(is_builtin("json_decode", Language::Php));
2285 }
2286
2287 #[test]
2292 fn test_is_builtin_kotlin_io() {
2293 assert!(is_builtin("println", Language::Kotlin));
2294 assert!(is_builtin("print", Language::Kotlin));
2295 assert!(is_builtin("readLine", Language::Kotlin));
2296 }
2297
2298 #[test]
2299 fn test_is_builtin_kotlin_scope() {
2300 assert!(is_builtin("let", Language::Kotlin));
2301 assert!(is_builtin("also", Language::Kotlin));
2302 assert!(is_builtin("apply", Language::Kotlin));
2303 assert!(is_builtin("run", Language::Kotlin));
2304 assert!(is_builtin("with", Language::Kotlin));
2305 }
2306
2307 #[test]
2308 fn test_is_builtin_kotlin_collections() {
2309 assert!(is_builtin("listOf", Language::Kotlin));
2310 assert!(is_builtin("mapOf", Language::Kotlin));
2311 assert!(is_builtin("setOf", Language::Kotlin));
2312 assert!(is_builtin("map", Language::Kotlin));
2313 assert!(is_builtin("filter", Language::Kotlin));
2314 assert!(is_builtin("forEach", Language::Kotlin));
2315 }
2316
2317 #[test]
2322 fn test_is_builtin_swift_io() {
2323 assert!(is_builtin("print", Language::Swift));
2324 assert!(is_builtin("debugPrint", Language::Swift));
2325 assert!(is_builtin("dump", Language::Swift));
2326 }
2327
2328 #[test]
2329 fn test_is_builtin_swift_functional() {
2330 assert!(is_builtin("map", Language::Swift));
2331 assert!(is_builtin("filter", Language::Swift));
2332 assert!(is_builtin("reduce", Language::Swift));
2333 assert!(is_builtin("forEach", Language::Swift));
2334 }
2335
2336 #[test]
2337 fn test_is_builtin_swift_collection() {
2338 assert!(is_builtin("contains", Language::Swift));
2339 assert!(is_builtin("count", Language::Swift));
2340 assert!(is_builtin("isEmpty", Language::Swift));
2341 assert!(is_builtin("append", Language::Swift));
2342 }
2343
2344 #[test]
2349 fn test_is_builtin_scala_io() {
2350 assert!(is_builtin("println", Language::Scala));
2351 assert!(is_builtin("print", Language::Scala));
2352 }
2353
2354 #[test]
2355 fn test_is_builtin_scala_functional() {
2356 assert!(is_builtin("map", Language::Scala));
2357 assert!(is_builtin("filter", Language::Scala));
2358 assert!(is_builtin("flatMap", Language::Scala));
2359 assert!(is_builtin("foreach", Language::Scala));
2360 assert!(is_builtin("reduce", Language::Scala));
2361 assert!(is_builtin("fold", Language::Scala));
2362 assert!(is_builtin("foldLeft", Language::Scala));
2363 assert!(is_builtin("foldRight", Language::Scala));
2364 assert!(is_builtin("collect", Language::Scala));
2365 }
2366
2367 #[test]
2372 fn test_is_builtin_bash_always_false() {
2373 assert!(!is_builtin("ls", Language::Bash));
2374 assert!(!is_builtin("echo", Language::Bash));
2375 assert!(!is_builtin("grep", Language::Bash));
2376 }
2377
2378 #[test]
2379 fn test_is_builtin_haskell_always_false() {
2380 assert!(!is_builtin("putStrLn", Language::Haskell));
2381 assert!(!is_builtin("map", Language::Haskell));
2382 }
2383
2384 #[test]
2385 fn test_is_builtin_elixir_always_false() {
2386 assert!(!is_builtin("IO.puts", Language::Elixir));
2387 assert!(!is_builtin("Enum.map", Language::Elixir));
2388 }
2389
2390 #[test]
2391 fn test_is_builtin_clojure_always_false() {
2392 assert!(!is_builtin("println", Language::Clojure));
2393 assert!(!is_builtin("map", Language::Clojure));
2394 }
2395
2396 #[test]
2397 fn test_is_builtin_ocaml_always_false() {
2398 assert!(!is_builtin("print_endline", Language::OCaml));
2399 assert!(!is_builtin("List.map", Language::OCaml));
2400 }
2401
2402 #[test]
2403 fn test_is_builtin_fsharp_always_false() {
2404 assert!(!is_builtin("printfn", Language::FSharp));
2405 assert!(!is_builtin("List.map", Language::FSharp));
2406 }
2407
2408 #[test]
2409 fn test_is_builtin_lua_always_false() {
2410 assert!(!is_builtin("print", Language::Lua));
2411 assert!(!is_builtin("pairs", Language::Lua));
2412 }
2413
2414 #[test]
2415 fn test_is_builtin_r_always_false() {
2416 assert!(!is_builtin("print", Language::R));
2417 assert!(!is_builtin("cat", Language::R));
2418 }
2419
2420 fn parse_and_find_node(
2426 code: &str,
2427 language: Language,
2428 node_kind: &str,
2429 ) -> Option<(tree_sitter::Tree, usize)> {
2430 let mut parser = tree_sitter::Parser::new();
2431
2432 let ts_language = match language {
2433 Language::Python => tree_sitter_python::LANGUAGE,
2434 Language::Rust => tree_sitter_rust::LANGUAGE,
2435 Language::JavaScript => tree_sitter_javascript::LANGUAGE,
2436 Language::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT,
2437 Language::Go => tree_sitter_go::LANGUAGE,
2438 Language::Java => tree_sitter_java::LANGUAGE,
2439 _ => return None,
2440 };
2441
2442 parser
2443 .set_language(&ts_language.into())
2444 .expect("Error loading grammar");
2445
2446 let tree = parser.parse(code, None)?;
2447 let root = tree.root_node();
2448
2449 fn find_node_recursive(node: Node<'_>, kind: &str) -> Option<usize> {
2450 if node.kind() == kind {
2451 return Some(node.id());
2452 }
2453 for child in node.children(&mut node.walk()) {
2454 if let Some(id) = find_node_recursive(child, kind) {
2455 return Some(id);
2456 }
2457 }
2458 None
2459 }
2460
2461 find_node_recursive(root, node_kind).map(|_| (tree, 0))
2462 }
2463
2464 fn find_node_in_tree<'a>(node: Node<'a>, kind: &str) -> Option<Node<'a>> {
2466 if node.kind() == kind {
2467 return Some(node);
2468 }
2469 for child in node.children(&mut node.walk()) {
2470 if let Some(found) = find_node_in_tree(child, kind) {
2471 return Some(found);
2472 }
2473 }
2474 None
2475 }
2476
2477 #[test]
2478 fn test_extract_signature_python() {
2479 let code = "def hello(name):\n return f'Hello {name}'";
2482 let mut parser = tree_sitter::Parser::new();
2483 parser
2484 .set_language(&tree_sitter_python::LANGUAGE.into())
2485 .unwrap();
2486 let tree = parser.parse(code, None).unwrap();
2487 let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2488
2489 let sig = extract_signature(func_node, code, Language::Python);
2490 assert!(sig.is_some());
2491 let sig = sig.unwrap();
2492 assert!(sig.contains("def hello"));
2493 assert!(sig.contains("name"));
2494 }
2495
2496 #[test]
2497 fn test_extract_signature_rust() {
2498 let code = "fn add(a: i32, b: i32) -> i32 { a + b }";
2499 let mut parser = tree_sitter::Parser::new();
2500 parser
2501 .set_language(&tree_sitter_rust::LANGUAGE.into())
2502 .unwrap();
2503 let tree = parser.parse(code, None).unwrap();
2504 let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2505
2506 let sig = extract_signature(func_node, code, Language::Rust);
2507 assert!(sig.is_some());
2508 let sig = sig.unwrap();
2509 assert!(sig.contains("fn add"));
2510 assert!(sig.contains("i32"));
2511 }
2512
2513 #[test]
2514 fn test_extract_signature_javascript() {
2515 let code = "function greet(name) { return 'Hello ' + name; }";
2516 let mut parser = tree_sitter::Parser::new();
2517 parser
2518 .set_language(&tree_sitter_javascript::LANGUAGE.into())
2519 .unwrap();
2520 let tree = parser.parse(code, None).unwrap();
2521 let func_node = find_node_in_tree(tree.root_node(), "function_declaration").unwrap();
2522
2523 let sig = extract_signature(func_node, code, Language::JavaScript);
2524 assert!(sig.is_some());
2525 let sig = sig.unwrap();
2526 assert!(sig.contains("function greet"));
2527 assert!(sig.contains("name"));
2528 }
2529
2530 #[test]
2531 fn test_extract_visibility_python_public() {
2532 let code = "def public_func():\n pass";
2533 let mut parser = tree_sitter::Parser::new();
2534 parser
2535 .set_language(&tree_sitter_python::LANGUAGE.into())
2536 .unwrap();
2537 let tree = parser.parse(code, None).unwrap();
2538 let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2539
2540 let vis = extract_visibility(func_node, code, Language::Python);
2541 assert_eq!(vis, Visibility::Public);
2542 }
2543
2544 #[test]
2545 fn test_extract_visibility_python_private() {
2546 let code = "def __private_func():\n pass";
2547 let mut parser = tree_sitter::Parser::new();
2548 parser
2549 .set_language(&tree_sitter_python::LANGUAGE.into())
2550 .unwrap();
2551 let tree = parser.parse(code, None).unwrap();
2552 let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2553
2554 let vis = extract_visibility(func_node, code, Language::Python);
2555 assert_eq!(vis, Visibility::Private);
2556 }
2557
2558 #[test]
2559 fn test_extract_visibility_python_protected() {
2560 let code = "def _protected_func():\n pass";
2561 let mut parser = tree_sitter::Parser::new();
2562 parser
2563 .set_language(&tree_sitter_python::LANGUAGE.into())
2564 .unwrap();
2565 let tree = parser.parse(code, None).unwrap();
2566 let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2567
2568 let vis = extract_visibility(func_node, code, Language::Python);
2569 assert_eq!(vis, Visibility::Protected);
2570 }
2571
2572 #[test]
2573 fn test_extract_visibility_python_dunder() {
2574 let code = "def __init__(self):\n pass";
2578 let mut parser = tree_sitter::Parser::new();
2579 parser
2580 .set_language(&tree_sitter_python::LANGUAGE.into())
2581 .unwrap();
2582 let tree = parser.parse(code, None).unwrap();
2583 let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2584
2585 let vis = extract_visibility(func_node, code, Language::Python);
2586 assert_eq!(vis, Visibility::Protected);
2589 }
2590
2591 #[test]
2592 fn test_extract_visibility_rust_pub() {
2593 let code = "pub fn public_func() {}";
2594 let mut parser = tree_sitter::Parser::new();
2595 parser
2596 .set_language(&tree_sitter_rust::LANGUAGE.into())
2597 .unwrap();
2598 let tree = parser.parse(code, None).unwrap();
2599 let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2600
2601 let vis = extract_visibility(func_node, code, Language::Rust);
2602 assert_eq!(vis, Visibility::Public);
2603 }
2604
2605 #[test]
2606 fn test_extract_visibility_rust_private() {
2607 let code = "fn private_func() {}";
2608 let mut parser = tree_sitter::Parser::new();
2609 parser
2610 .set_language(&tree_sitter_rust::LANGUAGE.into())
2611 .unwrap();
2612 let tree = parser.parse(code, None).unwrap();
2613 let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2614
2615 let vis = extract_visibility(func_node, code, Language::Rust);
2616 assert_eq!(vis, Visibility::Private);
2617 }
2618
2619 #[test]
2620 fn test_extract_visibility_rust_pub_crate() {
2621 let code = "pub(crate) fn crate_func() {}";
2622 let mut parser = tree_sitter::Parser::new();
2623 parser
2624 .set_language(&tree_sitter_rust::LANGUAGE.into())
2625 .unwrap();
2626 let tree = parser.parse(code, None).unwrap();
2627 let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2628
2629 let vis = extract_visibility(func_node, code, Language::Rust);
2630 assert_eq!(vis, Visibility::Internal);
2631 }
2632
2633 #[test]
2634 fn test_extract_visibility_go_exported() {
2635 let code = "func Exported() {}";
2636 let mut parser = tree_sitter::Parser::new();
2637 parser
2638 .set_language(&tree_sitter_go::LANGUAGE.into())
2639 .unwrap();
2640 let tree = parser.parse(code, None).unwrap();
2641 let func_node = find_node_in_tree(tree.root_node(), "function_declaration").unwrap();
2642
2643 let vis = extract_visibility(func_node, code, Language::Go);
2644 assert_eq!(vis, Visibility::Public);
2645 }
2646
2647 #[test]
2648 fn test_extract_visibility_go_unexported() {
2649 let code = "func unexported() {}";
2650 let mut parser = tree_sitter::Parser::new();
2651 parser
2652 .set_language(&tree_sitter_go::LANGUAGE.into())
2653 .unwrap();
2654 let tree = parser.parse(code, None).unwrap();
2655 let func_node = find_node_in_tree(tree.root_node(), "function_declaration").unwrap();
2656
2657 let vis = extract_visibility(func_node, code, Language::Go);
2658 assert_eq!(vis, Visibility::Private);
2659 }
2660
2661 #[test]
2662 fn test_extract_visibility_bash_always_public() {
2663 let code = "my_func() { echo hello; }";
2664 let mut parser = tree_sitter::Parser::new();
2665 parser
2666 .set_language(&tree_sitter_bash::LANGUAGE.into())
2667 .unwrap();
2668 let tree = parser.parse(code, None).unwrap();
2669 let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2670
2671 let vis = extract_visibility(func_node, code, Language::Bash);
2672 assert_eq!(vis, Visibility::Public);
2673 }
2674
2675 #[test]
2676 fn test_find_body_node_python() {
2677 let code = "def foo():\n x = 1\n return x";
2678 let mut parser = tree_sitter::Parser::new();
2679 parser
2680 .set_language(&tree_sitter_python::LANGUAGE.into())
2681 .unwrap();
2682 let tree = parser.parse(code, None).unwrap();
2683 let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2684
2685 let body = find_body_node(func_node, Language::Python);
2686 assert!(body.is_some());
2687 assert_eq!(body.unwrap().kind(), "block");
2688 }
2689
2690 #[test]
2691 fn test_find_body_node_rust() {
2692 let code = "fn foo() { let x = 1; x }";
2693 let mut parser = tree_sitter::Parser::new();
2694 parser
2695 .set_language(&tree_sitter_rust::LANGUAGE.into())
2696 .unwrap();
2697 let tree = parser.parse(code, None).unwrap();
2698 let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2699
2700 let body = find_body_node(func_node, Language::Rust);
2701 assert!(body.is_some());
2702 assert_eq!(body.unwrap().kind(), "block");
2703 }
2704
2705 #[test]
2706 fn test_find_body_node_javascript() {
2707 let code = "function foo() { return 1; }";
2708 let mut parser = tree_sitter::Parser::new();
2709 parser
2710 .set_language(&tree_sitter_javascript::LANGUAGE.into())
2711 .unwrap();
2712 let tree = parser.parse(code, None).unwrap();
2713 let func_node = find_node_in_tree(tree.root_node(), "function_declaration").unwrap();
2714
2715 let body = find_body_node(func_node, Language::JavaScript);
2716 assert!(body.is_some());
2717 assert_eq!(body.unwrap().kind(), "statement_block");
2718 }
2719
2720 #[test]
2721 fn test_extract_calls_python() {
2722 let code = "def foo():\n bar()\n custom_func(1, 2)";
2723 let mut parser = tree_sitter::Parser::new();
2724 parser
2725 .set_language(&tree_sitter_python::LANGUAGE.into())
2726 .unwrap();
2727 let tree = parser.parse(code, None).unwrap();
2728 let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2729
2730 let calls = extract_calls(func_node, code, Language::Python);
2731 assert!(calls.contains(&"bar".to_owned()));
2732 assert!(calls.contains(&"custom_func".to_owned()));
2733 }
2734
2735 #[test]
2736 fn test_extract_calls_python_filters_builtins() {
2737 let code = "def foo():\n print('hello')\n len([1,2,3])";
2738 let mut parser = tree_sitter::Parser::new();
2739 parser
2740 .set_language(&tree_sitter_python::LANGUAGE.into())
2741 .unwrap();
2742 let tree = parser.parse(code, None).unwrap();
2743 let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2744
2745 let calls = extract_calls(func_node, code, Language::Python);
2746 assert!(!calls.contains(&"print".to_owned()));
2748 assert!(!calls.contains(&"len".to_owned()));
2749 }
2750
2751 #[test]
2752 fn test_extract_calls_rust() {
2753 let code = "fn foo() { bar(); baz(1); }";
2754 let mut parser = tree_sitter::Parser::new();
2755 parser
2756 .set_language(&tree_sitter_rust::LANGUAGE.into())
2757 .unwrap();
2758 let tree = parser.parse(code, None).unwrap();
2759 let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2760
2761 let calls = extract_calls(func_node, code, Language::Rust);
2762 assert!(calls.contains(&"bar".to_owned()));
2763 assert!(calls.contains(&"baz".to_owned()));
2764 }
2765
2766 #[test]
2767 fn test_extract_docstring_rust() {
2768 let code = "/// This is a doc comment\nfn foo() {}";
2769 let mut parser = tree_sitter::Parser::new();
2770 parser
2771 .set_language(&tree_sitter_rust::LANGUAGE.into())
2772 .unwrap();
2773 let tree = parser.parse(code, None).unwrap();
2774 let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2775
2776 let docstring = extract_docstring(func_node, code, Language::Rust);
2777 assert!(docstring.is_some());
2778 assert!(docstring.unwrap().contains("This is a doc comment"));
2779 }
2780
2781 #[test]
2782 fn test_extract_docstring_rust_multiline() {
2783 let code = "/// Line 1\n/// Line 2\nfn foo() {}";
2784 let mut parser = tree_sitter::Parser::new();
2785 parser
2786 .set_language(&tree_sitter_rust::LANGUAGE.into())
2787 .unwrap();
2788 let tree = parser.parse(code, None).unwrap();
2789 let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2790
2791 let docstring = extract_docstring(func_node, code, Language::Rust);
2792 assert!(docstring.is_some());
2793 let doc = docstring.unwrap();
2794 assert!(doc.contains("Line 1"));
2795 assert!(doc.contains("Line 2"));
2796 }
2797
2798 fn collect_zig_calls(code: &str) -> HashSet<String> {
2804 let mut parser = tree_sitter::Parser::new();
2805 parser
2806 .set_language(&tree_sitter_zig::LANGUAGE.into())
2807 .expect("Error loading Zig grammar");
2808 let tree = parser.parse(code, None).expect("Failed to parse Zig code");
2809 let mut calls = HashSet::new();
2810 collect_calls_recursive(tree.root_node(), code, Language::Zig, &mut calls);
2811 calls
2812 }
2813
2814 #[test]
2815 fn test_zig_simple_call_extracted() {
2816 let code = r#"
2817fn main() void {
2818 const r = add(1, 2);
2819}
2820"#;
2821 let calls = collect_zig_calls(code);
2822 assert!(calls.contains("add"), "Expected 'add' in calls, got: {:?}", calls);
2823 }
2824
2825 #[test]
2826 fn test_zig_member_call_extracted() {
2827 let code = r#"
2828const std = @import("std");
2829fn main() void {
2830 std.debug.print("hi", .{});
2831}
2832"#;
2833 let calls = collect_zig_calls(code);
2834 assert!(calls.contains("print"), "Expected 'print' in calls, got: {:?}", calls);
2835 }
2836
2837 #[test]
2838 fn test_zig_no_false_positives_on_variable_declaration() {
2839 let code = r#"
2840fn main() void {
2841 const x = 42;
2842}
2843"#;
2844 let calls = collect_zig_calls(code);
2845 assert!(
2846 calls.is_empty(),
2847 "Expected no calls from a simple variable declaration, got: {:?}",
2848 calls
2849 );
2850 }
2851
2852 fn collect_dart_calls(code: &str) -> HashSet<String> {
2858 let mut parser = tree_sitter::Parser::new();
2859 parser
2860 .set_language(&tree_sitter_dart_orchard::LANGUAGE.into())
2861 .expect("Error loading Dart grammar");
2862 let tree = parser.parse(code, None).expect("Failed to parse Dart code");
2863 let mut calls = HashSet::new();
2864 collect_calls_recursive(tree.root_node(), code, Language::Dart, &mut calls);
2865 calls
2866 }
2867
2868 #[test]
2869 fn test_dart_simple_call_extracted() {
2870 let code = r#"
2871void main() {
2872 print('hello');
2873}
2874"#;
2875 let calls = collect_dart_calls(code);
2876 assert!(calls.contains("print"), "Expected 'print' in calls, got: {:?}", calls);
2877 }
2878
2879 #[test]
2880 fn test_dart_method_call_extracted() {
2881 let code = r#"
2882void main() {
2883 myObj.doSomething();
2884}
2885"#;
2886 let calls = collect_dart_calls(code);
2887 assert!(calls.contains("doSomething"), "Expected 'doSomething' in calls, got: {:?}", calls);
2888 }
2889
2890 #[test]
2891 fn test_dart_no_false_positives_on_variable_declaration() {
2892 let code = r#"
2893void main() {
2894 var x = 42;
2895}
2896"#;
2897 let calls = collect_dart_calls(code);
2898 assert!(
2899 calls.is_empty(),
2900 "Expected no calls from a simple variable declaration, got: {:?}",
2901 calls
2902 );
2903 }
2904
2905 fn parse_dart_symbols(code: &str) -> Vec<crate::types::Symbol> {
2911 let mut parser = super::super::core::Parser::new();
2912 parser.parse(code, Language::Dart).unwrap_or_default()
2913 }
2914
2915 #[test]
2916 fn test_dart_inheritance_extends() {
2917 let code = r#"
2918class Animal {
2919 void speak() {}
2920}
2921
2922class Dog extends Animal {
2923 void speak() {}
2924}
2925"#;
2926 let symbols = parse_dart_symbols(code);
2927 let dog = symbols
2928 .iter()
2929 .find(|s| s.name == "Dog")
2930 .expect("Dog class not found");
2931 assert_eq!(dog.extends.as_deref(), Some("Animal"));
2932 assert!(dog.implements.is_empty());
2933 }
2934
2935 #[test]
2936 fn test_dart_inheritance_implements() {
2937 let code = r#"
2938class Serializable {
2939 void serialize() {}
2940}
2941
2942class Printable {
2943 void print() {}
2944}
2945
2946class Document implements Serializable, Printable {
2947 void serialize() {}
2948 void print() {}
2949}
2950"#;
2951 let symbols = parse_dart_symbols(code);
2952 let doc = symbols
2953 .iter()
2954 .find(|s| s.name == "Document")
2955 .expect("Document class not found");
2956 assert!(doc.extends.is_none());
2957 assert!(doc.implements.contains(&"Serializable".to_owned()));
2958 assert!(doc.implements.contains(&"Printable".to_owned()));
2959 assert_eq!(doc.implements.len(), 2);
2960 }
2961
2962 #[test]
2963 fn test_dart_inheritance_with_mixins() {
2964 let code = r#"
2965mixin Swimming {
2966 void swim() {}
2967}
2968
2969mixin Flying {
2970 void fly() {}
2971}
2972
2973class Duck with Swimming, Flying {
2974 void quack() {}
2975}
2976"#;
2977 let symbols = parse_dart_symbols(code);
2978 let duck = symbols
2979 .iter()
2980 .find(|s| s.name == "Duck")
2981 .expect("Duck class not found");
2982 assert!(duck.extends.is_none());
2983 assert!(duck.implements.contains(&"Swimming".to_owned()));
2985 assert!(duck.implements.contains(&"Flying".to_owned()));
2986 assert_eq!(duck.implements.len(), 2);
2987 }
2988
2989 #[test]
2990 fn test_dart_inheritance_combined() {
2991 let code = r#"
2992class Animal {
2993 void speak() {}
2994}
2995
2996mixin Swimming {
2997 void swim() {}
2998}
2999
3000class Walkable {
3001 void walk() {}
3002}
3003
3004class Duck extends Animal with Swimming implements Walkable {
3005 void quack() {}
3006}
3007"#;
3008 let symbols = parse_dart_symbols(code);
3009 let duck = symbols
3010 .iter()
3011 .find(|s| s.name == "Duck")
3012 .expect("Duck class not found");
3013 assert_eq!(duck.extends.as_deref(), Some("Animal"));
3014 assert!(duck.implements.contains(&"Swimming".to_owned()));
3016 assert!(duck.implements.contains(&"Walkable".to_owned()));
3017 assert_eq!(duck.implements.len(), 2);
3018 }
3019
3020 #[test]
3021 fn test_dart_inheritance_no_inheritance() {
3022 let code = r#"
3023class SimpleClass {
3024 void doSomething() {}
3025}
3026"#;
3027 let symbols = parse_dart_symbols(code);
3028 let cls = symbols
3029 .iter()
3030 .find(|s| s.name == "SimpleClass")
3031 .expect("SimpleClass not found");
3032 assert!(cls.extends.is_none());
3033 assert!(cls.implements.is_empty());
3034 }
3035}