mod node_util;
pub use node_util::{
child_kind, end_keyword, last_child_kind, line_blank, line_indent, named_kids, node_bytes,
node_col, node_line, node_text,
};
use tree_sitter::Node;
use crate::correction::Correction;
use crate::parse::source::SourceFile;
pub fn method_node(node: Node<'_>) -> Option<Node<'_>> {
node.child_by_field_name("method")
.or_else(|| node.child_by_field_name("name"))
}
pub fn push_replace(
corrections: &mut Option<&mut Vec<Correction>>,
start: usize,
end: usize,
replacement: impl Into<String>,
cop_name: &'static str,
) -> bool {
let Some(corr) = corrections.as_deref_mut() else {
return false;
};
corr.push(Correction {
start,
end,
replacement: replacement.into(),
cop_name,
cop_index: 0,
});
true
}
pub fn is_screaming_snake_case(name: &[u8]) -> bool {
let Ok(s) = std::str::from_utf8(name) else {
return false;
};
if s.is_empty() {
return false;
}
let mut has_letter = false;
for c in s.chars() {
if c.is_uppercase() {
has_letter = true;
} else if c.is_ascii_digit() || c == '_' {
} else {
return false;
}
}
has_letter
}
pub fn call_method_name<'a>(source: &'a SourceFile, node: Node<'_>) -> Option<&'a [u8]> {
let meth = node
.child_by_field_name("method")
.or_else(|| node.child_by_field_name("name"))?;
Some(node_bytes(source, meth))
}
pub fn call_receiver(node: Node<'_>) -> Option<Node<'_>> {
node.child_by_field_name("receiver")
}
pub fn is_const_named(source: &SourceFile, node: Node<'_>, name: &[u8]) -> bool {
match node.kind() {
"constant" => node_bytes(source, node) == name,
"scope_resolution" => node
.child_by_field_name("name")
.map(|n| node_bytes(source, n) == name)
.unwrap_or(false),
_ => false,
}
}
pub fn is_const_assign(node: Node<'_>) -> bool {
if node.kind() != "assignment" {
return false;
}
node.child_by_field_name("left")
.or_else(|| node.named_child(0))
.is_some_and(|lhs| matches!(lhs.kind(), "constant" | "scope_resolution"))
}
pub fn argument_nodes<'a>(node: Node<'a>) -> Vec<Node<'a>> {
if let Some(args) = node.child_by_field_name("arguments") {
let mut cur = args.walk();
return args
.named_children(&mut cur)
.filter(|n| n.kind() != ",")
.collect();
}
Vec::new()
}
pub fn for_each_descendant(node: Node<'_>, mut f: impl FnMut(Node<'_>)) {
fn walk(node: Node<'_>, f: &mut impl FnMut(Node<'_>)) {
f(node);
let mut cur = node.walk();
for child in node.children(&mut cur) {
walk(child, f);
}
}
walk(node, &mut f);
}
pub fn collect_comments<'a>(root: Node<'a>) -> Vec<Node<'a>> {
let mut out = Vec::new();
fn walk<'a>(node: Node<'a>, out: &mut Vec<Node<'a>>) {
if node.kind() == "comment" {
out.push(node);
}
let mut cur = node.walk();
for child in node.children(&mut cur) {
walk(child, out);
}
}
walk(root, &mut out);
out
}
pub fn only_spaces(bytes: &[u8], start: usize, end: usize) -> bool {
if end <= start {
return true;
}
bytes[start..end.min(bytes.len())]
.iter()
.all(|&b| b == b' ' || b == b'\t')
}
pub fn only_hspace(bytes: &[u8], start: usize, end: usize) -> bool {
if end <= start {
return false;
}
bytes[start..end.min(bytes.len())]
.iter()
.all(|&b| b == b' ' || b == b'\t')
}
pub fn has_hspace(bytes: &[u8], start: usize, end: usize) -> bool {
if end <= start {
return false;
}
bytes[start..end.min(bytes.len())]
.iter()
.any(|&b| b == b' ' || b == b'\t')
}