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.
- package/native/code-gauge.wasm +0 -0
- package/native/src/complexity.rs +36 -35
- package/native/src/container.rs +7 -8
- package/native/src/dep_degree.rs +190 -97
- package/native/src/functions.rs +5 -3
- package/native/src/languages.rs +6 -1
- package/native/src/measure.rs +157 -83
- package/native/src/ncss.rs +16 -24
- package/native/src/tree_index.rs +145 -44
- package/native/src/util.rs +36 -5
- package/package.json +8 -8
package/native/src/tree_index.rs
CHANGED
|
@@ -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
|
|
11
|
-
/// nodes; TreeIndex borrows the tree and clears the
|
|
12
|
-
|
|
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),
|
|
26
|
-
/// `Node::kind` measures and UTF-8-validates the C
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"one tree is indexed per thread at a time"
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
102
|
-
.with_borrow(|
|
|
103
|
-
|
|
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
|
+
}
|
package/native/src/util.rs
CHANGED
|
@@ -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
|
-
|
|
73
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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.
|
|
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.
|
|
93
|
-
"code-gauge-linux-arm64-gnu": "10.4.
|
|
94
|
-
"code-gauge-linux-x64-musl": "10.4.
|
|
95
|
-
"code-gauge-linux-arm64-musl": "10.4.
|
|
96
|
-
"code-gauge-darwin-x64": "10.4.
|
|
97
|
-
"code-gauge-darwin-arm64": "10.4.
|
|
98
|
-
"code-gauge-win32-x64-msvc": "10.4.
|
|
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
|
}
|