1use crate::models::{Language, SearchResult, Span, SymbolKind};
13use anyhow::{Context, Result};
14use streaming_iterator::StreamingIterator;
15use tree_sitter::{Parser, Query, QueryCursor};
16
17pub fn parse(path: &str, source: &str) -> Result<Vec<SearchResult>> {
19 let mut parser = Parser::new();
20 let language = tree_sitter_c::LANGUAGE;
21
22 parser
23 .set_language(&language.into())
24 .context("Failed to set C language")?;
25
26 let tree = parser
27 .parse(source, None)
28 .context("Failed to parse C source")?;
29
30 let root_node = tree.root_node();
31
32 let mut symbols = Vec::new();
33
34 symbols.extend(extract_functions(source, &root_node, &language.into())?);
36 symbols.extend(extract_structs(source, &root_node, &language.into())?);
37 symbols.extend(extract_enums(source, &root_node, &language.into())?);
38 symbols.extend(extract_unions(source, &root_node, &language.into())?);
39 symbols.extend(extract_typedefs(source, &root_node, &language.into())?);
40 symbols.extend(extract_variables(source, &root_node, &language.into())?);
41 symbols.extend(extract_macros(source, &root_node, &language.into())?);
42
43 for symbol in &mut symbols {
45 symbol.path = path.to_string();
46 symbol.lang = Language::C;
47 }
48
49 Ok(symbols)
50}
51
52fn extract_functions(
54 source: &str,
55 root: &tree_sitter::Node,
56 language: &tree_sitter::Language,
57) -> Result<Vec<SearchResult>> {
58 let query_str = r#"
59 (function_definition
60 declarator: (function_declarator
61 declarator: (identifier) @name)) @function
62
63 (function_definition
64 declarator: (pointer_declarator
65 declarator: (function_declarator
66 declarator: (identifier) @name))) @function
67 "#;
68
69 let query = Query::new(language, query_str).context("Failed to create function query")?;
70
71 extract_symbols(source, root, &query, SymbolKind::Function, None)
72}
73
74fn extract_structs(
76 source: &str,
77 root: &tree_sitter::Node,
78 language: &tree_sitter::Language,
79) -> Result<Vec<SearchResult>> {
80 let query_str = r#"
81 (struct_specifier
82 name: (type_identifier) @name) @struct
83 "#;
84
85 let query = Query::new(language, query_str).context("Failed to create struct query")?;
86
87 extract_symbols(source, root, &query, SymbolKind::Struct, None)
88}
89
90fn extract_enums(
92 source: &str,
93 root: &tree_sitter::Node,
94 language: &tree_sitter::Language,
95) -> Result<Vec<SearchResult>> {
96 let query_str = r#"
97 (enum_specifier
98 name: (type_identifier) @name) @enum
99 "#;
100
101 let query = Query::new(language, query_str).context("Failed to create enum query")?;
102
103 extract_symbols(source, root, &query, SymbolKind::Enum, None)
104}
105
106fn extract_unions(
108 source: &str,
109 root: &tree_sitter::Node,
110 language: &tree_sitter::Language,
111) -> Result<Vec<SearchResult>> {
112 let query_str = r#"
113 (union_specifier
114 name: (type_identifier) @name) @union
115 "#;
116
117 let query = Query::new(language, query_str).context("Failed to create union query")?;
118
119 extract_symbols(source, root, &query, SymbolKind::Type, None)
120}
121
122fn extract_typedefs(
124 source: &str,
125 root: &tree_sitter::Node,
126 language: &tree_sitter::Language,
127) -> Result<Vec<SearchResult>> {
128 let query_str = r#"
129 (type_definition
130 declarator: (type_identifier) @name) @typedef
131 "#;
132
133 let query = Query::new(language, query_str).context("Failed to create typedef query")?;
134
135 extract_symbols(source, root, &query, SymbolKind::Type, None)
136}
137
138fn extract_variables(
140 source: &str,
141 root: &tree_sitter::Node,
142 language: &tree_sitter::Language,
143) -> Result<Vec<SearchResult>> {
144 let query_str = r#"
145 (declaration
146 declarator: (init_declarator
147 declarator: (identifier) @name)) @var
148
149 (declaration
150 declarator: (identifier) @name) @var
151 "#;
152
153 let query = Query::new(language, query_str).context("Failed to create variable query")?;
154
155 let mut cursor = QueryCursor::new();
157 let mut matches = cursor.matches(&query, *root, source.as_bytes());
158
159 let mut symbols = Vec::new();
160
161 while let Some(match_) = matches.next() {
162 let mut name = None;
163 let mut var_node = None;
164
165 for capture in match_.captures {
166 let capture_name: &str = query.capture_names()[capture.index as usize];
167 if capture_name == "name" {
168 name = Some(
169 capture
170 .node
171 .utf8_text(source.as_bytes())
172 .unwrap_or("")
173 .to_string(),
174 );
175 } else if capture_name == "var" {
176 var_node = Some(capture.node);
177 }
178 }
179
180 if let (Some(name), Some(node)) = (name, var_node) {
181 let span = node_to_span(&node);
182 let preview = extract_preview(source, &span);
183
184 symbols.push(SearchResult::new(
185 String::new(),
186 Language::C,
187 SymbolKind::Variable,
188 Some(name),
189 span,
190 None,
191 preview,
192 ));
193 }
194 }
195
196 Ok(symbols)
197}
198
199fn extract_macros(
201 source: &str,
202 root: &tree_sitter::Node,
203 language: &tree_sitter::Language,
204) -> Result<Vec<SearchResult>> {
205 let query_str = r#"
206 (preproc_def
207 name: (identifier) @name) @macro
208
209 (preproc_function_def
210 name: (identifier) @name) @macro
211 "#;
212
213 let query = Query::new(language, query_str).context("Failed to create macro query")?;
214
215 extract_symbols(source, root, &query, SymbolKind::Macro, None)
216}
217
218fn extract_symbols(
220 source: &str,
221 root: &tree_sitter::Node,
222 query: &Query,
223 kind: SymbolKind,
224 scope: Option<String>,
225) -> Result<Vec<SearchResult>> {
226 let mut cursor = QueryCursor::new();
227 let mut matches = cursor.matches(query, *root, source.as_bytes());
228
229 let mut symbols = Vec::new();
230
231 while let Some(match_) = matches.next() {
232 let mut name = None;
234 let mut full_node = None;
235
236 for capture in match_.captures {
237 let capture_name: &str = query.capture_names()[capture.index as usize];
238 if capture_name == "name" {
239 name = Some(
240 capture
241 .node
242 .utf8_text(source.as_bytes())
243 .unwrap_or("")
244 .to_string(),
245 );
246 } else {
247 full_node = Some(capture.node);
249 }
250 }
251
252 if let (Some(name), Some(node)) = (name, full_node) {
253 let span = node_to_span(&node);
254 let preview = extract_preview(source, &span);
255
256 symbols.push(SearchResult::new(
257 String::new(),
258 Language::C,
259 kind.clone(),
260 Some(name),
261 span,
262 scope.clone(),
263 preview,
264 ));
265 }
266 }
267
268 Ok(symbols)
269}
270
271fn node_to_span(node: &tree_sitter::Node) -> Span {
273 let start = node.start_position();
274 let end = node.end_position();
275
276 Span::new(
277 start.row + 1, start.column,
279 end.row + 1,
280 end.column,
281 )
282}
283
284fn extract_preview(source: &str, span: &Span) -> String {
286 let lines: Vec<&str> = source.lines().collect();
287
288 let start_idx = span.start_line - 1; let end_idx = (start_idx + 7).min(lines.len());
291
292 lines[start_idx..end_idx].join("\n")
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298
299 #[test]
300 fn test_parse_function() {
301 let source = r#"
302int add(int a, int b) {
303 return a + b;
304}
305 "#;
306
307 let symbols = parse("test.c", source).unwrap();
308 assert_eq!(symbols.len(), 1);
309 assert_eq!(symbols[0].symbol.as_deref(), Some("add"));
310 assert!(matches!(symbols[0].kind, SymbolKind::Function));
311 }
312
313 #[test]
314 fn test_parse_struct() {
315 let source = r#"
316struct User {
317 char name[50];
318 int age;
319};
320 "#;
321
322 let symbols = parse("test.c", source).unwrap();
323 assert_eq!(symbols.len(), 1);
324 assert_eq!(symbols[0].symbol.as_deref(), Some("User"));
325 assert!(matches!(symbols[0].kind, SymbolKind::Struct));
326 }
327
328 #[test]
329 fn test_parse_enum() {
330 let source = r#"
331enum Status {
332 STATUS_ACTIVE,
333 STATUS_INACTIVE,
334 STATUS_PENDING
335};
336 "#;
337
338 let symbols = parse("test.c", source).unwrap();
339 assert_eq!(symbols.len(), 1);
340 assert_eq!(symbols[0].symbol.as_deref(), Some("Status"));
341 assert!(matches!(symbols[0].kind, SymbolKind::Enum));
342 }
343
344 #[test]
345 fn test_parse_typedef() {
346 let source = r#"
347typedef struct {
348 int x;
349 int y;
350} Point;
351
352typedef int UserID;
353 "#;
354
355 let symbols = parse("test.c", source).unwrap();
356
357 let typedef_symbols: Vec<_> = symbols
358 .iter()
359 .filter(|s| matches!(s.kind, SymbolKind::Type))
360 .collect();
361
362 assert!(!typedef_symbols.is_empty());
363 assert!(
364 typedef_symbols
365 .iter()
366 .any(|s| s.symbol.as_deref() == Some("Point"))
367 );
368 }
369
370 #[test]
371 fn test_parse_union() {
372 let source = r#"
373union Data {
374 int i;
375 float f;
376 char str[20];
377};
378 "#;
379
380 let symbols = parse("test.c", source).unwrap();
381
382 let union_symbols: Vec<_> = symbols
383 .iter()
384 .filter(|s| matches!(s.kind, SymbolKind::Type))
385 .collect();
386
387 assert_eq!(union_symbols.len(), 1);
388 assert_eq!(union_symbols[0].symbol.as_deref(), Some("Data"));
389 }
390
391 #[test]
392 fn test_parse_global_variables() {
393 let source = r#"
394int global_counter = 0;
395static int internal_state;
396extern int external_value;
397 "#;
398
399 let symbols = parse("test.c", source).unwrap();
400
401 let var_symbols: Vec<_> = symbols
402 .iter()
403 .filter(|s| matches!(s.kind, SymbolKind::Variable))
404 .collect();
405
406 assert_eq!(var_symbols.len(), 3);
407 assert!(
408 var_symbols
409 .iter()
410 .any(|s| s.symbol.as_deref() == Some("global_counter"))
411 );
412 assert!(
413 var_symbols
414 .iter()
415 .any(|s| s.symbol.as_deref() == Some("internal_state"))
416 );
417 assert!(
418 var_symbols
419 .iter()
420 .any(|s| s.symbol.as_deref() == Some("external_value"))
421 );
422 }
423
424 #[test]
425 fn test_parse_pointer_function() {
426 let source = r#"
427int* create_array(int size) {
428 return malloc(size * sizeof(int));
429}
430 "#;
431
432 let symbols = parse("test.c", source).unwrap();
433 assert_eq!(symbols.len(), 1);
434 assert_eq!(symbols[0].symbol.as_deref(), Some("create_array"));
435 assert!(matches!(symbols[0].kind, SymbolKind::Function));
436 }
437
438 #[test]
439 fn test_parse_mixed_symbols() {
440 let source = r#"
441#include <stdio.h>
442
443#define MAX_SIZE 100
444
445typedef struct {
446 char name[50];
447 int age;
448} Person;
449
450enum Color {
451 RED,
452 GREEN,
453 BLUE
454};
455
456int global_count = 0;
457
458int increment(void) {
459 return ++global_count;
460}
461
462struct Node {
463 int data;
464 struct Node* next;
465};
466 "#;
467
468 let symbols = parse("test.c", source).unwrap();
469
470 assert!(symbols.len() >= 6);
472
473 let kinds: Vec<&SymbolKind> = symbols.iter().map(|s| &s.kind).collect();
474 assert!(kinds.contains(&&SymbolKind::Macro));
475 assert!(kinds.contains(&&SymbolKind::Type));
476 assert!(kinds.contains(&&SymbolKind::Enum));
477 assert!(kinds.contains(&&SymbolKind::Variable));
478 assert!(kinds.contains(&&SymbolKind::Function));
479 assert!(kinds.contains(&&SymbolKind::Struct));
480
481 let macro_symbols: Vec<_> = symbols
483 .iter()
484 .filter(|s| matches!(s.kind, SymbolKind::Macro))
485 .collect();
486 assert_eq!(macro_symbols.len(), 1);
487 assert_eq!(macro_symbols[0].symbol.as_deref(), Some("MAX_SIZE"));
488 }
489
490 #[test]
491 fn test_parse_struct_with_typedef() {
492 let source = r#"
493typedef struct Node {
494 int value;
495 struct Node* next;
496} Node;
497 "#;
498
499 let symbols = parse("test.c", source).unwrap();
500
501 assert!(!symbols.is_empty());
503 assert!(symbols.iter().any(|s| s.symbol.as_deref() == Some("Node")));
504 }
505
506 #[test]
507 fn test_local_variables_included() {
508 let source = r#"
509int global_var = 10;
510
511int calculate(int x) {
512 int local_var = x * 2;
513 return local_var;
514}
515 "#;
516
517 let symbols = parse("test.c", source).unwrap();
518
519 let var_symbols: Vec<_> = symbols
520 .iter()
521 .filter(|s| matches!(s.kind, SymbolKind::Variable))
522 .collect();
523
524 assert_eq!(var_symbols.len(), 2);
526 assert!(
527 var_symbols
528 .iter()
529 .any(|s| s.symbol.as_deref() == Some("global_var"))
530 );
531 assert!(
532 var_symbols
533 .iter()
534 .any(|s| s.symbol.as_deref() == Some("local_var"))
535 );
536 }
537
538 #[test]
539 fn test_parse_macros() {
540 let source = r#"
541#define MAX_SIZE 100
542#define MIN(a, b) ((a) < (b) ? (a) : (b))
543#define DEBUG_PRINT(x) printf("Debug: %s\n", x)
544
545int main() {
546 return 0;
547}
548 "#;
549
550 let symbols = parse("test.c", source).unwrap();
551
552 let macro_symbols: Vec<_> = symbols
553 .iter()
554 .filter(|s| matches!(s.kind, SymbolKind::Macro))
555 .collect();
556
557 assert_eq!(macro_symbols.len(), 3);
559 assert!(
560 macro_symbols
561 .iter()
562 .any(|s| s.symbol.as_deref() == Some("MAX_SIZE"))
563 );
564 assert!(
565 macro_symbols
566 .iter()
567 .any(|s| s.symbol.as_deref() == Some("MIN"))
568 );
569 assert!(
570 macro_symbols
571 .iter()
572 .any(|s| s.symbol.as_deref() == Some("DEBUG_PRINT"))
573 );
574 }
575}
576
577use crate::models::ImportType;
582use crate::parsers::{DependencyExtractor, ImportInfo};
583
584pub struct CDependencyExtractor;
586
587impl DependencyExtractor for CDependencyExtractor {
588 fn extract_dependencies(source: &str) -> Result<Vec<ImportInfo>> {
589 let mut parser = Parser::new();
590 let language = tree_sitter_c::LANGUAGE;
591
592 parser
593 .set_language(&language.into())
594 .context("Failed to set C language")?;
595
596 let tree = parser
597 .parse(source, None)
598 .context("Failed to parse C source")?;
599
600 let root_node = tree.root_node();
601
602 let mut imports = Vec::new();
603
604 imports.extend(extract_c_includes(source, &root_node)?);
606
607 Ok(imports)
608 }
609}
610
611fn extract_c_includes(source: &str, root: &tree_sitter::Node) -> Result<Vec<ImportInfo>> {
613 let language = tree_sitter_c::LANGUAGE;
614
615 let query_str = r#"
616 (preproc_include
617 path: (string_literal) @include_path) @include
618
619 (preproc_include
620 path: (system_lib_string) @include_path) @include
621 "#;
622
623 let query =
624 Query::new(&language.into(), query_str).context("Failed to create C include query")?;
625
626 let mut cursor = QueryCursor::new();
627 let mut matches = cursor.matches(&query, *root, source.as_bytes());
628
629 let mut imports = Vec::new();
630
631 while let Some(match_) = matches.next() {
632 let mut include_path = None;
633 let mut include_node = None;
634
635 for capture in match_.captures {
636 let capture_name: &str = query.capture_names()[capture.index as usize];
637 match capture_name {
638 "include_path" => {
639 let raw_path = capture.node.utf8_text(source.as_bytes()).unwrap_or("");
641 include_path = Some(
642 raw_path
643 .trim_matches(|c| c == '"' || c == '<' || c == '>')
644 .to_string(),
645 );
646 }
647 "include" => {
648 include_node = Some(capture.node);
649 }
650 _ => {}
651 }
652 }
653
654 if let (Some(path), Some(node)) = (include_path, include_node) {
655 let import_type = classify_c_include(&path, source, &node);
656 let line_number = node.start_position().row + 1;
657
658 imports.push(ImportInfo {
659 imported_path: path,
660 import_type,
661 line_number,
662 imported_symbols: None, });
664 }
665 }
666
667 Ok(imports)
668}
669
670fn classify_c_include(include_path: &str, source: &str, node: &tree_sitter::Node) -> ImportType {
672 let line_start = node.start_position();
674 let lines: Vec<&str> = source.lines().collect();
675
676 if line_start.row < lines.len() {
677 let line = lines[line_start.row];
678
679 if line.contains(&format!("\"{}\"", include_path)) {
681 return ImportType::Internal;
682 }
683 }
684
685 const STDLIB_HEADERS: &[&str] = &[
687 "stdio.h",
688 "stdlib.h",
689 "string.h",
690 "math.h",
691 "time.h",
692 "ctype.h",
693 "assert.h",
694 "errno.h",
695 "limits.h",
696 "float.h",
697 "stddef.h",
698 "stdint.h",
699 "stdbool.h",
700 "stdarg.h",
701 "setjmp.h",
702 "signal.h",
703 "locale.h",
704 "wchar.h",
705 "wctype.h",
706 "complex.h",
707 "fenv.h",
708 "inttypes.h",
709 "iso646.h",
710 "tgmath.h",
711 "threads.h",
712 ];
713
714 if STDLIB_HEADERS.contains(&include_path) {
715 return ImportType::Stdlib;
716 }
717
718 ImportType::External
720}
721
722pub fn resolve_c_include_to_path(
736 include_path: &str,
737 current_file_path: Option<&str>,
738) -> Option<String> {
739 let current_file = current_file_path?;
743
744 let current_dir = std::path::Path::new(current_file).parent()?;
746
747 let resolved = current_dir.join(include_path);
749
750 match resolved.canonicalize() {
753 Ok(normalized) => Some(normalized.to_string_lossy().replace('\\', "/")),
754 Err(_) => {
755 Some(resolved.to_string_lossy().replace('\\', "/"))
757 }
758 }
759}
760
761#[cfg(test)]
766mod resolution_tests {
767 use super::*;
768
769 #[test]
770 fn test_resolve_c_include_same_directory() {
771 let result = resolve_c_include_to_path("helper.h", Some("/project/src/main.c"));
772
773 assert!(result.is_some());
774 let path = result.unwrap();
775 assert!(path.ends_with("src/helper.h"));
776 }
777
778 #[test]
779 fn test_resolve_c_include_subdirectory() {
780 let result = resolve_c_include_to_path("utils/helper.h", Some("/project/src/main.c"));
781
782 assert!(result.is_some());
783 let path = result.unwrap();
784 assert!(path.ends_with("src/utils/helper.h"));
785 }
786
787 #[test]
788 fn test_resolve_c_include_parent_directory() {
789 let result = resolve_c_include_to_path("../include/common.h", Some("/project/src/main.c"));
790
791 assert!(result.is_some());
792 let path = result.unwrap();
793 assert!(path.contains("include") && path.contains("common.h"));
794 }
795
796 #[test]
797 fn test_resolve_c_include_no_current_file() {
798 let result = resolve_c_include_to_path("helper.h", None);
799
800 assert!(result.is_none());
801 }
802}
803
804#[cfg(test)]
805mod dependency_extraction_tests {
806 use super::*;
807
808 #[test]
809 fn test_extract_basic_includes() {
810 let source = r#"
811 #include <stdio.h>
812 #include <stdlib.h>
813 #include "utils.h"
814 #include "math/vector.h"
815 "#;
816
817 let deps = CDependencyExtractor::extract_dependencies(source).unwrap();
818
819 assert_eq!(deps.len(), 4, "Should extract 4 include statements");
820 assert!(deps.iter().any(|d| d.imported_path == "stdio.h"));
821 assert!(deps.iter().any(|d| d.imported_path == "stdlib.h"));
822 assert!(deps.iter().any(|d| d.imported_path == "utils.h"));
823 assert!(deps.iter().any(|d| d.imported_path == "math/vector.h"));
824 }
825
826 #[test]
827 fn test_macro_includes_filtered() {
828 let source = r#"
829 #include <stdio.h>
830 #include "config.h"
831
832 // Macro-based includes - should be filtered out
833 #define HEADER_NAME "dynamic.h"
834 #include HEADER_NAME
835
836 #define STRINGIFY(x) #x
837 #include STRINGIFY(runtime_header.h)
838
839 // Conditional includes with macros
840 #ifdef USE_FEATURE_X
841 #define FEATURE_HEADER <feature_x.h>
842 #include FEATURE_HEADER
843 #endif
844 "#;
845
846 let deps = CDependencyExtractor::extract_dependencies(source).unwrap();
847
848 assert_eq!(deps.len(), 2, "Should extract 2 static includes only");
851
852 assert!(deps.iter().any(|d| d.imported_path == "stdio.h"));
853 assert!(deps.iter().any(|d| d.imported_path == "config.h"));
854
855 assert!(!deps.iter().any(|d| d.imported_path.contains("HEADER_NAME")));
857 assert!(!deps.iter().any(|d| d.imported_path.contains("dynamic.h")));
858 assert!(
859 !deps
860 .iter()
861 .any(|d| d.imported_path.contains("runtime_header"))
862 );
863 assert!(
864 !deps
865 .iter()
866 .any(|d| d.imported_path.contains("FEATURE_HEADER"))
867 );
868 }
869
870 #[test]
871 fn test_include_classification() {
872 let source = r#"
873 #include <stdio.h>
874 #include "utils.h"
875 #include <mylib/api.h>
876 "#;
877
878 let deps = CDependencyExtractor::extract_dependencies(source).unwrap();
879
880 let stdio_dep = deps.iter().find(|d| d.imported_path == "stdio.h").unwrap();
882 assert!(
883 matches!(stdio_dep.import_type, ImportType::Stdlib),
884 "stdio.h should be classified as Stdlib"
885 );
886
887 let utils_dep = deps.iter().find(|d| d.imported_path == "utils.h").unwrap();
889 assert!(
890 matches!(utils_dep.import_type, ImportType::Internal),
891 "quoted include should be classified as Internal"
892 );
893
894 let mylib_dep = deps
896 .iter()
897 .find(|d| d.imported_path == "mylib/api.h")
898 .unwrap();
899 assert!(
900 matches!(mylib_dep.import_type, ImportType::External),
901 "non-stdlib angle bracket include should be classified as External"
902 );
903 }
904}