use tree_sitter::Node;
#[allow(dead_code)] pub fn node_text<'a>(node: Node<'a>, source: &'a [u8]) -> &'a str {
match node.utf8_text(source) {
Ok(s) => s,
Err(e) => {
tracing::debug!(
start = node.start_byte(),
end = node.end_byte(),
kind = node.kind(),
error = %e,
"semantic: utf8_text failed; falling back to empty string",
);
""
}
}
}
#[allow(dead_code)] pub fn find_node_at_range<'a>(n: Node<'a>, start: usize, end: usize) -> Option<Node<'a>> {
if n.start_byte() == start && n.end_byte() == end {
return Some(n);
}
for i in 0..n.named_child_count() {
if let Some(c) = n.named_child(i)
&& c.start_byte() <= start
&& c.end_byte() >= end
&& let Some(f) = find_node_at_range(c, start, end)
{
return Some(f);
}
}
None
}
#[allow(dead_code)] pub fn signature_first_line(node: Node, source: &[u8]) -> String {
let text = node_text(node, source);
let first = text.lines().next().unwrap_or(text);
if first.chars().count() > 80 {
let prefix: String = first.chars().take(80).collect();
format!("{prefix}…")
} else {
first.to_string()
}
}
#[cfg(any(
feature = "semantic-go",
feature = "semantic-c",
feature = "semantic-cpp",
feature = "semantic-rust"
))]
pub fn signature_up_to_body(node: Node, source: &[u8]) -> String {
if let Some(body) = node.child_by_field_name("body") {
return String::from_utf8_lossy(&source[node.start_byte()..body.start_byte()])
.trim()
.to_string();
}
signature_first_line(node, source)
}
#[allow(dead_code)] pub fn run_callee_query(
lang: &tree_sitter::Language,
query_str: &str,
source: &str,
range: crate::semantic::types::ByteRange,
) -> Result<Vec<String>, String> {
use streaming_iterator::StreamingIterator;
let mut parser = tree_sitter::Parser::new();
parser
.set_language(lang)
.map_err(|e| format!("Failed to set language: {e}"))?;
let tree = parser.parse(source, None).ok_or("Failed to parse source")?;
let root = tree.root_node();
let source_bytes = source.as_bytes();
let target = find_node_at_range(root, range.start_byte, range.end_byte)
.ok_or("Could not find node at given range")?;
let query =
tree_sitter::Query::new(lang, query_str).map_err(|e| format!("Query error: {e}"))?;
let mut cursor = tree_sitter::QueryCursor::new();
let mut matches = cursor.matches(&query, target, source_bytes);
let mut callees = Vec::new();
while let Some(m) = matches.next() {
for capture in m.captures {
let name = capture.node.utf8_text(source_bytes).unwrap_or("");
callees.push(name.to_string());
}
}
callees.sort();
callees.dedup();
Ok(callees)
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(feature = "semantic-clojure")]
fn byte_range_from_node_uses_1_based_lines() {
use crate::semantic::types::ByteRange;
use tree_sitter::Parser;
let mut p = Parser::new();
let lang: tree_sitter::Language = tree_sitter_clojure::LANGUAGE.into();
p.set_language(&lang).unwrap();
let src = "\n(defn foo [] :ok)\n";
let t = p.parse(src, None).unwrap();
let list_lit = t.root_node().named_child(0).unwrap();
let range = ByteRange::from(list_lit);
assert_eq!(range.start_line, 2);
assert_eq!(range.end_line, 2);
assert_eq!(range.start_byte, 1);
}
}