use lang_parsing_substrate::query;
use tree_sitter::Node;
pub fn get_node_text<'a>(node: &Node, source: &'a str) -> &'a str {
query::node_text(*node, source.as_bytes())
}
pub fn is_deallocation_call_name(func_name: &str) -> bool {
if crate::analyze::macro_semantics::is_container_unlink_macro(func_name) {
return false;
}
let lower_name = func_name.to_lowercase();
lower_name.starts_with("destroy_")
|| lower_name.starts_with("free_")
|| lower_name.starts_with("delete_")
|| lower_name.starts_with("cleanup_")
|| lower_name.starts_with("release_")
|| lower_name.starts_with("close_")
|| lower_name.ends_with("_destroy")
|| lower_name.ends_with("_free")
|| lower_name.ends_with("_delete")
|| lower_name.ends_with("_cleanup")
|| lower_name.ends_with("_release")
|| lower_name.ends_with("_close")
}
pub fn get_node_text_owned(node: &Node, source: &str) -> String {
query::node_text(*node, source.as_bytes()).to_string()
}
pub fn get_sanitized_node_text(node: &Node, source: &str) -> String {
let start = node.start_byte();
let end = node.end_byte();
let mut bytes = source.as_bytes()[start..end].to_vec();
for lit in
query::find_descendants_of_kinds(*node, &["comment", "string_literal", "char_literal"])
{
let lit_start = lit.start_byte().max(start);
let lit_end = lit.end_byte().min(end);
if lit_start < lit_end {
for b in &mut bytes[(lit_start - start)..(lit_end - start)] {
if *b != b'\n' {
*b = b' ';
}
}
}
}
String::from_utf8_lossy(&bytes).into_owned()
}
pub fn find_containing_function<'a>(node: &Node<'a>) -> Option<Node<'a>> {
if node.kind() == "function_definition" {
return Some(*node);
}
query::nearest_ancestor_of_kind(*node, "function_definition")
}
pub fn file_scope_descendants_of_kinds<'a>(root: Node<'a>, kinds: &[&str]) -> Vec<Node<'a>> {
let mut out = Vec::new();
let mut stack = vec![root];
while let Some(node) = stack.pop() {
if node.kind() == "function_definition" {
continue;
}
if kinds.contains(&node.kind()) {
out.push(node);
}
let mut cursor = node.walk();
let children: Vec<Node<'a>> = node.children(&mut cursor).collect();
stack.extend(children.into_iter().rev());
}
out
}
pub fn find_enclosing_declaration_for_identifier<'a>(
ident_node: &Node<'a>,
name: &str,
source: &str,
) -> Option<Node<'a>> {
let mut scopes = Vec::new();
let mut search_from = *ident_node;
while let Some(scope) = query::find_ancestor(search_from, |n| is_declaration_scope(&n)) {
scopes.push(scope);
search_from = scope;
}
find_declaration_in_scope_chain(&scopes, ident_node.start_byte(), name, source)
}
pub fn is_declaration_scope(node: &Node) -> bool {
matches!(node.kind(), "compound_statement" | "for_statement")
}
pub fn find_declaration_in_scope_chain<'a>(
scopes: &[Node<'a>],
ident_start: usize,
name: &str,
source: &str,
) -> Option<Node<'a>> {
for scope in scopes {
let mut declarations = Vec::new();
collect_declarations_transparent_to_preproc(scope, &mut declarations);
let best = declarations
.into_iter()
.filter(|child| {
child.start_byte() < ident_start && declaration_binds_name(child, name, source)
})
.max_by_key(|child| child.start_byte());
if best.is_some() {
return best;
}
}
None
}
fn collect_declarations_transparent_to_preproc<'a>(scope: &Node<'a>, out: &mut Vec<Node<'a>>) {
let condition_id = scope.child_by_field_name("condition").map(|n| n.id());
let name_id = scope.child_by_field_name("name").map(|n| n.id());
for i in 0..scope.child_count() {
let Some(child) = scope.child(i) else {
continue;
};
if Some(child.id()) == condition_id || Some(child.id()) == name_id {
continue;
}
match child.kind() {
"declaration" => out.push(child),
"preproc_if" | "preproc_ifdef" | "preproc_elif" | "preproc_else" | "case_statement" => {
collect_declarations_transparent_to_preproc(&child, out);
}
_ => {}
}
}
}
fn declaration_binds_name(decl_node: &Node, name: &str, source: &str) -> bool {
for i in 0..decl_node.child_count() {
let Some(child) = decl_node.child(i) else {
continue;
};
let declarator = match child.kind() {
"init_declarator" => child.child_by_field_name("declarator").unwrap_or(child),
"identifier" | "pointer_declarator" | "array_declarator" | "function_declarator" => {
child
}
_ => continue,
};
if get_identifier_from_declarator(&declarator, source) == name {
return true;
}
}
false
}
pub fn find_global_declaration_for_identifier<'a>(
ident_node: &Node<'a>,
name: &str,
source: &str,
) -> Option<Node<'a>> {
let mut top = *ident_node;
while let Some(p) = top.parent() {
top = p;
}
(0..top.child_count())
.filter_map(|i| top.child(i))
.find(|decl| decl.kind() == "declaration" && declaration_binds_name(decl, name, source))
}
pub enum IdentifierBinding<'a> {
Local(Node<'a>),
Parameter(String),
Global(Node<'a>),
}
pub fn resolve_identifier_binding<'a>(
ident_node: &Node<'a>,
name: &str,
source: &str,
) -> Option<IdentifierBinding<'a>> {
if let Some(decl) = find_enclosing_declaration_for_identifier(ident_node, name, source) {
return Some(IdentifierBinding::Local(decl));
}
if let Some(func) = find_containing_function(ident_node) {
if let Some(params) = get_function_parameters(&func, source) {
if let Some((_, ptype)) = params.iter().find(|(n, _)| n == name) {
return Some(IdentifierBinding::Parameter(ptype.clone()));
}
}
}
find_global_declaration_for_identifier(ident_node, name, source).map(IdentifierBinding::Global)
}
pub fn declaration_type_text(decl: &Node, source: &str) -> String {
(0..decl.child_count())
.filter_map(|i| decl.child(i))
.take_while(|c| {
!matches!(
c.kind(),
"identifier" | "init_declarator" | "pointer_declarator" | "array_declarator"
)
})
.map(|c| get_node_text(&c, source))
.collect::<Vec<_>>()
.join(" ")
}
pub fn declaration_has_qualifier(decl: &Node, qualifier: &str, source: &str) -> bool {
(0..decl.child_count()).any(|i| {
decl.child(i)
.is_some_and(|c| c.kind() == "type_qualifier" && get_node_text(&c, source) == qualifier)
})
}
pub fn declaration_has_storage_class(decl: &Node, storage_class: &str, source: &str) -> bool {
(0..decl.child_count()).any(|i| {
decl.child(i).is_some_and(|c| {
c.kind() == "storage_class_specifier" && get_node_text(&c, source) == storage_class
})
})
}
pub fn is_dereference_expression(node: &Node, source: &str) -> bool {
node.kind() == "pointer_expression"
&& node
.child_by_field_name("operator")
.is_some_and(|o| get_node_text(&o, source) == "*")
}
#[allow(dead_code)]
pub fn is_address_of_expression(node: &Node, source: &str) -> bool {
node.kind() == "pointer_expression"
&& node
.child_by_field_name("operator")
.is_some_and(|o| get_node_text(&o, source) == "&")
}
pub fn resolve_identifier_type(ident_node: &Node, name: &str, source: &str) -> Option<String> {
match resolve_identifier_binding(ident_node, name, source)? {
IdentifierBinding::Local(decl) | IdentifierBinding::Global(decl) => {
Some(declaration_type_text(&decl, source))
}
IdentifierBinding::Parameter(ptype) => Some(ptype),
}
}
pub fn is_inside_loop(node: &Node) -> bool {
query::find_ancestor(*node, |n| {
matches!(
n.kind(),
"for_statement" | "while_statement" | "do_statement"
)
})
.is_some()
}
#[allow(dead_code)]
pub fn is_inside_conditional(node: &Node) -> bool {
query::find_ancestor(*node, |n| {
matches!(n.kind(), "if_statement" | "switch_statement")
})
.is_some()
}
pub fn get_identifier_from_declarator(declarator: &Node, source: &str) -> String {
match declarator.kind() {
"identifier" => get_node_text_owned(declarator, source),
"pointer_declarator"
| "array_declarator"
| "function_declarator"
| "parenthesized_declarator" => {
for i in 0..declarator.child_count() {
if let Some(child) = declarator.child(i) {
if child.kind() == "identifier" {
return get_node_text_owned(&child, source);
}
let nested = get_identifier_from_declarator(&child, source);
if !nested.is_empty() {
return nested;
}
}
}
String::new() }
_ => String::new(), }
}
pub fn find_identifier_in_declarator(declarator: &Node, source: &str) -> Option<String> {
let name = get_identifier_from_declarator(declarator, source);
if name.is_empty() {
None
} else {
Some(name)
}
}
pub fn get_function_parameters(
function_node: &Node,
source: &str,
) -> Option<Vec<(String, String)>> {
let declarator = find_function_declarator(function_node)?;
extract_parameters(&declarator, source)
}
fn find_function_declarator<'a>(function_node: &Node<'a>) -> Option<Node<'a>> {
for i in 0..function_node.child_count() {
let child = function_node.child(i)?;
match child.kind() {
"function_declarator" => return Some(child),
"pointer_declarator" => {
if let Some(found) = find_function_declarator(&child) {
return Some(found);
}
}
_ => {}
}
}
None
}
fn extract_parameters(declarator_node: &Node, source: &str) -> Option<Vec<(String, String)>> {
let mut parameters = Vec::new();
for i in 0..declarator_node.child_count() {
if let Some(child) = declarator_node.child(i) {
if child.kind() == "parameter_list" {
for j in 0..child.child_count() {
if let Some(param) = child.child(j) {
if param.kind() == "parameter_declaration" {
if let Some((name, param_type)) = extract_parameter_info(¶m, source)
{
parameters.push((name, param_type));
}
}
}
}
}
}
}
if parameters.is_empty() {
None
} else {
Some(parameters)
}
}
fn extract_parameter_info(param_node: &Node, source: &str) -> Option<(String, String)> {
let param_text = get_node_text(param_node, source);
for i in 0..param_node.child_count() {
if let Some(child) = param_node.child(i) {
if matches!(
child.kind(),
"array_declarator" | "pointer_declarator" | "function_declarator"
) {
if let Some(identifier) = find_identifier_in_declarator(&child, source) {
return Some((identifier, param_text.to_string()));
}
} else if child.kind() == "identifier" {
let name = get_node_text(&child, source);
return Some((name.to_string(), param_text.to_string()));
}
}
}
None
}
pub fn is_function_parameter(function_node: &Node, var_name: &str, source: &str) -> bool {
for i in 0..function_node.child_count() {
if let Some(child) = function_node.child(i) {
if child.kind() == "function_declarator" {
for j in 0..child.child_count() {
if let Some(param_list) = child.child(j) {
if param_list.kind() == "parameter_list" {
let param_text = get_node_text(¶m_list, source);
let words: Vec<&str> = param_text
.split(|c: char| !c.is_alphanumeric() && c != '_')
.collect();
if words.contains(&var_name) {
return true;
}
}
}
}
}
}
}
false
}
pub fn is_array_parameter_type(param_type: &str) -> bool {
param_type.contains('[') || (param_type.contains('*') && !param_type.contains("const char *"))
}
pub fn is_pointer_type(type_str: &str) -> bool {
type_str.contains('*')
}
pub fn is_integer_type(type_str: &str) -> bool {
const INTEGER_TYPES: &[&str] = &[
"int",
"unsigned",
"long",
"short",
"char",
"size_t",
"ptrdiff_t",
];
INTEGER_TYPES.iter().any(|&t| {
type_str.contains(t) && !type_str.contains("uintptr_t") && !type_str.contains("intptr_t")
})
}
#[allow(dead_code)]
pub fn is_signed_type(type_str: &str) -> bool {
matches!(
type_str.trim(),
"int"
| "short"
| "long"
| "char"
| "signed"
| "signed int"
| "signed short"
| "signed long"
| "long long"
| "signed long long"
| "signed char"
| "int8_t"
| "int16_t"
| "int32_t"
| "int64_t"
| "ptrdiff_t"
| "ssize_t"
)
}
#[allow(dead_code)]
pub fn is_unsigned_type(type_str: &str) -> bool {
type_str.contains("unsigned")
|| matches!(
type_str.trim(),
"size_t" | "uint8_t" | "uint16_t" | "uint32_t" | "uint64_t" | "uintptr_t" | "uintmax_t"
)
}
pub fn get_binary_operator<'a>(node: &Node, source: &'a str) -> Option<&'a str> {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
let kind = child.kind();
if matches!(
kind,
"+" | "-"
| "*"
| "/"
| "%"
| "=="
| "!="
| "<"
| ">"
| "<="
| ">="
| "&&"
| "||"
| "&"
| "|"
| "^"
| "<<"
| ">>"
| "="
| "+="
| "-="
| "*="
| "/="
| "%="
| "&="
| "|="
| "^="
| "<<="
| ">>="
) {
return Some(get_node_text(&child, source));
}
}
}
None
}
#[allow(dead_code)]
pub fn find_array_size(array_name: &str, preceding_text: &str) -> Option<usize> {
let pattern = format!("{}[", array_name);
if let Some(pos) = preceding_text.rfind(&pattern) {
let after_bracket = &preceding_text[pos + pattern.len()..];
if let Some(close_bracket) = after_bracket.find(']') {
let size_str = after_bracket[..close_bracket].trim();
if let Ok(size) = size_str.parse::<usize>() {
return Some(size);
}
if size_str.contains('*') {
let parts: Vec<&str> = size_str.split('*').collect();
if parts.len() == 2 {
if let (Ok(a), Ok(b)) = (
parts[0].trim().parse::<usize>(),
parts[1].trim().parse::<usize>(),
) {
return Some(a * b);
}
}
}
}
}
None
}
#[allow(dead_code)]
pub fn get_type_size(type_name: &str) -> usize {
match type_name.trim() {
"char" | "signed char" | "unsigned char" | "int8_t" | "uint8_t" => 1,
"short" | "signed short" | "unsigned short" | "int16_t" | "uint16_t" => 2,
"int" | "signed int" | "unsigned int" | "int32_t" | "uint32_t" | "float" => 4,
"long" | "signed long" | "unsigned long" | "long long" | "signed long long"
| "unsigned long long" | "int64_t" | "uint64_t" | "double" | "size_t" | "ptrdiff_t" => 8,
"long double" => 16,
t if t.ends_with('*') => 8, _ => 4, }
}
pub fn is_write_context(node: &Node) -> bool {
let mut current = *node;
loop {
if let Some(parent) = current.parent() {
if parent.kind() == "assignment_expression" {
if let Some(left) = parent.child_by_field_name("left") {
return left.id() == current.id();
}
return false;
} else if parent.kind() == "subscript_expression" {
current = parent;
} else {
return false;
}
} else {
return false;
}
}
}
pub fn misparsed_cast_type_name<'a>(node: &Node, source: &'a str) -> Option<&'a str> {
let candidate = match node.kind() {
"binary_expression" => {
let op = node.child_by_field_name("operator")?;
if !matches!(get_node_text(&op, source), "&" | "*" | "-" | "+") {
return None;
}
node.child_by_field_name("left")?
}
"call_expression" => node.child_by_field_name("function")?,
_ => return None,
};
if candidate.kind() != "parenthesized_expression" {
return None;
}
if candidate.named_child_count() != 1 {
return None;
}
let inner = candidate.named_child(0)?;
if inner.kind() != "identifier" {
return None;
}
Some(get_node_text(&inner, source))
}
#[allow(dead_code)]
pub fn is_in_sizeof(node: &Node) -> bool {
query::nearest_ancestor_of_kind(*node, "sizeof_expression").is_some()
}
pub fn find_containing_for_loop<'a>(node: &Node<'a>) -> Option<Node<'a>> {
query::nearest_ancestor_of_kind(*node, "for_statement")
}
pub fn find_containing_if_statement<'a>(node: &Node<'a>) -> Option<Node<'a>> {
query::nearest_ancestor_of_kind(*node, "if_statement")
}
pub fn extract_struct_name_from_type(type_str: &str) -> Option<&str> {
let trimmed = type_str.trim();
let mut base = trimmed
.trim_end_matches('*')
.trim_end()
.trim_end_matches("const")
.trim_end_matches("volatile")
.trim();
loop {
let next = base
.strip_prefix("const ")
.or_else(|| base.strip_prefix("volatile "))
.unwrap_or(base)
.trim();
if next == base {
break;
}
base = next;
}
if matches!(
base,
"int"
| "unsigned int"
| "signed int"
| "short"
| "unsigned short"
| "long"
| "unsigned long"
| "long long"
| "unsigned long long"
| "char"
| "unsigned char"
| "signed char"
| "float"
| "double"
| "void"
| "_Bool"
) {
return None;
}
if base.ends_with("_t")
&& (base.starts_with("int") || base.starts_with("uint") || base.starts_with("size"))
{
return None;
}
if let Some(name) = base.strip_prefix("struct ") {
let name = name.trim();
if !name.is_empty() && name.chars().all(|c| c.is_alphanumeric() || c == '_') {
return Some(name);
}
return None;
}
if !base.is_empty()
&& base
.chars()
.next()
.is_some_and(|c| c.is_alphabetic() || c == '_')
&& base.chars().all(|c| c.is_alphanumeric() || c == '_')
{
return Some(base);
}
None
}
pub fn resolve_field_expression_type(
node: &Node,
source: &str,
type_map: &std::collections::HashMap<String, String>,
struct_field_types: &std::collections::HashMap<
String,
std::collections::HashMap<String, String>,
>,
) -> Option<String> {
let field_node = node.child_by_field_name("field")?;
let field_name = field_node.utf8_text(source.as_bytes()).ok()?;
let argument = node.child_by_field_name("argument")?;
let base_type = match argument.kind() {
"identifier" => {
let base_name = argument.utf8_text(source.as_bytes()).ok()?;
type_map.get(base_name)?.clone()
}
"field_expression" => {
resolve_field_expression_type(&argument, source, type_map, struct_field_types)?
}
"pointer_expression" => {
let inner = argument.child_by_field_name("argument")?;
let inner_name = inner.utf8_text(source.as_bytes()).ok()?;
let t = type_map.get(inner_name)?;
t.strip_suffix(" *")
.or_else(|| t.strip_suffix('*'))
.map(|s| s.trim().to_string())?
}
_ => return None,
};
let struct_name = extract_struct_name_from_type(&base_type)?;
struct_field_types
.get(struct_name)
.and_then(|fields| fields.get(field_name))
.cloned()
}
pub enum PackedSignal {
No,
Direct,
MacroCandidate(String),
}
pub fn struct_specifier_packed_signal(s: &Node, source: &str) -> PackedSignal {
for attr in query::find_descendants_of_kind(*s, "attribute_specifier") {
if get_node_text(&attr, source).contains("packed") {
return PackedSignal::Direct;
}
}
let Some(parent) = s.parent() else {
return PackedSignal::No;
};
if !matches!(
parent.kind(),
"declaration" | "field_declaration" | "type_definition"
) {
return PackedSignal::No;
}
let parent_text = get_node_text(&parent, source);
let struct_text = get_node_text(s, source);
let Some(tail) = parent_text.strip_prefix(struct_text) else {
return PackedSignal::No;
};
let Ok(ident_re) = regex::Regex::new(r"[A-Za-z_][A-Za-z0-9_]*") else {
return PackedSignal::No;
};
match ident_re.find(tail) {
Some(m) => PackedSignal::MacroCandidate(m.as_str().to_string()),
None => PackedSignal::No,
}
}
pub fn struct_specifier_is_packed(s: &Node, source: &str) -> bool {
match struct_specifier_packed_signal(s, source) {
PackedSignal::Direct => true,
PackedSignal::MacroCandidate(name) => macro_expands_to_packed(&name, source),
PackedSignal::No => false,
}
}
pub fn macro_expands_to_packed(name: &str, source: &str) -> bool {
let Ok(re) = regex::Regex::new(&format!(
r"(?m)^\s*#\s*define\s+{}\b.*$",
regex::escape(name)
)) else {
return false;
};
re.find(source)
.map(|m| m.as_str().contains("packed"))
.unwrap_or(false)
}
pub fn collect_packed_macro_names(source: &str, out: &mut std::collections::HashSet<String>) {
let Ok(re) = regex::Regex::new(r"(?m)^\s*#\s*define\s+([A-Za-z_][A-Za-z0-9_]*)\b.*$") else {
return;
};
for cap in re.captures_iter(source) {
let line = cap.get(0).map(|m| m.as_str()).unwrap_or("");
if line.contains("packed") {
if let Some(name) = cap.get(1) {
out.insert(name.as_str().to_string());
}
}
}
}
pub fn is_defined_macro_name(name: &str, source: &str) -> bool {
let Ok(re) = regex::Regex::new(&format!(r"(?m)^\s*#\s*define\s+{}\b", regex::escape(name)))
else {
return false;
};
re.is_match(source)
}
pub fn is_likely_macro_constant(name: &str) -> bool {
!name.is_empty()
&& name
.chars()
.all(|c| c.is_ascii_uppercase() || c == '_' || c.is_ascii_digit())
&& name
.chars()
.next()
.is_some_and(|c| c.is_ascii_uppercase() || c == '_')
}
pub fn collect_defined_macro_names(source: &str, out: &mut std::collections::HashSet<String>) {
let Ok(re) = regex::Regex::new(r"(?m)^\s*#\s*define\s+([A-Za-z_][A-Za-z0-9_]*)\b") else {
return;
};
for cap in re.captures_iter(source) {
if let Some(name) = cap.get(1) {
out.insert(name.as_str().to_string());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tree_sitter::Parser;
fn parse_c_code(code: &str) -> (tree_sitter::Tree, String) {
let mut parser = Parser::new();
let language = crate::parser::c_language();
parser.set_language(&language).unwrap();
let tree = parser.parse(code, None).unwrap();
(tree, code.to_string())
}
#[test]
fn test_get_node_text() {
let (tree, source) = parse_c_code("int x = 5;");
let root = tree.root_node();
let text = get_node_text(&root, &source);
assert_eq!(text, "int x = 5;");
}
#[test]
fn test_find_containing_function() {
let (tree, _source) = parse_c_code("void foo() { int x = 5; }");
let root = tree.root_node();
let func_def = root.child(0).unwrap();
assert_eq!(func_def.kind(), "function_definition");
let compound_stmt = func_def.child_by_field_name("body").unwrap();
let decl = compound_stmt.child(1).unwrap();
let containing_func = find_containing_function(&decl);
assert!(containing_func.is_some());
assert_eq!(containing_func.unwrap().kind(), "function_definition");
}
#[test]
fn test_find_array_size() {
let text = "int main() { int arr[10]; }";
let size = find_array_size("arr", text);
assert_eq!(size, Some(10));
}
#[test]
fn test_is_signed_type() {
assert!(is_signed_type("int"));
assert!(is_signed_type("signed int"));
assert!(is_signed_type("int32_t"));
assert!(!is_signed_type("unsigned int"));
assert!(!is_signed_type("size_t"));
}
#[test]
fn test_is_unsigned_type() {
assert!(is_unsigned_type("unsigned int"));
assert!(is_unsigned_type("size_t"));
assert!(is_unsigned_type("uint32_t"));
assert!(!is_unsigned_type("int"));
assert!(!is_unsigned_type("signed int"));
}
#[test]
fn test_get_type_size() {
assert_eq!(get_type_size("char"), 1);
assert_eq!(get_type_size("short"), 2);
assert_eq!(get_type_size("int"), 4);
assert_eq!(get_type_size("long"), 8);
assert_eq!(get_type_size("int *"), 8);
}
#[test]
fn test_find_enclosing_declaration_for_identifier_for_loop_var() {
let (tree, source) =
parse_c_code("void f(void) { for (int i = 0; i < 10; i++) { use(i); } }");
let root = tree.root_node();
let idents = query::find_descendants_of_kind(root, "identifier");
let occurrences: Vec<_> = idents
.iter()
.filter(|n| get_node_text(n, &source) == "i")
.collect();
assert_eq!(occurrences.len(), 4);
let decl_node = occurrences[0];
for occurrence in &occurrences[1..] {
let resolved = find_enclosing_declaration_for_identifier(occurrence, "i", &source);
assert!(
resolved.is_some(),
"expected occurrence at byte {} to resolve to the for-loop's own declaration",
occurrence.start_byte()
);
let resolved = resolved.unwrap();
assert!(resolved.start_byte() <= decl_node.start_byte());
assert_eq!(resolved.kind(), "declaration");
}
}
#[test]
fn test_find_enclosing_declaration_for_identifier_shadow_in_loop_body() {
let (tree, source) =
parse_c_code("void f(void) { for (int i = 0; i < 10; i++) { int i = 5; use(i); } }");
let root = tree.root_node();
let idents = query::find_descendants_of_kind(root, "identifier");
let use_i = idents
.iter()
.rev()
.find(|n| get_node_text(n, &source) == "i")
.unwrap();
let resolved = find_enclosing_declaration_for_identifier(use_i, "i", &source).unwrap();
let inner_decl_text = get_node_text(&resolved, &source);
assert!(
inner_decl_text.contains("= 5"),
"expected the inner shadowing declaration, got: {inner_decl_text}"
);
}
#[test]
fn test_find_enclosing_declaration_for_identifier_inside_ifdef() {
let (tree, source) = parse_c_code(
"int f(int x) { \
#ifdef NEED_AP_MLME\n\
int color = x;\n\
#endif\n\
return color; }",
);
let root = tree.root_node();
let idents = query::find_descendants_of_kind(root, "identifier");
let use_color = idents
.iter()
.rev()
.find(|n| get_node_text(n, &source) == "color")
.unwrap();
let resolved = find_enclosing_declaration_for_identifier(use_color, "color", &source);
assert!(
resolved.is_some(),
"expected the ifdef-nested declaration to resolve"
);
assert_eq!(resolved.unwrap().kind(), "declaration");
}
}