code-gauge 10.4.0 → 10.4.2

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.
@@ -4,6 +4,8 @@ use tree_sitter::Node;
4
4
  use crate::tree_index::NodeExt;
5
5
  use crate::util::{all_children, find_children_by_field_name, named_children, node_text, Source};
6
6
 
7
+ // code-gauge-allow-file fileNcss 648 -- holds the function-recognition and naming rules of every language
8
+
7
9
  /// Declarations without a body have no control flow, so they are signatures, not functions: C++
8
10
  /// pure-virtual/`= default`/`= delete` members, abstract/interface/extern methods (Java, C#,
9
11
  /// Kotlin), Go assembly-backed function declarations, and Rust trait method signatures, matching
@@ -194,11 +196,11 @@ fn find_parameters_node(node: Node<'_>) -> Option<Node<'_>> {
194
196
  })
195
197
  }
196
198
 
197
- pub fn collect_nodes<'t>(root: Node<'t>, matches: impl Fn(&str) -> bool) -> Vec<Node<'t>> {
199
+ pub fn collect_nodes<'t>(root: Node<'t>, matches: impl Fn(Node<'t>) -> bool) -> Vec<Node<'t>> {
198
200
  let mut nodes = Vec::new();
199
201
 
200
- fn visit<'t>(node: Node<'t>, matches: &impl Fn(&str) -> bool, nodes: &mut Vec<Node<'t>>) {
201
- if matches(node.kind_name()) {
202
+ fn visit<'t>(node: Node<'t>, matches: &impl Fn(Node<'t>) -> bool, nodes: &mut Vec<Node<'t>>) {
203
+ if matches(node) {
202
204
  nodes.push(node);
203
205
  }
204
206
 
@@ -36,7 +36,7 @@ enum GrammarId {
36
36
  Tsx,
37
37
  }
38
38
 
39
- const GRAMMAR_COUNT: usize = GrammarId::Tsx as usize + 1;
39
+ pub const GRAMMAR_COUNT: usize = GrammarId::Tsx as usize + 1;
40
40
 
41
41
  impl LanguageDefinition {
42
42
  pub fn grammar(&self) -> Language {
@@ -56,6 +56,11 @@ impl LanguageDefinition {
56
56
  }
57
57
  }
58
58
 
59
+ /// Distinct for each grammar, below GRAMMAR_COUNT.
60
+ pub fn grammar_index(&self) -> usize {
61
+ self.grammar_id as usize
62
+ }
63
+
59
64
  /// The grammar's node kind names, indexed by kind id.
60
65
  pub fn kind_names(&self) -> &'static [String] {
61
66
  static KIND_NAMES: [OnceLock<Vec<String>>; GRAMMAR_COUNT] =
@@ -3,7 +3,7 @@ use std::sync::OnceLock;
3
3
  use tree_sitter::Node;
4
4
 
5
5
  use crate::complexity::{is_lambda_body_block, measure_function_body_metrics, LanguageSets};
6
- use crate::dep_degree::measure_dep_degree;
6
+ use crate::dep_degree::measure_dep_degrees;
7
7
  use crate::duplication::{
8
8
  collect_cross_file_file_data, hash_text, measure_duplication, tokenize, DuplicationSettings,
9
9
  TokenizedSource,
@@ -12,7 +12,7 @@ use crate::functions::{
12
12
  collect_nodes, count_parameters, find_function_name, is_implemented_function,
13
13
  };
14
14
  use crate::languages::LanguageDefinition;
15
- use crate::tree_index::{NodeExt, TreeIndex};
15
+ use crate::tree_index::{KindSet, NodeExt, TreeIndex};
16
16
  use crate::types::{
17
17
  CrossFileFileData, FunctionMetrics, HalsteadCounts, LineMetrics, NativeMetrics,
18
18
  };
@@ -20,6 +20,7 @@ use crate::util::{
20
20
  all_children, is_js_whitespace, is_token, named_children, node_text, split_lines, Source,
21
21
  };
22
22
 
23
+ // code-gauge-allow functionNcss 32 -- assembles the file's metrics from every pass in one place
23
24
  pub fn measure(
24
25
  code: &str,
25
26
  language: &LanguageDefinition,
@@ -32,23 +33,26 @@ pub fn measure(
32
33
  let _index = TreeIndex::new(&tree, language)?;
33
34
  let root = tree.root_node();
34
35
  let code = &source;
35
- let sets = LanguageSets::new(language);
36
+ let sets = LanguageSets::of(language);
36
37
 
37
38
  // One walk collects both function and initializer-block candidates (Ruby's `block` is both).
38
- let candidates = collect_nodes(root, |kind| {
39
- sets.function_nodes.contains(kind) || INITIALIZER_NODE_TYPES.contains(&kind)
39
+ let candidates = collect_nodes(root, |node| {
40
+ sets.function_nodes.contains(node) || INITIALIZER_KINDS.contains(node)
40
41
  });
41
42
  let initializer_block_count = count_initializer_blocks(&candidates);
42
43
  let functions: Vec<Node<'_>> = candidates
43
44
  .into_iter()
44
45
  .filter(|node| {
45
- sets.function_nodes.contains(node.kind_name())
46
+ sets.function_nodes.contains(*node)
46
47
  && !is_lambda_body_block(*node)
47
48
  && is_implemented_function(*node)
48
49
  })
49
50
  .collect();
50
51
 
51
- let body_metrics = measure_function_body_metrics(root, &sets, code);
52
+ let body_metrics = measure_function_body_metrics(root, sets, code);
53
+ let (halstead_counts, mut halstead_counts_by_function) =
54
+ measure_halstead(root, &functions, code);
55
+ let dep_degrees = measure_dep_degrees(root, &functions, code, &sets.function_nodes);
52
56
  let inline_namespaces = crate::container::find_inline_namespaces(root, code);
53
57
  let function_metrics: Vec<FunctionMetrics> = functions
54
58
  .iter()
@@ -86,14 +90,15 @@ pub fn measure(
86
90
  nesting_depth: body_metrics.nesting_depth,
87
91
  ncss: body_metrics.ncss,
88
92
  parameter_count: count_parameters(*node, code),
89
- halstead_counts: measure_halstead(*node, code),
90
- dep_degree: measure_dep_degree(*node, code, &sets.function_nodes),
93
+ halstead_counts: halstead_counts_by_function
94
+ .remove(&node.id())
95
+ .expect("every function's Halstead counts are measured"),
96
+ dep_degree: dep_degrees[&node.id()],
91
97
  }
92
98
  })
93
99
  .collect();
94
100
 
95
101
  let (lines, code_line_numbers) = classify_lines(code, root);
96
- let halstead_counts = measure_halstead(root, code);
97
102
  let tokenized = tokenize(root, code);
98
103
 
99
104
  Ok(NativeMetrics {
@@ -144,16 +149,16 @@ pub fn measure(
144
149
  /// Initializer blocks run code of their own, so each is a component like a function: Java static
145
150
  /// and instance initializers, Kotlin `init` blocks, and JavaScript/TypeScript class `static`
146
151
  /// blocks. Their decisions already count as decisions outside functions.
147
- const INITIALIZER_NODE_TYPES: &[&str] = &[
152
+ static INITIALIZER_KINDS: KindSet = KindSet::new(&[
148
153
  "static_initializer",
149
154
  "anonymous_initializer",
150
155
  "class_static_block",
151
156
  "block",
152
- ];
157
+ ]);
153
158
 
154
159
  /// A block of a class that runs when the class or an instance is initialized.
155
160
  pub fn is_initializer_block(node: Node<'_>) -> bool {
156
- INITIALIZER_NODE_TYPES.contains(&node.kind_name())
161
+ INITIALIZER_KINDS.contains(node)
157
162
  // A bare block is an initializer only as a direct member of a Java class or enum body.
158
163
  && (node.kind_name() != "block"
159
164
  || node.parent_node().is_some_and(|parent| {
@@ -270,9 +275,9 @@ pub fn collect_function_token_sequences(
270
275
  let tree = parse_source(&source, language)?;
271
276
  let _index = TreeIndex::new(&tree, language)?;
272
277
  let root = tree.root_node();
273
- let sets = LanguageSets::new(language);
278
+ let sets = LanguageSets::of(language);
274
279
  Ok(
275
- collect_nodes(root, |kind| sets.function_nodes.contains(kind))
280
+ collect_nodes(root, |node| sets.function_nodes.contains(node))
276
281
  .into_iter()
277
282
  .filter(|node| !is_lambda_body_block(*node) && is_implemented_function(*node))
278
283
  .map(|node| {
@@ -321,13 +326,11 @@ fn collect_token_symbols(
321
326
  }
322
327
  // Remaining operand leaves are literals, normalized by kind; everything else (keywords,
323
328
  // operators, punctuation) is kept verbatim.
324
- symbols.push(hash_text(
325
- if operand_node_types().contains(node.kind_name()) {
326
- node.kind_name()
327
- } else {
328
- node_text(node, code)
329
- },
330
- ));
329
+ symbols.push(hash_text(if OPERAND_KINDS.contains(node) {
330
+ node.kind_name()
331
+ } else {
332
+ node_text(node, code)
333
+ }));
331
334
  }
332
335
 
333
336
  /// Parses the source from UTF-16, matching node-tree-sitter's JavaScript string semantics:
@@ -566,7 +569,7 @@ const OPERATOR_TEXTS: &[&str] = &[
566
569
  "continue",
567
570
  ];
568
571
 
569
- const OPERAND_NODE_TYPES: &[&str] = &[
572
+ static OPERAND_KINDS: KindSet = KindSet::new(&[
570
573
  "identifier",
571
574
  "implicit_parameter",
572
575
  "property_identifier",
@@ -624,11 +627,11 @@ const OPERAND_NODE_TYPES: &[&str] = &[
624
627
  "undefined",
625
628
  "nil",
626
629
  "none",
627
- ];
630
+ ]);
628
631
 
629
632
  /// Non-leaf literals counted as one Halstead operand without descending.
630
633
  /// `character_literal` is a leaf in Java but wraps its content in C# and Kotlin.
631
- const ATOMIC_OPERAND_NODE_TYPES: &[&str] = &[
634
+ static ATOMIC_OPERAND_KINDS: KindSet = KindSet::new(&[
632
635
  "interpreted_string_literal",
633
636
  "character_literal",
634
637
  "regex",
@@ -637,89 +640,160 @@ const ATOMIC_OPERAND_NODE_TYPES: &[&str] = &[
637
640
  "floating_point_type",
638
641
  "sized_type_specifier",
639
642
  "placeholder_type_specifier",
640
- ];
643
+ ]);
641
644
 
642
645
  fn operator_texts() -> &'static FxHashSet<&'static str> {
643
646
  static SET: OnceLock<FxHashSet<&'static str>> = OnceLock::new();
644
647
  SET.get_or_init(|| OPERATOR_TEXTS.iter().copied().collect())
645
648
  }
646
649
 
647
- fn operand_node_types() -> &'static FxHashSet<&'static str> {
648
- static SET: OnceLock<FxHashSet<&'static str>> = OnceLock::new();
649
- SET.get_or_init(|| OPERAND_NODE_TYPES.iter().copied().collect())
650
- }
650
+ /// Node kinds spelled like an operator.
651
+ static OPERATOR_KINDS: KindSet = KindSet::new(OPERATOR_TEXTS);
651
652
 
652
- fn atomic_operand_node_types() -> &'static FxHashSet<&'static str> {
653
- static SET: OnceLock<FxHashSet<&'static str>> = OnceLock::new();
654
- SET.get_or_init(|| ATOMIC_OPERAND_NODE_TYPES.iter().copied().collect())
653
+ /// Halstead operator and operand occurrences by text.
654
+ #[derive(Default)]
655
+ struct HalsteadTally<'a> {
656
+ operators: FxHashMap<&'a str, u64>,
657
+ operands: FxHashMap<&'a str, u64>,
655
658
  }
656
659
 
657
- fn measure_halstead(root: Node<'_>, code: &Source<'_>) -> HalsteadCounts {
658
- let mut operators: FxHashMap<&str, u64> = FxHashMap::default();
659
- let mut operands: FxHashMap<&str, u64> = FxHashMap::default();
660
-
661
- fn visit<'a>(
662
- node: Node<'a>,
663
- code: &Source<'a>,
664
- operators: &mut FxHashMap<&'a str, u64>,
665
- operands: &mut FxHashMap<&'a str, u64>,
666
- ) {
667
- if matches!(
668
- node.kind_name(),
669
- "comment" | "line_comment" | "block_comment"
670
- ) {
671
- return;
660
+ impl<'a> HalsteadTally<'a> {
661
+ fn counts(&self) -> HalsteadCounts {
662
+ HalsteadCounts {
663
+ distinct_operators: self.operators.len(),
664
+ distinct_operands: self.operands.len(),
665
+ total_operators: self.operators.values().sum(),
666
+ total_operands: self.operands.values().sum(),
672
667
  }
668
+ }
673
669
 
674
- if is_atomic_operand(node) {
675
- *operands.entry(node_text(node, code)).or_insert(0) += 1;
676
- return;
670
+ fn absorb(&mut self, other: HalsteadTally<'a>) {
671
+ for (text, count) in other.operators {
672
+ *self.operators.entry(text).or_insert(0) += count;
673
+ }
674
+ for (text, count) in other.operands {
675
+ *self.operands.entry(text).or_insert(0) += count;
677
676
  }
677
+ }
678
+ }
678
679
 
679
- // Operators are counted from leaf tokens only: keyword-named nodes always contain a
680
- // same-text anonymous keyword leaf, so counting the named node as well would double-count.
681
- if is_token(node) {
682
- let text = node_text(node, code);
683
- // Operands win over text matches so identifiers spelled like word operators stay operands;
684
- // C# `nameof(x)` is the one keyword operator the grammar parses as a plain callee.
685
- if is_csharp_nameof_callee(node, text) {
686
- *operators.entry(text).or_insert(0) += 1;
687
- } else if operand_node_types().contains(node.kind_name()) {
688
- *operands.entry(text).or_insert(0) += 1;
689
- } else if (operator_texts().contains(text)
690
- || operator_texts().contains(node.kind_name()))
691
- && is_countable_contextual_token(node, text)
692
- {
693
- let key = if text.is_empty() {
694
- node.kind_name()
695
- } else {
696
- text
697
- };
698
- *operators.entry(key).or_insert(0) += 1;
680
+ /// Halstead counts of the subtree of `root` and, by node id, of each function in `functions`.
681
+ fn measure_halstead<'a>(
682
+ root: Node<'a>,
683
+ functions: &[Node<'a>],
684
+ code: &Source<'a>,
685
+ ) -> (HalsteadCounts, FxHashMap<usize, HalsteadCounts>) {
686
+ let (counts, mut by_function) =
687
+ count_halstead(root, &functions.iter().map(Node::id).collect(), code);
688
+ for function in functions {
689
+ // The walk counts atomic operands and tokens without descending into them and skips
690
+ // comments, any of which can hold a function (a C++ lambda in a constrained `auto` type).
691
+ by_function
692
+ .entry(function.id())
693
+ .or_insert_with(|| count_halstead(*function, &FxHashSet::default(), code).0);
694
+ }
695
+ (counts, by_function)
696
+ }
697
+
698
+ /// Halstead counts of the subtree of `root` and, by node id, of each function in `function_ids`
699
+ /// whose subtree the walk enters: a function's tokens are counted the same wherever the walk
700
+ /// starts, so its tally is kept apart while its subtree is walked and then joins the enclosing one.
701
+ fn count_halstead<'a>(
702
+ root: Node<'a>,
703
+ function_ids: &FxHashSet<usize>,
704
+ code: &Source<'a>,
705
+ ) -> (HalsteadCounts, FxHashMap<usize, HalsteadCounts>) {
706
+ struct Walk<'a, 'w> {
707
+ code: &'w Source<'a>,
708
+ function_ids: &'w FxHashSet<usize>,
709
+ /// The tally of the walked subtree, then that of each function being walked.
710
+ tallies: Vec<HalsteadTally<'a>>,
711
+ by_function: FxHashMap<usize, HalsteadCounts>,
712
+ }
713
+
714
+ impl<'a> Walk<'a, '_> {
715
+ fn visit(&mut self, node: Node<'a>) {
716
+ let tally = self.tallies.last_mut().expect("the walked subtree's tally");
717
+ if !count_halstead_token(node, self.code, tally) {
718
+ return;
719
+ }
720
+ let is_function = self.function_ids.contains(&node.id());
721
+ if is_function {
722
+ self.tallies.push(HalsteadTally::default());
723
+ }
724
+ for child in all_children(node) {
725
+ self.visit(child);
726
+ }
727
+ if is_function {
728
+ let tally = self.tallies.pop().expect("pushed above");
729
+ self.by_function.insert(node.id(), tally.counts());
730
+ self.tallies
731
+ .last_mut()
732
+ .expect("the walked subtree's tally")
733
+ .absorb(tally);
699
734
  }
700
- return;
701
735
  }
736
+ }
702
737
 
703
- for child in all_children(node) {
704
- visit(child, code, operators, operands);
705
- }
738
+ let mut walk = Walk {
739
+ code,
740
+ function_ids,
741
+ tallies: vec![HalsteadTally::default()],
742
+ by_function: FxHashMap::default(),
743
+ };
744
+ walk.visit(root);
745
+ (walk.tallies[0].counts(), walk.by_function)
746
+ }
747
+
748
+ /// Counts the node into `tally` if it is a token or an atomic operand, returning whether its
749
+ /// children are to be counted instead.
750
+ fn count_halstead_token<'a>(
751
+ node: Node<'a>,
752
+ code: &Source<'a>,
753
+ tally: &mut HalsteadTally<'a>,
754
+ ) -> bool {
755
+ if matches!(
756
+ node.kind_name(),
757
+ "comment" | "line_comment" | "block_comment"
758
+ ) {
759
+ return false;
706
760
  }
707
761
 
708
- visit(root, code, &mut operators, &mut operands);
762
+ if is_atomic_operand(node) {
763
+ *tally.operands.entry(node_text(node, code)).or_insert(0) += 1;
764
+ return false;
765
+ }
709
766
 
710
- HalsteadCounts {
711
- distinct_operators: operators.len(),
712
- distinct_operands: operands.len(),
713
- total_operators: operators.values().sum(),
714
- total_operands: operands.values().sum(),
767
+ // Operators are counted from leaf tokens only: keyword-named nodes always contain a
768
+ // same-text anonymous keyword leaf, so counting the named node as well would double-count.
769
+ if !is_token(node) {
770
+ return true;
771
+ }
772
+ let text = node_text(node, code);
773
+ // Operands win over text matches so identifiers spelled like word operators stay operands;
774
+ // C# `nameof(x)` is the one keyword operator the grammar parses as a plain callee.
775
+ if is_csharp_nameof_callee(node, text) {
776
+ *tally.operators.entry(text).or_insert(0) += 1;
777
+ } else if OPERAND_KINDS.contains(node) {
778
+ *tally.operands.entry(text).or_insert(0) += 1;
779
+ } else if (operator_texts().contains(text) || OPERATOR_KINDS.contains(node))
780
+ && is_countable_contextual_token(node, text)
781
+ {
782
+ let key = if text.is_empty() {
783
+ node.kind_name()
784
+ } else {
785
+ text
786
+ };
787
+ *tally.operators.entry(key).or_insert(0) += 1;
715
788
  }
789
+ false
716
790
  }
717
791
 
718
- /// A literal counted as one operand without descending: ATOMIC_OPERAND_NODE_TYPES, or a Go raw
792
+ /// A literal counted as one operand without descending: ATOMIC_OPERAND_KINDS, or a Go raw
719
793
  /// string, which wraps its content like Go's interpreted strings (the Rust and C++
720
794
  /// `raw_string_literal`s count their content leaves instead).
721
795
  fn is_atomic_operand(node: Node<'_>) -> bool {
722
- atomic_operand_node_types().contains(node.kind_name())
796
+ ATOMIC_OPERAND_KINDS.contains(node)
723
797
  || (node.kind_name() == "raw_string_literal" && crate::tree_index::language_name() == "go")
724
798
  }
725
799
 
@@ -1,7 +1,6 @@
1
- use rustc_hash::FxHashSet;
2
1
  use tree_sitter::Node;
3
2
 
4
- use crate::tree_index::NodeExt;
3
+ use crate::tree_index::{KindSet, NodeExt};
5
4
  use crate::util::find_children_by_field_name;
6
5
 
7
6
  pub const COMMENT_NODE_TYPES: &[&str] = &["comment", "line_comment", "block_comment"];
@@ -12,7 +11,7 @@ pub const COMMENT_NODE_TYPES: &[&str] = &["comment", "line_comment", "block_comm
12
11
  /// constants are not counted either), and Kotlin lambda parameters (siblings of the lambda's
13
12
  /// statements). Kotlin's `try_expression` is excluded contextually below because Rust
14
13
  /// shares its name: Rust's `?` operator must keep counting as a tail expression.
15
- const POSITIONAL_EXCLUSION_TYPES: &[&str] = &[
14
+ static POSITIONAL_EXCLUSION_KINDS: KindSet = KindSet::new(&[
16
15
  "attribute_item",
17
16
  "inner_attribute_item",
18
17
  "empty_statement",
@@ -24,7 +23,7 @@ const POSITIONAL_EXCLUSION_TYPES: &[&str] = &[
24
23
  "shebang",
25
24
  "enum_entry",
26
25
  "lambda_parameters",
27
- ];
26
+ ]);
28
27
 
29
28
  /// TypeScript interface members count like Java interface members, but the same node types appear
30
29
  /// inside object-type annotations (`let x: { a: number }`), which are part of one declaration, so
@@ -59,9 +58,9 @@ const BODYLESS_NCSS_SPECIFIER_TYPES: &[&str] = &[
59
58
  pub fn ncss_contribution(
60
59
  node: Node<'_>,
61
60
  parent: Option<Node<'_>>,
62
- countable: &FxHashSet<&'static str>,
63
- containers: &FxHashSet<&'static str>,
64
- bare_body_parents: &FxHashSet<&'static str>,
61
+ countable: &KindSet,
62
+ containers: &KindSet,
63
+ bare_body_parents: &KindSet,
65
64
  ) -> u64 {
66
65
  if !node.is_named()
67
66
  || COMMENT_NODE_TYPES.contains(&node.kind_name())
@@ -82,8 +81,8 @@ pub fn ncss_contribution(
82
81
  // it stands like any other expression.
83
82
  let positional = (is_in_container_position(parent, containers)
84
83
  || is_bare_body(node, parent, bare_body_parents))
85
- && (!containers.contains(node.kind_name()) || node.kind_name() == "lambda_literal")
86
- && !POSITIONAL_EXCLUSION_TYPES.contains(&node.kind_name())
84
+ && (!containers.contains(node) || node.kind_name() == "lambda_literal")
85
+ && !POSITIONAL_EXCLUSION_KINDS.contains(node)
87
86
  && !crate::util::is_kotlin_try_expression(node);
88
87
  if (counts_through_node_type(node, countable)
89
88
  || positional
@@ -109,8 +108,8 @@ pub fn ncss_contribution(
109
108
  contribution
110
109
  }
111
110
 
112
- fn counts_through_node_type(node: Node<'_>, countable: &FxHashSet<&'static str>) -> bool {
113
- if !countable.contains(node.kind_name()) {
111
+ fn counts_through_node_type(node: Node<'_>, countable: &KindSet) -> bool {
112
+ if !countable.contains(node) {
114
113
  return false;
115
114
  }
116
115
  if BODYLESS_NCSS_SPECIFIER_TYPES.contains(&node.kind_name()) {
@@ -130,14 +129,11 @@ const TRANSPARENT_WRAPPER_TYPES: &[&str] = &["parenthesized_statements", "attrib
130
129
 
131
130
  /// Direct container children count positionally, as do the children of TRANSPARENT_WRAPPER_TYPES
132
131
  /// in that position.
133
- fn is_in_container_position(
134
- parent: Option<Node<'_>>,
135
- containers: &FxHashSet<&'static str>,
136
- ) -> bool {
132
+ fn is_in_container_position(parent: Option<Node<'_>>, containers: &KindSet) -> bool {
137
133
  let mut ancestor = parent;
138
134
  while let Some(current) = ancestor {
139
135
  if !TRANSPARENT_WRAPPER_TYPES.contains(&current.kind_name()) {
140
- return containers.contains(current.kind_name());
136
+ return containers.contains(current);
141
137
  }
142
138
  ancestor = current.parent_node();
143
139
  }
@@ -146,12 +142,8 @@ fn is_in_container_position(
146
142
 
147
143
  /// A braceless branch or loop body (`if (x) foo()`) stands for the single statement a braced body
148
144
  /// would hold.
149
- fn is_bare_body(
150
- node: Node<'_>,
151
- parent: Option<Node<'_>>,
152
- bare_body_parents: &FxHashSet<&'static str>,
153
- ) -> bool {
154
- parent.is_some_and(|parent| bare_body_parents.contains(parent.kind_name()))
145
+ fn is_bare_body(node: Node<'_>, parent: Option<Node<'_>>, bare_body_parents: &KindSet) -> bool {
146
+ parent.is_some_and(|parent| bare_body_parents.contains(parent))
155
147
  && ["consequence", "alternative", "body"]
156
148
  .iter()
157
149
  .any(|field_name| is_field_of_parent(node, parent, field_name))
@@ -159,13 +151,13 @@ fn is_bare_body(
159
151
 
160
152
  /// `export const x = 1` nests a countable declaration inside `export_statement`; only the inner
161
153
  /// declaration counts, mirroring how PMD counts one statement per declared entity.
162
- fn is_declaration_wrapper(node: Node<'_>, countable: &FxHashSet<&'static str>) -> bool {
154
+ fn is_declaration_wrapper(node: Node<'_>, countable: &KindSet) -> bool {
163
155
  if node.kind_name() != "export_statement" {
164
156
  return false;
165
157
  }
166
158
  node.child_by_field_name("declaration")
167
159
  .is_some_and(|declaration| {
168
- countable.contains(declaration.kind_name())
160
+ countable.contains(declaration)
169
161
  || declaration.kind_name() == "internal_module"
170
162
  || declaration.kind_name() == "ambient_declaration"
171
163
  })