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.
Files changed (63) hide show
  1. package/README.md +16 -15
  2. package/dist/crossFileDuplication.cjs +1 -1
  3. package/dist/crossFileDuplication.cjs.map +1 -1
  4. package/dist/crossFileDuplication.js +1 -1
  5. package/dist/crossFileDuplication.js.map +1 -1
  6. package/dist/diffCommand.cjs +1 -1
  7. package/dist/diffCommand.cjs.map +1 -1
  8. package/dist/diffCommand.js +1 -1
  9. package/dist/diffCommand.js.map +1 -1
  10. package/dist/duplication.cjs +1 -1
  11. package/dist/duplication.cjs.map +1 -1
  12. package/dist/duplication.d.ts +14 -28
  13. package/dist/duplication.js +1 -1
  14. package/dist/duplication.js.map +1 -1
  15. package/dist/index.cjs +1 -1
  16. package/dist/index.d.ts +0 -1
  17. package/dist/index.js +1 -1
  18. package/dist/languages.cjs +1 -1
  19. package/dist/languages.cjs.map +1 -1
  20. package/dist/languages.d.ts +5 -0
  21. package/dist/languages.js +1 -1
  22. package/dist/languages.js.map +1 -1
  23. package/dist/metrics.cjs +1 -1
  24. package/dist/metrics.cjs.map +1 -1
  25. package/dist/metrics.d.ts +14 -12
  26. package/dist/metrics.js +1 -1
  27. package/dist/metrics.js.map +1 -1
  28. package/dist/nativeMetrics.cjs +3 -1
  29. package/dist/nativeMetrics.cjs.map +1 -1
  30. package/dist/nativeMetrics.d.ts +24 -9
  31. package/dist/nativeMetrics.js +3 -1
  32. package/dist/nativeMetrics.js.map +1 -1
  33. package/dist/scan.cjs +1 -1
  34. package/dist/scan.cjs.map +1 -1
  35. package/dist/scan.js +1 -1
  36. package/dist/scan.js.map +1 -1
  37. package/dist/types.d.ts +5 -13
  38. package/native/Cargo.lock +523 -0
  39. package/native/Cargo.toml +45 -0
  40. package/native/build.rs +3 -0
  41. package/native/src/complexity.rs +627 -0
  42. package/native/src/dep_degree.rs +253 -0
  43. package/native/src/duplication.rs +2007 -0
  44. package/native/src/functions.rs +345 -0
  45. package/native/src/languages.rs +647 -0
  46. package/native/src/lib.rs +101 -0
  47. package/native/src/measure.rs +590 -0
  48. package/native/src/ncss.rs +263 -0
  49. package/native/src/types.rs +135 -0
  50. package/native/src/util.rs +139 -0
  51. package/package.json +17 -19
  52. package/scripts/buildNative.mjs +25 -0
  53. package/scripts/installNative.mjs +96 -0
  54. package/dist/depDegree.cjs +0 -2
  55. package/dist/depDegree.cjs.map +0 -1
  56. package/dist/depDegree.d.ts +0 -12
  57. package/dist/depDegree.js +0 -2
  58. package/dist/depDegree.js.map +0 -1
  59. package/dist/ncss.cjs +0 -2
  60. package/dist/ncss.cjs.map +0 -1
  61. package/dist/ncss.d.ts +0 -17
  62. package/dist/ncss.js +0 -2
  63. 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
+ }