mati_core/analysis/resolvers/
c.rs1use std::path::Path;
26
27use super::{FileIndex, LanguageResolver};
28use crate::analysis::parser::ImportStatement;
29use crate::analysis::walker::Language;
30
31pub struct CResolver;
32
33impl LanguageResolver for CResolver {
34 fn resolve(
35 &self,
36 import: &ImportStatement,
37 importing_file: &str,
38 file_index: &FileIndex,
39 ) -> Option<String> {
40 resolve_c_include(&import.path, importing_file, file_index)
41 }
42
43 fn language(&self) -> Language {
44 Language::C
45 }
46
47 fn name(&self) -> &'static str {
48 "c"
49 }
50}
51
52pub fn resolve_angle_bracket(
58 include_path: &str,
59 importing_file: &str,
60 file_index: &FileIndex,
61) -> Option<String> {
62 resolve_c_include(include_path, importing_file, file_index)
63}
64
65fn resolve_c_include(
66 include_path: &str,
67 importing_file: &str,
68 file_index: &FileIndex,
69) -> Option<String> {
70 let parent = Path::new(importing_file)
71 .parent()
72 .map(|p| p.to_string_lossy().into_owned())
73 .unwrap_or_default();
74
75 let relative = if parent.is_empty() {
77 include_path.to_string()
78 } else {
79 format!("{parent}/{include_path}")
80 };
81 if file_index.contains(&relative) {
82 return Some(relative);
83 }
84
85 if file_index.contains(include_path) {
87 return Some(include_path.to_string());
88 }
89
90 for prefix in &["include", "src"] {
92 let candidate = format!("{prefix}/{include_path}");
93 if file_index.contains(&candidate) {
94 return Some(candidate);
95 }
96 }
97
98 None
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use crate::analysis::parser::import::ImportKind;
105
106 fn idx(paths: &[&str]) -> FileIndex {
107 FileIndex::new(paths.iter().map(|s| s.to_string()))
108 }
109
110 fn import(path: &str) -> ImportStatement {
111 ImportStatement::new(path, ImportKind::Relative, 1)
112 }
113
114 #[test]
115 fn relative_include_resolves() {
116 let file_index = idx(&["src/main.c", "src/utils.h"]);
117 let result = CResolver.resolve(&import("utils.h"), "src/main.c", &file_index);
118 assert_eq!(result, Some("src/utils.h".into()));
119 }
120
121 #[test]
122 fn root_include_resolves() {
123 let file_index = idx(&["src/main.c", "config.h"]);
124 let result = CResolver.resolve(&import("config.h"), "src/main.c", &file_index);
125 assert_eq!(result, Some("config.h".into()));
126 }
127
128 #[test]
129 fn include_dir_resolves() {
130 let file_index = idx(&["src/main.c", "include/types.h"]);
131 let result = CResolver.resolve(&import("types.h"), "src/main.c", &file_index);
132 assert_eq!(result, Some("include/types.h".into()));
133 }
134
135 #[test]
136 fn nonexistent_returns_none() {
137 let file_index = idx(&["src/main.c"]);
138 assert_eq!(
139 CResolver.resolve(&import("missing.h"), "src/main.c", &file_index),
140 None
141 );
142 }
143}