code-gauge 4.7.1 → 4.8.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 (59) hide show
  1. package/README.md +1 -1
  2. package/THIRD-PARTY-NOTICES.txt +595 -29
  3. package/dist/cli.cjs +1 -1
  4. package/dist/cli.cjs.map +1 -1
  5. package/dist/cli.js +1 -1
  6. package/dist/cli.js.map +1 -1
  7. package/dist/cliConfig.cjs +1 -1
  8. package/dist/cliConfig.cjs.map +1 -1
  9. package/dist/cliConfig.js +1 -1
  10. package/dist/cliConfig.js.map +1 -1
  11. package/dist/crossFileDuplication.cjs +1 -1
  12. package/dist/crossFileDuplication.cjs.map +1 -1
  13. package/dist/crossFileDuplication.js +1 -1
  14. package/dist/crossFileDuplication.js.map +1 -1
  15. package/dist/crossFileNearMiss.cjs +1 -1
  16. package/dist/crossFileNearMiss.cjs.map +1 -1
  17. package/dist/crossFileNearMiss.js +1 -1
  18. package/dist/crossFileNearMiss.js.map +1 -1
  19. package/dist/diffCommand.cjs +1 -1
  20. package/dist/diffCommand.cjs.map +1 -1
  21. package/dist/diffCommand.js +1 -1
  22. package/dist/diffCommand.js.map +1 -1
  23. package/dist/duplicateSelection.cjs +1 -1
  24. package/dist/duplicateSelection.cjs.map +1 -1
  25. package/dist/duplicateSelection.js +1 -1
  26. package/dist/duplicateSelection.js.map +1 -1
  27. package/dist/duplication.cjs +1 -1
  28. package/dist/duplication.cjs.map +1 -1
  29. package/dist/duplication.js +1 -1
  30. package/dist/duplication.js.map +1 -1
  31. package/dist/git.cjs +1 -1
  32. package/dist/git.cjs.map +1 -1
  33. package/dist/git.js +1 -1
  34. package/dist/git.js.map +1 -1
  35. package/dist/languages.cjs +1 -1
  36. package/dist/languages.cjs.map +1 -1
  37. package/dist/languages.js +1 -1
  38. package/dist/languages.js.map +1 -1
  39. package/dist/regressionGate.cjs +1 -1
  40. package/dist/regressionGate.cjs.map +1 -1
  41. package/dist/regressionGate.js +1 -1
  42. package/dist/regressionGate.js.map +1 -1
  43. package/dist/scan.cjs +1 -1
  44. package/dist/scan.cjs.map +1 -1
  45. package/dist/scan.js +1 -1
  46. package/dist/scan.js.map +1 -1
  47. package/native/Cargo.lock +47 -15
  48. package/native/Cargo.toml +9 -9
  49. package/native/code-gauge.wasm +0 -0
  50. package/native/src/complexity.rs +26 -33
  51. package/native/src/dep_degree.rs +43 -32
  52. package/native/src/duplication.rs +55 -40
  53. package/native/src/functions.rs +20 -62
  54. package/native/src/languages.rs +28 -7
  55. package/native/src/measure.rs +16 -27
  56. package/native/src/ncss.rs +32 -23
  57. package/native/src/tree_index.rs +9 -0
  58. package/native/src/util.rs +12 -57
  59. package/package.json +10 -10
@@ -10,7 +10,7 @@ use crate::types::{
10
10
  CrossFileCandidate, CrossFileToken, CrossFileTokenRange, DuplicateBlockOccurrence,
11
11
  DuplicationMetrics,
12
12
  };
13
- use crate::util::{all_children, is_identifier_leaf, named_children, node_text, to_int32, Source};
13
+ use crate::util::{all_children, named_children, node_text, to_int32, Source};
14
14
 
15
15
  /// Block-like nodes considered as whole-subtree duplicate candidates.
16
16
  const DUPLICATE_BLOCK_TYPES: &[&str] = &[
@@ -62,10 +62,7 @@ const DUPLICATE_BLOCK_TYPES: &[&str] = &[
62
62
  "do_while_statement",
63
63
  "catch_block",
64
64
  "finally_block",
65
- "statements",
66
- "control_structure_body",
67
65
  "function_body",
68
- "jump_expression",
69
66
  "jsx_element",
70
67
  "jsx_self_closing_element",
71
68
  "if",
@@ -106,10 +103,9 @@ const STATEMENT_CONTAINER_TYPES: &[&str] = &[
106
103
  "default_case",
107
104
  "compilation_unit",
108
105
  "switch_section",
109
- "statements",
110
106
  "enum_class_body",
111
- "control_structure_body",
112
107
  "function_body",
108
+ "lambda_literal",
113
109
  ];
114
110
 
115
111
  /// C# type bodies are `declaration_list`s, a name Rust also uses for `mod`/`impl`/`trait` bodies;
@@ -122,8 +118,8 @@ const CSHARP_DECLARATION_LIST_PARENT_TYPES: &[&str] = &[
122
118
  "namespace_declaration",
123
119
  ];
124
120
 
125
- /// Whole-subtree duplicate candidates: DUPLICATE_BLOCK_TYPES plus Kotlin's `try_expression`,
126
- /// distinguished by its clause children from Rust's `try_expression` (the `?` operator).
121
+ /// Whole-subtree duplicate candidates: DUPLICATE_BLOCK_TYPES plus Kotlin's `try_expression`, which
122
+ /// shares its name with Rust's `?` operator (see is_kotlin_try_expression).
127
123
  fn is_duplicate_block(node: Node<'_>) -> bool {
128
124
  if !node.is_named() {
129
125
  return false;
@@ -149,8 +145,6 @@ fn is_statement_container(node: Node<'_>) -> bool {
149
145
  /// Identifier leaves anonymized by occurrence order so consistently renamed copies still match.
150
146
  const ANONYMIZED_IDENTIFIER_TYPES: &[&str] = &[
151
147
  "identifier",
152
- "simple_identifier",
153
- "interpolated_identifier",
154
148
  "implicit_parameter",
155
149
  "constant",
156
150
  "instance_variable",
@@ -172,8 +166,6 @@ const LITERAL_KIND_BY_TYPE: &[(&str, &str)] = &[
172
166
  ("integer_literal", "#num"),
173
167
  ("float_literal", "#num"),
174
168
  ("real_literal", "#num"),
175
- ("hex_literal", "#num"),
176
- ("bin_literal", "#num"),
177
169
  ("int_literal", "#num"),
178
170
  ("rune_literal", "#char"),
179
171
  ("imaginary_literal", "#num"),
@@ -188,7 +180,6 @@ const LITERAL_KIND_BY_TYPE: &[(&str, &str)] = &[
188
180
  ("string_content", "#str"),
189
181
  ("string_literal_content", "#str"),
190
182
  ("character_literal_content", "#char"),
191
- ("character_escape_seq", "#char"),
192
183
  ("verbatim_string_literal", "#str"),
193
184
  ("interpolated_string_expression", "#str"),
194
185
  ("raw_string_content", "#str"),
@@ -208,12 +199,7 @@ const LITERAL_KIND_BY_TYPE: &[(&str, &str)] = &[
208
199
  ("regex_pattern", "#regex"),
209
200
  ];
210
201
 
211
- const COMMENT_TYPES: &[&str] = &[
212
- "comment",
213
- "line_comment",
214
- "block_comment",
215
- "multiline_comment",
216
- ];
202
+ const COMMENT_TYPES: &[&str] = &["comment", "line_comment", "block_comment"];
217
203
 
218
204
  /// Children of a string node that carry only literal content; anything else is interpolation.
219
205
  const STRING_FRAGMENT_TYPES: &[&str] = &[
@@ -222,7 +208,6 @@ const STRING_FRAGMENT_TYPES: &[&str] = &[
222
208
  "string_content",
223
209
  "string_literal_content",
224
210
  "character_literal_content",
225
- "character_escape_seq",
226
211
  "raw_string_content",
227
212
  "raw_string_start",
228
213
  "raw_string_end",
@@ -286,7 +271,6 @@ const STRING_CONTENT_FRAGMENT_TYPES: &[&str] = &[
286
271
  "string_content",
287
272
  "string_literal_content",
288
273
  "character_literal_content",
289
- "character_escape_seq",
290
274
  "raw_string_content",
291
275
  "escape_sequence",
292
276
  "heredoc_content",
@@ -552,12 +536,12 @@ fn collect_tokens<'a>(
552
536
  container_statement_ranges: &mut Vec<Vec<TokenRange>>,
553
537
  ) -> TokenRange {
554
538
  let start_token_index = tokens.len();
555
- let atomic_kind = if is_identifier_leaf(node) {
539
+ let atomic_kind = if node.child_count() == 0 {
556
540
  None
557
541
  } else {
558
542
  atomic_literal_kind(node)
559
543
  };
560
- if is_identifier_leaf(node) {
544
+ if node.child_count() == 0 {
561
545
  append_leaf_token(node, code, tokens);
562
546
  } else if let Some(atomic_kind) = atomic_kind {
563
547
  // Interpolation-free strings collapse to their kind tag so copies differing only in
@@ -580,7 +564,12 @@ fn collect_tokens<'a>(
580
564
  block_ranges,
581
565
  container_statement_ranges,
582
566
  );
583
- if is_container && child.is_named() && !COMMENT_TYPES.contains(&child.kind_name()) {
567
+ // A Kotlin lambda's parameters sit beside its statements.
568
+ if is_container
569
+ && child.is_named()
570
+ && !COMMENT_TYPES.contains(&child.kind_name())
571
+ && child.kind_name() != "lambda_parameters"
572
+ {
584
573
  statement_ranges.push(child_range);
585
574
  }
586
575
  }
@@ -664,14 +653,19 @@ fn append_leaf_token<'a>(node: Node<'_>, code: &Source<'a>, tokens: &mut Vec<Tok
664
653
  return;
665
654
  }
666
655
 
667
- // A Kotlin bound callable-reference receiver (`xs::size`) renames like a variable unless it is
668
- // PascalCase, the discriminator used for static receivers everywhere else.
669
- let is_variable_receiver = crate::util::is_kotlin_callable_receiver(node)
670
- && !pascal_case_regex().is_match(node_text(node, code));
656
+ if crate::util::is_kotlin_keyword_literal(node, code) {
657
+ tokens.push(make_text_token(
658
+ Cow::Borrowed(node_text(node, code)),
659
+ None,
660
+ false,
661
+ start_row,
662
+ end_row,
663
+ ));
664
+ return;
665
+ }
671
666
  if node.is_named()
672
- && (is_variable_receiver
673
- || (ANONYMIZED_IDENTIFIER_TYPES.contains(&node.kind_name())
674
- && !is_semantic_name_leaf(node, code)))
667
+ && ANONYMIZED_IDENTIFIER_TYPES.contains(&node.kind_name())
668
+ && !is_semantic_name_leaf(node, code)
675
669
  {
676
670
  tokens.push(Token {
677
671
  is_id: true,
@@ -795,8 +789,15 @@ fn is_semantic_name_leaf(node: Node<'_>, code: &Source<'_>) -> bool {
795
789
  };
796
790
 
797
791
  // Java method references (`Foo::bar`) and Kotlin callable references (`::bar`) name their
798
- // identifiers without grammar fields.
799
- if parent.kind_name() == "method_reference" || parent.kind_name() == "callable_reference" {
792
+ // identifiers without grammar fields, and Kotlin spells type names as identifiers.
793
+ if matches!(
794
+ parent.kind_name(),
795
+ "method_reference" | "callable_reference" | "user_type"
796
+ ) {
797
+ return true;
798
+ }
799
+ // A Kotlin type parameter's name has no field (C# puts its own in `name`).
800
+ if parent.kind_name() == "type_parameter" && parent.child_by_field_name("name").is_none() {
800
801
  return true;
801
802
  }
802
803
 
@@ -823,16 +824,30 @@ fn is_semantic_name_leaf(node: Node<'_>, code: &Source<'_>) -> bool {
823
824
  }
824
825
  }
825
826
 
826
- // Kotlin (no grammar fields): a callee (`foo(...)`), a member name (`a.foo`), an infix function
827
- // (`a shl b`), and a named argument (`foo(name = x)`) are API names.
828
- if node.kind_name() == "simple_identifier" {
829
- if parent.kind_name() == "navigation_suffix" {
830
- return true;
831
- }
827
+ // Kotlin (no callee or member fields): a callee (`foo(...)`, whose call ends in Kotlin's own
828
+ // argument nodes), a member name (`a.foo`), an infix function (`a shl b`), and a named
829
+ // argument (`foo(name = x)`) are API names.
830
+ if node.kind_name() == "identifier" {
832
831
  let is_first_named = parent
833
832
  .named_child(0)
834
833
  .is_some_and(|first| first.id() == node.id());
835
- if parent.kind_name() == "call_expression" && is_first_named {
834
+ let is_last_named = parent
835
+ .named_child(parent.named_child_count().saturating_sub(1))
836
+ .is_some_and(|last| last.id() == node.id());
837
+ if parent.kind_name() == "navigation_expression" && is_last_named && !is_first_named {
838
+ return true;
839
+ }
840
+ if parent.kind_name() == "call_expression"
841
+ && is_first_named
842
+ && parent
843
+ .named_child(parent.named_child_count().saturating_sub(1))
844
+ .is_some_and(|arguments| {
845
+ matches!(
846
+ arguments.kind_name(),
847
+ "value_arguments" | "annotated_lambda"
848
+ )
849
+ })
850
+ {
836
851
  return true;
837
852
  }
838
853
  if parent.kind_name() == "infix_expression"
@@ -37,8 +37,8 @@ pub fn is_implemented_function(node: Node<'_>) -> bool {
37
37
  .is_some_and(|value| value.kind_name() == "arrow_expression_clause");
38
38
  }
39
39
 
40
- // The Kotlin grammar has no fields; an implemented function or accessor holds a
41
- // `function_body` child.
40
+ // Kotlin has no `body` field; an implemented function or accessor holds a `function_body`
41
+ // child.
42
42
  if node.kind_name() == "getter"
43
43
  || node.kind_name() == "setter"
44
44
  || node.kind_name() == "function_declaration"
@@ -175,7 +175,7 @@ fn find_parameters_node(node: Node<'_>) -> Option<Node<'_>> {
175
175
  if node.kind_name() == "setter"
176
176
  && named_children(node)
177
177
  .iter()
178
- .any(|child| child.kind_name() == "parameter_with_optional_type")
178
+ .any(|child| child.kind_name() == "identifier")
179
179
  {
180
180
  return Some(node);
181
181
  }
@@ -343,14 +343,17 @@ pub fn find_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
343
343
  // (`run = { ... }`, `obj.run = { ... }`) takes the assigned name.
344
344
  if node.kind_name() == "lambda_literal" || node.kind_name() == "anonymous_function" {
345
345
  let mut holder = parent;
346
- while holder.kind_name() == "prefix_expression" {
346
+ while matches!(
347
+ holder.kind_name(),
348
+ "labeled_expression" | "annotated_expression"
349
+ ) {
347
350
  holder = holder.parent_node()?;
348
351
  }
349
352
  if holder.kind_name() == "property_declaration" {
350
353
  return find_kotlin_property_name(holder, code);
351
354
  }
352
355
  if holder.kind_name() == "assignment" {
353
- return find_kotlin_assignment_name(holder, code);
356
+ return find_assignment_target_name(holder, code);
354
357
  }
355
358
  }
356
359
  // A `lambda { }` / `proc { }` block is measured, but the call around it is what gets bound.
@@ -852,8 +855,8 @@ fn find_string_literal_content(literal: Node<'_>, code: &Source<'_>) -> Option<S
852
855
  }
853
856
 
854
857
  /// A compound assignment (`x += f`) does not bind the function to its target, so only a plain `=`
855
- /// names it. Grammars with an `operator` field (C/C++, C#, Java) expose it directly; Go and Kotlin
856
- /// have none, so the operator is the assignment's own anonymous token child.
858
+ /// names it. Grammars with an `operator` field (C/C++, C#, Java, Kotlin) expose it directly; Go has
859
+ /// none, so the operator is the assignment's own anonymous token child.
857
860
  fn is_plain_assignment(assignment: Node<'_>, code: &Source<'_>) -> bool {
858
861
  match assignment.child_by_field_name("operator") {
859
862
  Some(operator) => node_text(operator, code) == "=",
@@ -863,9 +866,9 @@ fn is_plain_assignment(assignment: Node<'_>, code: &Source<'_>) -> bool {
863
866
  }
864
867
  }
865
868
 
866
- /// The assigned identifier, or the member name of a JS member, Rust/C++ field, C# member, or Java
867
- /// field access (`a.b.run` names `run`) or a C++ qualified name (`N::run`); subscripts (`o["run"]`)
868
- /// name nothing.
869
+ /// The assigned identifier, or the member name of a JS member, Rust/C++ field, C# member, Java
870
+ /// field access, or Kotlin navigation (`a.b.run` names `run`) or a C++ qualified name (`N::run`);
871
+ /// subscripts (`o["run"]`) name nothing.
869
872
  fn find_assignment_target_name(assignment: Node<'_>, code: &Source<'_>) -> Option<String> {
870
873
  if !is_plain_assignment(assignment, code) {
871
874
  return None;
@@ -877,34 +880,17 @@ fn find_assignment_target_name(assignment: Node<'_>, code: &Source<'_>) -> Optio
877
880
  "field_expression" => target.child_by_field_name("field")?,
878
881
  "member_access_expression" => target.child_by_field_name("name")?,
879
882
  "field_access" => target.child_by_field_name("field")?,
883
+ "navigation_expression" => target.named_child(target.named_child_count() - 1)?,
880
884
  "qualified_identifier" => return unwrap_declarator_name(Some(target), code),
881
885
  _ => return None,
882
886
  };
883
887
  Some(node_text(name, code).to_string())
884
888
  }
885
889
 
886
- /// The Kotlin assignment target: the variable itself or the member of a trailing navigation suffix
887
- /// (`obj.run` names `run`); a trailing indexing suffix (`arr[0] = { }`) names nothing, like
888
- /// subscripts in the other languages.
889
- fn find_kotlin_assignment_name(assignment: Node<'_>, code: &Source<'_>) -> Option<String> {
890
- if !is_plain_assignment(assignment, code) {
891
- return None;
892
- }
893
- let target = first_named_child_of_kind(assignment, "directly_assignable_expression")?;
894
- let children = named_children(target);
895
- let holder = match children.last()? {
896
- last if last.kind_name() == "navigation_suffix" => *last,
897
- last if last.kind_name() == "simple_identifier" && children.len() == 1 => target,
898
- _ => return None,
899
- };
900
- first_named_child_of_kind(holder, "simple_identifier")
901
- .map(|name| node_text(name, code).to_string())
902
- }
903
-
904
890
  /// Names of C# and Kotlin members whose grammars carry no usable `name` field: accessors are
905
891
  /// named after their property (`Count.get`; an expression-bodied property is its own getter),
906
- /// Kotlin functions by their identifier child, Kotlin secondary constructors and C# destructors
907
- /// after their class, and C# operators like C++ ones.
892
+ /// Kotlin secondary constructors and C# destructors after their class, and C# operators like C++
893
+ /// ones.
908
894
  fn find_member_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
909
895
  match node.kind_name() {
910
896
  "accessor_declaration" => {
@@ -923,20 +909,17 @@ fn find_member_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String
923
909
  };
924
910
  Some(format!(
925
911
  "{}.{keyword}",
926
- find_kotlin_accessor_owner_name(node, code)?
912
+ find_kotlin_property_name(node.parent_node()?, code)?
927
913
  ))
928
914
  }
929
- "function_declaration" if node.child_by_field_name("name").is_none() => {
930
- first_named_child_of_kind(node, "simple_identifier")
931
- .map(|name| node_text(name, code).to_string())
932
- }
933
915
  "secondary_constructor" => {
934
916
  let mut ancestor = node.parent_node();
935
917
  while let Some(current) = ancestor {
936
918
  if current.kind_name() == "class_declaration"
937
919
  || current.kind_name() == "object_declaration"
938
920
  {
939
- return first_named_child_of_kind(current, "type_identifier")
921
+ return current
922
+ .child_by_field_name("name")
940
923
  .map(|name| node_text(name, code).to_string());
941
924
  }
942
925
  ancestor = current.parent_node();
@@ -967,38 +950,13 @@ fn csharp_property_name(owner: Node<'_>, code: &Source<'_>) -> Option<String> {
967
950
  }
968
951
  }
969
952
 
970
- /// The property a Kotlin accessor belongs to: its parent when the accessor follows the initializer
971
- /// on the same line, otherwise (accessor on its own line) the grammar emits it as a class-body
972
- /// sibling after the property, any preceding accessor, and any comments between them.
973
- fn find_kotlin_accessor_owner_name(accessor: Node<'_>, code: &Source<'_>) -> Option<String> {
974
- if let Some(name) = accessor
975
- .parent_node()
976
- .and_then(|parent| find_kotlin_property_name(parent, code))
977
- {
978
- return Some(name);
979
- }
980
- let mut sibling = accessor.prev_named_sibling();
981
- while let Some(current) = sibling {
982
- if current.kind_name() == "property_declaration" {
983
- return find_kotlin_property_name(current, code);
984
- }
985
- if !matches!(current.kind_name(), "getter" | "setter")
986
- && !crate::ncss::COMMENT_NODE_TYPES.contains(&current.kind_name())
987
- {
988
- return None;
989
- }
990
- sibling = current.prev_named_sibling();
991
- }
992
- None
993
- }
994
-
995
953
  /// The declared name of a Kotlin `property_declaration` (`val name: T`), if it declares one.
996
954
  fn find_kotlin_property_name(property: Node<'_>, code: &Source<'_>) -> Option<String> {
997
955
  if property.kind_name() != "property_declaration" {
998
956
  return None;
999
957
  }
1000
958
  let declaration = first_named_child_of_kind(property, "variable_declaration")?;
1001
- first_named_child_of_kind(declaration, "simple_identifier")
959
+ first_named_child_of_kind(declaration, "identifier")
1002
960
  .map(|name| node_text(name, code).to_string())
1003
961
  }
1004
962
 
@@ -15,6 +15,9 @@ pub struct LanguageDefinition {
15
15
  pub nesting_node_types: &'static [&'static str],
16
16
  pub ncss_node_types: &'static [&'static str],
17
17
  pub ncss_container_node_types: &'static [&'static str],
18
+ /// Parents whose braceless `consequence`/`alternative`/`body` child counts like the single
19
+ /// statement of a braced body, for grammars that wrap it in no statement node.
20
+ pub ncss_bare_body_parent_node_types: &'static [&'static str],
18
21
  }
19
22
 
20
23
  #[derive(Clone, Copy)]
@@ -43,8 +46,8 @@ impl LanguageDefinition {
43
46
  GrammarId::CSharp => tree_sitter_c_sharp::language(),
44
47
  GrammarId::Go => tree_sitter_go::language(),
45
48
  GrammarId::Java => tree_sitter_java::language(),
46
- GrammarId::JavaScript => tree_sitter_javascript::language(),
47
- GrammarId::Kotlin => tree_sitter_kotlin::language(),
49
+ GrammarId::JavaScript => tree_sitter_javascript::LANGUAGE.into(),
50
+ GrammarId::Kotlin => tree_sitter_kotlin::LANGUAGE.into(),
48
51
  GrammarId::Python => tree_sitter_python::language(),
49
52
  GrammarId::Ruby => tree_sitter_ruby::language(),
50
53
  GrammarId::Rust => tree_sitter_rust::language(),
@@ -661,16 +664,21 @@ const KOTLIN_NCSS_NODES: &[&str] = &[
661
664
  "getter",
662
665
  "setter",
663
666
  ];
664
- // `control_structure_body` holds a braceless branch/loop body (`if (x) foo()`), which counts like
665
- // the single statement of a braced one; `function_body` holds an expression body (`fun f() = x`).
667
+ // `function_body` holds an expression body (`fun f() = x`).
666
668
  const KOTLIN_NCSS_CONTAINERS: &[&str] = &[
667
669
  "source_file",
668
- "import_list",
669
- "statements",
670
+ "block",
670
671
  "class_body",
671
672
  "enum_class_body",
672
- "control_structure_body",
673
673
  "function_body",
674
+ "lambda_literal",
675
+ ];
676
+ const KOTLIN_NCSS_BARE_BODY_PARENTS: &[&str] = &[
677
+ "if_expression",
678
+ "for_statement",
679
+ "while_statement",
680
+ "do_while_statement",
681
+ "when_entry",
674
682
  ];
675
683
 
676
684
  pub const LANGUAGES: &[LanguageDefinition] = &[
@@ -684,6 +692,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
684
692
  nesting_node_types: COMMON_NESTING_NODES,
685
693
  ncss_node_types: JS_NCSS_NODES,
686
694
  ncss_container_node_types: &[],
695
+ ncss_bare_body_parent_node_types: &[],
687
696
  },
688
697
  LanguageDefinition {
689
698
  name: "jsx",
@@ -695,6 +704,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
695
704
  nesting_node_types: COMMON_NESTING_NODES,
696
705
  ncss_node_types: JS_NCSS_NODES,
697
706
  ncss_container_node_types: &[],
707
+ ncss_bare_body_parent_node_types: &[],
698
708
  },
699
709
  LanguageDefinition {
700
710
  name: "typescript",
@@ -706,6 +716,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
706
716
  nesting_node_types: COMMON_NESTING_NODES,
707
717
  ncss_node_types: JS_NCSS_NODES,
708
718
  ncss_container_node_types: &[],
719
+ ncss_bare_body_parent_node_types: &[],
709
720
  },
710
721
  LanguageDefinition {
711
722
  name: "tsx",
@@ -717,6 +728,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
717
728
  nesting_node_types: COMMON_NESTING_NODES,
718
729
  ncss_node_types: JS_NCSS_NODES,
719
730
  ncss_container_node_types: &[],
731
+ ncss_bare_body_parent_node_types: &[],
720
732
  },
721
733
  LanguageDefinition {
722
734
  name: "python",
@@ -728,6 +740,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
728
740
  nesting_node_types: COMMON_NESTING_NODES,
729
741
  ncss_node_types: PYTHON_NCSS_NODES,
730
742
  ncss_container_node_types: &[],
743
+ ncss_bare_body_parent_node_types: &[],
731
744
  },
732
745
  LanguageDefinition {
733
746
  name: "go",
@@ -739,6 +752,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
739
752
  nesting_node_types: GO_NESTING_NODES,
740
753
  ncss_node_types: GO_NCSS_NODES,
741
754
  ncss_container_node_types: &[],
755
+ ncss_bare_body_parent_node_types: &[],
742
756
  },
743
757
  LanguageDefinition {
744
758
  name: "rust",
@@ -750,6 +764,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
750
764
  nesting_node_types: COMMON_NESTING_NODES,
751
765
  ncss_node_types: RUST_NCSS_NODES,
752
766
  ncss_container_node_types: RUST_NCSS_CONTAINERS,
767
+ ncss_bare_body_parent_node_types: &[],
753
768
  },
754
769
  LanguageDefinition {
755
770
  name: "java",
@@ -761,6 +776,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
761
776
  nesting_node_types: JAVA_DECISION_NODES,
762
777
  ncss_node_types: JAVA_NCSS_NODES,
763
778
  ncss_container_node_types: &[],
779
+ ncss_bare_body_parent_node_types: &[],
764
780
  },
765
781
  LanguageDefinition {
766
782
  name: "ruby",
@@ -772,6 +788,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
772
788
  nesting_node_types: RUBY_DECISION_NODES,
773
789
  ncss_node_types: RUBY_NCSS_NODES,
774
790
  ncss_container_node_types: RUBY_NCSS_CONTAINERS,
791
+ ncss_bare_body_parent_node_types: &[],
775
792
  },
776
793
  LanguageDefinition {
777
794
  name: "c",
@@ -783,6 +800,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
783
800
  nesting_node_types: C_DECISION_NODES,
784
801
  ncss_node_types: C_NCSS_NODES,
785
802
  ncss_container_node_types: &[],
803
+ ncss_bare_body_parent_node_types: &[],
786
804
  },
787
805
  LanguageDefinition {
788
806
  name: "cpp",
@@ -794,6 +812,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
794
812
  nesting_node_types: CPP_DECISION_NODES,
795
813
  ncss_node_types: CPP_NCSS_NODES,
796
814
  ncss_container_node_types: &[],
815
+ ncss_bare_body_parent_node_types: &[],
797
816
  },
798
817
  LanguageDefinition {
799
818
  name: "csharp",
@@ -805,6 +824,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
805
824
  nesting_node_types: CSHARP_DECISION_NODES,
806
825
  ncss_node_types: CSHARP_NCSS_NODES,
807
826
  ncss_container_node_types: &[],
827
+ ncss_bare_body_parent_node_types: &[],
808
828
  },
809
829
  LanguageDefinition {
810
830
  name: "kotlin",
@@ -816,6 +836,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
816
836
  nesting_node_types: KOTLIN_DECISION_NODES,
817
837
  ncss_node_types: KOTLIN_NCSS_NODES,
818
838
  ncss_container_node_types: KOTLIN_NCSS_CONTAINERS,
839
+ ncss_bare_body_parent_node_types: KOTLIN_NCSS_BARE_BODY_PARENTS,
819
840
  },
820
841
  ];
821
842
 
@@ -16,10 +16,7 @@ use crate::tree_index::{NodeExt, TreeIndex};
16
16
  use crate::types::{
17
17
  CrossFileFileData, FunctionMetrics, HalsteadCounts, LineMetrics, NativeMetrics,
18
18
  };
19
- use crate::util::{
20
- all_children, is_identifier_leaf, is_js_whitespace, named_children, node_text, split_lines,
21
- Source,
22
- };
19
+ use crate::util::{all_children, is_js_whitespace, named_children, node_text, split_lines, Source};
23
20
 
24
21
  pub fn measure(
25
22
  code: &str,
@@ -166,23 +163,20 @@ fn is_csharp_top_level_statement(node: Node<'_>) -> bool {
166
163
  fn has_top_level_statements(root: Node<'_>, language: &LanguageDefinition) -> bool {
167
164
  const KOTLIN_DECLARATIONS: &[&str] = &[
168
165
  "package_header",
169
- "import_list",
166
+ "import",
170
167
  "class_declaration",
171
168
  "object_declaration",
172
169
  "function_declaration",
173
170
  "property_declaration",
174
171
  "type_alias",
175
- "shebang_line",
172
+ "shebang",
176
173
  "file_annotation",
177
- "getter",
178
- "setter",
179
174
  ];
180
175
  let children = named_children(root);
181
176
  match language.name {
182
177
  "csharp" => children.into_iter().any(is_csharp_top_level_statement),
183
- // The grammar mis-parses some valid declarations (non-empty companion objects, `fun
184
- // interface`) and leaves recovery residue at the top level, so a file with parse errors is
185
- // never taken for a script.
178
+ // Error recovery leaves residue at the top level, so a file with parse errors is never taken
179
+ // for a script.
186
180
  "kotlin" => {
187
181
  !root.has_error()
188
182
  && children.iter().any(|child| {
@@ -233,8 +227,6 @@ fn to_cross_file_data(
233
227
  /// Name-carrying leaf types anonymized by tokenize_function so consistent renames still match.
234
228
  const IDENTIFIER_LEAF_NODE_TYPES: &[&str] = &[
235
229
  "identifier",
236
- "simple_identifier",
237
- "interpolated_identifier",
238
230
  "implicit_parameter",
239
231
  "property_identifier",
240
232
  "field_identifier",
@@ -278,7 +270,7 @@ fn collect_token_symbols(
278
270
  ) {
279
271
  if matches!(
280
272
  node.kind_name(),
281
- "comment" | "line_comment" | "block_comment" | "multiline_comment"
273
+ "comment" | "line_comment" | "block_comment"
282
274
  ) {
283
275
  return;
284
276
  }
@@ -286,12 +278,16 @@ fn collect_token_symbols(
286
278
  symbols.push(hash_text(node.kind_name()));
287
279
  return;
288
280
  }
289
- if !is_identifier_leaf(node) {
281
+ if node.child_count() != 0 {
290
282
  for child in all_children(node) {
291
283
  collect_token_symbols(child, code, symbols, id_index_by_name);
292
284
  }
293
285
  return;
294
286
  }
287
+ if crate::util::is_kotlin_keyword_literal(node, code) {
288
+ symbols.push(hash_text(node_text(node, code)));
289
+ return;
290
+ }
295
291
  if IDENTIFIER_LEAF_NODE_TYPES.contains(&node.kind_name()) {
296
292
  let next_index = id_index_by_name.len();
297
293
  let index = *id_index_by_name
@@ -322,7 +318,7 @@ fn parse_source(
322
318
  .set_language(&language.grammar())
323
319
  .map_err(|error| error.to_string())?;
324
320
  parser
325
- .parse_utf16(source.to_utf16(), None)
321
+ .parse_utf16_le(source.to_utf16(), None)
326
322
  .ok_or_else(|| "parse failed".to_string())
327
323
  }
328
324
 
@@ -380,7 +376,7 @@ fn collect_comment_spans(root: Node<'_>) -> Vec<CommentSpan> {
380
376
  fn visit(node: Node<'_>, spans: &mut Vec<CommentSpan>) {
381
377
  if matches!(
382
378
  node.kind_name(),
383
- "comment" | "line_comment" | "block_comment" | "multiline_comment"
379
+ "comment" | "line_comment" | "block_comment"
384
380
  ) {
385
381
  for row in node.start_position().row..=node.end_position().row {
386
382
  // Node columns are UTF-16 code units x 2 (the tree is parsed from UTF-16);
@@ -549,8 +545,6 @@ const OPERATOR_TEXTS: &[&str] = &[
549
545
 
550
546
  const OPERAND_NODE_TYPES: &[&str] = &[
551
547
  "identifier",
552
- "simple_identifier",
553
- "interpolated_identifier",
554
548
  "implicit_parameter",
555
549
  "property_identifier",
556
550
  "field_identifier",
@@ -578,8 +572,6 @@ const OPERAND_NODE_TYPES: &[&str] = &[
578
572
  "integer_literal",
579
573
  "float_literal",
580
574
  "real_literal",
581
- "hex_literal",
582
- "bin_literal",
583
575
  "int_literal",
584
576
  "rune_literal",
585
577
  "imaginary_literal",
@@ -614,13 +606,10 @@ const OPERAND_NODE_TYPES: &[&str] = &[
614
606
  ];
615
607
 
616
608
  /// Non-leaf literals counted as one Halstead operand without descending.
617
- /// `character_literal` is a leaf in Java and Kotlin but wraps a content node in C#; Kotlin's
618
- /// suffixed numbers (`1L`, `1u`) wrap the bare literal, so `1` and `1L` stay distinct.
609
+ /// `character_literal` is a leaf in Java but wraps its content in C# and Kotlin.
619
610
  const ATOMIC_OPERAND_NODE_TYPES: &[&str] = &[
620
611
  "interpreted_string_literal",
621
612
  "character_literal",
622
- "long_literal",
623
- "unsigned_literal",
624
613
  "regex",
625
614
  "user_defined_literal",
626
615
  "integral_type",
@@ -656,7 +645,7 @@ fn measure_halstead(root: Node<'_>, code: &Source<'_>) -> HalsteadCounts {
656
645
  ) {
657
646
  if matches!(
658
647
  node.kind_name(),
659
- "comment" | "line_comment" | "block_comment" | "multiline_comment"
648
+ "comment" | "line_comment" | "block_comment"
660
649
  ) {
661
650
  return;
662
651
  }
@@ -668,7 +657,7 @@ fn measure_halstead(root: Node<'_>, code: &Source<'_>) -> HalsteadCounts {
668
657
 
669
658
  // Operators are counted from leaf tokens only: keyword-named nodes always contain a
670
659
  // same-text anonymous keyword leaf, so counting the named node as well would double-count.
671
- if is_identifier_leaf(node) {
660
+ if node.child_count() == 0 {
672
661
  let text = node_text(node, code);
673
662
  // Operands win over text matches so identifiers spelled like word operators stay operands;
674
663
  // C# `nameof(x)` is the one keyword operator the grammar parses as a plain callee.