use anyhow::{Context, Result};
use streaming_iterator::StreamingIterator;
use tree_sitter::{Parser, Query, QueryCursor};
use crate::models::{Language, SearchResult, Span, SymbolKind};
pub fn parse(path: &str, source: &str) -> Result<Vec<SearchResult>> {
let mut parser = Parser::new();
let language = tree_sitter_cpp::LANGUAGE;
parser
.set_language(&language.into())
.context("Failed to set C++ language")?;
let tree = parser
.parse(source, None)
.context("Failed to parse C++ source")?;
let root_node = tree.root_node();
let mut symbols = Vec::new();
symbols.extend(extract_functions(source, &root_node, &language.into())?);
symbols.extend(extract_classes(source, &root_node, &language.into())?);
symbols.extend(extract_structs(source, &root_node, &language.into())?);
symbols.extend(extract_namespaces(source, &root_node, &language.into())?);
symbols.extend(extract_enums(source, &root_node, &language.into())?);
symbols.extend(extract_methods(source, &root_node, &language.into())?);
symbols.extend(extract_local_variables(source, &root_node, &language.into())?);
symbols.extend(extract_type_aliases(source, &root_node, &language.into())?);
for symbol in &mut symbols {
symbol.path = path.to_string();
symbol.lang = Language::Cpp;
}
Ok(symbols)
}
fn extract_functions(
source: &str,
root: &tree_sitter::Node,
language: &tree_sitter::Language,
) -> Result<Vec<SearchResult>> {
let query_str = r#"
(function_definition
declarator: (function_declarator
declarator: (identifier) @name)) @function
(function_definition
declarator: (function_declarator
declarator: (qualified_identifier
name: (identifier) @name))) @function
(template_declaration
(function_definition
declarator: (function_declarator
declarator: (identifier) @name))) @function
"#;
let query = Query::new(language, query_str)
.context("Failed to create function query")?;
extract_symbols(source, root, &query, SymbolKind::Function, None)
}
fn extract_classes(
source: &str,
root: &tree_sitter::Node,
language: &tree_sitter::Language,
) -> Result<Vec<SearchResult>> {
let query_str = r#"
(class_specifier
name: (type_identifier) @name) @class
(template_declaration
(class_specifier
name: (type_identifier) @name)) @class
"#;
let query = Query::new(language, query_str)
.context("Failed to create class query")?;
extract_symbols(source, root, &query, SymbolKind::Class, None)
}
fn extract_structs(
source: &str,
root: &tree_sitter::Node,
language: &tree_sitter::Language,
) -> Result<Vec<SearchResult>> {
let query_str = r#"
(struct_specifier
name: (type_identifier) @name) @struct
(template_declaration
(struct_specifier
name: (type_identifier) @name)) @struct
"#;
let query = Query::new(language, query_str)
.context("Failed to create struct query")?;
extract_symbols(source, root, &query, SymbolKind::Struct, None)
}
fn extract_namespaces(
source: &str,
root: &tree_sitter::Node,
language: &tree_sitter::Language,
) -> Result<Vec<SearchResult>> {
let query_str = r#"
(namespace_definition
name: (_) @name) @namespace
"#;
let query = Query::new(language, query_str)
.context("Failed to create namespace query")?;
extract_symbols(source, root, &query, SymbolKind::Namespace, None)
}
fn extract_enums(
source: &str,
root: &tree_sitter::Node,
language: &tree_sitter::Language,
) -> Result<Vec<SearchResult>> {
let query_str = r#"
(enum_specifier
name: (type_identifier) @name) @enum
"#;
let query = Query::new(language, query_str)
.context("Failed to create enum query")?;
extract_symbols(source, root, &query, SymbolKind::Enum, None)
}
fn extract_methods(
source: &str,
root: &tree_sitter::Node,
language: &tree_sitter::Language,
) -> Result<Vec<SearchResult>> {
let query_str = r#"
(class_specifier
name: (type_identifier) @class_name
body: (field_declaration_list
(function_definition
declarator: (function_declarator
declarator: (field_identifier) @method_name)))) @class
(class_specifier
name: (type_identifier) @class_name
body: (field_declaration_list
(function_definition
declarator: (function_declarator
declarator: (destructor_name) @method_name)))) @class
(struct_specifier
name: (type_identifier) @struct_name
body: (field_declaration_list
(function_definition
declarator: (function_declarator
declarator: (field_identifier) @method_name)))) @struct
(struct_specifier
name: (type_identifier) @struct_name
body: (field_declaration_list
(function_definition
declarator: (function_declarator
declarator: (destructor_name) @method_name)))) @struct
"#;
let query = Query::new(language, query_str)
.context("Failed to create method query")?;
let mut cursor = QueryCursor::new();
let mut matches = cursor.matches(&query, *root, source.as_bytes());
let mut symbols = Vec::new();
while let Some(match_) = matches.next() {
let mut scope_name = None;
let mut scope_type = None;
let mut method_name = None;
let mut method_node = None;
for capture in match_.captures {
let capture_name: &str = &query.capture_names()[capture.index as usize];
match capture_name {
"class_name" => {
scope_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
scope_type = Some("class");
}
"struct_name" => {
scope_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
scope_type = Some("struct");
}
"method_name" => {
method_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
let mut current = capture.node;
while let Some(parent) = current.parent() {
if parent.kind() == "function_definition" {
method_node = Some(parent);
break;
}
current = parent;
}
}
_ => {}
}
}
if let (Some(scope_name), Some(scope_type), Some(method_name), Some(node)) =
(scope_name, scope_type, method_name, method_node) {
let scope = format!("{} {}", scope_type, scope_name);
let span = node_to_span(&node);
let preview = extract_preview(source, &span);
symbols.push(SearchResult::new(
String::new(),
Language::Cpp,
SymbolKind::Method,
Some(method_name),
span,
Some(scope),
preview,
));
}
}
Ok(symbols)
}
fn extract_local_variables(
source: &str,
root: &tree_sitter::Node,
language: &tree_sitter::Language,
) -> Result<Vec<SearchResult>> {
let query_str = r#"
(declaration
declarator: (init_declarator
declarator: (identifier) @name)) @var
"#;
let query = Query::new(language, query_str)
.context("Failed to create local variable query")?;
let mut cursor = QueryCursor::new();
let mut matches = cursor.matches(&query, *root, source.as_bytes());
let mut symbols = Vec::new();
while let Some(match_) = matches.next() {
let mut name = None;
let mut var_node = None;
for capture in match_.captures {
let capture_name: &str = &query.capture_names()[capture.index as usize];
match capture_name {
"name" => {
name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
}
"var" => {
var_node = Some(capture.node);
}
_ => {}
}
}
if let (Some(name), Some(node)) = (name, var_node) {
let mut is_local_var = false;
let mut current = node;
while let Some(parent) = current.parent() {
if parent.kind() == "function_definition" {
is_local_var = true;
break;
}
current = parent;
}
if is_local_var {
let span = node_to_span(&node);
let preview = extract_preview(source, &span);
symbols.push(SearchResult::new(
String::new(),
Language::Cpp,
SymbolKind::Variable,
Some(name),
span,
None, preview,
));
}
}
}
Ok(symbols)
}
fn extract_type_aliases(
source: &str,
root: &tree_sitter::Node,
language: &tree_sitter::Language,
) -> Result<Vec<SearchResult>> {
let query_str = r#"
(type_definition
declarator: (type_identifier) @name) @typedef
(alias_declaration
name: (type_identifier) @name) @using
"#;
let query = Query::new(language, query_str)
.context("Failed to create type alias query")?;
extract_symbols(source, root, &query, SymbolKind::Type, None)
}
fn extract_symbols(
source: &str,
root: &tree_sitter::Node,
query: &Query,
kind: SymbolKind,
scope: Option<String>,
) -> Result<Vec<SearchResult>> {
let mut cursor = QueryCursor::new();
let mut matches = cursor.matches(query, *root, source.as_bytes());
let mut symbols = Vec::new();
let mut seen_names = std::collections::HashSet::new();
while let Some(match_) = matches.next() {
let mut name = None;
let mut name_node = None;
let mut full_node = None;
for capture in match_.captures {
let capture_name: &str = &query.capture_names()[capture.index as usize];
if capture_name == "name" {
name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
name_node = Some(capture.node);
} else {
full_node = Some(capture.node);
}
}
if let (Some(name), Some(name_node), Some(node)) = (name, name_node, full_node) {
let name_key = (name_node.start_byte(), name_node.end_byte(), name.clone());
if seen_names.contains(&name_key) {
continue; }
seen_names.insert(name_key);
let span = node_to_span(&node);
let preview = extract_preview(source, &span);
symbols.push(SearchResult::new(
String::new(),
Language::Cpp,
kind.clone(),
Some(name),
span,
scope.clone(),
preview,
));
}
}
Ok(symbols)
}
fn node_to_span(node: &tree_sitter::Node) -> Span {
let start = node.start_position();
let end = node.end_position();
Span::new(
start.row + 1, start.column,
end.row + 1,
end.column,
)
}
fn extract_preview(source: &str, span: &Span) -> String {
let lines: Vec<&str> = source.lines().collect();
let start_idx = (span.start_line - 1) as usize; let end_idx = (start_idx + 7).min(lines.len());
lines[start_idx..end_idx].join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_function() {
let source = r#"
int add(int a, int b) {
return a + b;
}
"#;
let symbols = parse("test.cpp", source).unwrap();
assert_eq!(symbols.len(), 1);
assert_eq!(symbols[0].symbol.as_deref(), Some("add"));
assert!(matches!(symbols[0].kind, SymbolKind::Function));
}
#[test]
fn test_parse_class() {
let source = r#"
class User {
private:
std::string name;
int age;
public:
User(std::string n, int a) : name(n), age(a) {}
};
"#;
let symbols = parse("test.cpp", source).unwrap();
let class_symbols: Vec<_> = symbols.iter()
.filter(|s| matches!(s.kind, SymbolKind::Class))
.collect();
assert_eq!(class_symbols.len(), 1);
assert_eq!(class_symbols[0].symbol.as_deref(), Some("User"));
}
#[test]
fn test_parse_namespace() {
let source = r#"
namespace MyNamespace {
int value = 42;
}
namespace Nested::Namespace {
void function() {}
}
"#;
let symbols = parse("test.cpp", source).unwrap();
let namespace_symbols: Vec<_> = symbols.iter()
.filter(|s| matches!(s.kind, SymbolKind::Namespace))
.collect();
assert!(namespace_symbols.len() >= 1);
assert!(namespace_symbols.iter().any(|s| s.symbol.as_deref() == Some("MyNamespace")));
}
#[test]
fn test_parse_struct() {
let source = r#"
struct Point {
int x;
int y;
};
"#;
let symbols = parse("test.cpp", source).unwrap();
assert_eq!(symbols.len(), 1);
assert_eq!(symbols[0].symbol.as_deref(), Some("Point"));
assert!(matches!(symbols[0].kind, SymbolKind::Struct));
}
#[test]
fn test_parse_enum() {
let source = r#"
enum Color {
RED,
GREEN,
BLUE
};
enum class Status {
Active,
Inactive
};
"#;
let symbols = parse("test.cpp", source).unwrap();
let enum_symbols: Vec<_> = symbols.iter()
.filter(|s| matches!(s.kind, SymbolKind::Enum))
.collect();
assert_eq!(enum_symbols.len(), 2);
assert!(enum_symbols.iter().any(|s| s.symbol.as_deref() == Some("Color")));
assert!(enum_symbols.iter().any(|s| s.symbol.as_deref() == Some("Status")));
}
#[test]
fn test_parse_template_class() {
let source = r#"
template <typename T>
class Container {
private:
T value;
public:
Container(T v) : value(v) {}
T getValue() { return value; }
};
"#;
let symbols = parse("test.cpp", source).unwrap();
let class_symbols: Vec<_> = symbols.iter()
.filter(|s| matches!(s.kind, SymbolKind::Class))
.collect();
assert_eq!(class_symbols.len(), 1);
assert_eq!(class_symbols[0].symbol.as_deref(), Some("Container"));
}
#[test]
fn test_parse_template_function() {
let source = r#"
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
"#;
let symbols = parse("test.cpp", source).unwrap();
assert_eq!(symbols.len(), 1);
assert_eq!(symbols[0].symbol.as_deref(), Some("max"));
assert!(matches!(symbols[0].kind, SymbolKind::Function));
}
#[test]
fn test_parse_class_with_methods() {
let source = r#"
class Calculator {
public:
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
};
"#;
let symbols = parse("test.cpp", source).unwrap();
let method_symbols: Vec<_> = symbols.iter()
.filter(|s| matches!(s.kind, SymbolKind::Method))
.collect();
assert_eq!(method_symbols.len(), 2);
assert!(method_symbols.iter().any(|s| s.symbol.as_deref() == Some("add")));
assert!(method_symbols.iter().any(|s| s.symbol.as_deref() == Some("subtract")));
for method in method_symbols {
}
}
#[test]
fn test_parse_using_declaration() {
let source = r#"
using StringVector = std::vector<std::string>;
using IntPtr = int*;
"#;
let symbols = parse("test.cpp", source).unwrap();
let type_symbols: Vec<_> = symbols.iter()
.filter(|s| matches!(s.kind, SymbolKind::Type))
.collect();
assert!(type_symbols.len() >= 1);
assert!(type_symbols.iter().any(|s| s.symbol.as_deref() == Some("StringVector")));
}
#[test]
fn test_parse_typedef() {
let source = r#"
typedef unsigned int uint;
typedef struct {
int x, y;
} Point;
"#;
let symbols = parse("test.cpp", source).unwrap();
let type_symbols: Vec<_> = symbols.iter()
.filter(|s| matches!(s.kind, SymbolKind::Type))
.collect();
assert!(type_symbols.len() >= 1);
}
#[test]
fn test_parse_mixed_symbols() {
let source = r#"
namespace Math {
class Vector {
private:
double x, y;
public:
Vector(double x, double y) : x(x), y(y) {}
double magnitude() {
return sqrt(x*x + y*y);
}
};
enum Operation {
ADD,
SUBTRACT
};
template <typename T>
T multiply(T a, T b) {
return a * b;
}
}
"#;
let symbols = parse("test.cpp", source).unwrap();
assert!(symbols.len() >= 5);
let kinds: Vec<&SymbolKind> = symbols.iter().map(|s| &s.kind).collect();
assert!(kinds.contains(&&SymbolKind::Namespace));
assert!(kinds.contains(&&SymbolKind::Class));
assert!(kinds.contains(&&SymbolKind::Enum));
assert!(kinds.contains(&&SymbolKind::Method));
assert!(kinds.contains(&&SymbolKind::Function));
}
#[test]
fn test_parse_nested_namespace() {
let source = r#"
namespace Outer {
namespace Inner {
void function() {}
}
}
"#;
let symbols = parse("test.cpp", source).unwrap();
let namespace_symbols: Vec<_> = symbols.iter()
.filter(|s| matches!(s.kind, SymbolKind::Namespace))
.collect();
assert_eq!(namespace_symbols.len(), 2);
assert!(namespace_symbols.iter().any(|s| s.symbol.as_deref() == Some("Outer")));
assert!(namespace_symbols.iter().any(|s| s.symbol.as_deref() == Some("Inner")));
}
#[test]
fn test_parse_virtual_methods() {
let source = r#"
class Base {
public:
virtual void draw() = 0;
virtual void update() {}
};
class Derived : public Base {
public:
void draw() override {
// Implementation
}
};
"#;
let symbols = parse("test.cpp", source).unwrap();
let class_symbols: Vec<_> = symbols.iter()
.filter(|s| matches!(s.kind, SymbolKind::Class))
.collect();
assert_eq!(class_symbols.len(), 2);
assert!(class_symbols.iter().any(|s| s.symbol.as_deref() == Some("Base")));
assert!(class_symbols.iter().any(|s| s.symbol.as_deref() == Some("Derived")));
let method_symbols: Vec<_> = symbols.iter()
.filter(|s| matches!(s.kind, SymbolKind::Method))
.collect();
assert!(method_symbols.len() >= 2);
}
#[test]
fn test_parse_operator_overload() {
let source = r#"
class Complex {
private:
double real, imag;
public:
Complex operator+(const Complex& other) {
return Complex(real + other.real, imag + other.imag);
}
};
"#;
let symbols = parse("test.cpp", source).unwrap();
let class_symbols: Vec<_> = symbols.iter()
.filter(|s| matches!(s.kind, SymbolKind::Class))
.collect();
assert_eq!(class_symbols.len(), 1);
assert_eq!(class_symbols[0].symbol.as_deref(), Some("Complex"));
}
#[test]
fn test_local_variables_included() {
let source = r#"
int calculate(int input) {
int localVar = input * 2;
auto result = localVar + 10;
return result;
}
class Calculator {
public:
int compute(int value) {
int temp = value * 3;
auto final = temp + 5;
return final;
}
};
"#;
let symbols = parse("test.cpp", source).unwrap();
let variables: Vec<_> = symbols.iter()
.filter(|s| matches!(s.kind, SymbolKind::Variable))
.collect();
assert!(variables.iter().any(|v| v.symbol.as_deref() == Some("localVar")));
assert!(variables.iter().any(|v| v.symbol.as_deref() == Some("result")));
assert!(variables.iter().any(|v| v.symbol.as_deref() == Some("temp")));
assert!(variables.iter().any(|v| v.symbol.as_deref() == Some("final")));
for var in variables {
}
}
#[test]
fn test_parse_destructor() {
let source = r#"
class Resource {
private:
int* data;
public:
Resource() {
data = new int[100];
}
~Resource() {
delete[] data;
}
};
"#;
let symbols = parse("test.cpp", source).unwrap();
let class_symbols: Vec<_> = symbols.iter()
.filter(|s| matches!(s.kind, SymbolKind::Class))
.collect();
assert_eq!(class_symbols.len(), 1);
assert_eq!(class_symbols[0].symbol.as_deref(), Some("Resource"));
let method_symbols: Vec<_> = symbols.iter()
.filter(|s| matches!(s.kind, SymbolKind::Method))
.collect();
assert!(method_symbols.len() >= 1, "Expected at least constructor or destructor to be extracted");
for method in &method_symbols {
println!("Found method: {:?}", method.symbol);
}
let has_destructor = method_symbols.iter().any(|s| {
s.symbol.as_deref()
.map(|name| name.contains("~") || name == "Resource")
.unwrap_or(false)
});
if !has_destructor {
println!("WARNING: Destructor extraction may not be working");
}
}
}
use crate::models::ImportType;
use crate::parsers::{DependencyExtractor, ImportInfo};
pub struct CppDependencyExtractor;
impl DependencyExtractor for CppDependencyExtractor {
fn extract_dependencies(source: &str) -> Result<Vec<ImportInfo>> {
let mut parser = Parser::new();
let language = tree_sitter_cpp::LANGUAGE;
parser
.set_language(&language.into())
.context("Failed to set C++ language")?;
let tree = parser
.parse(source, None)
.context("Failed to parse C++ source")?;
let root_node = tree.root_node();
let mut imports = Vec::new();
imports.extend(extract_cpp_includes(source, &root_node)?);
Ok(imports)
}
}
fn extract_cpp_includes(
source: &str,
root: &tree_sitter::Node,
) -> Result<Vec<ImportInfo>> {
let language = tree_sitter_cpp::LANGUAGE;
let query_str = r#"
(preproc_include
path: (string_literal) @include_path) @include
(preproc_include
path: (system_lib_string) @include_path) @include
"#;
let query = Query::new(&language.into(), query_str)
.context("Failed to create C++ include query")?;
let mut cursor = QueryCursor::new();
let mut matches = cursor.matches(&query, *root, source.as_bytes());
let mut imports = Vec::new();
while let Some(match_) = matches.next() {
let mut include_path = None;
let mut include_node = None;
for capture in match_.captures {
let capture_name: &str = &query.capture_names()[capture.index as usize];
match capture_name {
"include_path" => {
let raw_path = capture.node.utf8_text(source.as_bytes()).unwrap_or("");
include_path = Some(raw_path.trim_matches(|c| c == '"' || c == '<' || c == '>').to_string());
}
"include" => {
include_node = Some(capture.node);
}
_ => {}
}
}
if let (Some(path), Some(node)) = (include_path, include_node) {
let import_type = classify_cpp_include(&path, source, &node);
let line_number = node.start_position().row + 1;
imports.push(ImportInfo {
imported_path: path,
import_type,
line_number,
imported_symbols: None, });
}
}
Ok(imports)
}
fn classify_cpp_include(include_path: &str, source: &str, node: &tree_sitter::Node) -> ImportType {
let line_start = node.start_position();
let lines: Vec<&str> = source.lines().collect();
if line_start.row < lines.len() {
let line = lines[line_start.row];
if line.contains(&format!("\"{}\"", include_path)) {
return ImportType::Internal;
}
}
const CPP_STDLIB_HEADERS: &[&str] = &[
"stdio.h", "stdlib.h", "string.h", "math.h", "time.h",
"ctype.h", "assert.h", "errno.h", "limits.h", "float.h",
"stddef.h", "stdint.h", "stdbool.h", "stdarg.h", "setjmp.h",
"signal.h", "locale.h", "wchar.h", "wctype.h", "complex.h",
"fenv.h", "inttypes.h", "iso646.h", "tgmath.h", "threads.h",
"algorithm", "any", "array", "atomic", "barrier", "bit",
"bitset", "charconv", "chrono", "codecvt", "compare", "complex",
"concepts", "condition_variable", "coroutine", "deque", "exception",
"execution", "expected", "filesystem", "format", "forward_list",
"fstream", "functional", "future", "initializer_list", "iomanip",
"ios", "iosfwd", "iostream", "istream", "iterator", "latch",
"limits", "list", "locale", "map", "mdspan", "memory",
"memory_resource", "mutex", "new", "numbers", "numeric", "optional",
"ostream", "queue", "random", "ranges", "ratio", "regex",
"scoped_allocator", "semaphore", "set", "shared_mutex", "source_location",
"span", "sstream", "stack", "stacktrace", "stdexcept", "stop_token",
"streambuf", "string", "string_view", "strstream", "syncstream",
"system_error", "thread", "tuple", "type_traits", "typeindex",
"typeinfo", "unordered_map", "unordered_set", "utility", "valarray",
"variant", "vector", "version",
"cassert", "cctype", "cerrno", "cfenv", "cfloat", "cinttypes",
"climits", "clocale", "cmath", "csetjmp", "csignal", "cstdarg",
"cstddef", "cstdint", "cstdio", "cstdlib", "cstring", "ctime",
"cuchar", "cwchar", "cwctype",
];
if CPP_STDLIB_HEADERS.contains(&include_path) {
return ImportType::Stdlib;
}
ImportType::External
}
pub fn resolve_cpp_include_to_path(
include_path: &str,
current_file_path: Option<&str>,
) -> Option<String> {
let current_file = current_file_path?;
let current_dir = std::path::Path::new(current_file).parent()?;
let resolved = current_dir.join(include_path);
match resolved.canonicalize() {
Ok(normalized) => Some(normalized.display().to_string()),
Err(_) => {
Some(resolved.display().to_string())
}
}
}
#[cfg(test)]
mod resolution_tests {
use super::*;
#[test]
fn test_resolve_cpp_include_same_directory() {
let result = resolve_cpp_include_to_path(
"helper.hpp",
Some("/project/src/main.cpp"),
);
assert!(result.is_some());
let path = result.unwrap();
assert!(path.ends_with("src/helper.hpp") || path.ends_with("src\\helper.hpp"));
}
#[test]
fn test_resolve_cpp_include_subdirectory() {
let result = resolve_cpp_include_to_path(
"utils/helper.hpp",
Some("/project/src/main.cpp"),
);
assert!(result.is_some());
let path = result.unwrap();
assert!(path.ends_with("src/utils/helper.hpp") || path.ends_with("src\\utils\\helper.hpp"));
}
#[test]
fn test_resolve_cpp_include_parent_directory() {
let result = resolve_cpp_include_to_path(
"../include/common.hpp",
Some("/project/src/main.cpp"),
);
assert!(result.is_some());
let path = result.unwrap();
assert!(path.contains("include") && path.contains("common.hpp"));
}
#[test]
fn test_resolve_cpp_include_h_extension() {
let result = resolve_cpp_include_to_path(
"legacy.h",
Some("/project/src/main.cpp"),
);
assert!(result.is_some());
let path = result.unwrap();
assert!(path.ends_with("src/legacy.h") || path.ends_with("src\\legacy.h"));
}
#[test]
fn test_resolve_cpp_include_no_current_file() {
let result = resolve_cpp_include_to_path(
"helper.hpp",
None,
);
assert!(result.is_none());
}
}