1use lanekeep_query::CompiledQuery;
32use std::collections::HashMap;
33
34use lanekeep_lang::binding::{Binding, BindingResolver};
35use tree_sitter::{Node, Tree};
36
37pub type Handle = u32;
39
40#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct StructureFingerprint {
47 pub hash: String,
49 pub nodes: u32,
53}
54
55fn child_indices(node: Node<'_>) -> std::ops::Range<u32> {
60 0..u32::try_from(node.child_count()).unwrap_or(u32::MAX)
61}
62
63#[derive(Debug)]
65pub struct NodeArena {
66 tree: Tree,
67 source: String,
68 paths: Vec<Vec<u32>>,
70 by_id: HashMap<usize, Handle>,
72}
73
74impl NodeArena {
75 #[must_use]
79 pub fn new(tree: Tree, source: String) -> Self {
80 let root_id = tree.root_node().id();
81 let mut arena = Self {
82 tree,
83 source,
84 paths: Vec::new(),
85 by_id: HashMap::new(),
86 };
87 arena.paths.push(Vec::new());
88 arena.by_id.insert(root_id, 0);
89 arena
90 }
91
92 pub const ROOT: Handle = 0;
96
97 #[must_use]
99 pub fn source(&self) -> &str {
100 &self.source
101 }
102
103 #[must_use]
106 pub fn len(&self) -> usize {
107 self.paths.len()
108 }
109
110 #[must_use]
112 pub fn is_empty(&self) -> bool {
113 self.paths.len() <= 1
114 }
115
116 fn node_at(&self, path: &[u32]) -> Option<Node<'_>> {
118 let mut node = self.tree.root_node();
119 for index in path {
120 node = node.child(*index)?;
121 }
122 Some(node)
123 }
124
125 fn node(&self, handle: Handle) -> Option<Node<'_>> {
127 let path = self.paths.get(handle as usize)?;
128 self.node_at(path)
129 }
130
131 fn intern(&mut self, id: usize, path: Vec<u32>) -> Handle {
133 if let Some(existing) = self.by_id.get(&id) {
134 return *existing;
135 }
136 let handle = Handle::try_from(self.paths.len()).unwrap_or(Handle::MAX);
138 self.paths.push(path);
139 self.by_id.insert(id, handle);
140 handle
141 }
142
143 fn intern_child(&mut self, parent_path: &[u32], index: u32) -> Option<Handle> {
148 let mut path = parent_path.to_vec();
149 path.push(index);
150 let id = self.node_at(&path)?.id();
151 Some(self.intern(id, path))
152 }
153
154 #[must_use]
156 pub fn kind(&self, handle: Handle) -> Option<&'static str> {
157 self.node(handle).map(|node| node.kind())
158 }
159
160 #[must_use]
162 pub fn is_named(&self, handle: Handle) -> Option<bool> {
163 self.node(handle).map(|node| node.is_named())
164 }
165
166 #[must_use]
168 pub fn text(&self, handle: Handle) -> Option<&str> {
169 let node = self.node(handle)?;
170 self.source.get(node.byte_range())
171 }
172
173 #[must_use]
175 pub fn position(&self, handle: Handle) -> Option<(u32, u32)> {
176 let node = self.node(handle)?;
177 let start = node.start_position();
178 Some((
179 u32::try_from(start.row)
180 .unwrap_or(u32::MAX)
181 .saturating_add(1),
182 u32::try_from(start.column)
183 .unwrap_or(u32::MAX)
184 .saturating_add(1),
185 ))
186 }
187
188 #[must_use]
190 pub fn byte_range(&self, handle: Handle) -> Option<(usize, usize)> {
191 self.node(handle)
192 .map(|node| (node.start_byte(), node.end_byte()))
193 }
194
195 #[must_use]
200 pub fn resolve_binding(
201 &self,
202 handle: Handle,
203 resolver: &dyn BindingResolver,
204 ) -> Option<Binding> {
205 let node = self.node(handle)?;
206 resolver.resolve(&self.tree, &self.source, node)
207 }
208
209 #[must_use]
211 pub fn is_shadowed(&self, handle: Handle, resolver: &dyn BindingResolver) -> bool {
212 self.node(handle)
213 .is_some_and(|node| resolver.is_shadowed(&self.tree, &self.source, node))
214 }
215
216 #[must_use]
224 pub const fn tree(&self) -> &Tree {
225 &self.tree
226 }
227
228 #[must_use]
234 pub fn path_of(&self, node: Node<'_>) -> Option<Vec<u32>> {
235 let mut path = Vec::new();
237 let mut current = node;
238 while let Some(parent) = current.parent() {
239 let index = child_indices(parent)
240 .find(|i| parent.child(*i).is_some_and(|c| c.id() == current.id()))?;
241 path.push(index);
242 current = parent;
243 }
244 path.reverse();
245
246 if self
247 .node_at(&path)
248 .is_none_or(|found| found.id() != node.id())
249 {
250 return None;
251 }
252 Some(path)
253 }
254
255 pub fn intern_path(&mut self, path: Vec<u32>) -> Option<Handle> {
257 let id = self.node_at(&path)?.id();
258 Some(self.intern(id, path))
259 }
260
261 pub fn parent(&mut self, handle: Handle) -> Option<Handle> {
263 let path = self.paths.get(handle as usize)?.clone();
264 if path.is_empty() {
265 return None;
266 }
267
268 let parent_path = path[..path.len() - 1].to_vec();
269 let id = self.node_at(&parent_path)?.id();
270 Some(self.intern(id, parent_path))
271 }
272
273 pub fn children(&mut self, handle: Handle) -> Vec<Handle> {
275 self.children_matching(handle, false)
276 }
277
278 pub fn named_children(&mut self, handle: Handle) -> Vec<Handle> {
280 self.children_matching(handle, true)
281 }
282
283 #[must_use]
314 pub fn structure_fingerprint(&self, handle: Handle) -> Option<StructureFingerprint> {
315 let node = self.node(handle)?;
316 let mut fold = Fold::new();
317 fold.node(node);
318 let nodes = fold.nodes;
319 Some(StructureFingerprint {
320 hash: fold.finish(),
321 nodes,
322 })
323 }
324
325 fn children_matching(&mut self, handle: Handle, named_only: bool) -> Vec<Handle> {
326 let Some(path) = self.paths.get(handle as usize).cloned() else {
327 return Vec::new();
328 };
329
330 let indices: Vec<u32> = {
331 let Some(node) = self.node_at(&path) else {
332 return Vec::new();
333 };
334 child_indices(node)
335 .filter(|i| !named_only || node.child(*i).is_some_and(|c| c.is_named()))
336 .collect()
337 };
338
339 indices
340 .into_iter()
341 .filter_map(|i| self.intern_child(&path, i))
342 .collect()
343 }
344
345 #[must_use]
351 pub fn query_subtree(
352 &self,
353 handle: Handle,
354 query: &CompiledQuery,
355 ) -> Vec<Vec<(String, Vec<u32>)>> {
356 let Some(path) = self.paths.get(handle as usize) else {
357 return Vec::new();
358 };
359 let Some(node) = self.node_at(path) else {
360 return Vec::new();
361 };
362
363 let mut found = Vec::new();
364 query.for_each_match_in(node, self.source.as_bytes(), |m| {
365 found.push(
366 m.captures
367 .iter()
368 .filter_map(|(name, node)| {
369 self.path_of(*node).map(|path| ((*name).to_owned(), path))
370 })
371 .collect::<Vec<_>>(),
372 );
373 });
374 found
375 }
376
377 #[must_use]
384 pub fn closest_ancestor_paths(
385 &self,
386 handle: Handle,
387 query: &CompiledQuery,
388 ) -> Option<Vec<(String, Vec<u32>)>> {
389 let path = self.paths.get(handle as usize)?.clone();
390
391 for depth in (0..path.len()).rev() {
393 let Some(ancestor) = self.node_at(&path[..depth]) else {
394 continue;
395 };
396
397 let mut matched: Option<Vec<(String, Vec<u32>)>> = None;
398 query.for_each_match_in(ancestor, self.source.as_bytes(), |m| {
399 if matched.is_some() || !m.captures.iter().any(|(_, node)| *node == ancestor) {
400 return;
401 }
402 matched = Some(
403 m.captures
404 .iter()
405 .filter_map(|(name, node)| {
406 self.path_of(*node).map(|p| ((*name).to_owned(), p))
407 })
408 .collect(),
409 );
410 });
411
412 if matched.is_some() {
413 return matched;
414 }
415 }
416 None
417 }
418
419 pub fn ancestors(&mut self, handle: Handle) -> Vec<Handle> {
421 let Some(path) = self.paths.get(handle as usize).cloned() else {
422 return Vec::new();
423 };
424
425 let mut out = Vec::with_capacity(path.len());
426 for depth in (0..path.len()).rev() {
427 let ancestor_path = path[..depth].to_vec();
428 let Some(id) = self.node_at(&ancestor_path).map(|n| n.id()) else {
429 break;
430 };
431 out.push(self.intern(id, ancestor_path));
432 }
433 out
434 }
435}
436
437struct Fold {
443 hasher: blake3::Hasher,
444 nodes: u32,
445}
446
447impl Fold {
448 fn new() -> Self {
449 let mut hasher = blake3::Hasher::new();
450 hasher.update(&[1]);
453 Self { hasher, nodes: 0 }
454 }
455
456 fn node(&mut self, node: Node<'_>) {
459 if node.is_extra() {
460 return;
461 }
462 self.nodes = self.nodes.saturating_add(1);
463
464 let kind = node.kind();
465 let len = u32::try_from(kind.len()).unwrap_or(u32::MAX);
466 self.hasher.update(&len.to_le_bytes());
467 self.hasher.update(kind.as_bytes());
468
469 let mut count_cursor = node.walk();
473 let mut count: u32 = 0;
474 for child in node.children(&mut count_cursor) {
475 if !child.is_extra() {
476 count = count.saturating_add(1);
477 }
478 }
479 self.hasher.update(&count.to_le_bytes());
480
481 let mut child_cursor = node.walk();
482 for child in node.children(&mut child_cursor) {
483 self.node(child);
484 }
485 }
486
487 fn finish(self) -> String {
488 self.hasher.finalize().to_hex().to_string()
489 }
490}
491
492#[cfg(test)]
493mod tests {
494 use lanekeep_lang::Language;
495 use lanekeep_lang_js::TypeScript;
496
497 use super::*;
498
499 fn arena(source: &str) -> NodeArena {
500 let mut parser = tree_sitter::Parser::new();
501 parser
502 .set_language(&TypeScript.grammar())
503 .expect("grammar loads");
504 let tree = parser.parse(source, None).expect("parses");
505 NodeArena::new(tree, source.to_owned())
506 }
507
508 #[test]
509 fn the_root_is_always_handle_zero() {
510 let arena = arena("const x = 1;");
511 assert_eq!(NodeArena::ROOT, 0);
512 assert_eq!(arena.kind(0), Some("program"));
513 }
514
515 #[test]
516 fn resolves_kind_text_and_position() {
517 let mut arena = arena("const x = 1;\nconst y = 2;");
518 let statements = arena.named_children(NodeArena::ROOT);
519 assert_eq!(statements.len(), 2);
520
521 assert_eq!(arena.kind(statements[0]), Some("lexical_declaration"));
522 assert_eq!(arena.text(statements[0]), Some("const x = 1;"));
523 assert_eq!(arena.position(statements[0]), Some((1, 1)));
524 assert_eq!(arena.position(statements[1]), Some((2, 1)));
525 }
526
527 #[test]
528 fn walks_down_and_back_up() {
529 let mut arena = arena("const x = 1;");
530 let root = NodeArena::ROOT;
531 let declaration = arena.named_children(root)[0];
532 let declarator = arena.named_children(declaration)[0];
533
534 assert_eq!(arena.parent(declarator), Some(declaration));
535 assert_eq!(arena.parent(declaration), Some(root));
536 assert_eq!(arena.parent(root), None, "the root has no parent");
537 }
538
539 #[test]
540 fn handles_are_stable_for_the_same_node() {
541 let mut arena = arena("const x = 1;");
545 let root = NodeArena::ROOT;
546 let declaration = arena.named_children(root)[0];
547
548 let again = arena.named_children(root)[0];
549 assert_eq!(
550 declaration, again,
551 "the same child must intern to the same handle"
552 );
553
554 let declarator = arena.named_children(declaration)[0];
555 assert_eq!(
556 arena.parent(declarator),
557 Some(declaration),
558 "reaching a node from below must give the handle it already had"
559 );
560 }
561
562 #[test]
563 fn interning_is_lazy() {
564 let arena = arena("const a = 1; const b = 2; function c() { return [1,2,3] }");
567 assert!(
568 arena.is_empty(),
569 "only the root should be interned before any traversal"
570 );
571 assert_eq!(arena.len(), 1);
572 }
573
574 #[test]
575 fn only_touched_nodes_are_interned() {
576 let mut arena = arena("const a = 1; const b = 2; const c = 3;");
577 let before = arena.len();
578 let _ = arena.named_children(NodeArena::ROOT);
579 let after = arena.len();
580
581 assert!(after > before);
582 assert!(
583 after < 20,
584 "should intern three statements, not the whole tree: {after}"
585 );
586 }
587
588 #[test]
589 fn named_children_excludes_anonymous_tokens() {
590 let mut arena = arena("const x = 1;");
591 let declaration = arena.named_children(NodeArena::ROOT)[0];
592
593 let all = arena.children(declaration);
594 let named = arena.named_children(declaration);
595 assert!(all.len() > named.len(), "`const` and `;` are anonymous");
596 assert!(named.iter().all(|h| arena.is_named(*h) == Some(true)));
597 }
598
599 #[test]
600 fn ancestors_run_innermost_first_and_end_at_the_root() {
601 let mut arena = arena("function f() { return 1; }");
602 let root = NodeArena::ROOT;
603 let function = arena.named_children(root)[0];
604 let body = arena
605 .named_children(function)
606 .last()
607 .copied()
608 .expect("has a body");
609 let statement = arena.named_children(body)[0];
610
611 let ancestors = arena.ancestors(statement);
612 assert_eq!(ancestors.first(), Some(&body), "innermost first");
613 assert_eq!(ancestors.last(), Some(&root), "ending at the root");
614 assert!(ancestors.contains(&function));
615 }
616
617 #[test]
618 fn the_root_has_no_ancestors() {
619 let mut arena = arena("const x = 1;");
620 assert!(arena.ancestors(NodeArena::ROOT).is_empty());
621 }
622
623 #[test]
624 fn an_unknown_handle_yields_nothing_rather_than_panicking() {
625 let mut arena = arena("const x = 1;");
627 assert_eq!(arena.kind(9999), None);
628 assert_eq!(arena.text(9999), None);
629 assert_eq!(arena.position(9999), None);
630 assert_eq!(arena.parent(9999), None);
631 assert!(arena.children(9999).is_empty());
632 assert!(arena.ancestors(9999).is_empty());
633 }
634
635 #[test]
636 fn interns_a_node_reached_through_the_tree() {
637 let mut arena = arena("const x = 1;");
640
641 let (path, expected_kind) = {
642 let target = arena
643 .tree()
644 .root_node()
645 .child(0)
646 .and_then(|n| n.child(1))
647 .expect("has a declarator");
648 (arena.path_of(target).expect("has a path"), target.kind())
649 };
650
651 let handle = arena.intern_path(path.clone()).expect("interns");
652 assert_eq!(arena.kind(handle), Some(expected_kind));
653 assert_eq!(
654 arena.intern_path(path),
655 Some(handle),
656 "interning the same path twice must give the same handle"
657 );
658 }
659
660 #[test]
661 fn rejects_a_node_from_a_different_tree() {
662 let mut parser = tree_sitter::Parser::new();
665 parser
666 .set_language(&TypeScript.grammar())
667 .expect("grammar loads");
668 let other = parser
669 .parse("function totallyDifferent() { return 42 }", None)
670 .expect("parses");
671 let foreign = other.root_node().child(0).expect("has a child");
672
673 let arena = arena("const x = 1;");
674 assert_eq!(
675 arena.path_of(foreign),
676 None,
677 "a node from another tree must not be reducible to a path here"
678 );
679 }
680
681 #[test]
682 fn text_is_correct_for_multibyte_source() {
683 let mut arena = arena("const emoji = '🎯';\nconst after = 1;");
684 let statements = arena.named_children(NodeArena::ROOT);
685 assert_eq!(arena.text(statements[0]), Some("const emoji = '🎯';"));
686 assert_eq!(
687 arena.position(statements[1]),
688 Some((2, 1)),
689 "a multibyte character must not shift the following line"
690 );
691 }
692
693 fn py_arena(source: &str) -> NodeArena {
696 let mut parser = tree_sitter::Parser::new();
697 parser
698 .set_language(&lanekeep_lang_python::Python.grammar())
699 .expect("grammar loads");
700 let tree = parser.parse(source, None).expect("parses");
701 NodeArena::new(tree, source.to_owned())
702 }
703
704 #[test]
705 fn fingerprint_erases_identifier_names() {
706 let a = arena("function f() { return a + b }");
709 let b = arena("function g() { return c + d }");
710 assert_eq!(
711 a.structure_fingerprint(NodeArena::ROOT),
712 b.structure_fingerprint(NodeArena::ROOT)
713 );
714 }
715
716 #[test]
717 fn fingerprint_erases_literal_values_but_not_kinds() {
718 let one = arena("const x = 1;");
722 let two = arena("const x = 2;");
723 assert_eq!(
724 one.structure_fingerprint(NodeArena::ROOT),
725 two.structure_fingerprint(NodeArena::ROOT)
726 );
727
728 let string = arena("const x = 'a';");
729 assert_ne!(
730 one.structure_fingerprint(NodeArena::ROOT),
731 string.structure_fingerprint(NodeArena::ROOT)
732 );
733 }
734
735 #[test]
736 fn fingerprint_is_operator_sensitive() {
737 let plus = arena("function f() { return a + b }");
740 let minus = arena("function f() { return a - b }");
741 assert_ne!(
742 plus.structure_fingerprint(NodeArena::ROOT),
743 minus.structure_fingerprint(NodeArena::ROOT)
744 );
745 }
746
747 #[test]
748 fn fingerprint_is_statement_sensitive() {
749 let one = arena("function f() { return a + b }");
750 let two = arena("function f() { return a + b; a() }");
751 assert_ne!(
752 one.structure_fingerprint(NodeArena::ROOT),
753 two.structure_fingerprint(NodeArena::ROOT)
754 );
755 }
756
757 #[test]
758 fn fingerprint_ignores_comments() {
759 let plain = arena("function f() { return a + b }");
762 let commented = arena("// a doc comment\nfunction f() { return a + b }");
763 assert_eq!(
764 plain.structure_fingerprint(NodeArena::ROOT),
765 commented.structure_fingerprint(NodeArena::ROOT)
766 );
767 }
768
769 #[test]
770 fn fingerprint_counts_every_non_extra_node_exactly() {
771 let one = arena("const x = 1;");
777 assert_eq!(
778 one.structure_fingerprint(NodeArena::ROOT)
779 .expect("the root resolves")
780 .nodes,
781 8
782 );
783
784 let two = arena("const x = 1;\nconst y = 2;");
785 assert_eq!(
786 two.structure_fingerprint(NodeArena::ROOT)
787 .expect("the root resolves")
788 .nodes,
789 15
790 );
791 }
792
793 #[test]
794 fn fingerprint_is_subtree_scoped() {
795 let mut in_function = arena("function f() { return a + b }");
799 let fn_decl = in_function.named_children(NodeArena::ROOT)[0];
800 let body = in_function.named_children(fn_decl).last().copied().unwrap();
802 let return_stmt = in_function.named_children(body)[0];
803 let in_function_expr = in_function.named_children(return_stmt)[0];
804
805 let mut in_initializer = arena("const x = a + b;");
806 let declaration = in_initializer.named_children(NodeArena::ROOT)[0];
807 let declarator = in_initializer.named_children(declaration)[0];
808 let in_initializer_expr = in_initializer
810 .named_children(declarator)
811 .last()
812 .copied()
813 .unwrap();
814
815 assert_eq!(
816 in_function.structure_fingerprint(in_function_expr),
817 in_initializer.structure_fingerprint(in_initializer_expr)
818 );
819 }
820
821 #[test]
822 fn fingerprint_is_deterministic_across_calls_and_parses() {
823 let first = arena("function f() { return a + b }");
824 let once = first
825 .structure_fingerprint(NodeArena::ROOT)
826 .expect("the root resolves");
827 let twice = first
828 .structure_fingerprint(NodeArena::ROOT)
829 .expect("the root resolves");
830 assert_eq!(
831 once, twice,
832 "the same arena must answer the same fingerprint twice"
833 );
834
835 let again = arena("function f() { return a + b }");
836 assert_eq!(
837 once,
838 again
839 .structure_fingerprint(NodeArena::ROOT)
840 .expect("the root resolves"),
841 "a fresh parse of the same bytes must hash identically"
842 );
843 }
844
845 #[test]
846 fn fingerprint_of_a_dead_handle_is_none() {
847 let arena = arena("const x = 1;");
848 assert_eq!(arena.structure_fingerprint(9999), None);
849 }
850
851 #[test]
852 fn fingerprint_is_language_agnostic() {
853 let a = py_arena("def f():\n return a + b\n");
856 let b = py_arena("def g():\n return c + d\n");
857 assert_eq!(
858 a.structure_fingerprint(NodeArena::ROOT),
859 b.structure_fingerprint(NodeArena::ROOT)
860 );
861 }
862
863 #[test]
864 fn fingerprint_normalization_holds_for_python() {
865 let plus = py_arena("def f():\n return a + b\n");
870 let minus = py_arena("def f():\n return a - b\n");
871 assert_ne!(
872 plus.structure_fingerprint(NodeArena::ROOT),
873 minus.structure_fingerprint(NodeArena::ROOT),
874 "a `+` and a `-` are different anonymous kinds in Python too"
875 );
876
877 let one = py_arena("def f():\n return 1\n");
878 let string = py_arena("def f():\n return 'a'\n");
879 assert_ne!(
880 one.structure_fingerprint(NodeArena::ROOT),
881 string.structure_fingerprint(NodeArena::ROOT),
882 "`integer` and `string` are different kinds in Python too"
883 );
884
885 let single = py_arena("def f():\n return a + b\n");
886 let two_statements = py_arena("def f():\n return a + b\n g()\n");
887 assert_ne!(
888 single.structure_fingerprint(NodeArena::ROOT),
889 two_statements.structure_fingerprint(NodeArena::ROOT),
890 "a second statement changes the shape in Python too"
891 );
892 }
893
894 #[test]
895 fn fingerprint_hashes_a_broken_parse_as_its_broken_shape() {
896 let source = "const x = ;\n";
901 let mut parser = tree_sitter::Parser::new();
902 parser
903 .set_language(&TypeScript.grammar())
904 .expect("grammar loads");
905 let tree = parser.parse(source, None).expect("parses");
906 assert!(
907 tree.root_node().has_error(),
908 "the fixture must really be broken"
909 );
910
911 let arena = NodeArena::new(tree, source.to_owned());
912 let first = arena
913 .structure_fingerprint(NodeArena::ROOT)
914 .expect("a broken parse still folds");
915 let second = arena
916 .structure_fingerprint(NodeArena::ROOT)
917 .expect("a broken parse still folds");
918 assert_eq!(
919 first, second,
920 "the fingerprint of a broken parse must be stable across calls"
921 );
922 assert!(
923 first.nodes >= 2,
924 "the fold covered the erroring shape, not nothing"
925 );
926 }
927}