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 for child in node.children(&mut node.walk()) {
123 if child.kind() == "block"
124 || child.kind() == "compound_statement"
125 || child.kind() == "function_body"
126 {
127 let start = node.start_byte();
128 let end = child.start_byte();
129 return Some(source_code[start..end].trim().to_owned().replace('\n', " "));
130 }
131 }
132 None
133 },
134 Language::Ruby | Language::Lua => {
135 let start = node.start_byte();
136 let mut end = start;
137 for byte in &source_code.as_bytes()[start..] {
138 end += 1;
139 if *byte == b'\n' {
140 break;
141 }
142 }
143 Some(source_code[start..end].trim().to_owned())
144 },
145 Language::Bash => {
146 let start = node.start_byte();
147 let mut end = start;
148 for byte in &source_code.as_bytes()[start..] {
149 if *byte == b'{' {
150 break;
151 }
152 end += 1;
153 }
154 Some(source_code[start..end].trim().to_owned())
155 },
156 Language::Haskell
157 | Language::OCaml
158 | Language::FSharp
159 | Language::Elixir
160 | Language::Clojure
161 | Language::R => {
162 let start = node.start_byte();
163 let mut end = start;
164 for byte in &source_code.as_bytes()[start..] {
165 end += 1;
166 if *byte == b'\n' || *byte == b'=' {
167 break;
168 }
169 }
170 Some(source_code[start..end].trim().to_owned())
171 },
172 };
173
174 sig_node.or_else(|| {
175 let start = node.start_byte();
176 let end = std::cmp::min(start + 200, source_code.len());
177 let safe_start = safe_char_boundary(source_code, start);
179 let safe_end = safe_char_boundary(source_code, end);
180 if safe_start >= safe_end {
181 return None;
182 }
183 let text = &source_code[safe_start..safe_end];
184 text.lines().next().map(|s| s.trim().to_owned())
185 })
186}
187
188pub fn extract_docstring(node: Node<'_>, source_code: &str, language: Language) -> Option<String> {
190 match language {
191 Language::Python => {
192 let mut cursor = node.walk();
193 for child in node.children(&mut cursor) {
194 if child.kind() == "block" {
195 for stmt in child.children(&mut child.walk()) {
196 if stmt.kind() == "expression_statement" {
197 for expr in stmt.children(&mut stmt.walk()) {
198 if expr.kind() == "string" {
199 if let Ok(text) = expr.utf8_text(source_code.as_bytes()) {
200 return Some(
201 text.trim_matches(|c| c == '"' || c == '\'')
202 .trim()
203 .to_owned(),
204 );
205 }
206 }
207 }
208 }
209 }
210 }
211 }
212 None
213 },
214 Language::JavaScript | Language::TypeScript => {
215 if let Some(prev_sibling) = node.prev_sibling() {
216 if prev_sibling.kind() == "comment" {
217 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
218 if text.starts_with("/**") {
219 return Some(clean_jsdoc(text));
220 }
221 }
222 }
223 }
224 None
225 },
226 Language::Rust => {
227 let start_byte = node.start_byte();
228 let safe_boundary = source_code.floor_char_boundary(start_byte);
230 let lines_before: Vec<_> = source_code[..safe_boundary]
231 .lines()
232 .rev()
233 .take_while(|line| line.trim().starts_with("///") || line.trim().is_empty())
234 .collect();
235
236 if !lines_before.is_empty() {
237 let doc: Vec<String> = lines_before
238 .into_iter()
239 .rev()
240 .filter_map(|line| {
241 let trimmed = line.trim();
242 trimmed.strip_prefix("///").map(|s| s.trim().to_owned())
243 })
244 .collect();
245
246 if !doc.is_empty() {
247 return Some(doc.join(" "));
248 }
249 }
250 None
251 },
252 Language::Go => {
253 if let Some(prev_sibling) = node.prev_sibling() {
254 if prev_sibling.kind() == "comment" {
255 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
256 return Some(text.trim_start_matches("//").trim().to_owned());
257 }
258 }
259 }
260 None
261 },
262 Language::Java => {
263 if let Some(prev_sibling) = node.prev_sibling() {
264 if prev_sibling.kind() == "block_comment" {
265 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
266 if text.starts_with("/**") {
267 return Some(clean_javadoc(text));
268 }
269 }
270 }
271 }
272 None
273 },
274 Language::C | Language::Cpp => {
275 if let Some(prev_sibling) = node.prev_sibling() {
276 if prev_sibling.kind() == "comment" {
277 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
278 if text.starts_with("/**") || text.starts_with("/*") {
279 return Some(clean_jsdoc(text));
280 }
281 return Some(text.trim_start_matches("//").trim().to_owned());
282 }
283 }
284 }
285 None
286 },
287 Language::CSharp => {
288 let start_byte = node.start_byte();
289 let safe_boundary = source_code.floor_char_boundary(start_byte);
291 let lines_before: Vec<_> = source_code[..safe_boundary]
292 .lines()
293 .rev()
294 .take_while(|line| line.trim().starts_with("///") || line.trim().is_empty())
295 .collect();
296
297 if !lines_before.is_empty() {
298 let doc: Vec<String> = lines_before
299 .into_iter()
300 .rev()
301 .filter_map(|line| {
302 let trimmed = line.trim();
303 trimmed.strip_prefix("///").map(|s| s.trim().to_owned())
304 })
305 .collect();
306
307 if !doc.is_empty() {
308 return Some(doc.join(" "));
309 }
310 }
311 None
312 },
313 Language::Ruby => {
314 if let Some(prev_sibling) = node.prev_sibling() {
315 if prev_sibling.kind() == "comment" {
316 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
317 return Some(text.trim_start_matches('#').trim().to_owned());
318 }
319 }
320 }
321 None
322 },
323 Language::Php | Language::Kotlin | Language::Swift | Language::Scala => {
324 if let Some(prev_sibling) = node.prev_sibling() {
325 let kind = prev_sibling.kind();
326 if kind == "comment" || kind == "multiline_comment" || kind == "block_comment" {
327 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
328 if text.starts_with("/**") {
329 return Some(clean_jsdoc(text));
330 }
331 }
332 }
333 }
334 None
335 },
336 Language::Bash => {
337 if let Some(prev_sibling) = node.prev_sibling() {
338 if prev_sibling.kind() == "comment" {
339 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
340 return Some(text.trim_start_matches('#').trim().to_owned());
341 }
342 }
343 }
344 None
345 },
346 Language::Haskell => {
347 if let Some(prev_sibling) = node.prev_sibling() {
348 if prev_sibling.kind() == "comment" {
349 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
350 let cleaned = text
351 .trim_start_matches("{-")
352 .trim_end_matches("-}")
353 .trim_start_matches("--")
354 .trim();
355 return Some(cleaned.to_owned());
356 }
357 }
358 }
359 None
360 },
361 Language::Elixir => {
362 if let Some(prev_sibling) = node.prev_sibling() {
363 if prev_sibling.kind() == "comment" {
364 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
365 return Some(text.trim_start_matches('#').trim().to_owned());
366 }
367 }
368 }
369 None
370 },
371 Language::Clojure => None,
372 Language::OCaml | Language::FSharp => {
373 if let Some(prev_sibling) = node.prev_sibling() {
374 if prev_sibling.kind() == "comment" {
375 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
376 let cleaned = text
377 .trim_start_matches("(**")
378 .trim_start_matches("(*")
379 .trim_end_matches("*)")
380 .trim();
381 return Some(cleaned.to_owned());
382 }
383 }
384 }
385 None
386 },
387 Language::Lua => {
388 if let Some(prev_sibling) = node.prev_sibling() {
389 if prev_sibling.kind() == "comment" {
390 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
391 let cleaned = text
392 .trim_start_matches("--[[")
393 .trim_end_matches("]]")
394 .trim_start_matches("--")
395 .trim();
396 return Some(cleaned.to_owned());
397 }
398 }
399 }
400 None
401 },
402 Language::R => {
403 if let Some(prev_sibling) = node.prev_sibling() {
404 if prev_sibling.kind() == "comment" {
405 if let Ok(text) = prev_sibling.utf8_text(source_code.as_bytes()) {
406 return Some(text.trim_start_matches('#').trim().to_owned());
407 }
408 }
409 }
410 None
411 },
412 }
413}
414
415pub fn extract_parent(node: Node<'_>, source_code: &str) -> Option<String> {
417 let mut current = node.parent()?;
418
419 while let Some(parent) = current.parent() {
420 if ["class_definition", "class_declaration", "struct_item", "impl_item"]
421 .contains(&parent.kind())
422 {
423 for child in parent.children(&mut parent.walk()) {
424 if child.kind() == "identifier" || child.kind() == "type_identifier" {
425 if let Ok(name) = child.utf8_text(source_code.as_bytes()) {
426 return Some(name.to_owned());
427 }
428 }
429 }
430 }
431 current = parent;
432 }
433
434 None
435}
436
437pub fn extract_visibility(node: Node<'_>, source_code: &str, language: Language) -> Visibility {
439 match language {
440 Language::Python => {
441 if let Some(name_node) = node.child_by_field_name("name") {
442 if let Ok(name) = name_node.utf8_text(source_code.as_bytes()) {
443 if name.starts_with("__") && !name.ends_with("__") {
444 return Visibility::Private;
445 } else if name.starts_with('_') {
446 return Visibility::Protected;
447 }
448 }
449 }
450 Visibility::Public
451 },
452 Language::Rust => {
453 for child in node.children(&mut node.walk()) {
454 if child.kind() == "visibility_modifier" {
455 if let Ok(text) = child.utf8_text(source_code.as_bytes()) {
456 if text.contains("pub(crate)") || text.contains("pub(super)") {
457 return Visibility::Internal;
458 } else if text.starts_with("pub") {
459 return Visibility::Public;
460 }
461 }
462 }
463 }
464 Visibility::Private
465 },
466 Language::JavaScript | Language::TypeScript => {
467 for child in node.children(&mut node.walk()) {
468 let kind = child.kind();
469 if kind == "private" || kind == "accessibility_modifier" {
470 if let Ok(text) = child.utf8_text(source_code.as_bytes()) {
471 return match text {
472 "private" => Visibility::Private,
473 "protected" => Visibility::Protected,
474 _ => Visibility::Public,
475 };
476 }
477 }
478 }
479 if let Some(name_node) = node.child_by_field_name("name") {
480 if let Ok(name) = name_node.utf8_text(source_code.as_bytes()) {
481 if name.starts_with('#') {
482 return Visibility::Private;
483 }
484 }
485 }
486 Visibility::Public
487 },
488 Language::Go => {
489 if let Some(name_node) = node.child_by_field_name("name") {
490 if let Ok(name) = name_node.utf8_text(source_code.as_bytes()) {
491 if let Some(first_char) = name.chars().next() {
492 if first_char.is_lowercase() {
493 return Visibility::Private;
494 }
495 }
496 }
497 }
498 Visibility::Public
499 },
500 Language::Java => {
501 for child in node.children(&mut node.walk()) {
502 if child.kind() == "modifiers" {
503 if let Ok(text) = child.utf8_text(source_code.as_bytes()) {
504 if text.contains("private") {
505 return Visibility::Private;
506 } else if text.contains("protected") {
507 return Visibility::Protected;
508 } else if text.contains("public") {
509 return Visibility::Public;
510 }
511 }
512 }
513 }
514 Visibility::Internal
515 },
516 Language::C | Language::Cpp => {
517 for child in node.children(&mut node.walk()) {
518 if child.kind() == "storage_class_specifier" {
519 if let Ok(text) = child.utf8_text(source_code.as_bytes()) {
520 if text == "static" {
521 return Visibility::Private;
522 }
523 }
524 }
525 }
526 Visibility::Public
527 },
528 Language::CSharp | Language::Kotlin | Language::Swift | Language::Scala => {
529 for child in node.children(&mut node.walk()) {
530 let kind = child.kind();
531 if kind == "modifier" || kind == "modifiers" || kind == "visibility_modifier" {
532 if let Ok(text) = child.utf8_text(source_code.as_bytes()) {
533 if text.contains("private") {
534 return Visibility::Private;
535 } else if text.contains("protected") {
536 return Visibility::Protected;
537 } else if text.contains("internal") {
538 return Visibility::Internal;
539 } else if text.contains("public") {
540 return Visibility::Public;
541 }
542 }
543 }
544 }
545 Visibility::Internal
546 },
547 Language::Ruby => {
548 if let Some(name_node) = node.child_by_field_name("name") {
549 if let Ok(name) = name_node.utf8_text(source_code.as_bytes()) {
550 if name.starts_with('_') {
551 return Visibility::Private;
552 }
553 }
554 }
555 Visibility::Public
556 },
557 Language::Php => {
558 for child in node.children(&mut node.walk()) {
559 if child.kind() == "visibility_modifier" {
560 if let Ok(text) = child.utf8_text(source_code.as_bytes()) {
561 return match text {
562 "private" => Visibility::Private,
563 "protected" => Visibility::Protected,
564 "public" => Visibility::Public,
565 _ => Visibility::Public,
566 };
567 }
568 }
569 }
570 Visibility::Public
571 },
572 Language::Bash => Visibility::Public,
573 Language::Haskell
574 | Language::Elixir
575 | Language::Clojure
576 | Language::OCaml
577 | Language::FSharp
578 | Language::Lua
579 | Language::R => Visibility::Public,
580 }
581}
582
583pub fn extract_calls(node: Node<'_>, source_code: &str, language: Language) -> Vec<String> {
585 let mut calls = HashSet::new();
586
587 let body_node = find_body_node(node, language);
588 if let Some(body) = body_node {
589 collect_calls_recursive(body, source_code, language, &mut calls);
590 }
591
592 if calls.is_empty() {
593 collect_calls_recursive(node, source_code, language, &mut calls);
594 }
595
596 calls.into_iter().collect()
597}
598
599pub fn find_body_node(node: Node<'_>, language: Language) -> Option<Node<'_>> {
601 match language {
602 Language::Python => {
603 for child in node.children(&mut node.walk()) {
604 if child.kind() == "block" {
605 return Some(child);
606 }
607 }
608 },
609 Language::Rust => {
610 for child in node.children(&mut node.walk()) {
611 if child.kind() == "block" {
612 return Some(child);
613 }
614 }
615 },
616 Language::JavaScript | Language::TypeScript => {
617 for child in node.children(&mut node.walk()) {
618 let kind = child.kind();
619 if kind == "statement_block" {
620 return Some(child);
621 }
622 if kind == "arrow_function" {
623 if let Some(body) = find_body_node(child, language) {
624 return Some(body);
625 }
626 return Some(child);
627 }
628 }
629 if node.kind() == "arrow_function" {
630 for child in node.children(&mut node.walk()) {
631 let kind = child.kind();
632 if kind != "formal_parameters"
633 && kind != "identifier"
634 && kind != "=>"
635 && kind != "("
636 && kind != ")"
637 && kind != ","
638 {
639 return Some(child);
640 }
641 }
642 return Some(node);
643 }
644 },
645 Language::Go => {
646 for child in node.children(&mut node.walk()) {
647 if child.kind() == "block" {
648 return Some(child);
649 }
650 }
651 },
652 Language::Java => {
653 for child in node.children(&mut node.walk()) {
654 if child.kind() == "block" {
655 return Some(child);
656 }
657 }
658 },
659 Language::C | Language::Cpp => {
660 for child in node.children(&mut node.walk()) {
661 if child.kind() == "compound_statement" {
662 return Some(child);
663 }
664 }
665 },
666 Language::CSharp | Language::Php | Language::Kotlin | Language::Swift | Language::Scala => {
667 for child in node.children(&mut node.walk()) {
668 let kind = child.kind();
669 if kind == "block" || kind == "compound_statement" || kind == "function_body" {
670 return Some(child);
671 }
672 }
673 },
674 Language::Ruby => {
675 for child in node.children(&mut node.walk()) {
676 if child.kind() == "body_statement" || child.kind() == "do_block" {
677 return Some(child);
678 }
679 }
680 },
681 Language::Bash => {
682 for child in node.children(&mut node.walk()) {
683 if child.kind() == "compound_statement" {
684 return Some(child);
685 }
686 }
687 },
688 Language::Haskell
689 | Language::Elixir
690 | Language::Clojure
691 | Language::OCaml
692 | Language::FSharp
693 | Language::R => {
694 return Some(node);
695 },
696 Language::Lua => {
697 for child in node.children(&mut node.walk()) {
698 if child.kind() == "block" {
699 return Some(child);
700 }
701 }
702 },
703 }
704 None
705}
706
707const MAX_RECURSION_DEPTH: usize = 1000;
710
711pub fn collect_calls_recursive(
715 node: Node<'_>,
716 source_code: &str,
717 language: Language,
718 calls: &mut HashSet<String>,
719) {
720 collect_calls_recursive_with_depth(node, source_code, language, calls, 0);
721}
722
723fn collect_calls_recursive_with_depth(
725 node: Node<'_>,
726 source_code: &str,
727 language: Language,
728 calls: &mut HashSet<String>,
729 depth: usize,
730) {
731 if depth >= MAX_RECURSION_DEPTH {
733 return;
734 }
735
736 let kind = node.kind();
737
738 let call_name = match language {
739 Language::Python => {
740 if kind == "call" {
741 node.child_by_field_name("function").and_then(|f| {
742 if f.kind() == "identifier" {
743 f.utf8_text(source_code.as_bytes()).ok().map(String::from)
744 } else if f.kind() == "attribute" {
745 f.child_by_field_name("attribute")
746 .and_then(|a| a.utf8_text(source_code.as_bytes()).ok())
747 .map(String::from)
748 } else {
749 None
750 }
751 })
752 } else {
753 None
754 }
755 },
756 Language::Rust => {
757 if kind == "call_expression" {
758 node.child_by_field_name("function").and_then(|f| {
759 if f.kind() == "identifier" {
760 f.utf8_text(source_code.as_bytes()).ok().map(String::from)
761 } else if f.kind() == "field_expression" {
762 f.child_by_field_name("field")
763 .and_then(|a| a.utf8_text(source_code.as_bytes()).ok())
764 .map(String::from)
765 } else if f.kind() == "scoped_identifier" {
766 f.utf8_text(source_code.as_bytes()).ok().map(String::from)
767 } else {
768 None
769 }
770 })
771 } else if kind == "macro_invocation" {
772 node.child_by_field_name("macro")
773 .and_then(|m| m.utf8_text(source_code.as_bytes()).ok())
774 .map(|s| format!("{}!", s))
775 } else {
776 None
777 }
778 },
779 Language::JavaScript | Language::TypeScript => {
780 if kind == "call_expression" {
781 node.child_by_field_name("function").and_then(|f| {
782 if f.kind() == "identifier" {
783 f.utf8_text(source_code.as_bytes()).ok().map(String::from)
784 } else if f.kind() == "member_expression" {
785 f.child_by_field_name("property")
786 .and_then(|p| p.utf8_text(source_code.as_bytes()).ok())
787 .map(String::from)
788 } else {
789 None
790 }
791 })
792 } else {
793 None
794 }
795 },
796 Language::Go => {
797 if kind == "call_expression" {
798 node.child_by_field_name("function").and_then(|f| {
799 if f.kind() == "identifier" {
800 f.utf8_text(source_code.as_bytes()).ok().map(String::from)
801 } else if f.kind() == "selector_expression" {
802 f.child_by_field_name("field")
803 .and_then(|a| a.utf8_text(source_code.as_bytes()).ok())
804 .map(String::from)
805 } else {
806 None
807 }
808 })
809 } else {
810 None
811 }
812 },
813 Language::Java => {
814 if kind == "method_invocation" {
815 node.child_by_field_name("name")
816 .and_then(|n| n.utf8_text(source_code.as_bytes()).ok())
817 .map(String::from)
818 } else {
819 None
820 }
821 },
822 Language::C | Language::Cpp => {
823 if kind == "call_expression" {
824 node.child_by_field_name("function").and_then(|f| {
825 if f.kind() == "identifier" {
826 f.utf8_text(source_code.as_bytes()).ok().map(String::from)
827 } else if f.kind() == "field_expression" {
828 f.child_by_field_name("field")
829 .and_then(|a| a.utf8_text(source_code.as_bytes()).ok())
830 .map(String::from)
831 } else {
832 None
833 }
834 })
835 } else {
836 None
837 }
838 },
839 Language::CSharp | Language::Php | Language::Kotlin | Language::Swift | Language::Scala => {
840 if kind == "invocation_expression" || kind == "call_expression" {
841 node.children(&mut node.walk())
842 .find(|child| child.kind() == "identifier" || child.kind() == "simple_name")
843 .and_then(|child| child.utf8_text(source_code.as_bytes()).ok())
844 .map(|s| s.to_owned())
845 } else {
846 None
847 }
848 },
849 Language::Ruby => {
850 if kind == "call" || kind == "method_call" {
851 node.child_by_field_name("method")
852 .and_then(|m| m.utf8_text(source_code.as_bytes()).ok())
853 .map(String::from)
854 } else {
855 None
856 }
857 },
858 Language::Bash => {
859 if kind == "command" {
860 node.child_by_field_name("name")
861 .and_then(|n| n.utf8_text(source_code.as_bytes()).ok())
862 .map(String::from)
863 } else {
864 None
865 }
866 },
867 Language::Haskell
868 | Language::Elixir
869 | Language::Clojure
870 | Language::OCaml
871 | Language::FSharp
872 | Language::Lua
873 | Language::R => {
874 if kind == "function_call" || kind == "call" || kind == "application" {
875 node.children(&mut node.walk())
876 .find(|child| child.kind() == "identifier" || child.kind() == "variable")
877 .and_then(|child| child.utf8_text(source_code.as_bytes()).ok())
878 .map(|s| s.to_owned())
879 } else {
880 None
881 }
882 },
883 };
884
885 if let Some(name) = call_name {
886 if !is_builtin(&name, language) {
887 calls.insert(name);
888 }
889 }
890
891 for child in node.children(&mut node.walk()) {
892 collect_calls_recursive_with_depth(child, source_code, language, calls, depth + 1);
893 }
894}
895
896pub fn is_builtin(name: &str, language: Language) -> bool {
898 match language {
899 Language::Python => {
900 matches!(
901 name,
902 "print"
903 | "len"
904 | "range"
905 | "str"
906 | "int"
907 | "float"
908 | "list"
909 | "dict"
910 | "set"
911 | "tuple"
912 | "bool"
913 | "type"
914 | "isinstance"
915 | "hasattr"
916 | "getattr"
917 | "setattr"
918 | "super"
919 | "iter"
920 | "next"
921 | "open"
922 | "input"
923 | "format"
924 | "enumerate"
925 | "zip"
926 | "map"
927 | "filter"
928 | "sorted"
929 | "reversed"
930 | "sum"
931 | "min"
932 | "max"
933 | "abs"
934 | "round"
935 | "ord"
936 | "chr"
937 | "hex"
938 | "bin"
939 | "oct"
940 )
941 },
942 Language::JavaScript | Language::TypeScript => {
943 matches!(
944 name,
945 "console"
946 | "log"
947 | "error"
948 | "warn"
949 | "parseInt"
950 | "parseFloat"
951 | "setTimeout"
952 | "setInterval"
953 | "clearTimeout"
954 | "clearInterval"
955 | "JSON"
956 | "stringify"
957 | "parse"
958 | "toString"
959 | "valueOf"
960 | "push"
961 | "pop"
962 | "shift"
963 | "unshift"
964 | "slice"
965 | "splice"
966 | "map"
967 | "filter"
968 | "reduce"
969 | "forEach"
970 | "find"
971 | "findIndex"
972 | "includes"
973 | "indexOf"
974 | "join"
975 | "split"
976 | "replace"
977 )
978 },
979 Language::Rust => {
980 matches!(
981 name,
982 "println!"
983 | "print!"
984 | "eprintln!"
985 | "eprint!"
986 | "format!"
987 | "vec!"
988 | "panic!"
989 | "assert!"
990 | "assert_eq!"
991 | "assert_ne!"
992 | "debug!"
993 | "info!"
994 | "warn!"
995 | "error!"
996 | "trace!"
997 | "unwrap"
998 | "expect"
999 | "ok"
1000 | "err"
1001 | "some"
1002 | "none"
1003 | "clone"
1004 | "to_string"
1005 | "into"
1006 | "from"
1007 | "default"
1008 | "iter"
1009 | "into_iter"
1010 | "collect"
1011 | "map"
1012 | "filter"
1013 )
1014 },
1015 Language::Go => {
1016 matches!(
1017 name,
1018 "fmt"
1019 | "Println"
1020 | "Printf"
1021 | "Sprintf"
1022 | "Errorf"
1023 | "make"
1024 | "new"
1025 | "len"
1026 | "cap"
1027 | "append"
1028 | "copy"
1029 | "delete"
1030 | "close"
1031 | "panic"
1032 | "recover"
1033 | "print"
1034 )
1035 },
1036 Language::Java => {
1037 matches!(
1038 name,
1039 "println"
1040 | "print"
1041 | "printf"
1042 | "toString"
1043 | "equals"
1044 | "hashCode"
1045 | "getClass"
1046 | "clone"
1047 | "notify"
1048 | "wait"
1049 | "get"
1050 | "set"
1051 | "add"
1052 | "remove"
1053 | "size"
1054 | "isEmpty"
1055 | "contains"
1056 | "iterator"
1057 | "valueOf"
1058 | "parseInt"
1059 )
1060 },
1061 Language::C | Language::Cpp => {
1062 matches!(
1063 name,
1064 "printf"
1065 | "scanf"
1066 | "malloc"
1067 | "free"
1068 | "memcpy"
1069 | "memset"
1070 | "strlen"
1071 | "strcpy"
1072 | "strcmp"
1073 | "strcat"
1074 | "sizeof"
1075 | "cout"
1076 | "cin"
1077 | "endl"
1078 | "cerr"
1079 | "clog"
1080 )
1081 },
1082 Language::CSharp => {
1083 matches!(
1084 name,
1085 "WriteLine"
1086 | "Write"
1087 | "ReadLine"
1088 | "ToString"
1089 | "Equals"
1090 | "GetHashCode"
1091 | "GetType"
1092 | "Add"
1093 | "Remove"
1094 | "Contains"
1095 | "Count"
1096 | "Clear"
1097 | "ToList"
1098 | "ToArray"
1099 )
1100 },
1101 Language::Ruby => {
1102 matches!(
1103 name,
1104 "puts"
1105 | "print"
1106 | "p"
1107 | "gets"
1108 | "each"
1109 | "map"
1110 | "select"
1111 | "reject"
1112 | "reduce"
1113 | "inject"
1114 | "find"
1115 | "any?"
1116 | "all?"
1117 | "include?"
1118 | "empty?"
1119 | "nil?"
1120 | "length"
1121 | "size"
1122 )
1123 },
1124 Language::Php => {
1125 matches!(
1126 name,
1127 "echo"
1128 | "print"
1129 | "var_dump"
1130 | "print_r"
1131 | "isset"
1132 | "empty"
1133 | "array"
1134 | "count"
1135 | "strlen"
1136 | "strpos"
1137 | "substr"
1138 | "explode"
1139 | "implode"
1140 | "json_encode"
1141 | "json_decode"
1142 )
1143 },
1144 Language::Kotlin => {
1145 matches!(
1146 name,
1147 "println"
1148 | "print"
1149 | "readLine"
1150 | "toString"
1151 | "equals"
1152 | "hashCode"
1153 | "map"
1154 | "filter"
1155 | "forEach"
1156 | "let"
1157 | "also"
1158 | "apply"
1159 | "run"
1160 | "with"
1161 | "listOf"
1162 | "mapOf"
1163 | "setOf"
1164 )
1165 },
1166 Language::Swift => {
1167 matches!(
1168 name,
1169 "print"
1170 | "debugPrint"
1171 | "dump"
1172 | "map"
1173 | "filter"
1174 | "reduce"
1175 | "forEach"
1176 | "contains"
1177 | "count"
1178 | "isEmpty"
1179 | "append"
1180 )
1181 },
1182 Language::Scala => {
1183 matches!(
1184 name,
1185 "println"
1186 | "print"
1187 | "map"
1188 | "filter"
1189 | "flatMap"
1190 | "foreach"
1191 | "reduce"
1192 | "fold"
1193 | "foldLeft"
1194 | "foldRight"
1195 | "collect"
1196 )
1197 },
1198 Language::Bash
1199 | Language::Haskell
1200 | Language::Elixir
1201 | Language::Clojure
1202 | Language::OCaml
1203 | Language::FSharp
1204 | Language::Lua
1205 | Language::R => false,
1206 }
1207}
1208
1209pub fn clean_jsdoc(text: &str) -> String {
1211 text.lines()
1212 .map(|line| {
1213 line.trim()
1214 .trim_start_matches("/**")
1215 .trim_start_matches("/*")
1216 .trim_start_matches('*')
1217 .trim_end_matches("*/")
1218 .trim()
1219 })
1220 .filter(|line| !line.is_empty())
1221 .collect::<Vec<_>>()
1222 .join(" ")
1223}
1224
1225pub fn clean_javadoc(text: &str) -> String {
1227 clean_jsdoc(text)
1228}
1229
1230pub fn extract_inheritance(
1232 node: Node<'_>,
1233 source_code: &str,
1234 language: Language,
1235) -> (Option<String>, Vec<String>) {
1236 let mut extends = None;
1237 let mut implements = Vec::new();
1238
1239 match language {
1240 Language::Python => {
1241 if node.kind() == "class_definition" {
1243 if let Some(args) = node.child_by_field_name("superclasses") {
1244 for child in args.children(&mut args.walk()) {
1245 if child.kind() == "identifier" || child.kind() == "attribute" {
1246 if let Ok(name) = child.utf8_text(source_code.as_bytes()) {
1247 if extends.is_none() {
1248 extends = Some(name.to_owned());
1249 } else {
1250 implements.push(name.to_owned());
1251 }
1252 }
1253 }
1254 }
1255 }
1256 }
1257 },
1258 Language::JavaScript | Language::TypeScript => {
1259 if node.kind() == "class_declaration" || node.kind() == "class" {
1261 for child in node.children(&mut node.walk()) {
1262 if child.kind() == "class_heritage" {
1263 for heritage in child.children(&mut child.walk()) {
1264 if heritage.kind() == "extends_clause" {
1265 for type_node in heritage.children(&mut heritage.walk()) {
1266 if type_node.kind() == "identifier"
1267 || type_node.kind() == "type_identifier"
1268 {
1269 if let Ok(name) =
1270 type_node.utf8_text(source_code.as_bytes())
1271 {
1272 extends = Some(name.to_owned());
1273 }
1274 }
1275 }
1276 } else if heritage.kind() == "implements_clause" {
1277 for type_node in heritage.children(&mut heritage.walk()) {
1278 if type_node.kind() == "identifier"
1279 || type_node.kind() == "type_identifier"
1280 {
1281 if let Ok(name) =
1282 type_node.utf8_text(source_code.as_bytes())
1283 {
1284 implements.push(name.to_owned());
1285 }
1286 }
1287 }
1288 }
1289 }
1290 }
1291 }
1292 }
1293 },
1294 Language::Rust => {
1295 if node.kind() == "impl_item" {
1298 let mut has_for = false;
1299 for child in node.children(&mut node.walk()) {
1300 if child.kind() == "for" {
1301 has_for = true;
1302 }
1303 if child.kind() == "type_identifier" || child.kind() == "generic_type" {
1304 if let Ok(name) = child.utf8_text(source_code.as_bytes()) {
1305 if has_for {
1306 } else {
1308 implements.push(name.to_owned());
1310 }
1311 }
1312 }
1313 }
1314 }
1315 },
1316 Language::Go => {
1317 if node.kind() == "type_declaration" {
1319 for child in node.children(&mut node.walk()) {
1320 if child.kind() == "type_spec" {
1321 for spec_child in child.children(&mut child.walk()) {
1322 if spec_child.kind() == "struct_type" {
1323 for field in spec_child.children(&mut spec_child.walk()) {
1324 if field.kind() == "field_declaration" {
1325 let has_name = field.child_by_field_name("name").is_some();
1327 if !has_name {
1328 if let Some(type_node) =
1329 field.child_by_field_name("type")
1330 {
1331 if let Ok(name) =
1332 type_node.utf8_text(source_code.as_bytes())
1333 {
1334 implements.push(name.to_owned());
1335 }
1336 }
1337 }
1338 }
1339 }
1340 }
1341 }
1342 }
1343 }
1344 }
1345 },
1346 Language::Java => {
1347 if node.kind() == "class_declaration" {
1349 for child in node.children(&mut node.walk()) {
1350 if child.kind() == "superclass" {
1351 for type_node in child.children(&mut child.walk()) {
1352 if type_node.kind() == "type_identifier" {
1353 if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1354 extends = Some(name.to_owned());
1355 }
1356 }
1357 }
1358 } else if child.kind() == "super_interfaces" {
1359 for type_list in child.children(&mut child.walk()) {
1360 if type_list.kind() == "type_list" {
1361 for type_node in type_list.children(&mut type_list.walk()) {
1362 if type_node.kind() == "type_identifier" {
1363 if let Ok(name) =
1364 type_node.utf8_text(source_code.as_bytes())
1365 {
1366 implements.push(name.to_owned());
1367 }
1368 }
1369 }
1370 }
1371 }
1372 }
1373 }
1374 }
1375 },
1376 Language::C | Language::Cpp => {
1377 if node.kind() == "class_specifier" || node.kind() == "struct_specifier" {
1379 for child in node.children(&mut node.walk()) {
1380 if child.kind() == "base_class_clause" {
1381 for base in child.children(&mut child.walk()) {
1382 if base.kind() == "type_identifier" {
1383 if let Ok(name) = base.utf8_text(source_code.as_bytes()) {
1384 if extends.is_none() {
1385 extends = Some(name.to_owned());
1386 } else {
1387 implements.push(name.to_owned());
1388 }
1389 }
1390 }
1391 }
1392 }
1393 }
1394 }
1395 },
1396 Language::CSharp => {
1397 if node.kind() == "class_declaration" {
1399 for child in node.children(&mut node.walk()) {
1400 if child.kind() == "base_list" {
1401 for base in child.children(&mut child.walk()) {
1402 if base.kind() == "identifier" || base.kind() == "generic_name" {
1403 if let Ok(name) = base.utf8_text(source_code.as_bytes()) {
1404 if name.starts_with('I') && name.len() > 1 {
1405 implements.push(name.to_owned());
1407 } else if extends.is_none() {
1408 extends = Some(name.to_owned());
1409 } else {
1410 implements.push(name.to_owned());
1411 }
1412 }
1413 }
1414 }
1415 }
1416 }
1417 }
1418 },
1419 Language::Ruby => {
1420 if node.kind() == "class" {
1422 for child in node.children(&mut node.walk()) {
1423 if child.kind() == "superclass" {
1424 for type_node in child.children(&mut child.walk()) {
1425 if type_node.kind() == "constant" {
1426 if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1427 extends = Some(name.to_owned());
1428 }
1429 }
1430 }
1431 }
1432 }
1433 }
1434 },
1435 Language::Php => {
1436 if node.kind() == "class_declaration" {
1438 for child in node.children(&mut node.walk()) {
1439 if child.kind() == "base_clause" {
1440 for type_node in child.children(&mut child.walk()) {
1441 if type_node.kind() == "name" {
1442 if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1443 extends = Some(name.to_owned());
1444 }
1445 }
1446 }
1447 } else if child.kind() == "class_interface_clause" {
1448 for type_node in child.children(&mut child.walk()) {
1449 if type_node.kind() == "name" {
1450 if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1451 implements.push(name.to_owned());
1452 }
1453 }
1454 }
1455 }
1456 }
1457 }
1458 },
1459 Language::Kotlin => {
1460 if node.kind() == "class_declaration" {
1462 for child in node.children(&mut node.walk()) {
1463 if child.kind() == "delegation_specifiers" {
1464 for spec in child.children(&mut child.walk()) {
1465 if spec.kind() == "delegation_specifier" {
1466 for type_node in spec.children(&mut spec.walk()) {
1467 if type_node.kind() == "user_type" {
1468 if let Ok(name) =
1469 type_node.utf8_text(source_code.as_bytes())
1470 {
1471 if extends.is_none() {
1472 extends = Some(name.to_owned());
1473 } else {
1474 implements.push(name.to_owned());
1475 }
1476 }
1477 }
1478 }
1479 }
1480 }
1481 }
1482 }
1483 }
1484 },
1485 Language::Swift => {
1486 if node.kind() == "class_declaration" {
1488 for child in node.children(&mut node.walk()) {
1489 if child.kind() == "type_inheritance_clause" {
1490 for type_node in child.children(&mut child.walk()) {
1491 if type_node.kind() == "type_identifier" {
1492 if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1493 if extends.is_none() {
1494 extends = Some(name.to_owned());
1495 } else {
1496 implements.push(name.to_owned());
1497 }
1498 }
1499 }
1500 }
1501 }
1502 }
1503 }
1504 },
1505 Language::Scala => {
1506 if node.kind() == "class_definition" {
1508 for child in node.children(&mut node.walk()) {
1509 if child.kind() == "extends_clause" {
1510 for type_node in child.children(&mut child.walk()) {
1511 if type_node.kind() == "type_identifier" {
1512 if let Ok(name) = type_node.utf8_text(source_code.as_bytes()) {
1513 if extends.is_none() {
1514 extends = Some(name.to_owned());
1515 } else {
1516 implements.push(name.to_owned());
1517 }
1518 }
1519 }
1520 }
1521 }
1522 }
1523 }
1524 },
1525 Language::Bash
1526 | Language::Haskell
1527 | Language::Elixir
1528 | Language::Clojure
1529 | Language::OCaml
1530 | Language::FSharp
1531 | Language::Lua
1532 | Language::R => {},
1533 }
1534
1535 (extends, implements)
1536}
1537
1538pub fn map_symbol_kind(capture_name: &str) -> SymbolKind {
1540 match capture_name {
1541 "function" => SymbolKind::Function,
1542 "class" => SymbolKind::Class,
1543 "method" => SymbolKind::Method,
1544 "struct" => SymbolKind::Struct,
1545 "enum" => SymbolKind::Enum,
1546 "interface" => SymbolKind::Interface,
1547 "trait" => SymbolKind::Trait,
1548 _ => SymbolKind::Function,
1549 }
1550}
1551
1552#[cfg(test)]
1553mod tests {
1554 use super::*;
1555
1556 #[test]
1561 fn test_safe_char_boundary_ascii() {
1562 let s = "hello world";
1563 assert_eq!(safe_char_boundary(s, 0), 0);
1564 assert_eq!(safe_char_boundary(s, 5), 5);
1565 assert_eq!(safe_char_boundary(s, 11), 11);
1566 }
1567
1568 #[test]
1569 fn test_safe_char_boundary_beyond_length() {
1570 let s = "hello";
1571 assert_eq!(safe_char_boundary(s, 100), 5);
1572 assert_eq!(safe_char_boundary(s, 5), 5);
1573 }
1574
1575 #[test]
1576 fn test_safe_char_boundary_empty_string() {
1577 let s = "";
1578 assert_eq!(safe_char_boundary(s, 0), 0);
1579 assert_eq!(safe_char_boundary(s, 10), 0);
1580 }
1581
1582 #[test]
1583 fn test_safe_char_boundary_multibyte_utf8() {
1584 let s = "䏿–‡";
1586 assert_eq!(safe_char_boundary(s, 0), 0);
1588 assert_eq!(safe_char_boundary(s, 1), 0);
1590 assert_eq!(safe_char_boundary(s, 2), 0);
1592 assert_eq!(safe_char_boundary(s, 3), 3);
1594 assert_eq!(safe_char_boundary(s, 4), 3);
1596 }
1597
1598 #[test]
1599 fn test_safe_char_boundary_emoji() {
1600 let s = "Hello 👋 World";
1602 assert_eq!(safe_char_boundary(s, 6), 6);
1604 assert_eq!(safe_char_boundary(s, 7), 6);
1606 assert_eq!(safe_char_boundary(s, 8), 6);
1607 assert_eq!(safe_char_boundary(s, 9), 6);
1608 assert_eq!(safe_char_boundary(s, 10), 10);
1610 }
1611
1612 #[test]
1613 fn test_safe_char_boundary_mixed_content() {
1614 let s = "aбв"; assert_eq!(safe_char_boundary(s, 0), 0);
1617 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); }
1623
1624 #[test]
1629 fn test_clean_jsdoc_simple() {
1630 let input = "/** This is a simple doc */";
1631 assert_eq!(clean_jsdoc(input), "This is a simple doc");
1632 }
1633
1634 #[test]
1635 fn test_clean_jsdoc_multiline() {
1636 let input = "/**\n * Line 1\n * Line 2\n */";
1637 let result = clean_jsdoc(input);
1638 assert!(result.contains("Line 1"));
1640 assert!(result.contains("Line 2"));
1641 }
1642
1643 #[test]
1644 fn test_clean_jsdoc_with_asterisks() {
1645 let input = "/**\n * First line\n * Second line\n * Third line\n */";
1646 let result = clean_jsdoc(input);
1647 assert!(result.contains("First line"));
1648 assert!(result.contains("Second line"));
1649 assert!(result.contains("Third line"));
1650 }
1651
1652 #[test]
1653 fn test_clean_jsdoc_empty() {
1654 let input = "/** */";
1655 assert_eq!(clean_jsdoc(input), "");
1656 }
1657
1658 #[test]
1659 fn test_clean_jsdoc_c_style_comment() {
1660 let input = "/* Regular C comment */";
1661 assert_eq!(clean_jsdoc(input), "Regular C comment");
1662 }
1663
1664 #[test]
1665 fn test_clean_jsdoc_with_tags() {
1666 let input = "/**\n * Description\n * @param x The x value\n * @returns Result\n */";
1667 let result = clean_jsdoc(input);
1668 assert!(result.contains("Description"));
1669 assert!(result.contains("@param x"));
1670 assert!(result.contains("@returns"));
1671 }
1672
1673 #[test]
1674 fn test_clean_jsdoc_whitespace_handling() {
1675 let input = "/** \n * Lots of spaces \n */";
1676 assert!(clean_jsdoc(input).contains("Lots of spaces"));
1677 }
1678
1679 #[test]
1684 fn test_clean_javadoc_simple() {
1685 let input = "/** JavaDoc comment */";
1686 assert_eq!(clean_javadoc(input), "JavaDoc comment");
1687 }
1688
1689 #[test]
1690 fn test_clean_javadoc_multiline() {
1691 let input = "/**\n * Method description.\n * @param name The name\n */";
1692 let result = clean_javadoc(input);
1693 assert!(result.contains("Method description"));
1694 assert!(result.contains("@param name"));
1695 }
1696
1697 #[test]
1702 fn test_map_symbol_kind_function() {
1703 assert_eq!(map_symbol_kind("function"), SymbolKind::Function);
1704 }
1705
1706 #[test]
1707 fn test_map_symbol_kind_class() {
1708 assert_eq!(map_symbol_kind("class"), SymbolKind::Class);
1709 }
1710
1711 #[test]
1712 fn test_map_symbol_kind_method() {
1713 assert_eq!(map_symbol_kind("method"), SymbolKind::Method);
1714 }
1715
1716 #[test]
1717 fn test_map_symbol_kind_struct() {
1718 assert_eq!(map_symbol_kind("struct"), SymbolKind::Struct);
1719 }
1720
1721 #[test]
1722 fn test_map_symbol_kind_enum() {
1723 assert_eq!(map_symbol_kind("enum"), SymbolKind::Enum);
1724 }
1725
1726 #[test]
1727 fn test_map_symbol_kind_interface() {
1728 assert_eq!(map_symbol_kind("interface"), SymbolKind::Interface);
1729 }
1730
1731 #[test]
1732 fn test_map_symbol_kind_trait() {
1733 assert_eq!(map_symbol_kind("trait"), SymbolKind::Trait);
1734 }
1735
1736 #[test]
1737 fn test_map_symbol_kind_unknown() {
1738 assert_eq!(map_symbol_kind("unknown"), SymbolKind::Function);
1740 assert_eq!(map_symbol_kind(""), SymbolKind::Function);
1741 assert_eq!(map_symbol_kind("random"), SymbolKind::Function);
1742 }
1743
1744 #[test]
1749 fn test_is_builtin_python_print() {
1750 assert!(is_builtin("print", Language::Python));
1751 assert!(is_builtin("len", Language::Python));
1752 assert!(is_builtin("range", Language::Python));
1753 assert!(is_builtin("str", Language::Python));
1754 assert!(is_builtin("int", Language::Python));
1755 assert!(is_builtin("float", Language::Python));
1756 assert!(is_builtin("list", Language::Python));
1757 assert!(is_builtin("dict", Language::Python));
1758 assert!(is_builtin("set", Language::Python));
1759 assert!(is_builtin("tuple", Language::Python));
1760 }
1761
1762 #[test]
1763 fn test_is_builtin_python_type_funcs() {
1764 assert!(is_builtin("bool", Language::Python));
1765 assert!(is_builtin("type", Language::Python));
1766 assert!(is_builtin("isinstance", Language::Python));
1767 assert!(is_builtin("hasattr", Language::Python));
1768 assert!(is_builtin("getattr", Language::Python));
1769 assert!(is_builtin("setattr", Language::Python));
1770 assert!(is_builtin("super", Language::Python));
1771 }
1772
1773 #[test]
1774 fn test_is_builtin_python_itertools() {
1775 assert!(is_builtin("iter", Language::Python));
1776 assert!(is_builtin("next", Language::Python));
1777 assert!(is_builtin("enumerate", Language::Python));
1778 assert!(is_builtin("zip", Language::Python));
1779 assert!(is_builtin("map", Language::Python));
1780 assert!(is_builtin("filter", Language::Python));
1781 assert!(is_builtin("sorted", Language::Python));
1782 assert!(is_builtin("reversed", Language::Python));
1783 }
1784
1785 #[test]
1786 fn test_is_builtin_python_math() {
1787 assert!(is_builtin("sum", Language::Python));
1788 assert!(is_builtin("min", Language::Python));
1789 assert!(is_builtin("max", Language::Python));
1790 assert!(is_builtin("abs", Language::Python));
1791 assert!(is_builtin("round", Language::Python));
1792 }
1793
1794 #[test]
1795 fn test_is_builtin_python_not_builtin() {
1796 assert!(!is_builtin("my_function", Language::Python));
1797 assert!(!is_builtin("custom_print", Language::Python));
1798 assert!(!is_builtin("calculate", Language::Python));
1799 }
1800
1801 #[test]
1806 fn test_is_builtin_js_console() {
1807 assert!(is_builtin("console", Language::JavaScript));
1808 assert!(is_builtin("log", Language::JavaScript));
1809 assert!(is_builtin("error", Language::JavaScript));
1810 assert!(is_builtin("warn", Language::JavaScript));
1811 }
1812
1813 #[test]
1814 fn test_is_builtin_js_parsing() {
1815 assert!(is_builtin("parseInt", Language::JavaScript));
1816 assert!(is_builtin("parseFloat", Language::JavaScript));
1817 assert!(is_builtin("JSON", Language::JavaScript));
1818 assert!(is_builtin("stringify", Language::JavaScript));
1819 assert!(is_builtin("parse", Language::JavaScript));
1820 }
1821
1822 #[test]
1823 fn test_is_builtin_js_timers() {
1824 assert!(is_builtin("setTimeout", Language::JavaScript));
1825 assert!(is_builtin("setInterval", Language::JavaScript));
1826 assert!(is_builtin("clearTimeout", Language::JavaScript));
1827 assert!(is_builtin("clearInterval", Language::JavaScript));
1828 }
1829
1830 #[test]
1831 fn test_is_builtin_js_array_methods() {
1832 assert!(is_builtin("push", Language::JavaScript));
1833 assert!(is_builtin("pop", Language::JavaScript));
1834 assert!(is_builtin("shift", Language::JavaScript));
1835 assert!(is_builtin("unshift", Language::JavaScript));
1836 assert!(is_builtin("slice", Language::JavaScript));
1837 assert!(is_builtin("splice", Language::JavaScript));
1838 assert!(is_builtin("map", Language::JavaScript));
1839 assert!(is_builtin("filter", Language::JavaScript));
1840 assert!(is_builtin("reduce", Language::JavaScript));
1841 assert!(is_builtin("forEach", Language::JavaScript));
1842 }
1843
1844 #[test]
1845 fn test_is_builtin_ts_same_as_js() {
1846 assert!(is_builtin("console", Language::TypeScript));
1847 assert!(is_builtin("map", Language::TypeScript));
1848 assert!(is_builtin("filter", Language::TypeScript));
1849 }
1850
1851 #[test]
1852 fn test_is_builtin_js_not_builtin() {
1853 assert!(!is_builtin("myFunction", Language::JavaScript));
1854 assert!(!is_builtin("customLog", Language::JavaScript));
1855 }
1856
1857 #[test]
1862 fn test_is_builtin_rust_macros() {
1863 assert!(is_builtin("println!", Language::Rust));
1864 assert!(is_builtin("print!", Language::Rust));
1865 assert!(is_builtin("eprintln!", Language::Rust));
1866 assert!(is_builtin("eprint!", Language::Rust));
1867 assert!(is_builtin("format!", Language::Rust));
1868 assert!(is_builtin("vec!", Language::Rust));
1869 assert!(is_builtin("panic!", Language::Rust));
1870 assert!(is_builtin("assert!", Language::Rust));
1871 assert!(is_builtin("assert_eq!", Language::Rust));
1872 assert!(is_builtin("assert_ne!", Language::Rust));
1873 }
1874
1875 #[test]
1876 fn test_is_builtin_rust_logging() {
1877 assert!(is_builtin("debug!", Language::Rust));
1878 assert!(is_builtin("info!", Language::Rust));
1879 assert!(is_builtin("warn!", Language::Rust));
1880 assert!(is_builtin("error!", Language::Rust));
1881 assert!(is_builtin("trace!", Language::Rust));
1882 }
1883
1884 #[test]
1885 fn test_is_builtin_rust_common_methods() {
1886 assert!(is_builtin("unwrap", Language::Rust));
1887 assert!(is_builtin("expect", Language::Rust));
1888 assert!(is_builtin("ok", Language::Rust));
1889 assert!(is_builtin("err", Language::Rust));
1890 assert!(is_builtin("some", Language::Rust));
1891 assert!(is_builtin("none", Language::Rust));
1892 assert!(is_builtin("clone", Language::Rust));
1893 assert!(is_builtin("to_string", Language::Rust));
1894 assert!(is_builtin("into", Language::Rust));
1895 assert!(is_builtin("from", Language::Rust));
1896 assert!(is_builtin("default", Language::Rust));
1897 }
1898
1899 #[test]
1900 fn test_is_builtin_rust_iterators() {
1901 assert!(is_builtin("iter", Language::Rust));
1902 assert!(is_builtin("into_iter", Language::Rust));
1903 assert!(is_builtin("collect", Language::Rust));
1904 assert!(is_builtin("map", Language::Rust));
1905 assert!(is_builtin("filter", Language::Rust));
1906 }
1907
1908 #[test]
1909 fn test_is_builtin_rust_not_builtin() {
1910 assert!(!is_builtin("my_function", Language::Rust));
1911 assert!(!is_builtin("process_data", Language::Rust));
1912 }
1913
1914 #[test]
1919 fn test_is_builtin_go_fmt() {
1920 assert!(is_builtin("fmt", Language::Go));
1921 assert!(is_builtin("Println", Language::Go));
1922 assert!(is_builtin("Printf", Language::Go));
1923 assert!(is_builtin("Sprintf", Language::Go));
1924 assert!(is_builtin("Errorf", Language::Go));
1925 }
1926
1927 #[test]
1928 fn test_is_builtin_go_memory() {
1929 assert!(is_builtin("make", Language::Go));
1930 assert!(is_builtin("new", Language::Go));
1931 assert!(is_builtin("len", Language::Go));
1932 assert!(is_builtin("cap", Language::Go));
1933 assert!(is_builtin("append", Language::Go));
1934 assert!(is_builtin("copy", Language::Go));
1935 assert!(is_builtin("delete", Language::Go));
1936 }
1937
1938 #[test]
1939 fn test_is_builtin_go_control() {
1940 assert!(is_builtin("close", Language::Go));
1941 assert!(is_builtin("panic", Language::Go));
1942 assert!(is_builtin("recover", Language::Go));
1943 assert!(is_builtin("print", Language::Go));
1944 }
1945
1946 #[test]
1947 fn test_is_builtin_go_not_builtin() {
1948 assert!(!is_builtin("ProcessData", Language::Go));
1949 assert!(!is_builtin("handleRequest", Language::Go));
1950 }
1951
1952 #[test]
1957 fn test_is_builtin_java_io() {
1958 assert!(is_builtin("println", Language::Java));
1959 assert!(is_builtin("print", Language::Java));
1960 assert!(is_builtin("printf", Language::Java));
1961 }
1962
1963 #[test]
1964 fn test_is_builtin_java_object() {
1965 assert!(is_builtin("toString", Language::Java));
1966 assert!(is_builtin("equals", Language::Java));
1967 assert!(is_builtin("hashCode", Language::Java));
1968 assert!(is_builtin("getClass", Language::Java));
1969 assert!(is_builtin("clone", Language::Java));
1970 assert!(is_builtin("notify", Language::Java));
1971 assert!(is_builtin("wait", Language::Java));
1972 }
1973
1974 #[test]
1975 fn test_is_builtin_java_collections() {
1976 assert!(is_builtin("get", Language::Java));
1977 assert!(is_builtin("set", Language::Java));
1978 assert!(is_builtin("add", Language::Java));
1979 assert!(is_builtin("remove", Language::Java));
1980 assert!(is_builtin("size", Language::Java));
1981 assert!(is_builtin("isEmpty", Language::Java));
1982 assert!(is_builtin("contains", Language::Java));
1983 assert!(is_builtin("iterator", Language::Java));
1984 }
1985
1986 #[test]
1987 fn test_is_builtin_java_not_builtin() {
1988 assert!(!is_builtin("processData", Language::Java));
1989 assert!(!is_builtin("calculateTotal", Language::Java));
1990 }
1991
1992 #[test]
1997 fn test_is_builtin_c_io() {
1998 assert!(is_builtin("printf", Language::C));
1999 assert!(is_builtin("scanf", Language::C));
2000 }
2001
2002 #[test]
2003 fn test_is_builtin_c_memory() {
2004 assert!(is_builtin("malloc", Language::C));
2005 assert!(is_builtin("free", Language::C));
2006 assert!(is_builtin("memcpy", Language::C));
2007 assert!(is_builtin("memset", Language::C));
2008 }
2009
2010 #[test]
2011 fn test_is_builtin_c_string() {
2012 assert!(is_builtin("strlen", Language::C));
2013 assert!(is_builtin("strcpy", Language::C));
2014 assert!(is_builtin("strcmp", Language::C));
2015 assert!(is_builtin("strcat", Language::C));
2016 }
2017
2018 #[test]
2019 fn test_is_builtin_cpp_streams() {
2020 assert!(is_builtin("cout", Language::Cpp));
2021 assert!(is_builtin("cin", Language::Cpp));
2022 assert!(is_builtin("endl", Language::Cpp));
2023 assert!(is_builtin("cerr", Language::Cpp));
2024 assert!(is_builtin("clog", Language::Cpp));
2025 }
2026
2027 #[test]
2028 fn test_is_builtin_c_not_builtin() {
2029 assert!(!is_builtin("process_data", Language::C));
2030 assert!(!is_builtin("custom_malloc", Language::C));
2031 }
2032
2033 #[test]
2038 fn test_is_builtin_csharp_console() {
2039 assert!(is_builtin("WriteLine", Language::CSharp));
2040 assert!(is_builtin("Write", Language::CSharp));
2041 assert!(is_builtin("ReadLine", Language::CSharp));
2042 }
2043
2044 #[test]
2045 fn test_is_builtin_csharp_object() {
2046 assert!(is_builtin("ToString", Language::CSharp));
2047 assert!(is_builtin("Equals", Language::CSharp));
2048 assert!(is_builtin("GetHashCode", Language::CSharp));
2049 assert!(is_builtin("GetType", Language::CSharp));
2050 }
2051
2052 #[test]
2053 fn test_is_builtin_csharp_collections() {
2054 assert!(is_builtin("Add", Language::CSharp));
2055 assert!(is_builtin("Remove", Language::CSharp));
2056 assert!(is_builtin("Contains", Language::CSharp));
2057 assert!(is_builtin("Count", Language::CSharp));
2058 assert!(is_builtin("Clear", Language::CSharp));
2059 assert!(is_builtin("ToList", Language::CSharp));
2060 assert!(is_builtin("ToArray", Language::CSharp));
2061 }
2062
2063 #[test]
2068 fn test_is_builtin_ruby_io() {
2069 assert!(is_builtin("puts", Language::Ruby));
2070 assert!(is_builtin("print", Language::Ruby));
2071 assert!(is_builtin("p", Language::Ruby));
2072 assert!(is_builtin("gets", Language::Ruby));
2073 }
2074
2075 #[test]
2076 fn test_is_builtin_ruby_enumerable() {
2077 assert!(is_builtin("each", Language::Ruby));
2078 assert!(is_builtin("map", Language::Ruby));
2079 assert!(is_builtin("select", Language::Ruby));
2080 assert!(is_builtin("reject", Language::Ruby));
2081 assert!(is_builtin("reduce", Language::Ruby));
2082 assert!(is_builtin("inject", Language::Ruby));
2083 assert!(is_builtin("find", Language::Ruby));
2084 }
2085
2086 #[test]
2087 fn test_is_builtin_ruby_predicates() {
2088 assert!(is_builtin("any?", Language::Ruby));
2089 assert!(is_builtin("all?", Language::Ruby));
2090 assert!(is_builtin("include?", Language::Ruby));
2091 assert!(is_builtin("empty?", Language::Ruby));
2092 assert!(is_builtin("nil?", Language::Ruby));
2093 }
2094
2095 #[test]
2100 fn test_is_builtin_php_io() {
2101 assert!(is_builtin("echo", Language::Php));
2102 assert!(is_builtin("print", Language::Php));
2103 assert!(is_builtin("var_dump", Language::Php));
2104 assert!(is_builtin("print_r", Language::Php));
2105 }
2106
2107 #[test]
2108 fn test_is_builtin_php_checks() {
2109 assert!(is_builtin("isset", Language::Php));
2110 assert!(is_builtin("empty", Language::Php));
2111 }
2112
2113 #[test]
2114 fn test_is_builtin_php_array_string() {
2115 assert!(is_builtin("array", Language::Php));
2116 assert!(is_builtin("count", Language::Php));
2117 assert!(is_builtin("strlen", Language::Php));
2118 assert!(is_builtin("strpos", Language::Php));
2119 assert!(is_builtin("substr", Language::Php));
2120 assert!(is_builtin("explode", Language::Php));
2121 assert!(is_builtin("implode", Language::Php));
2122 assert!(is_builtin("json_encode", Language::Php));
2123 assert!(is_builtin("json_decode", Language::Php));
2124 }
2125
2126 #[test]
2131 fn test_is_builtin_kotlin_io() {
2132 assert!(is_builtin("println", Language::Kotlin));
2133 assert!(is_builtin("print", Language::Kotlin));
2134 assert!(is_builtin("readLine", Language::Kotlin));
2135 }
2136
2137 #[test]
2138 fn test_is_builtin_kotlin_scope() {
2139 assert!(is_builtin("let", Language::Kotlin));
2140 assert!(is_builtin("also", Language::Kotlin));
2141 assert!(is_builtin("apply", Language::Kotlin));
2142 assert!(is_builtin("run", Language::Kotlin));
2143 assert!(is_builtin("with", Language::Kotlin));
2144 }
2145
2146 #[test]
2147 fn test_is_builtin_kotlin_collections() {
2148 assert!(is_builtin("listOf", Language::Kotlin));
2149 assert!(is_builtin("mapOf", Language::Kotlin));
2150 assert!(is_builtin("setOf", Language::Kotlin));
2151 assert!(is_builtin("map", Language::Kotlin));
2152 assert!(is_builtin("filter", Language::Kotlin));
2153 assert!(is_builtin("forEach", Language::Kotlin));
2154 }
2155
2156 #[test]
2161 fn test_is_builtin_swift_io() {
2162 assert!(is_builtin("print", Language::Swift));
2163 assert!(is_builtin("debugPrint", Language::Swift));
2164 assert!(is_builtin("dump", Language::Swift));
2165 }
2166
2167 #[test]
2168 fn test_is_builtin_swift_functional() {
2169 assert!(is_builtin("map", Language::Swift));
2170 assert!(is_builtin("filter", Language::Swift));
2171 assert!(is_builtin("reduce", Language::Swift));
2172 assert!(is_builtin("forEach", Language::Swift));
2173 }
2174
2175 #[test]
2176 fn test_is_builtin_swift_collection() {
2177 assert!(is_builtin("contains", Language::Swift));
2178 assert!(is_builtin("count", Language::Swift));
2179 assert!(is_builtin("isEmpty", Language::Swift));
2180 assert!(is_builtin("append", Language::Swift));
2181 }
2182
2183 #[test]
2188 fn test_is_builtin_scala_io() {
2189 assert!(is_builtin("println", Language::Scala));
2190 assert!(is_builtin("print", Language::Scala));
2191 }
2192
2193 #[test]
2194 fn test_is_builtin_scala_functional() {
2195 assert!(is_builtin("map", Language::Scala));
2196 assert!(is_builtin("filter", Language::Scala));
2197 assert!(is_builtin("flatMap", Language::Scala));
2198 assert!(is_builtin("foreach", Language::Scala));
2199 assert!(is_builtin("reduce", Language::Scala));
2200 assert!(is_builtin("fold", Language::Scala));
2201 assert!(is_builtin("foldLeft", Language::Scala));
2202 assert!(is_builtin("foldRight", Language::Scala));
2203 assert!(is_builtin("collect", Language::Scala));
2204 }
2205
2206 #[test]
2211 fn test_is_builtin_bash_always_false() {
2212 assert!(!is_builtin("ls", Language::Bash));
2213 assert!(!is_builtin("echo", Language::Bash));
2214 assert!(!is_builtin("grep", Language::Bash));
2215 }
2216
2217 #[test]
2218 fn test_is_builtin_haskell_always_false() {
2219 assert!(!is_builtin("putStrLn", Language::Haskell));
2220 assert!(!is_builtin("map", Language::Haskell));
2221 }
2222
2223 #[test]
2224 fn test_is_builtin_elixir_always_false() {
2225 assert!(!is_builtin("IO.puts", Language::Elixir));
2226 assert!(!is_builtin("Enum.map", Language::Elixir));
2227 }
2228
2229 #[test]
2230 fn test_is_builtin_clojure_always_false() {
2231 assert!(!is_builtin("println", Language::Clojure));
2232 assert!(!is_builtin("map", Language::Clojure));
2233 }
2234
2235 #[test]
2236 fn test_is_builtin_ocaml_always_false() {
2237 assert!(!is_builtin("print_endline", Language::OCaml));
2238 assert!(!is_builtin("List.map", Language::OCaml));
2239 }
2240
2241 #[test]
2242 fn test_is_builtin_fsharp_always_false() {
2243 assert!(!is_builtin("printfn", Language::FSharp));
2244 assert!(!is_builtin("List.map", Language::FSharp));
2245 }
2246
2247 #[test]
2248 fn test_is_builtin_lua_always_false() {
2249 assert!(!is_builtin("print", Language::Lua));
2250 assert!(!is_builtin("pairs", Language::Lua));
2251 }
2252
2253 #[test]
2254 fn test_is_builtin_r_always_false() {
2255 assert!(!is_builtin("print", Language::R));
2256 assert!(!is_builtin("cat", Language::R));
2257 }
2258
2259 fn parse_and_find_node(
2265 code: &str,
2266 language: Language,
2267 node_kind: &str,
2268 ) -> Option<(tree_sitter::Tree, usize)> {
2269 let mut parser = tree_sitter::Parser::new();
2270
2271 let ts_language = match language {
2272 Language::Python => tree_sitter_python::LANGUAGE,
2273 Language::Rust => tree_sitter_rust::LANGUAGE,
2274 Language::JavaScript => tree_sitter_javascript::LANGUAGE,
2275 Language::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT,
2276 Language::Go => tree_sitter_go::LANGUAGE,
2277 Language::Java => tree_sitter_java::LANGUAGE,
2278 _ => return None,
2279 };
2280
2281 parser
2282 .set_language(&ts_language.into())
2283 .expect("Error loading grammar");
2284
2285 let tree = parser.parse(code, None)?;
2286 let root = tree.root_node();
2287
2288 fn find_node_recursive(node: Node<'_>, kind: &str) -> Option<usize> {
2289 if node.kind() == kind {
2290 return Some(node.id());
2291 }
2292 for child in node.children(&mut node.walk()) {
2293 if let Some(id) = find_node_recursive(child, kind) {
2294 return Some(id);
2295 }
2296 }
2297 None
2298 }
2299
2300 find_node_recursive(root, node_kind).map(|_| (tree, 0))
2301 }
2302
2303 fn find_node_in_tree<'a>(node: Node<'a>, kind: &str) -> Option<Node<'a>> {
2305 if node.kind() == kind {
2306 return Some(node);
2307 }
2308 for child in node.children(&mut node.walk()) {
2309 if let Some(found) = find_node_in_tree(child, kind) {
2310 return Some(found);
2311 }
2312 }
2313 None
2314 }
2315
2316 #[test]
2317 fn test_extract_signature_python() {
2318 let code = "def hello(name):\n return f'Hello {name}'";
2321 let mut parser = tree_sitter::Parser::new();
2322 parser
2323 .set_language(&tree_sitter_python::LANGUAGE.into())
2324 .unwrap();
2325 let tree = parser.parse(code, None).unwrap();
2326 let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2327
2328 let sig = extract_signature(func_node, code, Language::Python);
2329 assert!(sig.is_some());
2330 let sig = sig.unwrap();
2331 assert!(sig.contains("def hello"));
2332 assert!(sig.contains("name"));
2333 }
2334
2335 #[test]
2336 fn test_extract_signature_rust() {
2337 let code = "fn add(a: i32, b: i32) -> i32 { a + b }";
2338 let mut parser = tree_sitter::Parser::new();
2339 parser
2340 .set_language(&tree_sitter_rust::LANGUAGE.into())
2341 .unwrap();
2342 let tree = parser.parse(code, None).unwrap();
2343 let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2344
2345 let sig = extract_signature(func_node, code, Language::Rust);
2346 assert!(sig.is_some());
2347 let sig = sig.unwrap();
2348 assert!(sig.contains("fn add"));
2349 assert!(sig.contains("i32"));
2350 }
2351
2352 #[test]
2353 fn test_extract_signature_javascript() {
2354 let code = "function greet(name) { return 'Hello ' + name; }";
2355 let mut parser = tree_sitter::Parser::new();
2356 parser
2357 .set_language(&tree_sitter_javascript::LANGUAGE.into())
2358 .unwrap();
2359 let tree = parser.parse(code, None).unwrap();
2360 let func_node = find_node_in_tree(tree.root_node(), "function_declaration").unwrap();
2361
2362 let sig = extract_signature(func_node, code, Language::JavaScript);
2363 assert!(sig.is_some());
2364 let sig = sig.unwrap();
2365 assert!(sig.contains("function greet"));
2366 assert!(sig.contains("name"));
2367 }
2368
2369 #[test]
2370 fn test_extract_visibility_python_public() {
2371 let code = "def public_func():\n pass";
2372 let mut parser = tree_sitter::Parser::new();
2373 parser
2374 .set_language(&tree_sitter_python::LANGUAGE.into())
2375 .unwrap();
2376 let tree = parser.parse(code, None).unwrap();
2377 let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2378
2379 let vis = extract_visibility(func_node, code, Language::Python);
2380 assert_eq!(vis, Visibility::Public);
2381 }
2382
2383 #[test]
2384 fn test_extract_visibility_python_private() {
2385 let code = "def __private_func():\n pass";
2386 let mut parser = tree_sitter::Parser::new();
2387 parser
2388 .set_language(&tree_sitter_python::LANGUAGE.into())
2389 .unwrap();
2390 let tree = parser.parse(code, None).unwrap();
2391 let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2392
2393 let vis = extract_visibility(func_node, code, Language::Python);
2394 assert_eq!(vis, Visibility::Private);
2395 }
2396
2397 #[test]
2398 fn test_extract_visibility_python_protected() {
2399 let code = "def _protected_func():\n pass";
2400 let mut parser = tree_sitter::Parser::new();
2401 parser
2402 .set_language(&tree_sitter_python::LANGUAGE.into())
2403 .unwrap();
2404 let tree = parser.parse(code, None).unwrap();
2405 let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2406
2407 let vis = extract_visibility(func_node, code, Language::Python);
2408 assert_eq!(vis, Visibility::Protected);
2409 }
2410
2411 #[test]
2412 fn test_extract_visibility_python_dunder() {
2413 let code = "def __init__(self):\n pass";
2417 let mut parser = tree_sitter::Parser::new();
2418 parser
2419 .set_language(&tree_sitter_python::LANGUAGE.into())
2420 .unwrap();
2421 let tree = parser.parse(code, None).unwrap();
2422 let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2423
2424 let vis = extract_visibility(func_node, code, Language::Python);
2425 assert_eq!(vis, Visibility::Protected);
2428 }
2429
2430 #[test]
2431 fn test_extract_visibility_rust_pub() {
2432 let code = "pub fn public_func() {}";
2433 let mut parser = tree_sitter::Parser::new();
2434 parser
2435 .set_language(&tree_sitter_rust::LANGUAGE.into())
2436 .unwrap();
2437 let tree = parser.parse(code, None).unwrap();
2438 let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2439
2440 let vis = extract_visibility(func_node, code, Language::Rust);
2441 assert_eq!(vis, Visibility::Public);
2442 }
2443
2444 #[test]
2445 fn test_extract_visibility_rust_private() {
2446 let code = "fn private_func() {}";
2447 let mut parser = tree_sitter::Parser::new();
2448 parser
2449 .set_language(&tree_sitter_rust::LANGUAGE.into())
2450 .unwrap();
2451 let tree = parser.parse(code, None).unwrap();
2452 let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2453
2454 let vis = extract_visibility(func_node, code, Language::Rust);
2455 assert_eq!(vis, Visibility::Private);
2456 }
2457
2458 #[test]
2459 fn test_extract_visibility_rust_pub_crate() {
2460 let code = "pub(crate) fn crate_func() {}";
2461 let mut parser = tree_sitter::Parser::new();
2462 parser
2463 .set_language(&tree_sitter_rust::LANGUAGE.into())
2464 .unwrap();
2465 let tree = parser.parse(code, None).unwrap();
2466 let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2467
2468 let vis = extract_visibility(func_node, code, Language::Rust);
2469 assert_eq!(vis, Visibility::Internal);
2470 }
2471
2472 #[test]
2473 fn test_extract_visibility_go_exported() {
2474 let code = "func Exported() {}";
2475 let mut parser = tree_sitter::Parser::new();
2476 parser
2477 .set_language(&tree_sitter_go::LANGUAGE.into())
2478 .unwrap();
2479 let tree = parser.parse(code, None).unwrap();
2480 let func_node = find_node_in_tree(tree.root_node(), "function_declaration").unwrap();
2481
2482 let vis = extract_visibility(func_node, code, Language::Go);
2483 assert_eq!(vis, Visibility::Public);
2484 }
2485
2486 #[test]
2487 fn test_extract_visibility_go_unexported() {
2488 let code = "func unexported() {}";
2489 let mut parser = tree_sitter::Parser::new();
2490 parser
2491 .set_language(&tree_sitter_go::LANGUAGE.into())
2492 .unwrap();
2493 let tree = parser.parse(code, None).unwrap();
2494 let func_node = find_node_in_tree(tree.root_node(), "function_declaration").unwrap();
2495
2496 let vis = extract_visibility(func_node, code, Language::Go);
2497 assert_eq!(vis, Visibility::Private);
2498 }
2499
2500 #[test]
2501 fn test_extract_visibility_bash_always_public() {
2502 let code = "my_func() { echo hello; }";
2503 let mut parser = tree_sitter::Parser::new();
2504 parser
2505 .set_language(&tree_sitter_bash::LANGUAGE.into())
2506 .unwrap();
2507 let tree = parser.parse(code, None).unwrap();
2508 let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2509
2510 let vis = extract_visibility(func_node, code, Language::Bash);
2511 assert_eq!(vis, Visibility::Public);
2512 }
2513
2514 #[test]
2515 fn test_find_body_node_python() {
2516 let code = "def foo():\n x = 1\n return x";
2517 let mut parser = tree_sitter::Parser::new();
2518 parser
2519 .set_language(&tree_sitter_python::LANGUAGE.into())
2520 .unwrap();
2521 let tree = parser.parse(code, None).unwrap();
2522 let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2523
2524 let body = find_body_node(func_node, Language::Python);
2525 assert!(body.is_some());
2526 assert_eq!(body.unwrap().kind(), "block");
2527 }
2528
2529 #[test]
2530 fn test_find_body_node_rust() {
2531 let code = "fn foo() { let x = 1; x }";
2532 let mut parser = tree_sitter::Parser::new();
2533 parser
2534 .set_language(&tree_sitter_rust::LANGUAGE.into())
2535 .unwrap();
2536 let tree = parser.parse(code, None).unwrap();
2537 let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2538
2539 let body = find_body_node(func_node, Language::Rust);
2540 assert!(body.is_some());
2541 assert_eq!(body.unwrap().kind(), "block");
2542 }
2543
2544 #[test]
2545 fn test_find_body_node_javascript() {
2546 let code = "function foo() { return 1; }";
2547 let mut parser = tree_sitter::Parser::new();
2548 parser
2549 .set_language(&tree_sitter_javascript::LANGUAGE.into())
2550 .unwrap();
2551 let tree = parser.parse(code, None).unwrap();
2552 let func_node = find_node_in_tree(tree.root_node(), "function_declaration").unwrap();
2553
2554 let body = find_body_node(func_node, Language::JavaScript);
2555 assert!(body.is_some());
2556 assert_eq!(body.unwrap().kind(), "statement_block");
2557 }
2558
2559 #[test]
2560 fn test_extract_calls_python() {
2561 let code = "def foo():\n bar()\n custom_func(1, 2)";
2562 let mut parser = tree_sitter::Parser::new();
2563 parser
2564 .set_language(&tree_sitter_python::LANGUAGE.into())
2565 .unwrap();
2566 let tree = parser.parse(code, None).unwrap();
2567 let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2568
2569 let calls = extract_calls(func_node, code, Language::Python);
2570 assert!(calls.contains(&"bar".to_owned()));
2571 assert!(calls.contains(&"custom_func".to_owned()));
2572 }
2573
2574 #[test]
2575 fn test_extract_calls_python_filters_builtins() {
2576 let code = "def foo():\n print('hello')\n len([1,2,3])";
2577 let mut parser = tree_sitter::Parser::new();
2578 parser
2579 .set_language(&tree_sitter_python::LANGUAGE.into())
2580 .unwrap();
2581 let tree = parser.parse(code, None).unwrap();
2582 let func_node = find_node_in_tree(tree.root_node(), "function_definition").unwrap();
2583
2584 let calls = extract_calls(func_node, code, Language::Python);
2585 assert!(!calls.contains(&"print".to_owned()));
2587 assert!(!calls.contains(&"len".to_owned()));
2588 }
2589
2590 #[test]
2591 fn test_extract_calls_rust() {
2592 let code = "fn foo() { bar(); baz(1); }";
2593 let mut parser = tree_sitter::Parser::new();
2594 parser
2595 .set_language(&tree_sitter_rust::LANGUAGE.into())
2596 .unwrap();
2597 let tree = parser.parse(code, None).unwrap();
2598 let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2599
2600 let calls = extract_calls(func_node, code, Language::Rust);
2601 assert!(calls.contains(&"bar".to_owned()));
2602 assert!(calls.contains(&"baz".to_owned()));
2603 }
2604
2605 #[test]
2606 fn test_extract_docstring_rust() {
2607 let code = "/// This is a doc comment\nfn foo() {}";
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 docstring = extract_docstring(func_node, code, Language::Rust);
2616 assert!(docstring.is_some());
2617 assert!(docstring.unwrap().contains("This is a doc comment"));
2618 }
2619
2620 #[test]
2621 fn test_extract_docstring_rust_multiline() {
2622 let code = "/// Line 1\n/// Line 2\nfn foo() {}";
2623 let mut parser = tree_sitter::Parser::new();
2624 parser
2625 .set_language(&tree_sitter_rust::LANGUAGE.into())
2626 .unwrap();
2627 let tree = parser.parse(code, None).unwrap();
2628 let func_node = find_node_in_tree(tree.root_node(), "function_item").unwrap();
2629
2630 let docstring = extract_docstring(func_node, code, Language::Rust);
2631 assert!(docstring.is_some());
2632 let doc = docstring.unwrap();
2633 assert!(doc.contains("Line 1"));
2634 assert!(doc.contains("Line 2"));
2635 }
2636}