code-gauge 10.4.0 → 10.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,19 +1,32 @@
1
1
  use std::cell::{Cell, RefCell};
2
2
  use std::marker::PhantomData;
3
+ use std::sync::OnceLock;
3
4
 
4
5
  use rustc_hash::FxHashMap;
5
6
  use tree_sitter::{Node, Tree};
6
7
 
7
- use crate::languages::LanguageDefinition;
8
+ use crate::languages::{LanguageDefinition, GRAMMAR_COUNT};
9
+
10
+ /// A node of the indexed tree: its parent and where its children lie in CHILDREN.
11
+ struct IndexedNode {
12
+ parent: Option<Node<'static>>,
13
+ first_child: u32,
14
+ child_count: u32,
15
+ }
8
16
 
9
17
  thread_local! {
10
- /// Node id -> parent for the tree indexed on this thread. The lifetime is erased to store the
11
- /// nodes; TreeIndex borrows the tree and clears the map on drop, so no entry outlives it.
12
- static PARENTS: RefCell<FxHashMap<usize, Node<'static>>> = RefCell::new(FxHashMap::default());
18
+ /// Node id -> parent and children for the tree indexed on this thread. The lifetime is erased
19
+ /// to store the nodes; TreeIndex borrows the tree and clears the tables on drop, so no entry
20
+ /// outlives it.
21
+ static NODES: RefCell<FxHashMap<usize, IndexedNode>> = RefCell::new(FxHashMap::default());
22
+ /// The children of every indexed node with their field names, each node's children contiguous.
23
+ static CHILDREN: RefCell<Vec<(Node<'static>, Option<&'static str>)>> = const { RefCell::new(Vec::new()) };
13
24
  /// Node kind names of the indexed tree's language, indexed by kind id.
14
25
  static KIND_NAMES: Cell<&'static [String]> = const { Cell::new(&[]) };
15
26
  /// Name of the indexed tree's language.
16
27
  static LANGUAGE_NAME: Cell<&'static str> = const { Cell::new("") };
28
+ /// LanguageDefinition::grammar_index of the indexed tree's language.
29
+ static GRAMMAR_INDEX: Cell<Option<usize>> = const { Cell::new(None) };
17
30
  }
18
31
 
19
32
  /// The language of the tree indexed on this thread, or "" when none is.
@@ -22,8 +35,9 @@ pub fn language_name() -> &'static str {
22
35
  }
23
36
 
24
37
  /// Makes NodeExt lookups O(1) while the tree is being measured: tree-sitter's `Node::parent`
25
- /// rescans from the root on every call (linear in the sibling counts along the path), and
26
- /// `Node::kind` measures and UTF-8-validates the C string on every call.
38
+ /// rescans from the root on every call (linear in the sibling counts along the path), each
39
+ /// `Node::children` walk runs a fresh cursor, and `Node::kind` measures and UTF-8-validates the C
40
+ /// string on every call.
27
41
  pub struct TreeIndex<'t> {
28
42
  tree: PhantomData<&'t Tree>,
29
43
  }
@@ -38,51 +52,104 @@ impl<'t> TreeIndex<'t> {
38
52
  let index = TreeIndex { tree: PhantomData };
39
53
  KIND_NAMES.set(language.kind_names());
40
54
  LANGUAGE_NAME.set(language.name);
41
- PARENTS.with_borrow_mut(|parents| {
42
- assert!(
43
- parents.is_empty(),
44
- "one tree is indexed per thread at a time"
45
- );
46
- let mut cursor = tree.walk();
47
- let mut stack: Vec<Node<'static>> = Vec::new();
48
- loop {
49
- let node = erase_lifetime(cursor.node());
50
- if let Some(&parent) = stack.last() {
51
- parents.insert(node.id(), parent);
52
- }
53
- if cursor.goto_first_child() {
54
- stack.push(node);
55
- if stack.len() > MAX_TREE_DEPTH {
56
- return Err(format!("tree depth exceeds {MAX_TREE_DEPTH}"));
57
- }
58
- continue;
59
- }
60
- while !cursor.goto_next_sibling() {
61
- if !cursor.goto_parent() {
62
- return Ok(());
63
- }
64
- stack.pop();
65
- }
66
- }
55
+ GRAMMAR_INDEX.set(Some(language.grammar_index()));
56
+ NODES.with_borrow_mut(|nodes| {
57
+ CHILDREN.with_borrow_mut(|children| {
58
+ assert!(nodes.is_empty(), "one tree is indexed per thread at a time");
59
+ index_tree(tree, nodes, children)
60
+ })
67
61
  })?;
68
62
  Ok(index)
69
63
  }
70
64
  }
71
65
 
66
+ /// One cursor pass: each node's children are gathered on `pending` while its subtree is walked
67
+ /// and moved to `children` in one piece when the walk leaves it.
68
+ fn index_tree(
69
+ tree: &Tree,
70
+ nodes: &mut FxHashMap<usize, IndexedNode>,
71
+ children: &mut Vec<(Node<'static>, Option<&'static str>)>,
72
+ ) -> Result<(), String> {
73
+ let mut cursor = tree.walk();
74
+ // The entered ancestors, each with the length `pending` had when its first child was reached.
75
+ let mut stack: Vec<(Node<'static>, usize)> = Vec::new();
76
+ let mut pending: Vec<(Node<'static>, Option<&'static str>)> = Vec::new();
77
+ loop {
78
+ let node = erase_lifetime(cursor.node());
79
+ let parent = stack.last().map(|&(parent, _)| parent);
80
+ nodes.insert(
81
+ node.id(),
82
+ IndexedNode {
83
+ parent,
84
+ first_child: 0,
85
+ child_count: 0,
86
+ },
87
+ );
88
+ if parent.is_some() {
89
+ pending.push((node, erase_field_lifetime(cursor.field_name())));
90
+ }
91
+ if cursor.goto_first_child() {
92
+ stack.push((node, pending.len()));
93
+ if stack.len() > MAX_TREE_DEPTH {
94
+ return Err(format!("tree depth exceeds {MAX_TREE_DEPTH}"));
95
+ }
96
+ continue;
97
+ }
98
+ while !cursor.goto_next_sibling() {
99
+ if !cursor.goto_parent() {
100
+ return Ok(());
101
+ }
102
+ let (closed, first_pending) = stack.pop().expect("a child's parent was entered");
103
+ let entry = nodes
104
+ .get_mut(&closed.id())
105
+ .expect("every entered node is indexed");
106
+ entry.first_child = children.len() as u32;
107
+ entry.child_count = (pending.len() - first_pending) as u32;
108
+ children.extend(pending.drain(first_pending..));
109
+ }
110
+ }
111
+ }
112
+
72
113
  impl Drop for TreeIndex<'_> {
73
114
  fn drop(&mut self) {
74
- PARENTS.with_borrow_mut(|parents| parents.clear());
115
+ NODES.with_borrow_mut(|nodes| nodes.clear());
116
+ CHILDREN.with_borrow_mut(|children| children.clear());
75
117
  KIND_NAMES.set(&[]);
76
118
  LANGUAGE_NAME.set("");
119
+ GRAMMAR_INDEX.set(None);
77
120
  }
78
121
  }
79
122
 
80
123
  fn erase_lifetime(node: Node<'_>) -> Node<'static> {
81
- // SAFETY: Node only borrows its tree; the erased node is stored in PARENTS, which the
82
- // TreeIndex borrowing that tree empties before the tree can be dropped.
124
+ // SAFETY: Node only borrows its tree; the erased node is stored in NODES and CHILDREN, which
125
+ // the TreeIndex borrowing that tree empties before the tree can be dropped. Lookups are keyed
126
+ // by node id, an address inside the indexed tree, so a node handed back for a node of the
127
+ // indexed tree belongs to the tree that node borrows.
83
128
  unsafe { std::mem::transmute::<Node<'_>, Node<'static>>(node) }
84
129
  }
85
130
 
131
+ fn erase_field_lifetime(field_name: Option<&str>) -> Option<&'static str> {
132
+ // SAFETY: as in erase_lifetime; field names borrow the tree's language as nodes borrow the tree.
133
+ unsafe { std::mem::transmute::<Option<&str>, Option<&'static str>>(field_name) }
134
+ }
135
+
136
+ /// Calls `visit` with the node's children and their field names (TreeCursor::field_name), or
137
+ /// returns None when no installed index holds the node.
138
+ pub fn with_indexed_children<'t, R>(
139
+ node: Node<'t>,
140
+ visit: impl FnOnce(&[(Node<'t>, Option<&'t str>)]) -> R,
141
+ ) -> Option<R> {
142
+ let (first_child, child_count) = NODES.with_borrow(|nodes| {
143
+ nodes
144
+ .get(&node.id())
145
+ .map(|entry| (entry.first_child as usize, entry.child_count as usize))
146
+ })?;
147
+ CHILDREN.with_borrow(|children| {
148
+ let children = &children[first_child..first_child + child_count];
149
+ Some(visit(children))
150
+ })
151
+ }
152
+
86
153
  pub trait NodeExt<'t> {
87
154
  /// `Node::parent`, answered from the TreeIndex when one is installed.
88
155
  fn parent_node(self) -> Option<Node<'t>>;
@@ -98,15 +165,9 @@ impl<'t> NodeExt<'t> for Node<'t> {
98
165
  if self.start_byte() == self.end_byte() {
99
166
  return self.parent();
100
167
  }
101
- PARENTS
102
- .with_borrow(|parents| {
103
- parents.get(&self.id()).map(|parent| {
104
- // SAFETY: ids are addresses inside the indexed tree, which outlives the
105
- // index, so a hit means `parent` belongs to the tree `self` borrows.
106
- unsafe { std::mem::transmute::<Node<'static>, Node<'t>>(*parent) }
107
- })
108
- })
109
- .or_else(|| self.parent())
168
+ NODES
169
+ .with_borrow(|nodes| nodes.get(&self.id()).map(|entry| entry.parent))
170
+ .unwrap_or_else(|| self.parent())
110
171
  }
111
172
 
112
173
  fn kind_name(self) -> &'t str {
@@ -117,3 +178,43 @@ impl<'t> NodeExt<'t> for Node<'t> {
117
178
  .unwrap_or_else(|| self.kind())
118
179
  }
119
180
  }
181
+
182
+ /// A set of node kind names that answers for a node by its kind id while a TreeIndex is
183
+ /// installed: per grammar, a flag per kind id, built on the set's first use with that grammar.
184
+ pub struct KindSet {
185
+ names: &'static [&'static str],
186
+ flags_by_grammar: [OnceLock<Vec<bool>>; GRAMMAR_COUNT],
187
+ }
188
+
189
+ impl KindSet {
190
+ pub const fn new(names: &'static [&'static str]) -> KindSet {
191
+ KindSet {
192
+ names,
193
+ flags_by_grammar: [const { OnceLock::new() }; GRAMMAR_COUNT],
194
+ }
195
+ }
196
+
197
+ /// Whether the set holds `node.kind_name()`.
198
+ pub fn contains(&self, node: Node<'_>) -> bool {
199
+ GRAMMAR_INDEX
200
+ .get()
201
+ .and_then(|grammar_index| {
202
+ self.flags_by_grammar[grammar_index]
203
+ .get_or_init(|| {
204
+ KIND_NAMES
205
+ .get()
206
+ .iter()
207
+ .map(|name| self.contains_name(name))
208
+ .collect()
209
+ })
210
+ // An id past the grammar's kinds (ERROR's) is looked up by name, as kind_name does.
211
+ .get(usize::from(node.kind_id()))
212
+ .copied()
213
+ })
214
+ .unwrap_or_else(|| self.contains_name(node.kind_name()))
215
+ }
216
+
217
+ pub fn contains_name(&self, name: &str) -> bool {
218
+ self.names.contains(&name)
219
+ }
220
+ }
@@ -1,6 +1,6 @@
1
1
  use tree_sitter::Node;
2
2
 
3
- use crate::tree_index::NodeExt;
3
+ use crate::tree_index::{with_indexed_children, NodeExt};
4
4
 
5
5
  /// The measured source. Trees are parsed from UTF-16 (matching node-tree-sitter, which parses
6
6
  /// JavaScript strings as UTF-16 — tree-sitter's error recovery can differ between encodings), so
@@ -69,13 +69,44 @@ pub fn is_token(node: Node<'_>) -> bool {
69
69
  }
70
70
 
71
71
  pub fn named_children<'t>(node: Node<'t>) -> Vec<Node<'t>> {
72
- let mut cursor = node.walk();
73
- node.named_children(&mut cursor).collect()
72
+ with_indexed_children(node, |children| {
73
+ children
74
+ .iter()
75
+ .map(|&(child, _)| child)
76
+ .filter(Node::is_named)
77
+ .collect()
78
+ })
79
+ .unwrap_or_else(|| {
80
+ let mut cursor = node.walk();
81
+ node.named_children(&mut cursor).collect()
82
+ })
74
83
  }
75
84
 
76
85
  pub fn all_children<'t>(node: Node<'t>) -> Vec<Node<'t>> {
77
- let mut cursor = node.walk();
78
- node.children(&mut cursor).collect()
86
+ with_indexed_children(node, |children| {
87
+ children.iter().map(|&(child, _)| child).collect()
88
+ })
89
+ .unwrap_or_else(|| {
90
+ let mut cursor = node.walk();
91
+ node.children(&mut cursor).collect()
92
+ })
93
+ }
94
+
95
+ /// The children with their field names as a TreeCursor reports them.
96
+ pub fn children_with_field_names<'t>(node: Node<'t>) -> Vec<(Node<'t>, Option<&'t str>)> {
97
+ with_indexed_children(node, <[_]>::to_vec).unwrap_or_else(|| {
98
+ let mut children = Vec::new();
99
+ let mut cursor = node.walk();
100
+ if cursor.goto_first_child() {
101
+ loop {
102
+ children.push((cursor.node(), cursor.field_name()));
103
+ if !cursor.goto_next_sibling() {
104
+ break;
105
+ }
106
+ }
107
+ }
108
+ children
109
+ })
79
110
  }
80
111
 
81
112
  /// Children carrying the field, with node-tree-sitter's vendored-core semantics: extra children
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-gauge",
3
- "version": "10.4.0",
3
+ "version": "10.4.1",
4
4
  "description": "Measure code metrics with tree-sitter.",
5
5
  "keywords": [
6
6
  "cli",
@@ -89,12 +89,12 @@
89
89
  "node": ">=24"
90
90
  },
91
91
  "optionalDependencies": {
92
- "code-gauge-linux-x64-gnu": "10.4.0",
93
- "code-gauge-linux-arm64-gnu": "10.4.0",
94
- "code-gauge-linux-x64-musl": "10.4.0",
95
- "code-gauge-linux-arm64-musl": "10.4.0",
96
- "code-gauge-darwin-x64": "10.4.0",
97
- "code-gauge-darwin-arm64": "10.4.0",
98
- "code-gauge-win32-x64-msvc": "10.4.0"
92
+ "code-gauge-linux-x64-gnu": "10.4.1",
93
+ "code-gauge-linux-arm64-gnu": "10.4.1",
94
+ "code-gauge-linux-x64-musl": "10.4.1",
95
+ "code-gauge-linux-arm64-musl": "10.4.1",
96
+ "code-gauge-darwin-x64": "10.4.1",
97
+ "code-gauge-darwin-arm64": "10.4.1",
98
+ "code-gauge-win32-x64-msvc": "10.4.1"
99
99
  }
100
100
  }