#![allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
mod parser_cache;
use tree_sitter::Node as OtherNode;
use tree_sitter::Tree as OtherTree;
use tree_sitter::TreeCursor;
use crate::checker::Checker;
use crate::traits::{LanguageInfo, Search};
use parser_cache::parse_on_scratch_parser;
crate::observation::counter!(
node_resolved_sibling_lookups,
cfg(any(test, feature = "test-support"))
);
crate::observation::counter!(child_scan_cursors, cfg(any(test, feature = "test-support")));
#[derive(Clone, Debug)]
pub(crate) struct Tree(OtherTree);
impl Tree {
pub(crate) fn new<T: LanguageInfo>(code: &[u8]) -> Self {
let language = T::lang().get_ts_language().expect(
"invariant: the caller checked LANG::is_enabled, or reached this through AnyParser",
);
Self(parse_on_scratch_parser(&language, code))
}
pub(crate) fn from_ts_tree(tree: OtherTree) -> Self {
Self(tree)
}
pub(crate) fn get_root(&self) -> Node<'_> {
Node(self.0.root_node())
}
pub(crate) fn as_ts_tree(&self) -> &OtherTree {
&self.0
}
}
#[derive(Clone, Copy, Debug)]
pub struct Node<'a>(OtherNode<'a>);
impl<'a> Node<'a> {
#[must_use]
#[inline]
pub fn as_tree_sitter(&self) -> OtherNode<'a> {
self.0
}
#[must_use]
pub fn has_error(&self) -> bool {
self.0.has_error()
}
#[must_use]
#[inline]
pub fn is_error(&self) -> bool {
self.0.is_error()
}
#[must_use]
#[inline]
pub fn is_missing(&self) -> bool {
self.0.is_missing()
}
#[inline]
#[must_use]
pub fn id(&self) -> usize {
self.0.id()
}
#[inline]
#[must_use]
pub fn kind(&self) -> &'static str {
self.0.kind()
}
#[inline]
#[must_use]
pub fn kind_id(&self) -> u16 {
self.0.kind_id()
}
#[inline]
#[must_use]
pub fn utf8_text(&self, data: &'a [u8]) -> Option<&'a str> {
let bytes = data.get(self.0.start_byte()..self.0.end_byte())?;
std::str::from_utf8(bytes).ok()
}
#[inline]
#[must_use]
pub fn start_byte(&self) -> usize {
self.0.start_byte()
}
#[inline]
#[must_use]
pub fn end_byte(&self) -> usize {
self.0.end_byte()
}
#[inline]
#[must_use]
pub fn start_position(&self) -> (usize, usize) {
let temp = self.0.start_position();
(temp.row, temp.column)
}
#[inline]
#[must_use]
pub fn end_position(&self) -> (usize, usize) {
let temp = self.0.end_position();
(temp.row, temp.column)
}
#[inline]
#[must_use]
pub fn start_row(&self) -> usize {
self.0.start_position().row
}
#[inline]
#[must_use]
pub fn end_row(&self) -> usize {
self.0.end_position().row
}
#[inline]
#[must_use]
pub fn end_line(&self) -> usize {
let end = self.0.end_position();
if end.column == 0 {
end.row
} else {
end.row + 1
}
}
#[inline]
pub fn parent(&self) -> Option<Node<'a>> {
self.0.parent().map(Node)
}
#[inline]
#[must_use]
pub fn has_sibling(&self, ancestors: Ancestors<'a, '_>, id: u16) -> bool {
ancestors
.parent(self)
.is_some_and(|parent| parent.is_child(id))
}
#[inline]
pub fn previous_sibling(&self) -> Option<Node<'a>> {
node_resolved_sibling_lookups::record();
self.0.prev_sibling().map(Node)
}
#[inline]
pub(crate) fn next_sibling(&self) -> Option<Node<'a>> {
node_resolved_sibling_lookups::record();
self.0.next_sibling().map(Node)
}
#[inline]
#[must_use]
pub fn is_child(&self, id: u16) -> bool {
self.wraps_any(&[id])
}
#[inline]
#[must_use]
pub fn wraps_any(&self, ids: &[u16]) -> bool {
self.children().any(|c| ids.contains(&c.kind_id()))
}
#[inline]
#[must_use]
pub fn child_count(&self) -> usize {
self.0.child_count()
}
#[inline]
#[must_use]
pub fn is_named(&self) -> bool {
self.0.is_named()
}
#[inline]
pub fn child_by_field_name(&self, name: &str) -> Option<Node<'a>> {
self.0.child_by_field_name(name).map(Node)
}
#[inline]
#[must_use]
pub fn child(&self, pos: usize) -> Option<Node<'a>> {
self.0.child(pos as u32).map(Node)
}
#[inline]
#[must_use]
pub fn field_name_for_child(&self, child_index: u32) -> Option<&'static str> {
self.0.field_name_for_child(child_index)
}
#[inline]
#[must_use]
pub fn children(&self) -> Children<'a> {
child_scan_cursors::record();
let mut cursor = self.cursor();
let scan = ChildScan::descend(self, &mut cursor);
Children { cursor, scan }
}
#[inline]
pub fn children_with<'c>(&self, cursor: &'c mut Cursor<'a>) -> ChildrenWith<'c, 'a> {
let scan = ChildScan::seed(self, cursor);
ChildrenWith { cursor, scan }
}
#[inline]
#[must_use]
pub fn cursor(&self) -> Cursor<'a> {
Cursor(self.0.walk())
}
pub fn count_specific_ancestors<C: Checker>(
&self,
ancestors: Ancestors<'a, '_>,
check: fn(&Node) -> bool,
stop: fn(&Node) -> bool,
) -> usize {
let mut count = 0;
for (parent, above_parent) in ancestors.iter(self) {
if stop(&parent) {
break;
}
if check(&parent) && !C::is_else_if(&parent, above_parent) {
count += 1;
}
}
count
}
pub fn parent_grandparent_match(
&self,
ancestors: Ancestors<'a, '_>,
parent_pred: fn(&Node) -> bool,
grand_pred: fn(&Node) -> bool,
) -> bool {
let mut climb = ancestors.iter(self);
let Some((parent, _)) = climb.next() else {
return false;
};
if !parent_pred(&parent) {
return false;
}
let Some((grand, _)) = climb.next() else {
return false;
};
grand_pred(&grand)
}
#[must_use]
pub fn preorder(&self) -> Preorder<'a> {
Preorder {
stack: vec![*self],
cursor: self.cursor(),
}
}
#[must_use]
pub fn descendants_by_kind(&self, kinds: &[&str]) -> Vec<Node<'a>> {
self.preorder()
.filter(|node| kinds.contains(&node.kind()))
.collect()
}
}
#[derive(Clone, Copy, Debug)]
pub struct Ancestors<'tree, 'chain>(Option<&'chain [Node<'tree>]>);
impl<'tree, 'chain> Ancestors<'tree, 'chain> {
#[inline]
#[must_use]
pub const fn unknown() -> Self {
Self(None)
}
#[inline]
#[must_use]
pub const fn known(chain: &'chain [Node<'tree>]) -> Self {
Self(Some(chain))
}
#[must_use]
pub fn checked(chain: &'chain [Node<'tree>], node: &Node<'tree>) -> Self {
#[cfg(chain_audit)]
assert_eq!(
chain.last().map(Node::id),
node.parent().map(|parent| parent.id()),
"ancestor chain desynchronised on a {} node",
node.kind()
);
debug_assert!(
chain.last().is_none_or(|parent| {
parent.id() != node.id()
&& parent.start_byte() <= node.start_byte()
&& node.end_byte() <= parent.end_byte()
}),
"ancestor chain desynchronised on a {} node: chain.last() neither \
contains it nor differs from it",
node.kind()
);
Self::known(chain)
}
#[inline]
pub fn depth(self) -> Option<usize> {
self.0.map(<[Node<'tree>]>::len)
}
#[inline]
#[must_use]
pub fn parent(self, node: &Node<'tree>) -> Option<Node<'tree>> {
match self.0 {
Some(chain) => chain.last().copied(),
None => node.parent(),
}
}
#[inline]
#[must_use]
pub fn parent_has_kind(self, node: &Node<'tree>, kind: u16) -> bool {
self.parent(node).is_some_and(|p| p.kind_id() == kind)
}
#[inline]
#[must_use]
pub fn previous_sibling(self, node: &Node<'tree>) -> Option<Node<'tree>> {
let Some(chain) = self.0 else {
return node.previous_sibling();
};
let parent = chain.last()?;
let mut previous = None;
for child in parent.children() {
if child.id() == node.id() {
return previous;
}
previous = Some(child);
}
node.previous_sibling()
}
#[inline]
#[must_use]
pub fn iter(self, node: &Node<'tree>) -> AncestorIter<'tree, 'chain> {
match self.0 {
Some(chain) => AncestorIter::Chain(chain),
None => AncestorIter::Climb(node.parent()),
}
}
}
pub enum AncestorIter<'tree, 'chain> {
Chain(&'chain [Node<'tree>]),
Climb(Option<Node<'tree>>),
}
impl<'tree, 'chain> Iterator for AncestorIter<'tree, 'chain> {
type Item = (Node<'tree>, Ancestors<'tree, 'chain>);
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Chain(remaining) => {
let (&nearest, above) = remaining.split_last()?;
*remaining = above;
Some((nearest, Ancestors::known(above)))
}
Self::Climb(next) => {
let nearest = (*next)?;
*next = nearest.parent();
Some((nearest, Ancestors::unknown()))
}
}
}
}
pub struct Preorder<'a> {
stack: Vec<Node<'a>>,
cursor: Cursor<'a>,
}
impl<'a> Iterator for Preorder<'a> {
type Item = Node<'a>;
fn next(&mut self) -> Option<Self::Item> {
let node = self.stack.pop()?;
let first_child = self.stack.len();
let Self { stack, cursor } = self;
stack.extend(node.children_with(cursor));
stack[first_child..].reverse();
Some(node)
}
}
#[derive(Clone)]
pub struct Cursor<'a>(TreeCursor<'a>);
impl<'a> Cursor<'a> {
#[inline]
pub fn reset(&mut self, node: &Node<'a>) {
self.0.reset(node.0);
}
#[inline]
pub fn goto_next_sibling(&mut self) -> bool {
self.0.goto_next_sibling()
}
#[inline]
pub fn goto_first_child(&mut self) -> bool {
self.0.goto_first_child()
}
#[inline]
pub fn goto_first_child_for_byte(&mut self, byte: usize) -> Option<usize> {
self.0.goto_first_child_for_byte(byte)
}
#[inline]
#[must_use]
pub fn node(&self) -> Node<'a> {
Node(self.0.node())
}
}
struct ChildScan {
done: bool,
remaining: usize,
}
impl ChildScan {
fn seed<'a>(node: &Node<'a>, cursor: &mut Cursor<'a>) -> Self {
cursor.reset(node);
Self::descend(node, cursor)
}
fn descend<'a>(node: &Node<'a>, cursor: &mut Cursor<'a>) -> Self {
let done = !cursor.goto_first_child();
Self {
done,
remaining: if done { 0 } else { node.child_count() },
}
}
fn step<'a>(&mut self, cursor: &mut Cursor<'a>) -> Option<Node<'a>> {
if self.done {
return None;
}
let result = cursor.node();
self.done = !cursor.goto_next_sibling();
self.remaining = if self.done {
0
} else {
self.remaining.saturating_sub(1)
};
Some(result)
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
pub struct Children<'a> {
cursor: Cursor<'a>,
scan: ChildScan,
}
impl<'a> Iterator for Children<'a> {
type Item = Node<'a>;
fn next(&mut self) -> Option<Self::Item> {
self.scan.step(&mut self.cursor)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.scan.size_hint()
}
}
impl ExactSizeIterator for Children<'_> {}
pub struct ChildrenWith<'c, 'a> {
cursor: &'c mut Cursor<'a>,
scan: ChildScan,
}
impl<'a> Iterator for ChildrenWith<'_, 'a> {
type Item = Node<'a>;
fn next(&mut self) -> Option<Self::Item> {
self.scan.step(self.cursor)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.scan.size_hint()
}
}
impl ExactSizeIterator for ChildrenWith<'_, '_> {}
impl<'a> Search<'a> for Node<'a> {
fn act_on_node(&self, action: &mut dyn FnMut(&Node<'a>, Ancestors<'a, '_>)) {
let mut cursor = self.cursor();
let mut stack = Vec::new();
let mut chain: Vec<Node<'a>> = std::iter::successors(self.parent(), Node::parent).collect();
chain.reverse();
let depth = chain.len();
stack.push((*self, depth));
while let Some((node, depth)) = stack.pop() {
chain.truncate(depth);
action(&node, Ancestors::checked(&chain, &node));
chain.push(node);
let first_child = stack.len();
stack.extend(
node.children_with(&mut cursor)
.map(|child| (child, depth + 1)),
);
stack[first_child..].reverse();
}
}
fn first_child(&self, pred: fn(u16) -> bool) -> Option<Node<'a>> {
self.children().find(|&child| pred(child.kind_id()))
}
fn act_on_child(&self, action: &mut dyn FnMut(&Node<'a>)) {
for child in self.children() {
action(&child);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::langs::MozjsCode;
use crate::test_support::for_each_node_with_chain;
#[cfg(feature = "mozjs")]
fn sibling_chain_has_sibling(node: OtherNode, id: u16) -> bool {
node.parent().is_some_and(|parent| {
let mut cur = parent.child(0);
while let Some(c) = cur {
if c.kind_id() == id {
return true;
}
cur = c.next_sibling();
}
false
})
}
#[test]
#[cfg(feature = "rust")]
fn utf8_text_is_none_for_a_span_past_the_buffer() {
let code = b"fn f() {}\n";
let tree = Tree::new::<crate::langs::RustCode>(code);
let root = tree.get_root();
assert_eq!(root.utf8_text(code), Some("fn f() {}\n"));
assert_eq!(root.utf8_text(&code[..4]), None);
}
#[cfg(feature = "mozjs")]
#[test]
fn has_sibling_matches_the_retired_sibling_chain() {
let code = b"const o = { m: (a) => a + 1, n: function () {} }; foo.bar();";
let tree = Tree::new::<MozjsCode>(code);
let ts_tree = tree.as_ts_tree();
let mut kinds = std::collections::BTreeSet::new();
let mut stack = vec![ts_tree.root_node()];
while let Some(n) = stack.pop() {
kinds.insert(n.kind_id());
let mut child = n.child(0);
while let Some(c) = child {
stack.push(c);
child = c.next_sibling();
}
}
let absent_id = u16::MAX;
let mut stack = vec![ts_tree.root_node()];
let mut matched = 0;
while let Some(n) = stack.pop() {
let wrapped = Node(n);
for &id in kinds.iter().chain(std::iter::once(&absent_id)) {
let found = wrapped.has_sibling(Ancestors::unknown(), id);
assert_eq!(
found,
sibling_chain_has_sibling(n, id),
"has_sibling diverged from the retired sibling chain at node kind {} for id {id}",
n.kind(),
);
matched += usize::from(found);
}
let mut child = n.child(0);
while let Some(c) = child {
stack.push(c);
child = c.next_sibling();
}
}
assert!(
matched > 0,
"every answer was `false`, so the sibling scan was never exercised"
);
let root = Node(ts_tree.root_node());
assert!(!root.has_sibling(Ancestors::unknown(), absent_id));
for &id in &kinds {
assert!(
!root.has_sibling(Ancestors::unknown(), id),
"root node has no parent → no sibling"
);
}
}
#[cfg(feature = "mozjs")]
#[test]
fn children_matches_tree_sitter_child_walk() {
let code = b"const o = { m: (a) => a + 1 }; foo(); ;";
let tree = Tree::new::<MozjsCode>(code);
let ts_tree = tree.as_ts_tree();
let mut stack = vec![ts_tree.root_node()];
while let Some(n) = stack.pop() {
let wrapped = Node(n);
let expected: Vec<_> = (0..n.child_count() as u32)
.filter_map(|i| n.child(i))
.map(|c| (c.id(), c.kind_id()))
.collect();
let actual =
drain_checking_exact_size(wrapped.children(), expected.len(), "children", n.kind());
assert_eq!(
actual,
expected,
"children() diverged from child(i) walk at kind {}",
n.kind(),
);
for i in 0..n.child_count() as u32 {
if let Some(c) = n.child(i) {
stack.push(c);
}
}
}
}
#[cfg(feature = "cpp")]
#[test]
fn child_by_field_name_outlives_self_borrow() {
fn find_named_child<'a>(parent: Node<'a>) -> Option<Node<'a>> {
let borrowed: &Node<'a> = &parent;
borrowed.child_by_field_name("declarator")
}
let code = b"int answer = 42;";
let tree = Tree::new::<crate::langs::CppCode>(code);
let root = tree.get_root();
let mut held: Option<Node> = None;
let mut stack = vec![root];
while let Some(n) = stack.pop() {
if n.kind() == "declaration" {
held = find_named_child(n);
break;
}
for child in n.children() {
stack.push(child);
}
}
let declarator = held.expect("C declaration has a `declarator` field");
assert_eq!(declarator.kind(), "init_declarator");
}
#[cfg(feature = "cpp")]
#[test]
fn as_tree_sitter_round_trips_wrapper_kind() {
use crate::{CppParser, ParserTrait};
use std::path::Path;
let source = b"int main() { return 0; }";
let parser = CppParser::new(source.to_vec(), Path::new("example.cpp"), None);
let root = parser.root();
let ts_root = root.as_tree_sitter();
assert_eq!(ts_root.kind(), "translation_unit");
assert_eq!(ts_root.kind(), root.kind());
assert_eq!(ts_root.kind_id(), root.kind_id());
assert!(!ts_root.has_error());
assert!(ts_root.child_count() > 0);
}
#[cfg(feature = "cpp")]
fn ground_truth_preorder(node: OtherNode) -> Vec<usize> {
let mut out = vec![node.id()];
for i in 0..node.child_count() as u32 {
if let Some(child) = node.child(i) {
out.extend(ground_truth_preorder(child));
}
}
out
}
#[cfg(feature = "cpp")]
#[test]
fn preorder_matches_recursive_document_order() {
let code = b"int main() { int x = 1; foo(x); return 0; }";
let tree = Tree::new::<crate::langs::CppCode>(code);
let root = tree.get_root();
let actual: Vec<usize> = root.preorder().map(|n| n.id()).collect();
let expected = ground_truth_preorder(root.as_tree_sitter());
assert_eq!(
actual, expected,
"preorder diverged from recursive child(0..n) document order"
);
assert!(actual.len() > 5, "expected a multi-node tree");
assert_eq!(actual[0], root.id(), "root must be yielded first");
}
#[cfg(feature = "cpp")]
#[test]
fn descendants_by_kind_collects_matching_subtree_nodes() {
let code = b"int main() { int x = 1; return x + x; }";
let tree = Tree::new::<crate::langs::CppCode>(code);
let root = tree.get_root();
let found = root.descendants_by_kind(&["identifier"]);
let expected: Vec<usize> = root
.preorder()
.filter(|n| n.kind() == "identifier")
.map(|n| n.id())
.collect();
let actual: Vec<usize> = found.iter().map(Node::id).collect();
assert_eq!(actual, expected);
assert!(
found.len() >= 3,
"expected at least the `main`, `x` decl, and `x` uses"
);
assert!(
found.iter().all(|n| n.kind() == "identifier"),
"every collected node must match the requested kind"
);
assert!(root.descendants_by_kind(&["no_such_kind"]).is_empty());
assert!(
root.descendants_by_kind(&["identifier", "number_literal"])
.len()
> found.len(),
"adding `number_literal` must widen the match set"
);
}
#[cfg(feature = "mozjs")]
fn drain_checking_exact_size<'a>(
mut iter: impl ExactSizeIterator<Item = Node<'a>>,
child_count: usize,
what: &str,
kind: &str,
) -> Vec<(usize, u16)> {
assert_eq!(
iter.len(),
child_count,
"{what}().len() disagreed with child_count at kind {kind}"
);
let mut remaining = child_count;
let mut drained = Vec::with_capacity(remaining);
while let Some(child) = iter.next() {
remaining -= 1;
assert_eq!(
iter.len(),
remaining,
"{what}() size_hint drifted mid-iteration at kind {kind}"
);
drained.push((child.id(), child.kind_id()));
}
assert_eq!(
iter.len(),
0,
"{what}() was not drained to zero len at kind {kind}"
);
drained
}
#[cfg(any(
feature = "c",
feature = "elixir",
feature = "java",
feature = "javascript",
feature = "kotlin",
feature = "python",
feature = "ruby",
feature = "rust",
))]
fn ancestor_ids(ancestors: Ancestors<'_, '_>, node: &Node<'_>) -> Vec<usize> {
ancestors.iter(node).map(|(a, _)| a.id()).collect()
}
#[cfg(all(
feature = "c",
feature = "elixir",
feature = "java",
feature = "javascript",
feature = "kotlin",
feature = "python",
feature = "ruby",
feature = "rust",
))]
#[test]
fn a_known_chain_answers_exactly_what_climbing_answers() {
#[cfg(any(
feature = "c",
feature = "elixir",
feature = "java",
feature = "javascript",
feature = "kotlin",
feature = "python",
feature = "ruby",
feature = "rust",
))]
fn assert_parity<L: LanguageInfo>(label: &str, code: &[u8], must_nest: &[&str]) {
let mut nested_seen = vec![false; must_nest.len()];
let visited = for_each_node_with_chain::<L>(code, |node, chain| {
for (slot, kind) in nested_seen.iter_mut().zip(must_nest) {
*slot |= node.kind() == *kind
&& chain.iter().any(|ancestor| ancestor.kind() == *kind);
}
let known = Ancestors::known(chain);
let climbing = Ancestors::unknown();
assert_eq!(
known.parent(node).map(|p| p.id()),
climbing.parent(node).map(|p| p.id()),
"{label}: parent of {} disagrees",
node.kind()
);
assert_eq!(
known.previous_sibling(node).map(|p| p.id()),
climbing.previous_sibling(node).map(|p| p.id()),
"{label}: previous sibling of {} disagrees",
node.kind()
);
assert_eq!(
ancestor_ids(known, node),
ancestor_ids(climbing, node),
"{label}: ancestor chain of {} disagrees",
node.kind()
);
for (ancestor, above) in known.iter(node) {
assert_eq!(
above.parent(&ancestor).map(|p| p.id()),
ancestor.parent().map(|p| p.id()),
"{label}: sub-chain handed to {} is not its own",
ancestor.kind()
);
}
});
assert!(visited > 20, "{label}: fixture is too small to prove much");
for (found, kind) in nested_seen.iter().zip(must_nest) {
assert!(
found,
"{label}: no `{kind}` sits inside another `{kind}`, so the \
fixture no longer exercises the nesting it was added for"
);
}
}
assert_parity::<crate::langs::CCode>(
"c",
b"int main() { if (a) { int x; } else if (b) { for (int i = 0; i < 2; i++) x; } }",
&[],
);
assert_parity::<crate::langs::JavaCode>(
"java",
b"class A { void m() { if (a) {} else if (b) {} else {} for (int i = 0; i < 2; i++) {} } }",
&[],
);
assert_parity::<crate::langs::PythonCode>(
"python",
b"def f(a, b):\n if a:\n pass\n else:\n if b:\n pass\n return a and b or a\n",
&[],
);
assert_parity::<crate::langs::ElixirCode>(
"elixir",
b"defmodule M do\n def g do\n :ok\n end\n quote do\n def f do\n :ok\n end\n end\nend\n",
&["call"],
);
assert_parity::<crate::langs::RustCode>(
"rust",
b"fn f(a: bool) { if a { } else if a { } fn g(b: bool) { if b { } } }\n",
&["function_item"],
);
assert_parity::<crate::langs::KotlinCode>(
"kotlin",
b"fun f(x: Int) {\n when (x) {\n 1 -> {}\n else -> {}\n }\n fun g() {\n if (x > 0) {}\n }\n}\n",
&["function_declaration"],
);
assert_parity::<crate::langs::RubyCode>(
"ruby",
b"def f(x)\n case x\n when 1 then 1\n else 2\n end\n def g\n if x\n end\n end\nend\n",
&["method"],
);
assert_parity::<crate::langs::JavascriptCode>(
"javascript",
b"const f = a => { a => { g(() => 1); }; };\nconst o = { m: function () { return 1; } };\n",
&["arrow_function"],
);
}
#[cfg(all(feature = "c", feature = "mozjs"))]
#[test]
fn the_converted_traversals_scan_a_tree_on_one_cursor() {
let seed_tree = Tree::new::<crate::langs::CCode>(b"int main() { int a; }");
let _ = seed_tree.get_root().children().count();
assert!(
child_scan_cursors::observed() > 0,
"the seed scan must be counted"
);
let tree = Tree::new::<MozjsCode>(
b"const o = { m: (a) => a + 1, n: function () { return [1, 2, 3]; } };\nfoo(o);\n",
);
let before = child_scan_cursors::observed();
let visited = tree.get_root().preorder().count();
assert!(visited > 40, "fixture is too small to prove much");
assert_eq!(
child_scan_cursors::observed(),
before,
"preorder built a cursor per node; it holds one for the walk (#1112)"
);
let tree = Tree::new::<MozjsCode>(
b"function f(a) { return { g: (b) => b + 1, h: [1, 2, 3] }; }\nf(2);\n",
);
let root = tree.get_root();
let nodes = root.preorder().count();
assert!(nodes > 30, "fixture is too small to prove much");
let before = child_scan_cursors::observed();
let mut seen = 0_usize;
root.act_on_node(&mut |_, _| seen += 1);
let scans = child_scan_cursors::observed() - before;
assert_eq!(seen, nodes, "act_on_node must visit every node");
assert_eq!(
scans, 0,
"act_on_node built {scans} cursors over {nodes} nodes; it holds one \
for the walk (#1112)"
);
}
#[cfg(feature = "c")]
#[test]
fn parent_grandparent_match_is_false_when_either_link_is_absent() {
let tree = Tree::new::<crate::langs::CCode>(b"int main() { int a; }");
let root = tree.get_root();
let child = root.children().next().expect("the file has an item");
let grandchild = child
.children()
.next()
.expect("the function definition has children");
let yes: fn(&Node) -> bool = |_| true;
assert!(!root.parent_grandparent_match(Ancestors::unknown(), yes, yes));
assert!(!root.parent_grandparent_match(Ancestors::known(&[]), yes, yes));
assert!(!child.parent_grandparent_match(Ancestors::unknown(), yes, yes));
assert!(!child.parent_grandparent_match(Ancestors::known(&[root]), yes, yes));
assert!(grandchild.parent_grandparent_match(Ancestors::unknown(), yes, yes));
assert!(grandchild.parent_grandparent_match(Ancestors::known(&[root, child]), yes, yes));
}
#[cfg(feature = "mozjs")]
#[test]
fn children_with_yields_exactly_what_children_does() {
let code = b"const o = { m: (a) => a + 1, n: function () {} }; foo(); ;";
let tree = Tree::new::<MozjsCode>(code);
let root = tree.get_root();
let mut cursor = root.cursor();
let mut leaves = 0;
let mut widest = 0;
for node in root.preorder() {
let raw = node.as_tree_sitter();
let expected: Vec<_> = (0..raw.child_count() as u32)
.filter_map(|i| raw.child(i))
.map(|c| (c.id(), c.kind_id()))
.collect();
let actual = drain_checking_exact_size(
node.children_with(&mut cursor),
expected.len(),
"children_with",
node.kind(),
);
assert_eq!(
actual,
expected,
"children_with diverged from the child(i) walk at kind {}",
node.kind()
);
leaves += usize::from(expected.is_empty());
widest = widest.max(expected.len());
}
assert!(leaves > 0, "fixture must contain childless nodes");
assert!(widest > 2, "fixture must contain a multi-child node");
}
#[cfg(all(feature = "c", feature = "javascript", feature = "python"))]
#[test]
fn checked_accepts_the_chains_the_walkers_build() {
let mut equal_span_pairs = 0;
let mut check = |node: &Node<'_>, chain: &[Node<'_>]| {
let _ = Ancestors::checked(chain, node);
if let Some(parent) = chain.last()
&& parent.start_byte() == node.start_byte()
&& parent.end_byte() == node.end_byte()
{
equal_span_pairs += 1;
}
};
let visited = for_each_node_with_chain::<crate::langs::CCode>(
b"int main() { if (a) { int x; } else { f(a, b); } }",
&mut check,
) + for_each_node_with_chain::<crate::langs::JavascriptCode>(
b"const o = { m: (a) => a + 1 };\nfoo.bar();\n",
&mut check,
) + for_each_node_with_chain::<crate::langs::PythonCode>(
b"def f(a):\n if a:\n return [x for x in a]\n",
&mut check,
);
assert!(visited > 60, "fixtures are too small to prove much");
assert!(
equal_span_pairs > 0,
"no parent spans exactly what its child does, so this fixture set \
cannot tell non-strict containment from strict"
);
}
#[cfg(feature = "c")]
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "ancestor chain desynchronised")]
fn checked_rejects_a_chain_ending_in_the_node_itself() {
let tree = Tree::new::<crate::langs::CCode>(b"int main() { int a; }");
let body = tree
.get_root()
.preorder()
.find(|n| n.kind() == "compound_statement")
.expect("fixture has a function body");
let _ = Ancestors::checked(std::slice::from_ref(&body), &body);
}
#[cfg(feature = "c")]
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "ancestor chain desynchronised")]
fn checked_rejects_a_chain_from_a_disjoint_subtree() {
let tree = Tree::new::<crate::langs::CCode>(b"int main() { int a; int b; }");
let body = tree
.get_root()
.preorder()
.find(|n| n.kind() == "compound_statement")
.expect("fixture has a function body");
let declarations: Vec<Node<'_>> = body
.children()
.filter(|n| n.kind() == "declaration")
.collect();
assert_eq!(declarations.len(), 2, "fixture has two declarations");
let _ = Ancestors::checked(&declarations[..1], &declarations[1]);
}
#[cfg(feature = "javascript")]
#[test]
fn has_sibling_agrees_between_known_and_climbing() {
let code = b"const o = { m: (a) => a + 1, n: function () {} };\nconst p = a => a;\n";
let mut kinds = std::collections::BTreeSet::new();
for_each_node_with_chain::<crate::langs::JavascriptCode>(code, |node, _| {
kinds.insert(node.kind_id());
});
let absent = u16::MAX;
let mut agreed_true = 0;
let visited =
for_each_node_with_chain::<crate::langs::JavascriptCode>(code, |node, chain| {
for &id in kinds.iter().chain(std::iter::once(&absent)) {
let known = node.has_sibling(Ancestors::known(chain), id);
let climbing = node.has_sibling(Ancestors::unknown(), id);
assert_eq!(
known,
climbing,
"has_sibling({id}) on {} disagrees between chain and climb",
node.kind()
);
agreed_true += usize::from(known);
}
});
assert!(visited > 20, "fixture is too small to prove much");
assert!(
agreed_true > 0,
"every answer was `false`, so the sibling scan never ran to a match"
);
}
#[cfg(feature = "mozjs")]
#[test]
fn act_on_node_hands_a_subtree_its_real_ancestry() {
let code = b"var outer = function () { return 1; };\n";
let tree = Tree::new::<MozjsCode>(code);
let root = tree.get_root();
let subtree = root
.preorder()
.find(|n| n.kind() == "variable_declarator")
.expect("fixture has a variable_declarator");
assert!(
subtree.parent().is_some(),
"the walk must start below the root, else the seed is vacuous"
);
let mut visited = 0;
subtree.act_on_node(&mut |node, ancestors| {
assert_eq!(
ancestors.parent(node).map(|p| p.id()),
node.parent().map(|p| p.id()),
"parent of {} disagrees with the tree",
node.kind()
);
visited += 1;
});
assert!(visited > 3, "subtree is too small to prove much");
}
#[cfg(feature = "c")]
#[test]
fn previous_sibling_falls_back_on_a_chain_that_is_not_this_nodes() {
let code = b"int main() { int a; int b; }";
let tree = Tree::new::<crate::langs::CCode>(code);
let root = tree.get_root();
let body = root
.preorder()
.find(|n| n.kind() == "compound_statement")
.expect("fixture has a function body");
let declarations: Vec<Node<'_>> = body
.children()
.filter(|n| n.kind() == "declaration")
.collect();
assert_eq!(declarations.len(), 2, "fixture has two declarations");
let second = declarations[1];
let expected = second
.previous_sibling()
.map(|p| p.id())
.expect("the second declaration has a previous sibling");
let foreign = [root];
assert_eq!(
Ancestors::known(&foreign)
.previous_sibling(&second)
.map(|p| p.id()),
Some(expected),
"a mismatched chain must fall back, not report `None`"
);
assert!(
Ancestors::known(&[]).previous_sibling(&second).is_none(),
"an empty chain means `second` is the root, which has no siblings"
);
}
#[cfg(feature = "c")]
#[test]
fn count_specific_ancestors_agrees_between_known_and_climbing() {
let code =
b"int main() { int a; if (x) { int b; } for (int i = 0; i < 2; i++) { int c; } }";
let mut counted = 0;
let mut nonzero = 0;
let visited = for_each_node_with_chain::<crate::langs::CCode>(code, |node, chain| {
if node.kind() != "declaration" {
return;
}
let check: fn(&Node) -> bool = |n| {
matches!(
n.kind(),
"while_statement" | "for_statement" | "if_statement"
)
};
let stop: fn(&Node) -> bool = |n| n.kind() == "compound_statement";
let known = node.count_specific_ancestors::<crate::langs::CCode>(
Ancestors::known(chain),
check,
stop,
);
let climbing = node.count_specific_ancestors::<crate::langs::CCode>(
Ancestors::unknown(),
check,
stop,
);
assert_eq!(
known,
climbing,
"declaration at row {}: known chain counted {known}, climbing counted {climbing}",
node.start_row()
);
counted += 1;
nonzero += usize::from(known > 0);
});
assert!(visited > 20);
assert_eq!(counted, 4, "fixture must hold four declarations");
assert_eq!(
nonzero, 1,
"only the `for`-header declaration sits under a header with no block between"
);
}
}