use std::collections::HashSet;
use tree_sitter::Node;
use super::rs_macro_bind::{is_statement, tree_names};
use crate::parse;
const BLOCK_ITEM_KINDS: &[&str] = &[
"function_item",
"struct_item",
"enum_item",
"union_item",
"trait_item",
"type_item",
"const_item",
"static_item",
"mod_item",
];
fn pattern_field(kind: &str) -> Option<&'static str> {
Some(match kind {
"let_declaration" | "parameter" | "for_expression" | "let_condition" => "pattern",
"match_arm" => "pattern",
_ => return None,
})
}
pub(super) fn body_locals(node: Node, src: &[u8]) -> HashSet<String> {
let mut names = HashSet::new();
let mut stack = vec![node];
while let Some(current) = stack.pop() {
if current.kind() == "macro_invocation" && is_statement(current) {
names.extend(tree_names(current, src).outward.values);
}
if let Some(field) = pattern_field(current.kind()) {
if let Some(pattern) = current.child_by_field_name(field) {
bindings(pattern, src, &mut names);
}
}
if current.kind() == "closure_parameters" {
let mut cursor = current.walk();
for child in current.named_children(&mut cursor) {
if child.kind() != "parameter" {
bindings(child, src, &mut names);
}
}
}
let mut cursor = current.walk();
for child in current.named_children(&mut cursor) {
if child.kind() != "function_item" {
stack.push(child);
}
}
}
names
}
pub(super) fn bindings(pattern: Node, src: &[u8], names: &mut HashSet<String>) {
let mut stack = vec![pattern];
while let Some(node) = stack.pop() {
match node.kind() {
"identifier" | "shorthand_field_identifier" => {
let text = parse::node_text(node, src);
let text = text.strip_prefix("r#").unwrap_or(&text);
if text.starts_with(|c: char| c.is_lowercase() || c == '_') {
names.insert(text.to_string());
}
}
"scoped_identifier" | "range_pattern" | "field_identifier" => {}
"tuple_struct_pattern" | "struct_pattern" => {
let ty = node.child_by_field_name("type").map(|t| t.id());
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
if Some(child.id()) != ty {
stack.push(child);
}
}
}
"match_pattern" => {
let condition = node.child_by_field_name("condition").map(|c| c.id());
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
if Some(child.id()) != condition {
stack.push(child);
}
}
}
_ => {
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
stack.push(child);
}
}
}
}
}
pub(super) fn block_items(block: Node, src: &[u8]) -> Vec<String> {
let mut out = macro_items(block, src);
let mut cursor = block.walk();
for child in block.named_children(&mut cursor) {
if BLOCK_ITEM_KINDS.contains(&child.kind()) {
if let Some(name) = child.child_by_field_name("name") {
let text = parse::node_text(name, src);
out.push(text.strip_prefix("r#").unwrap_or(&text).to_string());
}
}
}
out
}
pub(super) fn macro_items(container: Node, src: &[u8]) -> Vec<String> {
let mut out = Vec::new();
let mut cursor = container.walk();
for child in container.named_children(&mut cursor) {
let invocation = match child.kind() {
"macro_invocation" => Some(child),
"expression_statement" => child
.named_child(0)
.filter(|c| c.kind() == "macro_invocation"),
_ => None,
};
if let Some(invocation) = invocation {
let names = tree_names(invocation, src).outward;
out.extend(names.items);
out.extend(names.values);
}
}
out
}
pub(super) fn macro_scopes(root: Node, src: &[u8]) -> Vec<String> {
let mut out = Vec::new();
let mut stack = vec![(root, String::new())];
while let Some((container, scope)) = stack.pop() {
let mut opaque = false;
let mut cursor = container.walk();
for child in container.named_children(&mut cursor) {
let invocation = match child.kind() {
"macro_invocation" => Some(child),
"expression_statement" => child
.named_child(0)
.filter(|c| c.kind() == "macro_invocation"),
_ => None,
};
opaque |= invocation.is_some();
if child.kind() == "mod_item" {
if let (Some(name), Some(body)) = (
child.child_by_field_name("name"),
child.child_by_field_name("body"),
) {
let text = parse::node_text(name, src);
let text = text.strip_prefix("r#").unwrap_or(&text);
let nested = if scope.is_empty() {
text.to_string()
} else {
format!("{scope}::{text}")
};
stack.push((body, nested));
}
}
}
if opaque {
out.push(scope);
}
}
out.sort();
out
}
pub(super) fn generic_names(item: Node, src: &[u8]) -> Vec<String> {
let Some(params) = item.child_by_field_name("type_parameters") else {
return Vec::new();
};
let mut out = Vec::new();
let mut cursor = params.walk();
for child in params.named_children(&mut cursor) {
if matches!(child.kind(), "type_parameter" | "const_parameter") {
if let Some(name) = child.child_by_field_name("name") {
out.push(parse::node_text(name, src).to_string());
}
}
}
out
}