code-gauge 4.3.0 → 4.3.1

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/README.md CHANGED
@@ -173,9 +173,9 @@ The `duplication` section tunes how clones are detected:
173
173
  import declarations count, and statement-shaped content is counted uniformly in expression
174
174
  positions too
175
175
  - Per-function and file-level nesting depth
176
- - Per-function cyclomatic complexity (McCabe, own body only), matching PMD's
177
- `CyclomaticComplexity` rule for Java; an API-only compatibility metric that ranking and the
178
- regression gate ignore
176
+ - Per-function cyclomatic complexity (McCabe, own body only), counted as NIST SP 500-235 defines it:
177
+ every decision and short-circuit operator adds one, one per case-labelled statement; an API-only
178
+ metric that ranking and the regression gate ignore
179
179
  - Per-function parameter counts and locations (name, node type, line span)
180
180
  - Within-file duplication: copy-pasted blocks matched on normalized tokens (identifiers anonymized
181
181
  consistently, literals by kind, and literal-dense data tables excluded unless their values also
package/dist/types.d.ts CHANGED
@@ -65,13 +65,12 @@ export interface FunctionMetrics {
65
65
  /** Exclusive end column, so functions can be tested for containment in each other. */
66
66
  endColumn: number;
67
67
  /**
68
- * McCabe cyclomatic complexity of the function's own body: 1 plus one per decision point
69
- * (branch, loop, non-default case label, catch, ternary, boolean operator, pattern guard). Java
70
- * follows PMD's `CyclomaticComplexity` instead, which also counts `throw` but counts `&&`/`||`
71
- * only inside branch, loop, ternary, and switch conditions and adds nothing for pattern labels
72
- * and guards. Nested function bodies are excluded, so summing over functions counts each
73
- * decision point once. Not used by ranking or the regression gate; exposed for consumers that
74
- * report PMD-compatible metrics.
68
+ * McCabe cyclomatic complexity of the function's own body, counted from source as NIST SP 500-235
69
+ * §4 prescribes: 1 plus one per decision (branch, loop, catch, ternary, pattern guard), per
70
+ * short-circuit operator (`&&`, `||`, `and`, `or`) wherever it appears, and per case-labelled
71
+ * statement or match arm (stacked labels share one; default and catch-all arms add none).
72
+ * Full-evaluation operators and `throw` add nothing. Nested function bodies are excluded, so
73
+ * summing over functions counts each decision once. Not used by ranking or the regression gate.
75
74
  */
76
75
  cyclomaticComplexity: number;
77
76
  cognitiveComplexity: number;
@@ -12,7 +12,6 @@ pub struct ComplexityResult {
12
12
  pub struct LanguageSets {
13
13
  pub function_nodes: HashSet<&'static str>,
14
14
  pub decision_nodes: HashSet<&'static str>,
15
- pub pmd_cyclomatic: bool,
16
15
  pub nesting_nodes: HashSet<&'static str>,
17
16
  pub ncss_nodes: HashSet<&'static str>,
18
17
  pub ncss_containers: HashSet<&'static str>,
@@ -23,7 +22,6 @@ impl LanguageSets {
23
22
  LanguageSets {
24
23
  function_nodes: language.function_node_types.iter().copied().collect(),
25
24
  decision_nodes: language.decision_node_types.iter().copied().collect(),
26
- pmd_cyclomatic: language.pmd_cyclomatic,
27
25
  nesting_nodes: language.nesting_node_types.iter().copied().collect(),
28
26
  ncss_nodes: language.ncss_node_types.iter().copied().collect(),
29
27
  ncss_containers: language.ncss_container_node_types.iter().copied().collect(),
@@ -201,7 +199,7 @@ impl FunctionBodyPass<'_, '_, '_> {
201
199
  // `if` keyword token), so only named nodes count as decisions.
202
200
  let is_decision = current.is_named()
203
201
  && self.sets.decision_nodes.contains(current.kind())
204
- && !is_default_switch_branch(current);
202
+ && !is_pathless_switch_branch(current, self.code);
205
203
  let is_case_clause = current.is_named() && CASE_CLAUSE_NODE_TYPES.contains(&current.kind());
206
204
  // Ruby's `case ... else` arm is an `else` node; like every other language's default branch
207
205
  // it nests its contents inside the switch (it cannot go in the Ruby nesting set because
@@ -216,18 +214,12 @@ impl FunctionBodyPass<'_, '_, '_> {
216
214
  // surcharge (Sonar cognitive-complexity semantics).
217
215
  let is_continuation = is_decision && is_flat_chain_continuation(current);
218
216
 
219
- if counts_for_own_body {
220
- self.top_frame().cyclomatic_complexity += if self.sets.pmd_cyclomatic {
221
- crate::cyclomatic::pmd_cyclomatic_increment(current, self.code)
222
- } else {
223
- // Each boolean operator and pattern guard is one more execution path; `else` adds
224
- // none.
225
- u64::from(
226
- is_decision
227
- || is_boolean_operator(current, self.code)
228
- || is_pattern_guard(current),
229
- )
230
- };
217
+ // Each branch, short-circuit operator, and pattern guard adds one path (McCabe; NIST SP
218
+ // 500-235 §4); `else` adds none.
219
+ if counts_for_own_body
220
+ && (is_decision || is_boolean_operator(current, self.code) || is_pattern_guard(current))
221
+ {
222
+ self.top_frame().cyclomatic_complexity += 1;
231
223
  }
232
224
  if is_decision && !is_case_clause {
233
225
  if is_continuation {
@@ -380,7 +372,7 @@ pub fn measure_complexity(
380
372
  // `if` keyword token), so only named nodes count as decisions.
381
373
  let is_decision = current.is_named()
382
374
  && sets.decision_nodes.contains(current.kind())
383
- && !is_default_switch_branch(current);
375
+ && !is_pathless_switch_branch(current, code);
384
376
  let is_case_clause = current.is_named() && CASE_CLAUSE_NODE_TYPES.contains(&current.kind());
385
377
  // Ruby's `case ... else` arm is an `else` node; like every other language's default branch
386
378
  // it nests its contents inside the switch (it cannot go in the Ruby nesting set because
@@ -633,13 +625,93 @@ fn is_flat_chain_continuation(node: Node<'_>) -> bool {
633
625
  .is_some_and(|alternative| alternative.id() == node.id())
634
626
  }
635
627
 
636
- /// Default branches of switch-like constructs add no decision.
637
- fn is_default_switch_branch(node: Node<'_>) -> bool {
628
+ /// Switch branches that add no path. NIST SP 500-235 counts one path per case-labelled statement:
629
+ /// a label-only case shares the statement of the case below it, and that statement adds a path
630
+ /// unless one of its stacked labels is an unguarded default or catch-all (`case 3: default: g();`
631
+ /// and `default: case 3: g();` are the default outcome) or all of them are catch-alls. Guards are
632
+ /// charged separately, so a guarded catch-all never absorbs a case stacked with it.
633
+ fn is_pathless_switch_branch(node: Node<'_>, code: &Source<'_>) -> bool {
634
+ if is_label_only_case(node) {
635
+ return true;
636
+ }
637
+ let mut stacked = vec![node];
638
+ let mut previous = node.prev_named_sibling();
639
+ while let Some(sibling) = previous {
640
+ if !crate::ncss::COMMENT_NODE_TYPES.contains(&sibling.kind()) {
641
+ if !is_label_only_case(sibling) {
642
+ break;
643
+ }
644
+ stacked.push(sibling);
645
+ }
646
+ previous = sibling.prev_named_sibling();
647
+ }
648
+ stacked
649
+ .iter()
650
+ .any(|label| is_default_switch_branch(*label, code) && !has_pattern_guard(*label))
651
+ || stacked
652
+ .iter()
653
+ .all(|label| is_default_switch_branch(*label, code))
654
+ }
655
+
656
+ /// A guard directly on the arm (C# `when`, Python `if`, Ruby `if`/`unless`) or inside its label or
657
+ /// pattern (Java `when`, Rust `if`).
658
+ fn has_pattern_guard(node: Node<'_>) -> bool {
659
+ crate::util::named_children(node).into_iter().any(|child| {
660
+ is_pattern_guard(child)
661
+ || crate::util::named_children(child)
662
+ .into_iter()
663
+ .any(is_pattern_guard)
664
+ })
665
+ }
666
+
667
+ /// A C/C++/JS/Java/C# case whose labels share the next case's statements; every grammar parses each
668
+ /// stacked label as its own case node.
669
+ fn is_label_only_case(node: Node<'_>) -> bool {
670
+ let children = non_comment_children(node);
671
+ match node.kind() {
672
+ "case_statement" => {
673
+ let value = node.child_by_field_name("value").map(|value| value.id());
674
+ children.iter().all(|child| Some(child.id()) == value)
675
+ }
676
+ "switch_case" | "switch_default" => node.child_by_field_name("body").is_none(),
677
+ "switch_block_statement_group" => {
678
+ children.iter().all(|child| child.kind() == "switch_label")
679
+ }
680
+ // A C# label is a pattern (`case 1:` parses as a constant pattern) plus an optional `when`
681
+ // guard; anything else, a `#if` block included, is the section's body.
682
+ "switch_section" => children.iter().all(|child| {
683
+ child.kind().ends_with("pattern")
684
+ || child.kind() == "discard"
685
+ || child.kind() == "when_clause"
686
+ }),
687
+ _ => false,
688
+ }
689
+ }
690
+
691
+ fn is_default_switch_branch(node: Node<'_>, code: &Source<'_>) -> bool {
638
692
  let kind = node.kind();
693
+ if kind == "switch_default" {
694
+ return true;
695
+ }
639
696
  if kind == "case_statement" {
640
697
  return node.child_by_field_name("value").is_none();
641
698
  }
642
699
 
700
+ // Java `default:` groups and `default ->` rules: a label with no expression or pattern, or a
701
+ // `case null, default` label, whose `default` the grammar parses as an identifier.
702
+ if kind == "switch_block_statement_group" || kind == "switch_rule" {
703
+ return non_comment_children(node)
704
+ .into_iter()
705
+ .filter(|child| child.kind() == "switch_label")
706
+ .any(|label| {
707
+ let parts = non_comment_children(label);
708
+ parts.is_empty()
709
+ || parts.iter().any(|part| {
710
+ part.kind() == "identifier" && node_text(*part, code) == "default"
711
+ })
712
+ });
713
+ }
714
+
643
715
  // C# `default:` sections and catch-all (`_`, `var x`) labels and arms, and Kotlin `else ->`
644
716
  // entries. A guarded catch-all (`_ when cond =>`) is still a default arm: only its guard
645
717
  // branches, which is_pattern_guard charges, like Python's `case _ if cond:` and Rust's
@@ -3,15 +3,16 @@ use tree_sitter::Node;
3
3
 
4
4
  use crate::util::{all_children, find_children_by_field_name, named_children, node_text, Source};
5
5
 
6
- /// C++ `function_definition` also covers pure-virtual/`= default`/`= delete` members; those have no
7
- /// `body` and are signatures, not implementations, matching how TypeScript method signatures are
8
- /// excluded. Java `method_declaration` is NOT here: PMD reports abstract/interface methods as
9
- /// methods (NCSS 1), so bodyless Java methods stay in the function list (as do C#'s and Kotlin's).
10
- /// C# auto-property accessors (`{ get; set; }`) and Kotlin visibility-only accessors (`private
6
+ /// Declarations without a body have no control flow, so they are signatures, not functions: C++
7
+ /// pure-virtual/`= default`/`= delete` members, abstract/interface/extern methods (Java, C#,
8
+ /// Kotlin), Go assembly-backed function declarations, and Rust trait method signatures, matching
9
+ /// how TypeScript method signatures are excluded. C# auto-property accessors (`{ get; set; }`) and Kotlin visibility-only accessors (`private
11
10
  /// set`) hold no code, so they need a body too; a C# property or indexer is a function only in its
12
11
  /// expression-bodied form (`int X => ...`), otherwise its accessors are the functions.
13
12
  const BODY_REQUIRED_FUNCTION_TYPES: &[&str] = &[
14
13
  "function_definition",
14
+ "function_declaration",
15
+ "method_declaration",
15
16
  "constructor_declaration",
16
17
  "compact_constructor_declaration",
17
18
  "function_signature_item",
@@ -35,8 +36,9 @@ pub fn is_implemented_function(node: Node<'_>) -> bool {
35
36
  .is_some_and(|value| value.kind() == "arrow_expression_clause");
36
37
  }
37
38
 
38
- // The Kotlin grammar has no fields; an implemented accessor holds a `function_body` child.
39
- if node.kind() == "getter" || node.kind() == "setter" {
39
+ // The Kotlin grammar has no fields; an implemented function or accessor holds a
40
+ // `function_body` child.
41
+ if node.kind() == "getter" || node.kind() == "setter" || node.kind() == "function_declaration" {
40
42
  return named_children(node)
41
43
  .iter()
42
44
  .any(|child| child.kind() == "function_body");
@@ -7,9 +7,6 @@ pub struct LanguageDefinition {
7
7
  grammar_id: GrammarId,
8
8
  pub function_node_types: &'static [&'static str],
9
9
  pub decision_node_types: &'static [&'static str],
10
- /// Whether cyclomatic complexity follows PMD's Java CycloVisitor (see cyclomatic.rs) instead of
11
- /// the generic one-path-per-decision rule.
12
- pub pmd_cyclomatic: bool,
13
10
  pub nesting_node_types: &'static [&'static str],
14
11
  pub ncss_node_types: &'static [&'static str],
15
12
  pub ncss_container_node_types: &'static [&'static str],
@@ -659,7 +656,6 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
659
656
  grammar_id: GrammarId::JavaScript,
660
657
  function_node_types: COMMON_FUNCTION_NODES,
661
658
  decision_node_types: COMMON_DECISION_NODES,
662
- pmd_cyclomatic: false,
663
659
  nesting_node_types: COMMON_NESTING_NODES,
664
660
  ncss_node_types: JS_NCSS_NODES,
665
661
  ncss_container_node_types: &[],
@@ -670,7 +666,6 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
670
666
  grammar_id: GrammarId::JavaScript,
671
667
  function_node_types: COMMON_FUNCTION_NODES,
672
668
  decision_node_types: COMMON_DECISION_NODES,
673
- pmd_cyclomatic: false,
674
669
  nesting_node_types: COMMON_NESTING_NODES,
675
670
  ncss_node_types: JS_NCSS_NODES,
676
671
  ncss_container_node_types: &[],
@@ -681,7 +676,6 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
681
676
  grammar_id: GrammarId::TypeScript,
682
677
  function_node_types: COMMON_FUNCTION_NODES,
683
678
  decision_node_types: COMMON_DECISION_NODES,
684
- pmd_cyclomatic: false,
685
679
  nesting_node_types: COMMON_NESTING_NODES,
686
680
  ncss_node_types: JS_NCSS_NODES,
687
681
  ncss_container_node_types: &[],
@@ -692,7 +686,6 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
692
686
  grammar_id: GrammarId::Tsx,
693
687
  function_node_types: COMMON_FUNCTION_NODES,
694
688
  decision_node_types: COMMON_DECISION_NODES,
695
- pmd_cyclomatic: false,
696
689
  nesting_node_types: COMMON_NESTING_NODES,
697
690
  ncss_node_types: JS_NCSS_NODES,
698
691
  ncss_container_node_types: &[],
@@ -703,7 +696,6 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
703
696
  grammar_id: GrammarId::Python,
704
697
  function_node_types: COMMON_FUNCTION_NODES,
705
698
  decision_node_types: COMMON_DECISION_NODES,
706
- pmd_cyclomatic: false,
707
699
  nesting_node_types: COMMON_NESTING_NODES,
708
700
  ncss_node_types: PYTHON_NCSS_NODES,
709
701
  ncss_container_node_types: &[],
@@ -714,7 +706,6 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
714
706
  grammar_id: GrammarId::Go,
715
707
  function_node_types: COMMON_FUNCTION_NODES,
716
708
  decision_node_types: GO_DECISION_NODES,
717
- pmd_cyclomatic: false,
718
709
  nesting_node_types: GO_NESTING_NODES,
719
710
  ncss_node_types: GO_NCSS_NODES,
720
711
  ncss_container_node_types: &[],
@@ -725,7 +716,6 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
725
716
  grammar_id: GrammarId::Rust,
726
717
  function_node_types: COMMON_FUNCTION_NODES,
727
718
  decision_node_types: COMMON_DECISION_NODES,
728
- pmd_cyclomatic: false,
729
719
  nesting_node_types: COMMON_NESTING_NODES,
730
720
  ncss_node_types: RUST_NCSS_NODES,
731
721
  ncss_container_node_types: RUST_NCSS_CONTAINERS,
@@ -736,7 +726,6 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
736
726
  grammar_id: GrammarId::Java,
737
727
  function_node_types: JAVA_FUNCTION_NODES,
738
728
  decision_node_types: JAVA_DECISION_NODES,
739
- pmd_cyclomatic: true,
740
729
  nesting_node_types: JAVA_DECISION_NODES,
741
730
  ncss_node_types: JAVA_NCSS_NODES,
742
731
  ncss_container_node_types: &[],
@@ -747,7 +736,6 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
747
736
  grammar_id: GrammarId::Ruby,
748
737
  function_node_types: RUBY_FUNCTION_NODES,
749
738
  decision_node_types: RUBY_DECISION_NODES,
750
- pmd_cyclomatic: false,
751
739
  nesting_node_types: RUBY_DECISION_NODES,
752
740
  ncss_node_types: RUBY_NCSS_NODES,
753
741
  ncss_container_node_types: RUBY_NCSS_CONTAINERS,
@@ -758,7 +746,6 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
758
746
  grammar_id: GrammarId::C,
759
747
  function_node_types: C_FUNCTION_NODES,
760
748
  decision_node_types: C_DECISION_NODES,
761
- pmd_cyclomatic: false,
762
749
  nesting_node_types: C_DECISION_NODES,
763
750
  ncss_node_types: C_NCSS_NODES,
764
751
  ncss_container_node_types: &[],
@@ -769,7 +756,6 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
769
756
  grammar_id: GrammarId::Cpp,
770
757
  function_node_types: C_FUNCTION_NODES,
771
758
  decision_node_types: CPP_DECISION_NODES,
772
- pmd_cyclomatic: false,
773
759
  nesting_node_types: CPP_DECISION_NODES,
774
760
  ncss_node_types: CPP_NCSS_NODES,
775
761
  ncss_container_node_types: &[],
@@ -780,7 +766,6 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
780
766
  grammar_id: GrammarId::CSharp,
781
767
  function_node_types: CSHARP_FUNCTION_NODES,
782
768
  decision_node_types: CSHARP_DECISION_NODES,
783
- pmd_cyclomatic: false,
784
769
  nesting_node_types: CSHARP_DECISION_NODES,
785
770
  ncss_node_types: CSHARP_NCSS_NODES,
786
771
  ncss_container_node_types: &[],
@@ -791,7 +776,6 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
791
776
  grammar_id: GrammarId::Kotlin,
792
777
  function_node_types: KOTLIN_FUNCTION_NODES,
793
778
  decision_node_types: KOTLIN_DECISION_NODES,
794
- pmd_cyclomatic: false,
795
779
  nesting_node_types: KOTLIN_DECISION_NODES,
796
780
  ncss_node_types: KOTLIN_NCSS_NODES,
797
781
  ncss_container_node_types: KOTLIN_NCSS_CONTAINERS,
package/native/src/lib.rs CHANGED
@@ -6,7 +6,6 @@ use napi_derive::napi;
6
6
  use crate::duplication::DuplicationSettings;
7
7
 
8
8
  mod complexity;
9
- mod cyclomatic;
10
9
  mod dep_degree;
11
10
  mod duplication;
12
11
  mod functions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-gauge",
3
- "version": "4.3.0",
3
+ "version": "4.3.1",
4
4
  "description": "Measure code metrics with tree-sitter.",
5
5
  "keywords": [
6
6
  "cli",
@@ -89,12 +89,12 @@
89
89
  "registry": "https://registry.npmjs.org/"
90
90
  },
91
91
  "optionalDependencies": {
92
- "code-gauge-linux-x64-gnu": "4.3.0",
93
- "code-gauge-linux-arm64-gnu": "4.3.0",
94
- "code-gauge-linux-x64-musl": "4.3.0",
95
- "code-gauge-linux-arm64-musl": "4.3.0",
96
- "code-gauge-darwin-x64": "4.3.0",
97
- "code-gauge-darwin-arm64": "4.3.0",
98
- "code-gauge-win32-x64-msvc": "4.3.0"
92
+ "code-gauge-linux-x64-gnu": "4.3.1",
93
+ "code-gauge-linux-arm64-gnu": "4.3.1",
94
+ "code-gauge-linux-x64-musl": "4.3.1",
95
+ "code-gauge-linux-arm64-musl": "4.3.1",
96
+ "code-gauge-darwin-x64": "4.3.1",
97
+ "code-gauge-darwin-arm64": "4.3.1",
98
+ "code-gauge-win32-x64-msvc": "4.3.1"
99
99
  }
100
100
  }
@@ -1,79 +0,0 @@
1
- use tree_sitter::Node;
2
-
3
- use crate::util::{named_children, node_text, Source};
4
-
5
- /// Cyclomatic paths a Java node adds to its function, following PMD 7.26.0's CycloVisitor with
6
- /// its default options: branches and loops add 1 plus the boolean paths of their condition,
7
- /// `throw`/`catch`/enhanced `for` add 1, and a switch adds the boolean paths of its tested
8
- /// expression plus the expression alternatives of each non-default label. `&&`/`||` outside these
9
- /// conditions (initializers, returns, arguments) and pattern labels and guards add nothing.
10
- pub fn pmd_cyclomatic_increment(node: Node<'_>, code: &Source<'_>) -> u64 {
11
- match node.kind() {
12
- "if_statement" | "while_statement" | "do_statement" | "for_statement"
13
- | "ternary_expression" => {
14
- 1 + node
15
- .child_by_field_name("condition")
16
- .map_or(0, |condition| {
17
- boolean_expression_complexity(condition, code)
18
- })
19
- }
20
- "enhanced_for_statement" | "catch_clause" | "throw_statement" => 1,
21
- "switch_expression" => node
22
- .child_by_field_name("condition")
23
- .map_or(0, |condition| {
24
- boolean_expression_complexity(condition, code)
25
- }),
26
- "switch_block_statement_group" | "switch_rule" => named_children(node)
27
- .into_iter()
28
- .filter(|child| child.kind() == "switch_label")
29
- .flat_map(named_children)
30
- .filter(|child| {
31
- child.kind() != "guard"
32
- && child.kind() != "pattern"
33
- && !crate::ncss::COMMENT_NODE_TYPES.contains(&child.kind())
34
- })
35
- .count() as u64,
36
- _ => 0,
37
- }
38
- }
39
-
40
- /// PMD's booleanExpressionComplexity: a conditional expression costs 2 plus its parts; any other
41
- /// expression costs its `&&`/`||` operators. PMD has no parenthesis nodes, so they are unwrapped.
42
- fn boolean_expression_complexity(expression: Node<'_>, code: &Source<'_>) -> u64 {
43
- let mut expression = expression;
44
- while expression.kind() == "parenthesized_expression" {
45
- let Some(inner) = named_children(expression)
46
- .into_iter()
47
- .find(|child| !crate::ncss::COMMENT_NODE_TYPES.contains(&child.kind()))
48
- else {
49
- return 0;
50
- };
51
- expression = inner;
52
- }
53
- if expression.kind() == "ternary_expression" {
54
- return 2 + ["condition", "consequence", "alternative"]
55
- .iter()
56
- .filter_map(|field| expression.child_by_field_name(field))
57
- .map(|part| boolean_expression_complexity(part, code))
58
- .sum::<u64>();
59
- }
60
- count_conditional_operators(expression, code)
61
- }
62
-
63
- /// `&&`/`||` binaries in the subtree, not descending into lambdas or class bodies, which PMD treats
64
- /// as find boundaries.
65
- fn count_conditional_operators(node: Node<'_>, code: &Source<'_>) -> u64 {
66
- if node.kind() == "lambda_expression" || node.kind() == "class_body" {
67
- return 0;
68
- }
69
- let own = u64::from(
70
- node.kind() == "binary_expression"
71
- && node
72
- .child_by_field_name("operator")
73
- .is_some_and(|operator| matches!(node_text(operator, code), "&&" | "||")),
74
- );
75
- own + named_children(node)
76
- .into_iter()
77
- .map(|child| count_conditional_operators(child, code))
78
- .sum::<u64>()
79
- }