code-gauge 5.0.0 → 5.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/native/Cargo.toml CHANGED
@@ -16,27 +16,22 @@ regex = "1"
16
16
  rustc-hash = "2"
17
17
 
18
18
  # Grammar crates are pinned exactly: every metric (and the golden-based test suite) is calibrated
19
- # against these grammar versions, so upgrades are deliberate, test-verified changes. The Kotlin
20
- # grammar is WillBooster's fork, which is not on crates.io, pinned to a release tag. Core 0.25 is
21
- # the first to load that grammar's ABI 15 parser, and tree-sitter-javascript 0.21.x pins
22
- # `cc ~1.0`, which conflicts with the core's `cc ^1.2`, hence 0.25.0. The core reports no field for
23
- # extra (error-recovery) children in ts_node_field_name_for_child, which
24
- # util::find_children_by_field_name compensates for.
25
- tree-sitter = "=0.25.10"
26
- tree-sitter-c = "=0.21.4"
27
- tree-sitter-c-sharp = "=0.21.3"
28
- tree-sitter-cpp = "=0.22.3"
29
- tree-sitter-go = "=0.21.2"
30
- tree-sitter-java = "=0.21.0"
31
- tree-sitter-kotlin = { package = "willbooster-tree-sitter-kotlin", git = "https://github.com/WillBooster/tree-sitter-kotlin", tag = "v1.1.0" }
32
- tree-sitter-javascript = "=0.25.0"
33
- tree-sitter-python = "=0.21.0"
34
- tree-sitter-ruby = "=0.21.0"
35
- # 0.21.0 breaks MSVC (its build script passes the GCC-only -Wno-unused-parameter
36
- # unconditionally; cl.exe fails with D8021); 0.21.2 gates the flag and the golden-based test
37
- # suite verifies its grammar changes leave every measured metric unchanged.
38
- tree-sitter-rust = "=0.21.2"
39
- tree-sitter-typescript = "=0.21.2"
19
+ # against these grammar versions, so upgrades are deliberate, test-verified changes. The
20
+ # `willbooster-*` crates are WillBooster's forks of the runtime and grammars. Every grammar crate
21
+ # depends only on `tree-sitter-language`, never on upstream's `tree-sitter` crate, whose
22
+ # `links = "tree-sitter"` would conflict with the forked runtime's.
23
+ tree-sitter = { package = "willbooster-tree-sitter", version = "1" }
24
+ tree-sitter-c = { package = "willbooster-tree-sitter-c", version = "=1.1.0" }
25
+ tree-sitter-c-sharp = { package = "willbooster-tree-sitter-c-sharp", version = "=1.0.3" }
26
+ tree-sitter-cpp = { package = "willbooster-tree-sitter-cpp", version = "=1.1.1" }
27
+ tree-sitter-go = "=0.25.0"
28
+ tree-sitter-java = "=0.23.5"
29
+ tree-sitter-javascript = { package = "willbooster-tree-sitter-javascript", version = "=1.0.3" }
30
+ tree-sitter-kotlin = { package = "willbooster-tree-sitter-kotlin", version = "=1.4.0" }
31
+ tree-sitter-python = "=0.25.0"
32
+ tree-sitter-ruby = "=0.23.1"
33
+ tree-sitter-rust = { package = "willbooster-tree-sitter-rust", version = "=1.1.1" }
34
+ tree-sitter-typescript = { package = "willbooster-tree-sitter-typescript", version = "=1.1.1" }
40
35
 
41
36
  # The WebAssembly build (src/wasm.rs) exports a plain C ABI instead of N-API.
42
37
  [target.'cfg(not(target_family = "wasm"))'.dependencies]
Binary file
@@ -93,7 +93,7 @@ const NON_BINDING_PARAMETER_FIELDS: &[&str] = &["type", "value"];
93
93
  /// One leaf of the def-use walk: its field in the parent and its function-scope chain.
94
94
  struct DepDegreeLeaf<'t> {
95
95
  node: Node<'t>,
96
- field_name: Option<&'static str>,
96
+ field_name: Option<&'t str>,
97
97
  /// Chain of nested-function scope ids below the measured function: "" for its own body,
98
98
  /// then "/0", "/0/1", ...
99
99
  scope: String,
@@ -221,7 +221,7 @@ fn implicit_accessor_definitions<'a>(function_node: Node<'_>, code: &Source<'a>)
221
221
  #[allow(clippy::too_many_arguments)]
222
222
  fn collect_dep_degree_leaves<'t>(
223
223
  node: Node<'t>,
224
- field_name: Option<&'static str>,
224
+ field_name: Option<&'t str>,
225
225
  scope: &str,
226
226
  next_scope_id: &mut usize,
227
227
  function_nodes: &FxHashSet<&'static str>,
@@ -234,7 +234,7 @@ fn collect_dep_degree_leaves<'t>(
234
234
  ) {
235
235
  return;
236
236
  }
237
- if node.child_count() == 0 {
237
+ if crate::util::is_token(node) {
238
238
  leaves.push(DepDegreeLeaf {
239
239
  node,
240
240
  field_name,
@@ -341,7 +341,7 @@ fn is_definition_field(holder: Node<'_>, field_name: Option<&str>) -> bool {
341
341
  /// fieldless child; a member pointer's `pointer_type_declarator` is the `name` of a
342
342
  /// `qualified_identifier`; an `array_declarator` size or a nested parameter has another field and
343
343
  /// stops the climb) to the outermost wrapper and its field in the declaration.
344
- fn unwrap_declarator_wrappers<'t>(leaf: &DepDegreeLeaf<'t>) -> (Node<'t>, Option<&'static str>) {
344
+ fn unwrap_declarator_wrappers<'t>(leaf: &DepDegreeLeaf<'t>) -> (Node<'t>, Option<&'t str>) {
345
345
  let mut current = leaf.node;
346
346
  let mut field_name = leaf.field_name;
347
347
  while let Some(parent) = current
@@ -439,11 +439,11 @@ fn is_kotlin_parameter(node: Node<'_>) -> bool {
439
439
  }
440
440
 
441
441
  /// Only called with small-arity parents (declarator wrappers, definition-list holders).
442
- fn field_name_in_parent(node: Node<'_>, parent: Node<'_>) -> Option<&'static str> {
442
+ fn field_name_in_parent<'t>(node: Node<'_>, parent: Node<'t>) -> Option<&'t str> {
443
443
  for index in 0..parent.child_count() {
444
444
  if let Some(child) = parent.child(index) {
445
445
  if child.id() == node.id() {
446
- return parent.field_name_for_child(index as u32);
446
+ return parent.field_name_for_child(index);
447
447
  }
448
448
  }
449
449
  }
@@ -10,7 +10,7 @@ use crate::types::{
10
10
  CrossFileCandidate, CrossFileToken, CrossFileTokenRange, DuplicateBlockOccurrence,
11
11
  DuplicationMetrics,
12
12
  };
13
- use crate::util::{all_children, named_children, node_text, to_int32, Source};
13
+ use crate::util::{all_children, is_token, 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] = &[
@@ -103,6 +103,7 @@ const STATEMENT_CONTAINER_TYPES: &[&str] = &[
103
103
  "default_case",
104
104
  "compilation_unit",
105
105
  "switch_section",
106
+ "extension_body",
106
107
  "enum_class_body",
107
108
  "function_body",
108
109
  "lambda_literal",
@@ -229,6 +230,8 @@ const STRING_FRAGMENT_TYPES: &[&str] = &[
229
230
  "string_literal_content",
230
231
  "character_literal_content",
231
232
  "raw_string_content",
233
+ "interpreted_string_literal_content",
234
+ "raw_string_literal_content",
232
235
  "raw_string_start",
233
236
  "raw_string_end",
234
237
  "escape_sequence",
@@ -292,6 +295,8 @@ const STRING_CONTENT_FRAGMENT_TYPES: &[&str] = &[
292
295
  "string_literal_content",
293
296
  "character_literal_content",
294
297
  "raw_string_content",
298
+ "interpreted_string_literal_content",
299
+ "raw_string_literal_content",
295
300
  "escape_sequence",
296
301
  "heredoc_content",
297
302
  ];
@@ -548,20 +553,25 @@ fn collect_tokens<'a>(
548
553
  block_ranges: &mut Vec<TokenRange>,
549
554
  container_statement_ranges: &mut Vec<Vec<TokenRange>>,
550
555
  ) {
556
+ /// `statement_list_statements` receives the statements of a Go `statement_list`, which wraps
557
+ /// the statements of blocks and case clauses and whose statements belong to the run of the
558
+ /// enclosing container.
551
559
  fn visit<'a>(
552
560
  node: Node<'_>,
553
561
  code: &Source<'a>,
554
562
  tokens: &mut Vec<Token<'a>>,
555
563
  block_ranges: &mut Vec<TokenRange>,
556
564
  container_statement_ranges: &mut Vec<Vec<TokenRange>>,
565
+ statement_list_statements: &mut Vec<TokenRange>,
557
566
  ) -> TokenRange {
558
567
  let start_token_index = tokens.len();
559
- let atomic_kind = if node.child_count() == 0 {
568
+ let is_token = is_token(node);
569
+ let atomic_kind = if is_token {
560
570
  None
561
571
  } else {
562
572
  atomic_literal_kind(node)
563
573
  };
564
- if node.child_count() == 0 {
574
+ if is_token {
565
575
  append_leaf_token(node, code, tokens);
566
576
  } else if let Some(atomic_kind) = atomic_kind {
567
577
  // Interpolation-free strings collapse to their kind tag so copies differing only in
@@ -575,14 +585,17 @@ fn collect_tokens<'a>(
575
585
  ));
576
586
  } else if !is_tokenless(node, code) {
577
587
  let mut statement_ranges: Vec<TokenRange> = Vec::new();
578
- let is_container = is_statement_container(node);
588
+ let is_statement_list = node.kind_name() == "statement_list";
589
+ let is_container = is_statement_list || is_statement_container(node);
579
590
  for child in all_children(node) {
591
+ let mut child_statements = Vec::new();
580
592
  let child_range = visit(
581
593
  child,
582
594
  code,
583
595
  tokens,
584
596
  block_ranges,
585
597
  container_statement_ranges,
598
+ &mut child_statements,
586
599
  );
587
600
  // A Kotlin lambda's parameters sit beside its statements.
588
601
  if is_container
@@ -590,13 +603,19 @@ fn collect_tokens<'a>(
590
603
  && !is_tokenless(child, code)
591
604
  && child.kind_name() != "lambda_parameters"
592
605
  {
593
- statement_ranges.push(child_range);
606
+ if child.kind_name() == "statement_list" {
607
+ statement_ranges.append(&mut child_statements);
608
+ } else {
609
+ statement_ranges.push(child_range);
610
+ }
594
611
  }
595
612
  }
596
- // Single-statement containers are recorded too: window enumeration needs two
597
- // statements and yields nothing for them, but cross-file matching catalogues each
598
- // container's full run.
599
- if is_container && !statement_ranges.is_empty() {
613
+ if is_statement_list {
614
+ *statement_list_statements = statement_ranges;
615
+ } else if is_container && !statement_ranges.is_empty() {
616
+ // Single-statement containers are recorded too: window enumeration needs two
617
+ // statements and yields nothing for them, but cross-file matching catalogues each
618
+ // container's full run.
600
619
  container_statement_ranges.push(statement_ranges);
601
620
  }
602
621
  }
@@ -615,7 +634,14 @@ fn collect_tokens<'a>(
615
634
  range
616
635
  }
617
636
 
618
- visit(root, code, tokens, block_ranges, container_statement_ranges);
637
+ visit(
638
+ root,
639
+ code,
640
+ tokens,
641
+ block_ranges,
642
+ container_statement_ranges,
643
+ &mut Vec::new(),
644
+ );
619
645
  }
620
646
 
621
647
  /// Comments and dependency declarations contribute no tokens, so no clone consists of them; the
@@ -935,7 +961,7 @@ fn is_semantic_name_leaf(node: Node<'_>, code: &Source<'_>) -> bool {
935
961
  .named_child(0)
936
962
  .is_some_and(|first| first.id() == node.id());
937
963
  let is_last_named = parent
938
- .named_child(parent.named_child_count().saturating_sub(1))
964
+ .named_child(parent.named_child_count().saturating_sub(1) as u32)
939
965
  .is_some_and(|last| last.id() == node.id());
940
966
  if parent.kind_name() == "navigation_expression" && is_last_named && !is_first_named {
941
967
  return true;
@@ -943,7 +969,7 @@ fn is_semantic_name_leaf(node: Node<'_>, code: &Source<'_>) -> bool {
943
969
  if parent.kind_name() == "call_expression"
944
970
  && is_first_named
945
971
  && parent
946
- .named_child(parent.named_child_count().saturating_sub(1))
972
+ .named_child(parent.named_child_count().saturating_sub(1) as u32)
947
973
  .is_some_and(|arguments| {
948
974
  matches!(
949
975
  arguments.kind_name(),
@@ -821,13 +821,16 @@ fn find_string_literal_content(literal: Node<'_>, code: &Source<'_>) -> Option<S
821
821
  {
822
822
  return None;
823
823
  }
824
- // Go exposes no content node at all: it names only the escapes, so the concatenation is
825
- // trusted only when the literal really spells its content out.
824
+ // Escapes alone do not spell the content out, so the concatenation is trusted only when a
825
+ // content node takes part in it.
826
826
  let mut content = String::new();
827
827
  let mut has_content_node = false;
828
828
  for child in &children {
829
829
  match child.kind_name() {
830
- "string_content" | "string_fragment" => {
830
+ "string_content"
831
+ | "string_fragment"
832
+ | "interpreted_string_literal_content"
833
+ | "raw_string_literal_content" => {
831
834
  has_content_node = true;
832
835
  content.push_str(node_text(*child, code));
833
836
  }
@@ -838,8 +841,8 @@ fn find_string_literal_content(literal: Node<'_>, code: &Source<'_>) -> Option<S
838
841
  if has_content_node && !content.is_empty() {
839
842
  return Some(content);
840
843
  }
841
- // A grammar that exposes no content child (Go's string literals) keeps its delimiters in the
842
- // text; an empty literal is left without a name.
844
+ // A literal without a content child (Go's rune literal) keeps its delimiters in the text; an
845
+ // empty literal is left without a name.
843
846
  let text = node_text(literal, code);
844
847
  let quote = text
845
848
  .chars()
@@ -880,7 +883,7 @@ fn find_assignment_target_name(assignment: Node<'_>, code: &Source<'_>) -> Optio
880
883
  "field_expression" => target.child_by_field_name("field")?,
881
884
  "member_access_expression" => target.child_by_field_name("name")?,
882
885
  "field_access" => target.child_by_field_name("field")?,
883
- "navigation_expression" => target.named_child(target.named_child_count() - 1)?,
886
+ "navigation_expression" => target.named_child(target.named_child_count() as u32 - 1)?,
884
887
  "qualified_identifier" => return unwrap_declarator_name(Some(target), code),
885
888
  _ => return None,
886
889
  };
@@ -975,7 +978,7 @@ fn is_value_of_parent(node: Node<'_>, parent: Node<'_>) -> bool {
975
978
  .is_some_and(|child| child.id() == node.id())
976
979
  {
977
980
  return matches!(
978
- parent.field_name_for_child(index as u32),
981
+ parent.field_name_for_child(index),
979
982
  None | Some("value")
980
983
  );
981
984
  }
@@ -41,24 +41,24 @@ const GRAMMAR_COUNT: usize = GrammarId::Tsx as usize + 1;
41
41
  impl LanguageDefinition {
42
42
  pub fn grammar(&self) -> Language {
43
43
  match self.grammar_id {
44
- GrammarId::C => tree_sitter_c::language(),
45
- GrammarId::Cpp => tree_sitter_cpp::language(),
46
- GrammarId::CSharp => tree_sitter_c_sharp::language(),
47
- GrammarId::Go => tree_sitter_go::language(),
48
- GrammarId::Java => tree_sitter_java::language(),
44
+ GrammarId::C => tree_sitter_c::LANGUAGE.into(),
45
+ GrammarId::Cpp => tree_sitter_cpp::LANGUAGE.into(),
46
+ GrammarId::CSharp => tree_sitter_c_sharp::LANGUAGE.into(),
47
+ GrammarId::Go => tree_sitter_go::LANGUAGE.into(),
48
+ GrammarId::Java => tree_sitter_java::LANGUAGE.into(),
49
49
  GrammarId::JavaScript => tree_sitter_javascript::LANGUAGE.into(),
50
50
  GrammarId::Kotlin => tree_sitter_kotlin::LANGUAGE.into(),
51
- GrammarId::Python => tree_sitter_python::language(),
52
- GrammarId::Ruby => tree_sitter_ruby::language(),
53
- GrammarId::Rust => tree_sitter_rust::language(),
54
- GrammarId::TypeScript => tree_sitter_typescript::language_typescript(),
55
- GrammarId::Tsx => tree_sitter_typescript::language_tsx(),
51
+ GrammarId::Python => tree_sitter_python::LANGUAGE.into(),
52
+ GrammarId::Ruby => tree_sitter_ruby::LANGUAGE.into(),
53
+ GrammarId::Rust => tree_sitter_rust::LANGUAGE.into(),
54
+ GrammarId::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
55
+ GrammarId::Tsx => tree_sitter_typescript::LANGUAGE_TSX.into(),
56
56
  }
57
57
  }
58
58
 
59
59
  /// The grammar's node kind names, indexed by kind id.
60
- pub fn kind_names(&self) -> &'static [&'static str] {
61
- static KIND_NAMES: [OnceLock<Vec<&'static str>>; GRAMMAR_COUNT] =
60
+ pub fn kind_names(&self) -> &'static [String] {
61
+ static KIND_NAMES: [OnceLock<Vec<String>>; GRAMMAR_COUNT] =
62
62
  [const { OnceLock::new() }; GRAMMAR_COUNT];
63
63
  KIND_NAMES[self.grammar_id as usize].get_or_init(|| {
64
64
  let grammar = self.grammar();
@@ -68,6 +68,7 @@ impl LanguageDefinition {
68
68
  .ok()
69
69
  .and_then(|id| grammar.node_kind_for_id(id))
70
70
  .unwrap_or_default()
71
+ .to_string()
71
72
  })
72
73
  .collect()
73
74
  })
@@ -361,7 +362,6 @@ const PYTHON_NCSS_NODES: &[&str] = &[
361
362
  "for_statement",
362
363
  "while_statement",
363
364
  "except_clause",
364
- "except_group_clause",
365
365
  "finally_clause",
366
366
  "with_statement",
367
367
  "match_statement",
@@ -595,6 +595,7 @@ const CSHARP_NCSS_NODES: &[&str] = &[
595
595
  "interface_declaration",
596
596
  "enum_declaration",
597
597
  "record_declaration",
598
+ "extension_declaration",
598
599
  "delegate_declaration",
599
600
  "field_declaration",
600
601
  "event_field_declaration",
@@ -16,7 +16,9 @@ use crate::tree_index::{NodeExt, TreeIndex};
16
16
  use crate::types::{
17
17
  CrossFileFileData, FunctionMetrics, HalsteadCounts, LineMetrics, NativeMetrics,
18
18
  };
19
- use crate::util::{all_children, is_js_whitespace, named_children, node_text, split_lines, Source};
19
+ use crate::util::{
20
+ all_children, is_js_whitespace, is_token, named_children, node_text, split_lines, Source,
21
+ };
20
22
 
21
23
  pub fn measure(
22
24
  code: &str,
@@ -274,11 +276,11 @@ fn collect_token_symbols(
274
276
  ) {
275
277
  return;
276
278
  }
277
- if atomic_operand_node_types().contains(node.kind_name()) {
279
+ if is_atomic_operand(node) {
278
280
  symbols.push(hash_text(node.kind_name()));
279
281
  return;
280
282
  }
281
- if node.child_count() != 0 {
283
+ if !is_token(node) {
282
284
  for child in all_children(node) {
283
285
  collect_token_symbols(child, code, symbols, id_index_by_name);
284
286
  }
@@ -584,8 +586,6 @@ const OPERAND_NODE_TYPES: &[&str] = &[
584
586
  "hex_floating_point_literal",
585
587
  "string",
586
588
  "string_literal",
587
- // Go raw strings are leaves with no content child, unlike Rust/C++ `raw_string_literal`s.
588
- "raw_string_literal",
589
589
  "verbatim_string_literal",
590
590
  "string_fragment",
591
591
  "multiline_string_fragment",
@@ -638,7 +638,7 @@ fn measure_halstead(root: Node<'_>, code: &Source<'_>) -> HalsteadCounts {
638
638
  let mut operands: FxHashMap<&str, u64> = FxHashMap::default();
639
639
 
640
640
  fn visit<'a>(
641
- node: Node<'_>,
641
+ node: Node<'a>,
642
642
  code: &Source<'a>,
643
643
  operators: &mut FxHashMap<&'a str, u64>,
644
644
  operands: &mut FxHashMap<&'a str, u64>,
@@ -650,14 +650,14 @@ fn measure_halstead(root: Node<'_>, code: &Source<'_>) -> HalsteadCounts {
650
650
  return;
651
651
  }
652
652
 
653
- if atomic_operand_node_types().contains(node.kind_name()) {
653
+ if is_atomic_operand(node) {
654
654
  *operands.entry(node_text(node, code)).or_insert(0) += 1;
655
655
  return;
656
656
  }
657
657
 
658
658
  // Operators are counted from leaf tokens only: keyword-named nodes always contain a
659
659
  // same-text anonymous keyword leaf, so counting the named node as well would double-count.
660
- if node.child_count() == 0 {
660
+ if is_token(node) {
661
661
  let text = node_text(node, code);
662
662
  // Operands win over text matches so identifiers spelled like word operators stay operands;
663
663
  // C# `nameof(x)` is the one keyword operator the grammar parses as a plain callee.
@@ -694,6 +694,14 @@ fn measure_halstead(root: Node<'_>, code: &Source<'_>) -> HalsteadCounts {
694
694
  }
695
695
  }
696
696
 
697
+ /// A literal counted as one operand without descending: ATOMIC_OPERAND_NODE_TYPES, or a Go raw
698
+ /// string, which wraps its content like Go's interpreted strings (the Rust and C++
699
+ /// `raw_string_literal`s count their content leaves instead).
700
+ fn is_atomic_operand(node: Node<'_>) -> bool {
701
+ atomic_operand_node_types().contains(node.kind_name())
702
+ || (node.kind_name() == "raw_string_literal" && crate::tree_index::language_name() == "go")
703
+ }
704
+
697
705
  /// tree-sitter-c-sharp parses `nameof(x)` as an invocation of an identifier named `nameof`.
698
706
  fn is_csharp_nameof_callee(node: Node<'_>, text: &str) -> bool {
699
707
  text == "nameof"
@@ -11,7 +11,7 @@ thread_local! {
11
11
  /// nodes; TreeIndex borrows the tree and clears the map on drop, so no entry outlives it.
12
12
  static PARENTS: RefCell<FxHashMap<usize, Node<'static>>> = RefCell::new(FxHashMap::default());
13
13
  /// Node kind names of the indexed tree's language, indexed by kind id.
14
- static KIND_NAMES: Cell<&'static [&'static str]> = const { Cell::new(&[]) };
14
+ static KIND_NAMES: Cell<&'static [String]> = const { Cell::new(&[]) };
15
15
  /// Name of the indexed tree's language.
16
16
  static LANGUAGE_NAME: Cell<&'static str> = const { Cell::new("") };
17
17
  }
@@ -87,7 +87,7 @@ pub trait NodeExt<'t> {
87
87
  /// `Node::parent`, answered from the TreeIndex when one is installed.
88
88
  fn parent_node(self) -> Option<Node<'t>>;
89
89
  /// `Node::kind`, answered from the TreeIndex when one is installed.
90
- fn kind_name(self) -> &'static str;
90
+ fn kind_name(self) -> &'t str;
91
91
  }
92
92
 
93
93
  impl<'t> NodeExt<'t> for Node<'t> {
@@ -109,11 +109,11 @@ impl<'t> NodeExt<'t> for Node<'t> {
109
109
  .or_else(|| self.parent())
110
110
  }
111
111
 
112
- fn kind_name(self) -> &'static str {
112
+ fn kind_name(self) -> &'t str {
113
113
  KIND_NAMES
114
114
  .get()
115
115
  .get(usize::from(self.kind_id()))
116
- .copied()
116
+ .map(String::as_str)
117
117
  .unwrap_or_else(|| self.kind())
118
118
  }
119
119
  }
@@ -61,6 +61,13 @@ pub fn is_kotlin_keyword_literal(node: Node<'_>, code: &Source<'_>) -> bool {
61
61
  && matches!(node_text(node, code), "true" | "false" | "null")
62
62
  }
63
63
 
64
+ /// Whether the node stands for one source token. C# spells a contextual keyword used as a name
65
+ /// (`type`, `field`, `alias`) as an `identifier` or `implicit_parameter` wrapping the keyword token,
66
+ /// which still names a variable like any other identifier.
67
+ pub fn is_token(node: Node<'_>) -> bool {
68
+ node.child_count() == 0 || matches!(node.kind_name(), "identifier" | "implicit_parameter")
69
+ }
70
+
64
71
  pub fn named_children<'t>(node: Node<'t>) -> Vec<Node<'t>> {
65
72
  let mut cursor = node.walk();
66
73
  node.named_children(&mut cursor).collect()
@@ -77,13 +84,13 @@ pub fn all_children<'t>(node: Node<'t>) -> Vec<Node<'t>> {
77
84
  /// them), which would desynchronize field-based extraction on malformed source.
78
85
  pub fn find_children_by_field_name<'t>(node: Node<'t>, field_name: &str) -> Vec<Node<'t>> {
79
86
  let mut children = Vec::new();
80
- let mut preceding_structural_field: Option<&'static str> = None;
87
+ let mut preceding_structural_field = None;
81
88
  for index in 0..node.child_count() {
82
89
  if let Some(child) = node.child(index) {
83
90
  let field = if child.is_extra() {
84
91
  preceding_structural_field
85
92
  } else {
86
- let field = node.field_name_for_child(index as u32);
93
+ let field = node.field_name_for_child(index);
87
94
  preceding_structural_field = field;
88
95
  field
89
96
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-gauge",
3
- "version": "5.0.0",
3
+ "version": "5.1.0",
4
4
  "description": "Measure code metrics with tree-sitter.",
5
5
  "keywords": [
6
6
  "cli",
@@ -62,6 +62,7 @@
62
62
  "verify-full": "bun wb verify --full"
63
63
  },
64
64
  "dependencies": {
65
+ "@willbooster/shared-lib": "10.18.0",
65
66
  "commander": "15.0.0"
66
67
  },
67
68
  "devDependencies": {
@@ -94,12 +95,12 @@
94
95
  "registry": "https://registry.npmjs.org/"
95
96
  },
96
97
  "optionalDependencies": {
97
- "code-gauge-linux-x64-gnu": "5.0.0",
98
- "code-gauge-linux-arm64-gnu": "5.0.0",
99
- "code-gauge-linux-x64-musl": "5.0.0",
100
- "code-gauge-linux-arm64-musl": "5.0.0",
101
- "code-gauge-darwin-x64": "5.0.0",
102
- "code-gauge-darwin-arm64": "5.0.0",
103
- "code-gauge-win32-x64-msvc": "5.0.0"
98
+ "code-gauge-linux-x64-gnu": "5.1.0",
99
+ "code-gauge-linux-arm64-gnu": "5.1.0",
100
+ "code-gauge-linux-x64-musl": "5.1.0",
101
+ "code-gauge-linux-arm64-musl": "5.1.0",
102
+ "code-gauge-darwin-x64": "5.1.0",
103
+ "code-gauge-darwin-arm64": "5.1.0",
104
+ "code-gauge-win32-x64-msvc": "5.1.0"
104
105
  }
105
106
  }