use cha_core::ClassInfo;
use tree_sitter::Node;
fn text<'a>(n: Node, src: &'a [u8]) -> &'a str {
n.utf8_text(src).unwrap_or("")
}
pub(crate) fn c_param_name(decl: Node, src: &[u8]) -> String {
let mut cur = decl;
loop {
match cur.kind() {
"identifier" => return text(cur, src).to_string(),
"pointer_declarator"
| "array_declarator"
| "function_declarator"
| "reference_declarator" => match cur.child_by_field_name("declarator") {
Some(n) => cur = n,
None => {
let mut c = cur.walk();
let Some(next) = cur.children(&mut c).find(|n| n.is_named()) else {
return String::new();
};
cur = next;
}
},
_ => return String::new(),
}
}
}
pub(crate) fn qualified_identifier_leaf(node: Node) -> Option<Node> {
let mut cur = node;
while cur.kind() == "qualified_identifier" {
let next = cur.child_by_field_name("name")?;
cur = next;
}
matches!(
cur.kind(),
"identifier" | "field_identifier" | "destructor_name" | "operator_name"
)
.then_some(cur)
}
pub(crate) fn attach_to_class(qualifier: &str, classes: &mut [ClassInfo]) {
let last = qualifier.rsplit("::").next().unwrap_or(qualifier);
let bare = strip_template_args(last);
if let Some(c) = classes.iter_mut().find(|c| c.name == bare) {
c.method_count += 1;
c.has_behavior = true;
}
}
fn strip_template_args(s: &str) -> &str {
let Some(lt) = s.find('<') else {
return s;
};
&s[..lt]
}
pub(crate) fn class_name_triple(name_node: Option<Node>, src: &[u8]) -> (String, usize, usize) {
let Some(n) = name_node else {
return (String::new(), 0, 0);
};
let name_n = if n.kind() == "template_type" {
n.child_by_field_name("name").unwrap_or(n)
} else {
n
};
let name = text(name_n, src).to_string();
(
name,
name_n.start_position().column,
name_n.end_position().column,
)
}
pub(crate) fn extract_cpp_base(class_node: Node, src: &[u8]) -> Option<String> {
let clause = find_child(class_node, "base_class_clause")?;
let base = find_first_type_in_clause(clause)?;
let raw = text(base, src);
Some(strip_template_args(raw).to_string())
}
fn find_child<'a>(node: Node<'a>, kind: &str) -> Option<Node<'a>> {
let mut c = node.walk();
node.children(&mut c).find(|n| n.kind() == kind)
}
fn find_first_type_in_clause(clause: Node) -> Option<Node> {
let mut cursor = clause.walk();
for ch in clause.children(&mut cursor) {
match ch.kind() {
"type_identifier" => return Some(ch),
"template_type" => return ch.child_by_field_name("name").or(Some(ch)),
_ => {}
}
}
None
}
pub(crate) fn extract_class_qualifier(node: Node, src: &[u8]) -> Option<String> {
let declarator = node.child_by_field_name("declarator")?;
let qid = descend_to_qualified_identifier(declarator)?;
build_qualifier_chain(qid, src)
}
fn descend_to_qualified_identifier(declarator: Node) -> Option<Node> {
let mut cur = declarator;
loop {
if cur.kind() == "qualified_identifier" {
return Some(cur);
}
cur = match cur.child_by_field_name("declarator") {
Some(n) => n,
None => first_named_child(cur)?,
};
}
}
fn first_named_child(node: Node) -> Option<Node> {
let mut c = node.walk();
node.children(&mut c).find(|n| n.is_named())
}
fn build_qualifier_chain(qid: Node, src: &[u8]) -> Option<String> {
let mut parts = Vec::new();
let mut cur = qid;
while cur.kind() == "qualified_identifier" {
if let Some(scope) = cur.child_by_field_name("scope") {
parts.push(text(scope, src).to_string());
}
match cur.child_by_field_name("name") {
Some(next) => cur = next,
None => break,
}
}
(!parts.is_empty()).then(|| parts.join("::"))
}