code-gauge 3.1.0 → 4.0.0
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 +16 -15
- package/dist/crossFileDuplication.cjs +1 -1
- package/dist/crossFileDuplication.cjs.map +1 -1
- package/dist/crossFileDuplication.js +1 -1
- package/dist/crossFileDuplication.js.map +1 -1
- package/dist/diffCommand.cjs +1 -1
- package/dist/diffCommand.cjs.map +1 -1
- package/dist/diffCommand.js +1 -1
- package/dist/diffCommand.js.map +1 -1
- package/dist/duplication.cjs +1 -1
- package/dist/duplication.cjs.map +1 -1
- package/dist/duplication.d.ts +14 -28
- package/dist/duplication.js +1 -1
- package/dist/duplication.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -1
- package/dist/languages.cjs +1 -1
- package/dist/languages.cjs.map +1 -1
- package/dist/languages.d.ts +5 -0
- package/dist/languages.js +1 -1
- package/dist/languages.js.map +1 -1
- package/dist/metrics.cjs +1 -1
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.ts +14 -12
- package/dist/metrics.js +1 -1
- package/dist/metrics.js.map +1 -1
- package/dist/nativeMetrics.cjs +3 -1
- package/dist/nativeMetrics.cjs.map +1 -1
- package/dist/nativeMetrics.d.ts +24 -9
- package/dist/nativeMetrics.js +3 -1
- package/dist/nativeMetrics.js.map +1 -1
- package/dist/scan.cjs +1 -1
- package/dist/scan.cjs.map +1 -1
- package/dist/scan.js +1 -1
- package/dist/scan.js.map +1 -1
- package/dist/types.d.ts +5 -13
- package/native/Cargo.lock +523 -0
- package/native/Cargo.toml +45 -0
- package/native/build.rs +3 -0
- package/native/src/complexity.rs +627 -0
- package/native/src/dep_degree.rs +253 -0
- package/native/src/duplication.rs +2007 -0
- package/native/src/functions.rs +345 -0
- package/native/src/languages.rs +647 -0
- package/native/src/lib.rs +101 -0
- package/native/src/measure.rs +590 -0
- package/native/src/ncss.rs +263 -0
- package/native/src/types.rs +135 -0
- package/native/src/util.rs +139 -0
- package/package.json +16 -19
- package/scripts/buildNative.mjs +25 -0
- package/scripts/installNative.mjs +96 -0
- package/dist/depDegree.cjs +0 -2
- package/dist/depDegree.cjs.map +0 -1
- package/dist/depDegree.d.ts +0 -12
- package/dist/depDegree.js +0 -2
- package/dist/depDegree.js.map +0 -1
- package/dist/ncss.cjs +0 -2
- package/dist/ncss.cjs.map +0 -1
- package/dist/ncss.d.ts +0 -17
- package/dist/ncss.js +0 -2
- package/dist/ncss.js.map +0 -1
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
use std::collections::{HashMap, HashSet};
|
|
2
|
+
use tree_sitter::Node;
|
|
3
|
+
|
|
4
|
+
use crate::complexity::is_lambda_body_block;
|
|
5
|
+
use crate::util::{node_text, Source};
|
|
6
|
+
|
|
7
|
+
/// Leaf node types treated as variable references by the def-use approximation.
|
|
8
|
+
const VARIABLE_NODE_TYPES: &[&str] = &[
|
|
9
|
+
"identifier",
|
|
10
|
+
"instance_variable",
|
|
11
|
+
"class_variable",
|
|
12
|
+
"global_variable",
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
/// Tokens directly after a variable that write it without reading it (`x = 1`, `x := 1`).
|
|
16
|
+
const PURE_ASSIGNMENT_OPERATORS: &[&str] = &["=", ":="];
|
|
17
|
+
|
|
18
|
+
/// Tokens directly after a variable that read then write it (`x += 1` depends on x's definition).
|
|
19
|
+
const COMPOUND_ASSIGNMENT_OPERATORS: &[&str] = &[
|
|
20
|
+
"+=", "-=", "*=", "/=", "%=", "**=", "//=", "<<=", ">>=", ">>>=", "&=", "|=", "^=", "&&=",
|
|
21
|
+
"||=", "??=", "@=", "&^=",
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
/// Parent type -> field under which an identifier is a definition target even when a type
|
|
25
|
+
/// annotation separates it from the `=` token, plus loop bindings with no assignment token at all.
|
|
26
|
+
/// Mirrors definitionFieldByParentType in metrics.ts.
|
|
27
|
+
const DEFINITION_FIELD_BY_PARENT_TYPE: &[(&str, &str)] = &[
|
|
28
|
+
("variable_declarator", "name"),
|
|
29
|
+
("let_declaration", "pattern"),
|
|
30
|
+
("assignment", "left"),
|
|
31
|
+
("var_spec", "name"),
|
|
32
|
+
("init_declarator", "declarator"),
|
|
33
|
+
("enhanced_for_statement", "name"),
|
|
34
|
+
("for_statement", "left"),
|
|
35
|
+
("for_in_statement", "left"),
|
|
36
|
+
("for_in_clause", "left"),
|
|
37
|
+
("for_range_loop", "declarator"),
|
|
38
|
+
("for_expression", "pattern"),
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
/// Multi-target lists (`a, b = ...`, `a, b := ...`) whose holder's `left` field marks definitions.
|
|
42
|
+
const DEFINITION_LIST_NODE_TYPES: &[&str] = &["expression_list", "pattern_list", "tuple_pattern"];
|
|
43
|
+
const DEFINITION_LIST_HOLDER_TYPES: &[&str] = &[
|
|
44
|
+
"assignment",
|
|
45
|
+
"assignment_statement",
|
|
46
|
+
"short_var_declaration",
|
|
47
|
+
"for_statement",
|
|
48
|
+
"for_in_clause",
|
|
49
|
+
"range_clause",
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
/// Parameter-position fields that annotate or initialize rather than bind (`x: T`, `x = default`).
|
|
53
|
+
const NON_BINDING_PARAMETER_FIELDS: &[&str] = &["type", "value"];
|
|
54
|
+
|
|
55
|
+
/// One leaf of the def-use walk: its field in the parent and its function-scope chain.
|
|
56
|
+
struct DepDegreeLeaf<'t> {
|
|
57
|
+
node: Node<'t>,
|
|
58
|
+
field_name: Option<&'static str>,
|
|
59
|
+
/// Chain of nested-function scope ids below the measured function: "" for its own body,
|
|
60
|
+
/// then "/0", "/0/1", ...
|
|
61
|
+
scope: String,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Approximate def-use pairs of the function's subtree; mirrors measureDepDegree in metrics.ts.
|
|
65
|
+
pub fn measure_dep_degree(
|
|
66
|
+
function_node: Node<'_>,
|
|
67
|
+
code: &Source<'_>,
|
|
68
|
+
function_nodes: &HashSet<&'static str>,
|
|
69
|
+
) -> u64 {
|
|
70
|
+
let mut leaves = Vec::new();
|
|
71
|
+
let mut next_scope_id = 0usize;
|
|
72
|
+
collect_dep_degree_leaves(
|
|
73
|
+
function_node,
|
|
74
|
+
None,
|
|
75
|
+
"",
|
|
76
|
+
&mut next_scope_id,
|
|
77
|
+
function_nodes,
|
|
78
|
+
&mut leaves,
|
|
79
|
+
true,
|
|
80
|
+
);
|
|
81
|
+
let mut definition_scopes_by_name: HashMap<&str, Vec<String>> = HashMap::new();
|
|
82
|
+
let mut pairs = 0u64;
|
|
83
|
+
for index in 0..leaves.len() {
|
|
84
|
+
let leaf = &leaves[index];
|
|
85
|
+
if !VARIABLE_NODE_TYPES.contains(&leaf.node.kind()) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
let name = node_text(leaf.node, code);
|
|
89
|
+
let next_text = leaves.get(index + 1).map(|next| node_text(next.node, code));
|
|
90
|
+
if next_text.is_some_and(|next| COMPOUND_ASSIGNMENT_OPERATORS.contains(&next)) {
|
|
91
|
+
if is_definition_visible(definition_scopes_by_name.get(name), &leaf.scope) {
|
|
92
|
+
pairs += 1;
|
|
93
|
+
}
|
|
94
|
+
add_definition(&mut definition_scopes_by_name, name, &leaf.scope);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if next_text.is_some_and(|next| PURE_ASSIGNMENT_OPERATORS.contains(&next))
|
|
98
|
+
|| is_structural_definition(leaf)
|
|
99
|
+
|| is_parameter_definition(leaf)
|
|
100
|
+
{
|
|
101
|
+
add_definition(&mut definition_scopes_by_name, name, &leaf.scope);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if is_definition_visible(definition_scopes_by_name.get(name), &leaf.scope) {
|
|
105
|
+
pairs += 1;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
pairs
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// Collects non-comment leaves with their parent field (one cursor pass, so children of
|
|
112
|
+
/// high-arity nodes cost O(1)) and function-scope chain; mirrors collectDepDegreeLeaves.
|
|
113
|
+
#[allow(clippy::too_many_arguments)]
|
|
114
|
+
fn collect_dep_degree_leaves<'t>(
|
|
115
|
+
node: Node<'t>,
|
|
116
|
+
field_name: Option<&'static str>,
|
|
117
|
+
scope: &str,
|
|
118
|
+
next_scope_id: &mut usize,
|
|
119
|
+
function_nodes: &HashSet<&'static str>,
|
|
120
|
+
leaves: &mut Vec<DepDegreeLeaf<'t>>,
|
|
121
|
+
is_measured_root: bool,
|
|
122
|
+
) {
|
|
123
|
+
if matches!(node.kind(), "comment" | "line_comment" | "block_comment") {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if node.child_count() == 0 {
|
|
127
|
+
leaves.push(DepDegreeLeaf {
|
|
128
|
+
node,
|
|
129
|
+
field_name,
|
|
130
|
+
scope: scope.to_string(),
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
let child_scope = if !is_measured_root && is_function_boundary(node, function_nodes) {
|
|
135
|
+
let scope = format!("{scope}/{next_scope_id}");
|
|
136
|
+
*next_scope_id += 1;
|
|
137
|
+
scope
|
|
138
|
+
} else {
|
|
139
|
+
scope.to_string()
|
|
140
|
+
};
|
|
141
|
+
let mut cursor = node.walk();
|
|
142
|
+
if cursor.goto_first_child() {
|
|
143
|
+
loop {
|
|
144
|
+
collect_dep_degree_leaves(
|
|
145
|
+
cursor.node(),
|
|
146
|
+
cursor.field_name(),
|
|
147
|
+
&child_scope,
|
|
148
|
+
next_scope_id,
|
|
149
|
+
function_nodes,
|
|
150
|
+
leaves,
|
|
151
|
+
false,
|
|
152
|
+
);
|
|
153
|
+
if !cursor.goto_next_sibling() {
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
fn is_function_boundary(node: Node<'_>, function_nodes: &HashSet<&'static str>) -> bool {
|
|
161
|
+
function_nodes.contains(node.kind()) && !is_lambda_body_block(node)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fn add_definition<'a>(
|
|
165
|
+
definition_scopes_by_name: &mut HashMap<&'a str, Vec<String>>,
|
|
166
|
+
name: &'a str,
|
|
167
|
+
scope: &str,
|
|
168
|
+
) {
|
|
169
|
+
let scopes = definition_scopes_by_name.entry(name).or_default();
|
|
170
|
+
if !scopes.iter().any(|existing| existing == scope) {
|
|
171
|
+
scopes.push(scope.to_string());
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// A definition reaches a read only from the read's own or an enclosing function scope.
|
|
176
|
+
fn is_definition_visible(definition_scopes: Option<&Vec<String>>, scope: &str) -> bool {
|
|
177
|
+
definition_scopes.is_some_and(|scopes| {
|
|
178
|
+
scopes.iter().any(|definition_scope| {
|
|
179
|
+
scope == definition_scope
|
|
180
|
+
|| (scope.len() > definition_scope.len()
|
|
181
|
+
&& scope.starts_with(definition_scope.as_str())
|
|
182
|
+
&& scope.as_bytes()[definition_scope.len()] == b'/')
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
fn is_structural_definition(leaf: &DepDegreeLeaf<'_>) -> bool {
|
|
188
|
+
let Some(parent) = leaf.node.parent() else {
|
|
189
|
+
return false;
|
|
190
|
+
};
|
|
191
|
+
if DEFINITION_FIELD_BY_PARENT_TYPE
|
|
192
|
+
.iter()
|
|
193
|
+
.any(|(parent_type, definition_field)| {
|
|
194
|
+
*parent_type == parent.kind() && leaf.field_name == Some(definition_field)
|
|
195
|
+
})
|
|
196
|
+
{
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
if !DEFINITION_LIST_NODE_TYPES.contains(&parent.kind()) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
let Some(holder) = parent.parent() else {
|
|
203
|
+
return false;
|
|
204
|
+
};
|
|
205
|
+
DEFINITION_LIST_HOLDER_TYPES.contains(&holder.kind())
|
|
206
|
+
&& field_name_in_parent(parent, holder) == Some("left")
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/// Mirrors isParameterDefinition in metrics.ts: an ancestor reached through declarator wrappers
|
|
210
|
+
/// (C/C++ function-pointer or array parameters) is a parameter-ish node, or the identifier
|
|
211
|
+
/// directly occupies a parameter field; type annotations and default values bind nothing.
|
|
212
|
+
fn is_parameter_definition(leaf: &DepDegreeLeaf<'_>) -> bool {
|
|
213
|
+
let mut current = leaf.node;
|
|
214
|
+
let mut depth = 0usize;
|
|
215
|
+
loop {
|
|
216
|
+
let Some(parent) = current.parent() else {
|
|
217
|
+
return false;
|
|
218
|
+
};
|
|
219
|
+
let parent_is_parameterish = parent.kind().contains("parameter");
|
|
220
|
+
// Beyond the grandparent, only declarator wrappers keep climbing; checking this before
|
|
221
|
+
// the field lookup also keeps reads inside high-arity nodes O(1).
|
|
222
|
+
if depth >= 1 && !parent_is_parameterish && !parent.kind().contains("declarator") {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
let field = if depth == 0 {
|
|
226
|
+
leaf.field_name
|
|
227
|
+
} else {
|
|
228
|
+
field_name_in_parent(current, parent)
|
|
229
|
+
};
|
|
230
|
+
if field.is_some_and(|name| NON_BINDING_PARAMETER_FIELDS.contains(&name)) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
if parent_is_parameterish
|
|
234
|
+
|| (depth == 0 && field.is_some_and(|name| name.contains("parameter")))
|
|
235
|
+
{
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
current = parent;
|
|
239
|
+
depth += 1;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/// Only called with small-arity parents (declarator wrappers, definition-list holders).
|
|
244
|
+
fn field_name_in_parent(node: Node<'_>, parent: Node<'_>) -> Option<&'static str> {
|
|
245
|
+
for index in 0..parent.child_count() {
|
|
246
|
+
if let Some(child) = parent.child(index) {
|
|
247
|
+
if child.id() == node.id() {
|
|
248
|
+
return parent.field_name_for_child(index as u32);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
None
|
|
253
|
+
}
|