code-gauge 3.1.0 → 4.1.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 +17 -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,345 @@
|
|
|
1
|
+
use std::collections::HashSet;
|
|
2
|
+
use tree_sitter::Node;
|
|
3
|
+
|
|
4
|
+
use crate::util::{all_children, find_children_by_field_name, named_children, node_text, Source};
|
|
5
|
+
|
|
6
|
+
/// C++ `function_definition` also covers pure-virtual/`= default`/`= delete` members; those have no
|
|
7
|
+
/// `body` and are signatures, not implementations, matching how TypeScript method signatures are
|
|
8
|
+
/// excluded. Java `method_declaration` is NOT here: PMD reports abstract/interface methods as
|
|
9
|
+
/// methods (NCSS 1), so bodyless Java methods stay in the function list.
|
|
10
|
+
const BODY_REQUIRED_FUNCTION_TYPES: &[&str] = &[
|
|
11
|
+
"function_definition",
|
|
12
|
+
"constructor_declaration",
|
|
13
|
+
"compact_constructor_declaration",
|
|
14
|
+
"function_signature_item",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
pub fn is_implemented_function(node: Node<'_>) -> bool {
|
|
18
|
+
if !BODY_REQUIRED_FUNCTION_TYPES.contains(&node.kind())
|
|
19
|
+
|| node.child_by_field_name("body").is_some()
|
|
20
|
+
{
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// C++ constructor/destructor function-try-blocks carry their `try_statement` outside the
|
|
25
|
+
// `body` field; they are implementations, unlike `= 0`/`= default`/`= delete` members.
|
|
26
|
+
named_children(node)
|
|
27
|
+
.iter()
|
|
28
|
+
.any(|child| child.kind() == "try_statement")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Counts declared parameters of a function/method, ignoring punctuation and comments.
|
|
32
|
+
pub fn count_parameters(node: Node<'_>, code: &Source<'_>) -> usize {
|
|
33
|
+
// An unparenthesized arrow-function parameter (`x => x + 1`) is a bare `parameter` field.
|
|
34
|
+
if node.child_by_field_name("parameter").is_some() {
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let Some(parameters_node) = find_parameters_node(node) else {
|
|
39
|
+
return 0;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// A Java bare lambda parameter (`x -> x + 1`) puts a lone identifier in the `parameters` field.
|
|
43
|
+
if parameters_node.kind() == "identifier" {
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Ruby block-locals after `;` (`{ |x; memo| ... }`) occupy `locals` fields and receive no arguments.
|
|
48
|
+
let block_local_ids: HashSet<usize> = find_children_by_field_name(parameters_node, "locals")
|
|
49
|
+
.iter()
|
|
50
|
+
.map(|child| child.id())
|
|
51
|
+
.collect();
|
|
52
|
+
let mut count = 0usize;
|
|
53
|
+
// Rust's `self` and Java's explicit receiver (`void f(X this)`) are not declared parameters,
|
|
54
|
+
// C/C++ `f(void)` declares none, and a Ruby block parameter (`&blk`) binds the block, which
|
|
55
|
+
// call sites pass outside the argument list.
|
|
56
|
+
for child in named_children(parameters_node) {
|
|
57
|
+
if child.kind() == "comment"
|
|
58
|
+
|| child.kind() == "self_parameter"
|
|
59
|
+
|| child.kind() == "receiver_parameter"
|
|
60
|
+
|| child.kind() == "block_parameter"
|
|
61
|
+
// Python's PEP 570/3102 markers (`/`, `*`) separate parameter kinds but bind nothing.
|
|
62
|
+
|| child.kind() == "positional_separator"
|
|
63
|
+
|| child.kind() == "keyword_separator"
|
|
64
|
+
|| block_local_ids.contains(&child.id())
|
|
65
|
+
|| is_void_parameter(child, code)
|
|
66
|
+
{
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
// Go declares several names per declaration (`a, b int`); each name is a parameter.
|
|
70
|
+
count += if child.kind() == "parameter_declaration" {
|
|
71
|
+
find_children_by_field_name(child, "name").len().max(1)
|
|
72
|
+
} else {
|
|
73
|
+
1
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// C++ C-style varargs (`int f(int a, ...)`) leave `...` as an anonymous token.
|
|
77
|
+
let anonymous_variadic_count = all_children(parameters_node)
|
|
78
|
+
.iter()
|
|
79
|
+
.filter(|child| !child.is_named() && node_text(**child, code) == "...")
|
|
80
|
+
.count();
|
|
81
|
+
count + anonymous_variadic_count
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// C/C++ `int f(void)` has a `parameter_declaration` whose type is a bare `void` with no declarator.
|
|
85
|
+
fn is_void_parameter(node: Node<'_>, code: &Source<'_>) -> bool {
|
|
86
|
+
node.kind() == "parameter_declaration"
|
|
87
|
+
&& node.child_by_field_name("declarator").is_none()
|
|
88
|
+
&& node
|
|
89
|
+
.child_by_field_name("type")
|
|
90
|
+
.is_some_and(|type_node| node_text(type_node, code) == "void")
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fn find_parameters_node(node: Node<'_>) -> Option<Node<'_>> {
|
|
94
|
+
if let Some(direct) = node.child_by_field_name("parameters") {
|
|
95
|
+
return Some(direct);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// A Java compact constructor implicitly takes the record's components, declared on the
|
|
99
|
+
// `record_declaration` two levels up (via `class_body`).
|
|
100
|
+
if node.kind() == "compact_constructor_declaration" {
|
|
101
|
+
return node
|
|
102
|
+
.parent()
|
|
103
|
+
.and_then(|parent| parent.parent())
|
|
104
|
+
.and_then(|grandparent| grandparent.child_by_field_name("parameters"));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// C/C++ parameters hang off the (possibly pointer/reference-wrapped) declarator.
|
|
108
|
+
let mut declarator = node.child_by_field_name("declarator");
|
|
109
|
+
while let Some(current) = declarator {
|
|
110
|
+
if let Some(parameters) = current.child_by_field_name("parameters") {
|
|
111
|
+
return Some(parameters);
|
|
112
|
+
}
|
|
113
|
+
declarator = next_declarator(current);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
named_children(node)
|
|
117
|
+
.into_iter()
|
|
118
|
+
.find(|child| child.kind() == "formal_parameters" || child.kind() == "parameter_list")
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
pub fn collect_nodes<'t>(root: Node<'t>, node_types: &HashSet<&'static str>) -> Vec<Node<'t>> {
|
|
122
|
+
let mut nodes = Vec::new();
|
|
123
|
+
|
|
124
|
+
fn visit<'t>(node: Node<'t>, node_types: &HashSet<&'static str>, nodes: &mut Vec<Node<'t>>) {
|
|
125
|
+
if node_types.contains(node.kind()) {
|
|
126
|
+
nodes.push(node);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for child in named_children(node) {
|
|
130
|
+
visit(child, node_types, nodes);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
visit(root, node_types, &mut nodes);
|
|
135
|
+
nodes
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
pub fn find_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
139
|
+
// JS truthiness: empty strings from MISSING nodes act like "no name" at every `if (name)`.
|
|
140
|
+
if let Some(wrapped_name) =
|
|
141
|
+
find_wrapped_component_name(node, code).filter(|name| !name.is_empty())
|
|
142
|
+
{
|
|
143
|
+
return Some(wrapped_name);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if let Some(name_node) = node.child_by_field_name("name") {
|
|
147
|
+
return Some(node_text(name_node, code).to_string());
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// C/C++ definitions name the function inside the (possibly pointer-wrapped) declarator chain.
|
|
151
|
+
if let Some(declarator_name) =
|
|
152
|
+
unwrap_declarator_name(node.child_by_field_name("declarator"), code)
|
|
153
|
+
.filter(|name| !name.is_empty())
|
|
154
|
+
{
|
|
155
|
+
return Some(declarator_name);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let parent = node.parent()?;
|
|
159
|
+
|
|
160
|
+
// A Rust closure bound to a simple `let` identifier takes that identifier as its name.
|
|
161
|
+
if node.kind() == "closure_expression" && parent.kind() == "let_declaration" {
|
|
162
|
+
let pattern_node = parent.child_by_field_name("pattern");
|
|
163
|
+
return match pattern_node {
|
|
164
|
+
Some(pattern) if pattern.kind() == "identifier" => {
|
|
165
|
+
Some(node_text(pattern, code).to_string())
|
|
166
|
+
}
|
|
167
|
+
_ => None,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// A C++ lambda assigned to a variable (`auto f = [](int x) { ... };`) takes the variable name.
|
|
172
|
+
if node.kind() == "lambda_expression" && parent.kind() == "init_declarator" {
|
|
173
|
+
return unwrap_declarator_name(parent.child_by_field_name("declarator"), code);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// A Go func literal bound via `add := func...` or `var add = func...` takes the identifier at
|
|
177
|
+
// the same list position.
|
|
178
|
+
if node.kind() == "func_literal" && parent.kind() == "expression_list" {
|
|
179
|
+
return find_go_func_literal_name(node, parent, code);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Ruby lambdas assigned to a name take that name.
|
|
183
|
+
if node.kind() == "lambda" && parent.kind() == "assignment" {
|
|
184
|
+
return find_ruby_assignment_name(parent, code);
|
|
185
|
+
}
|
|
186
|
+
if (node.kind() == "block" || node.kind() == "do_block") && is_ruby_lambda_call(parent, code) {
|
|
187
|
+
return match parent.parent() {
|
|
188
|
+
Some(grandparent) if grandparent.kind() == "assignment" => {
|
|
189
|
+
find_ruby_assignment_name(grandparent, code)
|
|
190
|
+
}
|
|
191
|
+
_ => None,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
parent
|
|
196
|
+
.child_by_field_name("name")
|
|
197
|
+
.map(|name| node_text(name, code).to_string())
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
fn find_ruby_assignment_name(assignment: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
201
|
+
let left_node = assignment.child_by_field_name("left")?;
|
|
202
|
+
if left_node.kind() == "identifier" || left_node.kind() == "constant" {
|
|
203
|
+
Some(node_text(left_node, code).to_string())
|
|
204
|
+
} else {
|
|
205
|
+
None
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fn is_ruby_lambda_call(node: Node<'_>, code: &Source<'_>) -> bool {
|
|
210
|
+
if node.kind() != "call" || node.child_by_field_name("receiver").is_some() {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
node.child_by_field_name("method").is_some_and(|method| {
|
|
214
|
+
method.kind() == "identifier"
|
|
215
|
+
&& (node_text(method, code) == "lambda" || node_text(method, code) == "proc")
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
fn find_go_func_literal_name(
|
|
220
|
+
node: Node<'_>,
|
|
221
|
+
expression_list: Node<'_>,
|
|
222
|
+
code: &Source<'_>,
|
|
223
|
+
) -> Option<String> {
|
|
224
|
+
let holder = expression_list.parent()?;
|
|
225
|
+
// Comments interleave with expressions in the list but have no matching binding target.
|
|
226
|
+
let values: Vec<Node<'_>> = named_children(expression_list)
|
|
227
|
+
.into_iter()
|
|
228
|
+
.filter(|child| child.kind() != "comment")
|
|
229
|
+
.collect();
|
|
230
|
+
let value_index = values.iter().position(|child| child.id() == node.id())?;
|
|
231
|
+
|
|
232
|
+
if holder.kind() == "short_var_declaration" {
|
|
233
|
+
let targets = holder.child_by_field_name("left").map(|left| {
|
|
234
|
+
named_children(left)
|
|
235
|
+
.into_iter()
|
|
236
|
+
.filter(|child| child.kind() != "comment")
|
|
237
|
+
.collect::<Vec<_>>()
|
|
238
|
+
});
|
|
239
|
+
return as_go_binding_name(
|
|
240
|
+
targets
|
|
241
|
+
.as_ref()
|
|
242
|
+
.and_then(|targets| targets.get(value_index))
|
|
243
|
+
.copied(),
|
|
244
|
+
code,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if holder.kind() == "var_spec" {
|
|
249
|
+
let target = find_children_by_field_name(holder, "name")
|
|
250
|
+
.get(value_index)
|
|
251
|
+
.copied();
|
|
252
|
+
return as_go_binding_name(target, code);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
None
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/// Go's blank identifier `_` discards the value and creates no callable binding.
|
|
259
|
+
fn as_go_binding_name(target: Option<Node<'_>>, code: &Source<'_>) -> Option<String> {
|
|
260
|
+
match target {
|
|
261
|
+
Some(target) if target.kind() == "identifier" && node_text(target, code) != "_" => {
|
|
262
|
+
Some(node_text(target, code).to_string())
|
|
263
|
+
}
|
|
264
|
+
_ => None,
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
fn find_wrapped_component_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
269
|
+
let mut current = node;
|
|
270
|
+
loop {
|
|
271
|
+
let arguments_node = current.parent();
|
|
272
|
+
let call_node = arguments_node.and_then(|arguments| arguments.parent());
|
|
273
|
+
let (Some(arguments_node), Some(call_node)) = (arguments_node, call_node) else {
|
|
274
|
+
return None;
|
|
275
|
+
};
|
|
276
|
+
if arguments_node.kind() != "arguments" || call_node.kind() != "call_expression" {
|
|
277
|
+
return None;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if !is_react_component_wrapper_call(call_node, code) {
|
|
281
|
+
return None;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if let Some(declarator_node) = call_node.parent() {
|
|
285
|
+
if declarator_node.kind() == "variable_declarator" {
|
|
286
|
+
return declarator_node
|
|
287
|
+
.child_by_field_name("name")
|
|
288
|
+
.map(|name| node_text(name, code).to_string());
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
current = call_node;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
fn is_react_component_wrapper_call(node: Node<'_>, code: &Source<'_>) -> bool {
|
|
297
|
+
let callee_node = node
|
|
298
|
+
.child_by_field_name("function")
|
|
299
|
+
.or_else(|| node.named_child(0));
|
|
300
|
+
callee_node.is_some_and(|callee| {
|
|
301
|
+
let text = node_text(callee, code);
|
|
302
|
+
text == "memo" || text == "React.memo" || text == "forwardRef" || text == "React.forwardRef"
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/// Unwraps a C/C++ declarator chain to the declared name; see unwrapDeclaratorName in metrics.ts.
|
|
307
|
+
fn unwrap_declarator_name(declarator: Option<Node<'_>>, code: &Source<'_>) -> Option<String> {
|
|
308
|
+
let mut current = declarator;
|
|
309
|
+
while let Some(node) = current {
|
|
310
|
+
match node.kind() {
|
|
311
|
+
"identifier" | "field_identifier" | "type_identifier" | "destructor_name"
|
|
312
|
+
| "operator_name" => {
|
|
313
|
+
return Some(node_text(node, code).to_string());
|
|
314
|
+
}
|
|
315
|
+
// A C++ conversion operator (`operator int()`) is its own declarator node.
|
|
316
|
+
"operator_cast" => {
|
|
317
|
+
let type_text = node
|
|
318
|
+
.child_by_field_name("type")
|
|
319
|
+
.map(|type_node| node_text(type_node, code))
|
|
320
|
+
.unwrap_or("");
|
|
321
|
+
return Some(format!("operator {type_text}").trim_end().to_string());
|
|
322
|
+
}
|
|
323
|
+
// Template specializations (`id<int>`) and qualified names both carry a `name` field.
|
|
324
|
+
"template_function" | "qualified_identifier" => {
|
|
325
|
+
current = node.child_by_field_name("name");
|
|
326
|
+
}
|
|
327
|
+
_ => {
|
|
328
|
+
current = next_declarator(node);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
None
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/// Steps into the inner declarator; `reference_declarator` and `parenthesized_declarator` do not
|
|
336
|
+
/// expose a `declarator` field in tree-sitter-cpp, so their sole named child is the inner node.
|
|
337
|
+
fn next_declarator(node: Node<'_>) -> Option<Node<'_>> {
|
|
338
|
+
if let Some(direct) = node.child_by_field_name("declarator") {
|
|
339
|
+
return Some(direct);
|
|
340
|
+
}
|
|
341
|
+
if node.kind() == "reference_declarator" || node.kind() == "parenthesized_declarator" {
|
|
342
|
+
return node.named_child(0);
|
|
343
|
+
}
|
|
344
|
+
None
|
|
345
|
+
}
|