use super::anon_fn_name;
use super::conditions::unwrap_parens;
use crate::labels::{DataLabel, Kind, classify, lookup};
use tree_sitter::Node;
#[inline]
pub(crate) fn text_of<'a>(n: Node<'a>, code: &'a [u8]) -> Option<String> {
std::str::from_utf8(&code[n.start_byte()..n.end_byte()])
.ok()
.map(|s| s.to_string())
}
pub(crate) fn root_receiver_text(n: Node, lang: &str, code: &[u8]) -> Option<String> {
match lookup(lang, n.kind()) {
Kind::CallFn | Kind::CallMethod => {
let inner = n
.child_by_field_name("object")
.or_else(|| n.child_by_field_name("receiver"))
.or_else(|| n.child_by_field_name("function"));
match inner {
Some(child) => root_receiver_text(child, lang, code),
None => text_of(n, code),
}
}
_ => text_of(n, code),
}
}
pub(crate) fn root_member_receiver(n: Node, code: &[u8]) -> Option<String> {
let mut cur = n;
for _ in 0..16 {
match cur.kind() {
"identifier" | "variable_name" | "this" | "self" => {
return text_of(cur, code);
}
"member_expression" | "attribute" => {
cur = cur.child_by_field_name("object")?;
}
"field_expression" => {
cur = cur.child_by_field_name("value")?;
}
"call_expression" => {
cur = cur.child_by_field_name("function")?;
}
"method_call_expression" => {
cur = cur
.child_by_field_name("object")
.or_else(|| cur.child_by_field_name("receiver"))?;
}
_ => return None,
}
}
None
}
pub(crate) fn is_raii_factory(lang: &str, callee: &str) -> bool {
fn matches_any(callee: &str, patterns: &[&str]) -> bool {
let cl = callee.to_ascii_lowercase();
let base = cl.split('<').next().unwrap_or(&cl);
patterns.iter().any(|p| base == *p || base.ends_with(p))
}
match lang {
"cpp" => {
static CPP_RAII_FACTORIES: &[&str] = &[
"make_unique",
"make_shared",
"std::make_unique",
"std::make_shared",
];
matches_any(callee, CPP_RAII_FACTORIES)
}
"rust" => {
static RUST_RAII_CONSTRUCTORS: &[&str] = &[
"file::open",
"file::create",
"box::new",
"bufwriter::new",
"bufreader::new",
"tcplistener::bind",
"tcpstream::connect",
"udpsocket::bind",
"mutex::new",
"rwlock::new",
"fs::file::open",
"fs::file::create",
"std::fs::file::open",
"std::fs::file::create",
];
matches_any(callee, RUST_RAII_CONSTRUCTORS)
}
_ => false,
}
}
pub(crate) fn find_constructor_type_child(n: Node) -> Option<Node> {
let mut cursor = n.walk();
n.children(&mut cursor)
.find(|c| matches!(c.kind(), "name" | "type_identifier" | "qualified_name"))
}
pub(crate) fn first_call_ident_with_span<'a>(
n: Node<'a>,
lang: &str,
code: &'a [u8],
) -> Option<(String, (usize, usize))> {
let mut cursor = n.walk();
for c in n.children(&mut cursor) {
match lookup(lang, c.kind()) {
Kind::CallFn | Kind::CallMethod | Kind::CallMacro => {
let span = (c.start_byte(), c.end_byte());
if lang == "cpp" && c.kind() == "new_expression" {
return Some(("new".to_string(), span));
}
if lang == "cpp" && c.kind() == "delete_expression" {
return Some(("delete".to_string(), span));
}
if lang == "ruby" && c.kind() == "subshell" {
return Some(("subshell".to_string(), span));
}
let ident = match lookup(lang, c.kind()) {
Kind::CallFn => c
.child_by_field_name("function")
.or_else(|| c.child_by_field_name("method"))
.or_else(|| c.child_by_field_name("name"))
.or_else(|| c.child_by_field_name("type"))
.or_else(|| c.child_by_field_name("constructor"))
.or_else(|| find_constructor_type_child(c))
.and_then(|f| {
let unwrapped = unwrap_parens(f);
if lookup(lang, unwrapped.kind()) == Kind::Function {
Some(anon_fn_name(unwrapped.start_byte()))
} else {
text_of(f, code)
}
}),
Kind::CallMethod => {
let func = c
.child_by_field_name("method")
.or_else(|| c.child_by_field_name("name"))
.and_then(|f| text_of(f, code));
let recv = c
.child_by_field_name("object")
.or_else(|| c.child_by_field_name("receiver"))
.or_else(|| c.child_by_field_name("scope"))
.and_then(|f| root_receiver_text(f, lang, code));
match (recv, func) {
(Some(r), Some(f)) => Some(format!("{r}.{f}")),
(_, Some(f)) => Some(f.to_string()),
_ => None,
}
}
Kind::CallMacro => c
.child_by_field_name("macro")
.and_then(|f| text_of(f, code)),
_ => None,
};
return ident.map(|s| (s, span));
}
Kind::Function => {
continue;
}
_ => {
if let Some(found) = first_call_ident_with_span(c, lang, code) {
return Some(found);
}
}
}
}
None
}
pub(crate) fn first_call_ident<'a>(n: Node<'a>, lang: &str, code: &'a [u8]) -> Option<String> {
first_call_ident_with_span(n, lang, code).map(|(s, _)| s)
}
pub(crate) fn find_classifiable_inner_call<'a>(
n: Node<'a>,
lang: &str,
code: &'a [u8],
extra: Option<&[crate::labels::RuntimeLabelRule]>,
) -> Option<(String, DataLabel, (usize, usize))> {
let mut cursor = n.walk();
for c in n.children(&mut cursor) {
if lookup(lang, c.kind()) == Kind::Function {
continue;
}
match lookup(lang, c.kind()) {
Kind::CallFn | Kind::CallMethod | Kind::CallMacro => {
let ident = match lookup(lang, c.kind()) {
Kind::CallFn => c
.child_by_field_name("function")
.or_else(|| c.child_by_field_name("method"))
.or_else(|| c.child_by_field_name("name"))
.or_else(|| c.child_by_field_name("type"))
.and_then(|f| text_of(f, code)),
Kind::CallMethod => {
let func = c
.child_by_field_name("method")
.or_else(|| c.child_by_field_name("name"))
.and_then(|f| text_of(f, code));
let recv = c
.child_by_field_name("object")
.or_else(|| c.child_by_field_name("receiver"))
.or_else(|| c.child_by_field_name("scope"))
.and_then(|f| root_receiver_text(f, lang, code));
match (recv, func) {
(Some(r), Some(f)) => Some(format!("{r}.{f}")),
(_, Some(f)) => Some(f),
_ => None,
}
}
Kind::CallMacro => c
.child_by_field_name("macro")
.and_then(|f| text_of(f, code)),
_ => None,
};
if let Some(ref id) = ident
&& let Some(lbl) = classify(lang, id, extra)
{
return Some((id.clone(), lbl, (c.start_byte(), c.end_byte())));
}
if let Some(found) = find_classifiable_inner_call(c, lang, code, extra) {
return Some(found);
}
}
_ => {
if let Some(found) = find_classifiable_inner_call(c, lang, code, extra) {
return Some(found);
}
}
}
}
None
}
pub(crate) fn member_expr_text(n: Node, code: &[u8]) -> Option<String> {
let path = member_expr_text_inner(n, code)?;
let mut dots = 0;
for (i, c) in path.char_indices() {
if c == '.' {
dots += 1;
}
if dots >= 3 {
return Some(path[..i].to_string());
}
}
Some(path)
}
pub(crate) fn member_expr_text_inner(n: Node, code: &[u8]) -> Option<String> {
match n.kind() {
"member_expression" | "attribute" | "selector_expression" => {
let obj = n
.child_by_field_name("object")
.or_else(|| n.child_by_field_name("value"))
.or_else(|| n.child_by_field_name("operand"))
.and_then(|o| member_expr_text_inner(o, code))
.or_else(|| {
n.child_by_field_name("object")
.or_else(|| n.child_by_field_name("value"))
.or_else(|| n.child_by_field_name("operand"))
.and_then(|o| text_of(o, code))
});
let prop = n
.child_by_field_name("property")
.or_else(|| n.child_by_field_name("attribute"))
.or_else(|| n.child_by_field_name("field"))
.and_then(|p| text_of(p, code));
match (obj, prop) {
(Some(o), Some(p)) => Some(format!("{o}.{p}")),
(_, Some(p)) => Some(p),
(Some(o), _) => Some(o),
_ => text_of(n, code),
}
}
_ => text_of(n, code),
}
}
pub(crate) fn first_member_label(
n: Node,
lang: &str,
code: &[u8],
extra_labels: Option<&[crate::labels::RuntimeLabelRule]>,
) -> Option<DataLabel> {
match n.kind() {
"member_expression" | "attribute" | "selector_expression" => {
if let Some(full) = member_expr_text(n, code) {
let mut candidate = full.as_str();
let mut first = true;
loop {
if let Some(lbl) = classify(lang, candidate, extra_labels) {
if first || matches!(lbl, DataLabel::Source(_)) {
return Some(lbl);
}
}
first = false;
match candidate.rsplit_once('.') {
Some((prefix, _)) => candidate = prefix,
None => break,
}
}
}
}
"subscript_expression" | "subscript" | "element_reference" => {
if let Some(obj) = n
.child_by_field_name("object")
.or_else(|| n.child_by_field_name("value"))
.or_else(|| n.child(0))
{
if let Some(txt) = text_of(obj, code)
&& let Some(lbl) = classify(lang, &txt, extra_labels)
{
return Some(lbl);
}
if let Some(lbl) = first_member_label(obj, lang, code, extra_labels) {
return Some(lbl);
}
}
}
_ => {}
}
let mut cursor = n.walk();
for child in n.children(&mut cursor) {
if let Some(lbl) = first_member_label(child, lang, code, extra_labels) {
return Some(lbl);
}
}
None
}
pub(crate) fn first_member_text(n: Node, code: &[u8]) -> Option<String> {
match n.kind() {
"member_expression" | "attribute" | "selector_expression" => member_expr_text(n, code),
"subscript_expression" | "subscript" | "element_reference" => n
.child_by_field_name("object")
.or_else(|| n.child_by_field_name("value"))
.or_else(|| n.child(0))
.and_then(|obj| text_of(obj, code)),
_ => {
let mut cursor = n.walk();
for child in n.children(&mut cursor) {
if let Some(t) = first_member_text(child, code) {
return Some(t);
}
}
None
}
}
}
pub(crate) fn collect_nested_function_nodes<'a>(n: Node<'a>, lang: &str) -> Vec<Node<'a>> {
let mut funcs = Vec::new();
collect_nested_functions_rec(n, lang, &mut funcs, false);
funcs
}
pub(crate) fn collect_nested_functions_rec<'a>(
n: Node<'a>,
lang: &str,
out: &mut Vec<Node<'a>>,
inside_function: bool,
) {
let kind = lookup(lang, n.kind());
if kind == Kind::Function && n.child_count() > 0 {
if inside_function {
return;
}
out.push(n);
return;
}
let mut cursor = n.walk();
for c in n.children(&mut cursor) {
collect_nested_functions_rec(c, lang, out, inside_function);
}
}
pub(crate) fn derive_anon_fn_name_from_context<'a>(
func_node: Node<'a>,
lang: &str,
code: &'a [u8],
) -> Option<String> {
let mut cur = func_node.parent()?;
while cur.kind() == "parenthesized_expression" {
cur = cur.parent()?;
}
let parent = cur;
let lhs_ident_text = |lhs: Node<'a>| -> Option<String> {
let lhs = unwrap_parens(lhs);
match lhs.kind() {
"identifier" | "variable_name" | "simple_identifier" => text_of(lhs, code),
"member_expression"
| "attribute"
| "field_expression"
| "selector_expression"
| "scoped_identifier" => lhs
.child_by_field_name("property")
.or_else(|| lhs.child_by_field_name("field"))
.or_else(|| lhs.child_by_field_name("name"))
.and_then(|n| text_of(n, code)),
_ => None,
}
};
match parent.kind() {
"variable_declarator" | "init_declarator" | "let_declaration" => parent
.child_by_field_name("name")
.or_else(|| parent.child_by_field_name("pattern"))
.and_then(|n| match n.kind() {
"identifier" | "variable_name" | "simple_identifier" => text_of(n, code),
_ => None, }),
"assignment_expression" | "assignment" => {
parent.child_by_field_name("left").and_then(lhs_ident_text)
}
"short_var_declaration" => {
let left = parent.child_by_field_name("left")?;
let mut cur = left.walk();
left.children(&mut cur).find_map(|c| {
(c.kind() == "identifier")
.then(|| text_of(c, code))
.flatten()
})
}
"var_spec" | "const_spec" => {
let names = parent.child_by_field_name("name")?;
let mut cur = names.walk();
names.children(&mut cur).find_map(|c| {
(c.kind() == "identifier")
.then(|| text_of(c, code))
.flatten()
})
}
_ => {
let grand = parent.parent()?;
match grand.kind() {
"variable_declarator" | "init_declarator" => grand
.child_by_field_name("name")
.and_then(|n| match n.kind() {
"identifier" | "variable_name" | "simple_identifier" => text_of(n, code),
_ => None,
}),
"assignment_expression" | "assignment" => {
grand.child_by_field_name("left").and_then(lhs_ident_text)
}
"short_var_declaration" => {
let left = grand.child_by_field_name("left")?;
let mut cur = left.walk();
left.children(&mut cur).find_map(|c| {
(c.kind() == "identifier")
.then(|| text_of(c, code))
.flatten()
})
}
"var_spec" | "const_spec" => {
let names = grand.child_by_field_name("name")?;
let mut cur = names.walk();
names.children(&mut cur).find_map(|c| {
(c.kind() == "identifier")
.then(|| text_of(c, code))
.flatten()
})
}
_ => None,
}
}
}
.and_then(|name| {
if name.is_empty()
|| name.contains(|c: char| !(c.is_alphanumeric() || c == '_' || c == '$'))
{
None
} else {
let _ = lang;
Some(name)
}
})
}
pub(crate) fn has_call_descendant(n: Node, lang: &str) -> bool {
let mut cursor = n.walk();
for c in n.children(&mut cursor) {
match lookup(lang, c.kind()) {
Kind::CallFn | Kind::CallMethod | Kind::CallMacro => return true,
_ => {
if has_call_descendant(c, lang) {
return true;
}
}
}
}
false
}
pub(crate) fn collect_idents_with_paths(
n: Node,
code: &[u8],
idents: &mut Vec<String>,
paths: &mut Vec<String>,
) {
match n.kind() {
"member_expression" | "attribute" | "selector_expression" | "field_expression" => {
if let Some(path) = member_expr_text(n, code) {
paths.push(path);
}
collect_idents(n, code, idents);
}
"identifier"
| "field_identifier"
| "property_identifier"
| "shorthand_property_identifier_pattern" => {
if let Some(txt) = text_of(n, code) {
idents.push(txt);
}
}
"variable_name" => {
if let Some(txt) = text_of(n, code) {
idents.push(txt.trim_start_matches('$').to_string());
}
}
_ => {
let mut c = n.walk();
for ch in n.children(&mut c) {
collect_idents_with_paths(ch, code, idents, paths);
}
}
}
}
pub(crate) fn collect_idents(n: Node, code: &[u8], out: &mut Vec<String>) {
match n.kind() {
"identifier"
| "field_identifier"
| "property_identifier"
| "shorthand_property_identifier_pattern"
| "name" => {
if let Some(txt) = text_of(n, code) {
out.push(txt);
}
}
"variable_name" => {
if let Some(txt) = text_of(n, code) {
out.push(txt.trim_start_matches('$').to_string());
}
}
_ => {
let mut c = n.walk();
for ch in n.children(&mut c) {
collect_idents(ch, code, out);
}
}
}
}
#[inline]
pub(crate) fn is_subscript_kind(kind: &str) -> bool {
matches!(
kind,
"subscript_expression" | "subscript" | "index_expression"
)
}
pub(crate) fn subscript_lhs_node<'a>(lhs: Node<'a>, lang: &str) -> Option<Node<'a>> {
if is_subscript_kind(lhs.kind()) {
return Some(lhs);
}
if lang == "go" && lhs.kind() == "expression_list" {
let mut cursor = lhs.walk();
let named: Vec<Node> = lhs.named_children(&mut cursor).collect();
if named.len() == 1 && is_subscript_kind(named[0].kind()) {
return Some(named[0]);
}
}
None
}
pub(crate) fn subscript_components<'a>(n: Node<'a>, code: &'a [u8]) -> Option<(String, String)> {
if !is_subscript_kind(n.kind()) {
return None;
}
let arr = n
.child_by_field_name("object")
.or_else(|| n.child_by_field_name("operand"))
.or_else(|| n.child_by_field_name("value"))
.or_else(|| n.child(0))?;
let idx = n
.child_by_field_name("index")
.or_else(|| n.child_by_field_name("subscript"))
.or_else(|| {
let mut cur = n.walk();
n.named_children(&mut cur).nth(1)
})?;
let arr_kind = arr.kind();
if !matches!(
arr_kind,
"identifier" | "variable_name" | "simple_identifier"
) {
return None;
}
let arr_text = text_of(arr, code)?;
let idx_text = text_of(idx, code)?;
Some((arr_text, idx_text))
}