#[cfg(feature = "c-ast")]
pub struct CppAstStrategy;
#[cfg(feature = "c-ast")]
fn cpp_node_to_ast_item(
node: &crate::models::unified_ast::UnifiedAstNode,
content: &str,
content_lines: &[&str],
) -> Option<crate::services::context::AstItem> {
let name = CppAstStrategy::extract_name_from_node(node, content);
let line_number = CppAstStrategy::byte_pos_to_line(node.source_range.start as usize, content_lines);
match &node.kind {
crate::models::unified_ast::AstKind::Function(_) => {
Some(crate::services::context::AstItem::Function {
name: name.unwrap_or_else(|| "anonymous_function".to_string()),
visibility: "public".to_string(),
is_async: false,
line: line_number,
})
}
crate::models::unified_ast::AstKind::Type(type_kind) => {
cpp_type_to_ast_item(type_kind, name, line_number)
}
_ => None,
}
}
#[cfg(feature = "c-ast")]
fn cpp_type_to_ast_item(
type_kind: &crate::models::unified_ast::TypeKind,
name: Option<String>,
line: usize,
) -> Option<crate::services::context::AstItem> {
match type_kind {
crate::models::unified_ast::TypeKind::Struct | crate::models::unified_ast::TypeKind::Class => {
let default = if matches!(type_kind, crate::models::unified_ast::TypeKind::Class) {
"anonymous_class"
} else {
"anonymous_struct"
};
Some(crate::services::context::AstItem::Struct {
name: name.unwrap_or_else(|| default.to_string()),
visibility: "public".to_string(),
fields_count: 0,
derives: vec![],
line,
})
}
crate::models::unified_ast::TypeKind::Enum => {
Some(crate::services::context::AstItem::Enum {
name: name.unwrap_or_else(|| "anonymous_enum".to_string()),
visibility: "public".to_string(),
variants_count: 0,
line,
})
}
_ => None,
}
}
#[cfg(feature = "c-ast")]
#[async_trait]
impl AstStrategy for CppAstStrategy {
async fn analyze(&self, path: &Path, _classifier: &FileClassifier) -> Result<FileContext> {
use crate::services::ast_cpp::CppAstParser;
use tokio::fs;
let content = fs::read_to_string(path).await?;
let mut parser = CppAstParser::new();
match parser.parse_file(path, &content) {
Ok(ast_dag) => {
let content_lines: Vec<&str> = content.lines().collect();
let items = ast_dag.nodes.iter()
.filter_map(|node| cpp_node_to_ast_item(node, &content, &content_lines))
.collect();
Ok(FileContext {
path: path.to_string_lossy().to_string(),
language: "cpp".to_string(),
items,
complexity_metrics: None,
})
}
Err(e) => {
tracing::warn!("Failed to parse C++ file {}: {}", path.display(), e);
Ok(FileContext {
path: path.to_string_lossy().to_string(),
language: "cpp".to_string(),
items: vec![],
complexity_metrics: None,
})
}
}
}
fn supports_extension(&self, ext: &str) -> bool {
matches!(ext, "cpp" | "cc" | "cxx" | "hpp" | "hxx" | "cu" | "cuh")
}
}
#[cfg(feature = "c-ast")]
impl CppAstStrategy {
fn extract_name_from_node(
node: &crate::models::unified_ast::UnifiedAstNode,
content: &str,
) -> Option<String> {
let start = node.source_range.start as usize;
let end = node.source_range.end as usize;
if start >= content.len() || end > content.len() || start >= end {
return None;
}
let source_text = &content[start..end];
match &node.kind {
crate::models::unified_ast::AstKind::Function(_) => {
Self::extract_function_name(source_text)
}
crate::models::unified_ast::AstKind::Type(_) => Self::extract_type_name(source_text),
_ => None,
}
}
pub(crate) fn extract_function_name(source_text: &str) -> Option<String> {
if let Some(paren_pos) = source_text.find('(') {
let before_paren = &source_text[..paren_pos];
let name = before_paren.split_whitespace().last().map(|s| {
s.trim_start_matches('*')
.trim_start_matches('~')
.to_string()
})?;
if before_paren.contains("operator") {
return Some("operator_overload".to_string());
}
Some(name)
} else {
None
}
}
pub(crate) fn extract_type_name(source_text: &str) -> Option<String> {
let words: Vec<&str> = source_text.split_whitespace().collect();
if words.len() >= 2 {
if words.len() >= 3 && words[0] == "enum" && words[1] == "class" {
let raw_name = words[2].trim_end_matches('{');
let name = raw_name.split('<').next().unwrap_or(raw_name);
return Some(name.to_string());
}
for i in 0..words.len() - 1 {
if matches!(words[i], "class" | "struct" | "enum" | "union" | "typedef") {
let raw_name = words[i + 1].trim_end_matches('{');
let name = raw_name.split('<').next().unwrap_or(raw_name);
return Some(name.to_string());
}
}
}
None
}
pub(crate) fn byte_pos_to_line(byte_pos: usize, content_lines: &[&str]) -> usize {
let mut current_pos = 0;
for (line_idx, line) in content_lines.iter().enumerate() {
if current_pos + line.len() >= byte_pos {
return line_idx + 1; }
current_pos += line.len() + 1; }
content_lines.len() }
}