code-gauge 10.1.0 → 10.2.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 (42) hide show
  1. package/README.md +33 -14
  2. package/THIRD-PARTY-NOTICES.txt +8 -8
  3. package/dist/checkCommand.cjs +1 -1
  4. package/dist/checkCommand.cjs.map +1 -1
  5. package/dist/checkCommand.js +1 -1
  6. package/dist/checkCommand.js.map +1 -1
  7. package/dist/crossFileDuplication.cjs +1 -1
  8. package/dist/crossFileDuplication.cjs.map +1 -1
  9. package/dist/crossFileDuplication.d.ts +3 -2
  10. package/dist/crossFileDuplication.js +1 -1
  11. package/dist/crossFileDuplication.js.map +1 -1
  12. package/dist/crossFileNearMiss.cjs +1 -1
  13. package/dist/crossFileNearMiss.cjs.map +1 -1
  14. package/dist/crossFileNearMiss.d.ts +6 -4
  15. package/dist/crossFileNearMiss.js +1 -1
  16. package/dist/crossFileNearMiss.js.map +1 -1
  17. package/dist/duplication.cjs +1 -1
  18. package/dist/duplication.cjs.map +1 -1
  19. package/dist/duplication.d.ts +22 -7
  20. package/dist/duplication.js +1 -1
  21. package/dist/duplication.js.map +1 -1
  22. package/dist/thresholdCheck.cjs +1 -1
  23. package/dist/thresholdCheck.cjs.map +1 -1
  24. package/dist/thresholdCheck.d.ts +2 -0
  25. package/dist/thresholdCheck.js +1 -1
  26. package/dist/thresholdCheck.js.map +1 -1
  27. package/dist/thresholds.cjs.map +1 -1
  28. package/dist/thresholds.d.ts +2 -3
  29. package/dist/thresholds.js.map +1 -1
  30. package/dist/types.d.ts +19 -8
  31. package/native/Cargo.lock +16 -16
  32. package/native/Cargo.toml +8 -8
  33. package/native/code-gauge.wasm +0 -0
  34. package/native/src/complexity.rs +10 -1
  35. package/native/src/container.rs +158 -7
  36. package/native/src/dep_degree.rs +13 -14
  37. package/native/src/duplication.rs +188 -75
  38. package/native/src/functions.rs +112 -24
  39. package/native/src/languages.rs +5 -0
  40. package/native/src/measure.rs +9 -4
  41. package/native/src/near_miss.rs +303 -52
  42. package/package.json +8 -8
@@ -14,7 +14,12 @@ const BODY_REQUIRED_FUNCTION_TYPES: &[&str] = &[
14
14
  "function_definition",
15
15
  "function_declaration",
16
16
  "method_declaration",
17
+ "conditional_method_declaration",
17
18
  "constructor_declaration",
19
+ "destructor_declaration",
20
+ "operator_declaration",
21
+ "conversion_operator_declaration",
22
+ "local_function_statement",
18
23
  "compact_constructor_declaration",
19
24
  "function_signature_item",
20
25
  "accessor_declaration",
@@ -140,6 +145,7 @@ fn is_void_parameter(node: Node<'_>, code: &Source<'_>) -> bool {
140
145
  }
141
146
 
142
147
  fn find_parameters_node(node: Node<'_>) -> Option<Node<'_>> {
148
+ let node = find_conditional_signature(node).unwrap_or(node);
143
149
  if let Some(direct) = node.child_by_field_name("parameters") {
144
150
  return Some(direct);
145
151
  }
@@ -250,7 +256,7 @@ fn wrapped_transparent_value(wrapper: Node<'_>) -> Option<Node<'_>> {
250
256
  /// Climbs from a value through the transparent wrappers around it (`(() => 1)`, `(() => 2) as Fn`,
251
257
  /// `<Fn>(() => 3)`, Rust `(|x| x) as fn(i32) -> i32`) to the outermost one, whose binding site
252
258
  /// names the value.
253
- fn unwrap_transparent_value_wrappers(node: Node<'_>) -> Node<'_> {
259
+ pub fn unwrap_transparent_value_wrappers(node: Node<'_>) -> Node<'_> {
254
260
  let mut bound = node;
255
261
  while let Some(wrapper) = bound.parent_node().filter(|wrapper| {
256
262
  wrapped_transparent_value(*wrapper).is_some_and(|value| value.id() == bound.id())
@@ -260,7 +266,94 @@ fn unwrap_transparent_value_wrappers(node: Node<'_>) -> Node<'_> {
260
266
  bound
261
267
  }
262
268
 
269
+ /// The declaration whose variable or field is the C++ lambda `value` held by `parent`, with the
270
+ /// declarator naming it.
271
+ fn find_cpp_lambda_declaration<'t>(
272
+ parent: Node<'t>,
273
+ value: Node<'_>,
274
+ code: &Source<'_>,
275
+ ) -> Option<(Node<'t>, Option<Node<'t>>)> {
276
+ let declaration = match parent.kind_name() {
277
+ "init_declarator" => Some(parent),
278
+ // A member initialized in its class (`Handler pick = [] {};`) holds its value directly, and
279
+ // a declaration of several members each value after the member it initializes.
280
+ "field_declaration" => {
281
+ let mut declarator = None;
282
+ for index in 0..parent.child_count() {
283
+ let child = parent.child(index)?;
284
+ if child.id() == value.id() {
285
+ return Some((parent, declarator));
286
+ }
287
+ if parent.field_name_for_child(index) == Some("declarator") && !child.is_extra() {
288
+ declarator = Some(child);
289
+ }
290
+ }
291
+ None
292
+ }
293
+ "argument_list" | "initializer_list" if binding_children(parent).len() == 1 => parent
294
+ .parent_node()
295
+ .filter(|holder| holder.kind_name() == "init_declarator")
296
+ .filter(|holder| declares_deduced_type(*holder))
297
+ // `auto f = {[] {}}` deduces a list holding the closure, not the closure itself;
298
+ // only the direct form `auto f{[] {}}` makes the variable the closure.
299
+ .filter(|holder| {
300
+ parent.kind_name() == "argument_list"
301
+ || !all_children(*holder)
302
+ .iter()
303
+ .any(|child| !child.is_named() && node_text(*child, code) == "=")
304
+ }),
305
+ _ => None,
306
+ }?;
307
+ Some((declaration, declaration.child_by_field_name("declarator")))
308
+ }
309
+
310
+ /// The node binding a function written as a value and the value it holds, through the forms
311
+ /// find_function_name names the function by: transparent wrappers, the label or annotation of a
312
+ /// Kotlin lambda, the `lambda`/`proc` call around a Ruby block, and the C++ initializer of a
313
+ /// variable or of a member declared in its class.
314
+ pub fn find_value_binding<'t>(node: Node<'t>, code: &Source<'_>) -> Option<(Node<'t>, Node<'t>)> {
315
+ let mut value = unwrap_transparent_value_wrappers(node);
316
+ let mut holder = value.parent_node()?;
317
+ if node.kind_name() == "lambda_expression" {
318
+ if let Some((declaration, _)) = find_cpp_lambda_declaration(holder, value, code) {
319
+ return Some((declaration, value));
320
+ }
321
+ }
322
+ if matches!(node.kind_name(), "block" | "do_block") && is_ruby_lambda_call(holder, code) {
323
+ value = unwrap_transparent_value_wrappers(holder);
324
+ holder = value.parent_node()?;
325
+ }
326
+ while matches!(
327
+ holder.kind_name(),
328
+ "labeled_expression" | "annotated_expression"
329
+ ) {
330
+ value = holder;
331
+ holder = value.parent_node()?;
332
+ }
333
+ Some((holder, value))
334
+ }
335
+
336
+ /// A C# method whose header is chosen by `#if` keeps its body outside the directive and its
337
+ /// alternative headers inside; the first one names the method and declares its parameters.
338
+ fn find_conditional_signature(node: Node<'_>) -> Option<Node<'_>> {
339
+ if node.kind_name() != "conditional_method_declaration" {
340
+ return None;
341
+ }
342
+ let mut pending = named_children(node);
343
+ pending.reverse();
344
+ while let Some(current) = pending.pop() {
345
+ if current.kind_name() == "method_signature" {
346
+ return Some(current);
347
+ }
348
+ if current.kind_name().starts_with("preproc_") {
349
+ pending.extend(named_children(current).into_iter().rev());
350
+ }
351
+ }
352
+ None
353
+ }
354
+
263
355
  pub fn find_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
356
+ let node = find_conditional_signature(node).unwrap_or(node);
264
357
  // JS truthiness: empty strings from MISSING nodes act like "no name" at every `if (name)`.
265
358
  if let Some(wrapped_name) =
266
359
  find_wrapped_component_name(node, code).filter(|name| !name.is_empty())
@@ -303,24 +396,8 @@ pub fn find_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
303
396
  // closure itself. With a written type (`std::thread worker([] {})`) the lambda is a constructor
304
397
  // argument, and the constructor stores whatever it likes, so it names nothing.
305
398
  if node.kind_name() == "lambda_expression" {
306
- let declaration = match parent.kind_name() {
307
- "init_declarator" => Some(parent),
308
- "argument_list" | "initializer_list" if binding_children(parent).len() == 1 => parent
309
- .parent_node()
310
- .filter(|holder| holder.kind_name() == "init_declarator")
311
- .filter(|holder| declares_deduced_type(*holder))
312
- // `auto f = {[] {}}` deduces a list holding the closure, not the closure itself;
313
- // only the direct form `auto f{[] {}}` makes the variable the closure.
314
- .filter(|holder| {
315
- parent.kind_name() == "argument_list"
316
- || !all_children(*holder)
317
- .iter()
318
- .any(|child| !child.is_named() && node_text(*child, code) == "=")
319
- }),
320
- _ => None,
321
- };
322
- if let Some(declaration) = declaration {
323
- return unwrap_declarator_name(declaration.child_by_field_name("declarator"), code);
399
+ if let Some((_, declarator)) = find_cpp_lambda_declaration(parent, bound, code) {
400
+ return unwrap_declarator_name(declarator, code).filter(|name| !name.is_empty());
324
401
  }
325
402
  }
326
403
 
@@ -425,7 +502,7 @@ pub fn find_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
425
502
 
426
503
  /// The key of a `pair` when it is a plain, Ruby symbol, or string-literal property name; a
427
504
  /// computed key (`[k]: ...`), an interpolated string or symbol, or an empty string names nothing.
428
- fn find_pair_key_name(pair: Node<'_>, code: &Source<'_>) -> Option<String> {
505
+ pub fn find_pair_key_name(pair: Node<'_>, code: &Source<'_>) -> Option<String> {
429
506
  let key = pair.child_by_field_name("key")?;
430
507
  match key.kind_name() {
431
508
  // A numeric key (`{ 1: () => {} }`) is as stable a property name as an identifier, signed
@@ -813,7 +890,7 @@ fn find_declared_type_spec<'t>(from: Node<'t>, name: &str, code: &Source<'t>) ->
813
890
  /// delimiters and prefixes (`"""k"""`, `r"k"`, `%q(k)`, `:"k"`) never leak into it. JavaScript
814
891
  /// splits the content into `string_fragment` and `escape_sequence` siblings; Ruby and Python emit
815
892
  /// `string_content` (Python nests escapes inside it). Interpolation makes the key unstable.
816
- fn find_string_literal_content(literal: Node<'_>, code: &Source<'_>) -> Option<String> {
893
+ pub fn find_string_literal_content(literal: Node<'_>, code: &Source<'_>) -> Option<String> {
817
894
  let children = named_children(literal);
818
895
  if children
819
896
  .iter()
@@ -986,7 +1063,7 @@ fn is_value_of_parent(node: Node<'_>, parent: Node<'_>) -> bool {
986
1063
  /// A value group of a Python or Ruby parallel assignment: the value list itself, or a tuple, list,
987
1064
  /// or array literal that destructuring takes apart (`a, (b, c) = x, (f, y)`, `a, b = [f, g]`). A set
988
1065
  /// or a mapping is unordered, so it groups nothing positionally.
989
- fn is_value_group(node: Node<'_>) -> bool {
1066
+ pub fn is_value_group(node: Node<'_>) -> bool {
990
1067
  matches!(
991
1068
  node.kind_name(),
992
1069
  "expression_list" | "right_assignment_list" | "tuple" | "list" | "array"
@@ -1009,6 +1086,12 @@ fn is_target_group(node: Node<'_>) -> bool {
1009
1086
  /// destructuring. The position this value takes in each group it sits in is collected on the way up
1010
1087
  /// to the assignment, then replayed on the target side.
1011
1088
  fn find_parallel_assignment_name(value: Node<'_>, code: &Source<'_>) -> Option<String> {
1089
+ let (_, target) = find_parallel_assignment_target(value)?;
1090
+ find_assignment_target_text(target, code)
1091
+ }
1092
+
1093
+ /// The assignment a value of a parallel assignment sits in and the target it is bound to.
1094
+ pub fn find_parallel_assignment_target<'t>(value: Node<'t>) -> Option<(Node<'t>, Node<'t>)> {
1012
1095
  let mut positions = Vec::new();
1013
1096
  let mut current = value;
1014
1097
  let assignment = loop {
@@ -1041,7 +1124,7 @@ fn find_parallel_assignment_name(value: Node<'_>, code: &Source<'_>) -> Option<S
1041
1124
  }
1042
1125
  target = aligned_target(*values, *child, target)?;
1043
1126
  }
1044
- find_assignment_target_text(target, code)
1127
+ Some((assignment, target))
1045
1128
  }
1046
1129
 
1047
1130
  /// The target a value takes within one group. Comments are named children of both sides but bind
@@ -1149,6 +1232,11 @@ fn find_assignment_target_text(target: Node<'_>, code: &Source<'_>) -> Option<St
1149
1232
  /// value: a value that is not an array goes to the first target that is not the star, descending
1150
1233
  /// into a nested group (`a, b = -> { 1 }` and `*a, b = -> { 1 }` both bind the lambda to a name).
1151
1234
  fn find_ruby_assignment_name(assignment: Node<'_>, code: &Source<'_>) -> Option<String> {
1235
+ find_assignment_target_text(find_single_assignment_target(assignment)?, code)
1236
+ }
1237
+
1238
+ /// The target a Ruby or Python assignment of one value binds it to.
1239
+ pub fn find_single_assignment_target(assignment: Node<'_>) -> Option<Node<'_>> {
1152
1240
  let left = assignment.child_by_field_name("left")?;
1153
1241
  let mut target = left;
1154
1242
  if matches!(
@@ -1161,7 +1249,7 @@ fn find_ruby_assignment_name(assignment: Node<'_>, code: &Source<'_>) -> Option<
1161
1249
  .find(|child| !is_splat(child))?;
1162
1250
  }
1163
1251
  }
1164
- find_assignment_target_text(target, code)
1252
+ Some(target)
1165
1253
  }
1166
1254
 
1167
1255
  fn is_ruby_lambda_call(node: Node<'_>, code: &Source<'_>) -> bool {
@@ -305,6 +305,7 @@ const JS_NCSS_NODES: &[&str] = &[
305
305
  "export_statement",
306
306
  "lexical_declaration",
307
307
  "variable_declaration",
308
+ "using_declaration",
308
309
  "function_declaration",
309
310
  "function_signature",
310
311
  "generator_function_declaration",
@@ -425,6 +426,8 @@ const RUST_NCSS_NODES: &[&str] = &[
425
426
  "type_item",
426
427
  "associated_type",
427
428
  "macro_definition",
429
+ "decl_macro",
430
+ "trait_alias",
428
431
  "field_declaration",
429
432
  "let_declaration",
430
433
  "expression_statement",
@@ -563,6 +566,7 @@ const CPP_NCSS_NODES: &[&str] = &[
563
566
  // expression-bodied property or indexer (`int X => ...`) is its own getter (see functions.rs).
564
567
  const CSHARP_FUNCTION_NODES: &[&str] = &[
565
568
  "method_declaration",
569
+ "conditional_method_declaration",
566
570
  "constructor_declaration",
567
571
  "destructor_declaration",
568
572
  "operator_declaration",
@@ -603,6 +607,7 @@ const CSHARP_NCSS_NODES: &[&str] = &[
603
607
  "property_declaration",
604
608
  "indexer_declaration",
605
609
  "method_declaration",
610
+ "conditional_method_declaration",
606
611
  "constructor_declaration",
607
612
  "destructor_declaration",
608
613
  "operator_declaration",
@@ -57,14 +57,19 @@ pub fn measure(
57
57
  .by_function
58
58
  .get(&node.id())
59
59
  .expect("every collected function node opens a frame in the body-metrics pass");
60
- FunctionMetrics {
61
- name: find_function_name(*node, code),
62
- container_name: crate::container::find_container_name(
60
+ let name = find_function_name(*node, code);
61
+ // Without a name of its own, a function has nothing an owner would qualify.
62
+ let container_name = name.as_ref().and_then(|_| {
63
+ crate::container::find_container_name(
63
64
  *node,
64
65
  &sets.function_nodes,
65
66
  code,
66
67
  &inline_namespaces,
67
- ),
68
+ )
69
+ });
70
+ FunctionMetrics {
71
+ name,
72
+ container_name,
68
73
  node_type: node.kind_name().to_string(),
69
74
  start_line: node.start_position().row + 1,
70
75
  // The tree is parsed from UTF-16, so columns are UTF-16 code units x 2 — halving