use std::path::Path;
use super::{FileIndex, LanguageResolver};
use crate::analysis::parser::ImportStatement;
use crate::analysis::walker::Language;
pub struct CppResolver;
impl LanguageResolver for CppResolver {
fn resolve(
&self,
import: &ImportStatement,
importing_file: &str,
file_index: &FileIndex,
) -> Option<String> {
resolve_cpp_include(&import.path, importing_file, file_index)
}
fn language(&self) -> Language {
Language::Cpp
}
fn name(&self) -> &'static str {
"cpp"
}
}
pub fn resolve_angle_bracket(
include_path: &str,
importing_file: &str,
file_index: &FileIndex,
) -> Option<String> {
resolve_cpp_include(include_path, importing_file, file_index)
}
fn resolve_cpp_include(
include_path: &str,
importing_file: &str,
file_index: &FileIndex,
) -> Option<String> {
let parent = Path::new(importing_file)
.parent()
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_default();
let relative = if parent.is_empty() {
include_path.to_string()
} else {
format!("{parent}/{include_path}")
};
if file_index.contains(&relative) {
return Some(relative);
}
if file_index.contains(include_path) {
return Some(include_path.to_string());
}
for prefix in &["include", "src"] {
let candidate = format!("{prefix}/{include_path}");
if file_index.contains(&candidate) {
return Some(candidate);
}
}
if Path::new(include_path).extension().is_none() {
for ext in &[".hpp", ".hxx", ".hh", ".h"] {
let with_ext = format!("{include_path}{ext}");
let rel = if parent.is_empty() {
with_ext.clone()
} else {
format!("{parent}/{with_ext}")
};
if file_index.contains(&rel) {
return Some(rel);
}
if file_index.contains(&with_ext) {
return Some(with_ext);
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::analysis::parser::import::ImportKind;
fn idx(paths: &[&str]) -> FileIndex {
FileIndex::new(paths.iter().map(|s| s.to_string()))
}
fn import(path: &str) -> ImportStatement {
ImportStatement::new(path, ImportKind::Relative, 1)
}
#[test]
fn relative_include_resolves() {
let file_index = idx(&["src/main.cpp", "src/utils.hpp"]);
let result = CppResolver.resolve(&import("utils.hpp"), "src/main.cpp", &file_index);
assert_eq!(result, Some("src/utils.hpp".into()));
}
#[test]
fn extensionless_include_tries_hpp() {
let file_index = idx(&["src/main.cpp", "src/utils.hpp"]);
let result = CppResolver.resolve(&import("utils"), "src/main.cpp", &file_index);
assert_eq!(result, Some("src/utils.hpp".into()));
}
#[test]
fn include_dir_resolves() {
let file_index = idx(&["src/main.cpp", "include/types.h"]);
let result = CppResolver.resolve(&import("types.h"), "src/main.cpp", &file_index);
assert_eq!(result, Some("include/types.h".into()));
}
#[test]
fn nonexistent_returns_none() {
let file_index = idx(&["src/main.cpp"]);
assert_eq!(
CppResolver.resolve(&import("missing.h"), "src/main.cpp", &file_index),
None
);
}
}