code-gauge 10.3.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/README.md +4 -4
- package/THIRD-PARTY-NOTICES.txt +36 -12
- package/dist/crossFileNearMiss.cjs +1 -1
- package/dist/crossFileNearMiss.cjs.map +1 -1
- package/dist/crossFileNearMiss.js +1 -1
- package/dist/crossFileNearMiss.js.map +1 -1
- package/native/Cargo.lock +24 -24
- package/native/Cargo.toml +12 -12
- package/native/code-gauge.wasm +0 -0
- package/native/src/complexity.rs +62 -55
- package/native/src/container.rs +7 -8
- package/native/src/dep_degree.rs +190 -97
- package/native/src/duplication.rs +37 -3
- package/native/src/functions.rs +15 -15
- package/native/src/languages.rs +8 -4
- package/native/src/measure.rs +157 -83
- package/native/src/ncss.rs +45 -36
- package/native/src/tree_index.rs +145 -44
- package/native/src/util.rs +42 -5
- package/package.json +13 -13
package/native/src/ncss.rs
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
use rustc_hash::FxHashSet;
|
|
2
1
|
use tree_sitter::Node;
|
|
3
2
|
|
|
4
|
-
use crate::tree_index::NodeExt;
|
|
3
|
+
use crate::tree_index::{KindSet, NodeExt};
|
|
5
4
|
use crate::util::find_children_by_field_name;
|
|
6
5
|
|
|
7
6
|
pub const COMMENT_NODE_TYPES: &[&str] = &["comment", "line_comment", "block_comment"];
|
|
8
7
|
|
|
9
8
|
/// Nodes never counted positionally inside NCSS containers: metadata, empty statements, Ruby
|
|
10
9
|
/// heredoc bodies (tree-sitter emits them as siblings of the statement that opened the heredoc),
|
|
11
|
-
///
|
|
12
|
-
///
|
|
13
|
-
///
|
|
10
|
+
/// TRANSPARENT_WRAPPER_TYPES (their children count instead), Kotlin enum entries (Java enum
|
|
11
|
+
/// constants are not counted either), and Kotlin lambda parameters (siblings of the lambda's
|
|
12
|
+
/// statements). Kotlin's `try_expression` is excluded contextually below because Rust
|
|
14
13
|
/// shares its name: Rust's `?` operator must keep counting as a tail expression.
|
|
15
|
-
|
|
14
|
+
static POSITIONAL_EXCLUSION_KINDS: KindSet = KindSet::new(&[
|
|
16
15
|
"attribute_item",
|
|
17
16
|
"inner_attribute_item",
|
|
18
17
|
"empty_statement",
|
|
19
18
|
"heredoc_body",
|
|
20
19
|
"parenthesized_statements",
|
|
20
|
+
"attributed_declaration",
|
|
21
21
|
"annotation",
|
|
22
22
|
"file_annotation",
|
|
23
23
|
"shebang",
|
|
24
24
|
"enum_entry",
|
|
25
25
|
"lambda_parameters",
|
|
26
|
-
];
|
|
26
|
+
]);
|
|
27
27
|
|
|
28
28
|
/// TypeScript interface members count like Java interface members, but the same node types appear
|
|
29
29
|
/// inside object-type annotations (`let x: { a: number }`), which are part of one declaration, so
|
|
@@ -58,9 +58,9 @@ const BODYLESS_NCSS_SPECIFIER_TYPES: &[&str] = &[
|
|
|
58
58
|
pub fn ncss_contribution(
|
|
59
59
|
node: Node<'_>,
|
|
60
60
|
parent: Option<Node<'_>>,
|
|
61
|
-
countable: &
|
|
62
|
-
containers: &
|
|
63
|
-
bare_body_parents: &
|
|
61
|
+
countable: &KindSet,
|
|
62
|
+
containers: &KindSet,
|
|
63
|
+
bare_body_parents: &KindSet,
|
|
64
64
|
) -> u64 {
|
|
65
65
|
if !node.is_named()
|
|
66
66
|
|| COMMENT_NODE_TYPES.contains(&node.kind_name())
|
|
@@ -81,8 +81,8 @@ pub fn ncss_contribution(
|
|
|
81
81
|
// it stands like any other expression.
|
|
82
82
|
let positional = (is_in_container_position(parent, containers)
|
|
83
83
|
|| is_bare_body(node, parent, bare_body_parents))
|
|
84
|
-
&& (!containers.contains(node
|
|
85
|
-
&& !
|
|
84
|
+
&& (!containers.contains(node) || node.kind_name() == "lambda_literal")
|
|
85
|
+
&& !POSITIONAL_EXCLUSION_KINDS.contains(node)
|
|
86
86
|
&& !crate::util::is_kotlin_try_expression(node);
|
|
87
87
|
if (counts_through_node_type(node, countable)
|
|
88
88
|
|| positional
|
|
@@ -92,6 +92,12 @@ pub fn ncss_contribution(
|
|
|
92
92
|
contribution += 1;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
// A Rust `else if` clause also stands for the `if` that other grammars nest in their else
|
|
96
|
+
// clause and charge separately.
|
|
97
|
+
if crate::util::is_else_if_clause(node) {
|
|
98
|
+
contribution += 1;
|
|
99
|
+
}
|
|
100
|
+
|
|
95
101
|
// A bare else branch (Java/Go/Kotlin `alternative:` without an else-clause wrapper) counts 1
|
|
96
102
|
// like the `else` keyword does in PMD; an `else if` chain charges the nested if separately on
|
|
97
103
|
// top.
|
|
@@ -102,8 +108,8 @@ pub fn ncss_contribution(
|
|
|
102
108
|
contribution
|
|
103
109
|
}
|
|
104
110
|
|
|
105
|
-
fn counts_through_node_type(node: Node<'_>, countable: &
|
|
106
|
-
if !countable.contains(node
|
|
111
|
+
fn counts_through_node_type(node: Node<'_>, countable: &KindSet) -> bool {
|
|
112
|
+
if !countable.contains(node) {
|
|
107
113
|
return false;
|
|
108
114
|
}
|
|
109
115
|
if BODYLESS_NCSS_SPECIFIER_TYPES.contains(&node.kind_name()) {
|
|
@@ -117,16 +123,17 @@ fn counts_through_node_type(node: Node<'_>, countable: &FxHashSet<&'static str>)
|
|
|
117
123
|
true
|
|
118
124
|
}
|
|
119
125
|
|
|
120
|
-
///
|
|
121
|
-
///
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
/// Ruby's `(foo; bar)` statement parentheses and Rust's attributes-plus-declaration pairs: their
|
|
127
|
+
/// children count when the wrapper itself sits in a container.
|
|
128
|
+
const TRANSPARENT_WRAPPER_TYPES: &[&str] = &["parenthesized_statements", "attributed_declaration"];
|
|
129
|
+
|
|
130
|
+
/// Direct container children count positionally, as do the children of TRANSPARENT_WRAPPER_TYPES
|
|
131
|
+
/// in that position.
|
|
132
|
+
fn is_in_container_position(parent: Option<Node<'_>>, containers: &KindSet) -> bool {
|
|
126
133
|
let mut ancestor = parent;
|
|
127
134
|
while let Some(current) = ancestor {
|
|
128
|
-
if current.kind_name()
|
|
129
|
-
return containers.contains(current
|
|
135
|
+
if !TRANSPARENT_WRAPPER_TYPES.contains(¤t.kind_name()) {
|
|
136
|
+
return containers.contains(current);
|
|
130
137
|
}
|
|
131
138
|
ancestor = current.parent_node();
|
|
132
139
|
}
|
|
@@ -135,12 +142,8 @@ fn is_in_container_position(
|
|
|
135
142
|
|
|
136
143
|
/// A braceless branch or loop body (`if (x) foo()`) stands for the single statement a braced body
|
|
137
144
|
/// would hold.
|
|
138
|
-
fn is_bare_body(
|
|
139
|
-
|
|
140
|
-
parent: Option<Node<'_>>,
|
|
141
|
-
bare_body_parents: &FxHashSet<&'static str>,
|
|
142
|
-
) -> bool {
|
|
143
|
-
parent.is_some_and(|parent| bare_body_parents.contains(parent.kind_name()))
|
|
145
|
+
fn is_bare_body(node: Node<'_>, parent: Option<Node<'_>>, bare_body_parents: &KindSet) -> bool {
|
|
146
|
+
parent.is_some_and(|parent| bare_body_parents.contains(parent))
|
|
144
147
|
&& ["consequence", "alternative", "body"]
|
|
145
148
|
.iter()
|
|
146
149
|
.any(|field_name| is_field_of_parent(node, parent, field_name))
|
|
@@ -148,13 +151,13 @@ fn is_bare_body(
|
|
|
148
151
|
|
|
149
152
|
/// `export const x = 1` nests a countable declaration inside `export_statement`; only the inner
|
|
150
153
|
/// declaration counts, mirroring how PMD counts one statement per declared entity.
|
|
151
|
-
fn is_declaration_wrapper(node: Node<'_>, countable: &
|
|
154
|
+
fn is_declaration_wrapper(node: Node<'_>, countable: &KindSet) -> bool {
|
|
152
155
|
if node.kind_name() != "export_statement" {
|
|
153
156
|
return false;
|
|
154
157
|
}
|
|
155
158
|
node.child_by_field_name("declaration")
|
|
156
159
|
.is_some_and(|declaration| {
|
|
157
|
-
countable.contains(declaration
|
|
160
|
+
countable.contains(declaration)
|
|
158
161
|
|| declaration.kind_name() == "internal_module"
|
|
159
162
|
|| declaration.kind_name() == "ambient_declaration"
|
|
160
163
|
})
|
|
@@ -238,12 +241,18 @@ fn counts_contextually(node: Node<'_>, parent: Option<Node<'_>>) -> bool {
|
|
|
238
241
|
child.kind_name() == "declaration" || child.kind_name() == "function_definition"
|
|
239
242
|
});
|
|
240
243
|
}
|
|
241
|
-
// A Rust item-position macro invocation (`foo! {}` at module level) has no
|
|
242
|
-
// wrapper; the semicolon form does and already counts through it.
|
|
243
|
-
if node.kind_name() == "macro_invocation"
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
244
|
+
// A Rust item-position macro invocation (`foo! {}` at module level, possibly attributed) has no
|
|
245
|
+
// expression_statement wrapper; the semicolon form does and already counts through it.
|
|
246
|
+
if node.kind_name() == "macro_invocation" {
|
|
247
|
+
let item_parent = match parent {
|
|
248
|
+
Some(wrapper) if wrapper.kind_name() == "attributed_declaration" => {
|
|
249
|
+
wrapper.parent_node()
|
|
250
|
+
}
|
|
251
|
+
_ => parent,
|
|
252
|
+
};
|
|
253
|
+
return item_parent.is_some_and(|item_parent| {
|
|
254
|
+
matches!(item_parent.kind_name(), "source_file" | "declaration_list")
|
|
255
|
+
});
|
|
247
256
|
}
|
|
248
257
|
// Go struct fields and interface members count like other languages' member declarations, but
|
|
249
258
|
// only inside a named type declaration; inline anonymous types (`var x struct{ ... }`,
|
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
|
|
@@ -155,6 +186,12 @@ pub fn to_int32(value: i64) -> i32 {
|
|
|
155
186
|
value as i32
|
|
156
187
|
}
|
|
157
188
|
|
|
189
|
+
/// A Rust `else if`: tree-sitter-rust flattens the chain into `else_clause`s that hold the
|
|
190
|
+
/// condition of the `if` they continue, while a final `else` holds only its block.
|
|
191
|
+
pub fn is_else_if_clause(node: Node<'_>) -> bool {
|
|
192
|
+
node.kind_name() == "else_clause" && node.child_by_field_name("condition").is_some()
|
|
193
|
+
}
|
|
194
|
+
|
|
158
195
|
/// Kotlin's `try { } catch { }` shares its node kind with Rust's `?` operator; only the Kotlin form
|
|
159
196
|
/// starts with the `try` keyword.
|
|
160
197
|
pub fn is_kotlin_try_expression(node: Node<'_>) -> bool {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-gauge",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.4.1",
|
|
4
4
|
"description": "Measure code metrics with tree-sitter.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -72,15 +72,15 @@
|
|
|
72
72
|
"@tsconfig/bun": "1.0.10",
|
|
73
73
|
"@tsconfig/node-lts": "24.0.0",
|
|
74
74
|
"@tsconfig/node-ts": "23.6.4",
|
|
75
|
-
"@types/bun": "1.4.
|
|
75
|
+
"@types/bun": "1.4.4",
|
|
76
76
|
"@types/node": "25.9.4",
|
|
77
77
|
"@willbooster/oxlint-config": "1.4.9",
|
|
78
|
-
"@willbooster/wb": "
|
|
79
|
-
"build-ts": "21.0.
|
|
78
|
+
"@willbooster/wb": "23.0.2",
|
|
79
|
+
"build-ts": "21.0.31",
|
|
80
80
|
"conventional-changelog-conventionalcommits": "9.3.1",
|
|
81
81
|
"lefthook": "2.2.2",
|
|
82
|
-
"oxlint": "1.
|
|
83
|
-
"oxlint-tsgolint": "7.0.
|
|
82
|
+
"oxlint": "1.89.0",
|
|
83
|
+
"oxlint-tsgolint": "7.0.2005",
|
|
84
84
|
"semantic-release": "25.0.5",
|
|
85
85
|
"vitest": "4.1.9",
|
|
86
86
|
"wrangler": "4.135.0"
|
|
@@ -89,12 +89,12 @@
|
|
|
89
89
|
"node": ">=24"
|
|
90
90
|
},
|
|
91
91
|
"optionalDependencies": {
|
|
92
|
-
"code-gauge-linux-x64-gnu": "10.
|
|
93
|
-
"code-gauge-linux-arm64-gnu": "10.
|
|
94
|
-
"code-gauge-linux-x64-musl": "10.
|
|
95
|
-
"code-gauge-linux-arm64-musl": "10.
|
|
96
|
-
"code-gauge-darwin-x64": "10.
|
|
97
|
-
"code-gauge-darwin-arm64": "10.
|
|
98
|
-
"code-gauge-win32-x64-msvc": "10.
|
|
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
|
}
|