code-gauge 4.2.2 → 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 +12 -3
- package/dist/cliConfig.cjs +1 -1
- package/dist/cliConfig.cjs.map +1 -1
- package/dist/cliConfig.js +1 -1
- package/dist/diffCommand.cjs +1 -1
- package/dist/diffCommand.cjs.map +1 -1
- package/dist/diffCommand.js +1 -1
- package/dist/diffCommand.js.map +1 -1
- package/dist/duplication.cjs +1 -1
- package/dist/duplication.cjs.map +1 -1
- package/dist/duplication.js +1 -1
- package/dist/duplication.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/languages.cjs +1 -1
- package/dist/languages.cjs.map +1 -1
- package/dist/languages.d.ts +3 -1
- package/dist/languages.js +1 -1
- package/dist/languages.js.map +1 -1
- package/dist/metrics.cjs +1 -1
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.js +1 -1
- package/dist/metrics.js.map +1 -1
- package/dist/nativeMetrics.cjs +1 -1
- package/dist/nativeMetrics.cjs.map +1 -1
- package/dist/nativeMetrics.js +1 -1
- package/dist/nativeMetrics.js.map +1 -1
- package/dist/scan.cjs +1 -1
- package/dist/scan.cjs.map +1 -1
- package/dist/scan.js +1 -1
- package/dist/scan.js.map +1 -1
- package/dist/types.d.ts +14 -2
- package/native/src/complexity.rs +197 -36
- package/native/src/functions.rs +9 -7
- package/native/src/lib.rs +1 -1
- package/native/src/measure.rs +2 -0
- package/native/src/types.rs +2 -0
- package/package.json +14 -14
package/native/src/complexity.rs
CHANGED
|
@@ -74,7 +74,7 @@ const SWITCH_LIKE_NODE_TYPES: &[&str] = &[
|
|
|
74
74
|
"case_match",
|
|
75
75
|
];
|
|
76
76
|
|
|
77
|
-
// Per-case decision nodes
|
|
77
|
+
// Per-case decision nodes: cyclomatic-only, because the switch itself carries the cognitive cost.
|
|
78
78
|
const CASE_CLAUSE_NODE_TYPES: &[&str] = &[
|
|
79
79
|
"case_clause",
|
|
80
80
|
"switch_case",
|
|
@@ -95,6 +95,7 @@ const CASE_CLAUSE_NODE_TYPES: &[&str] = &[
|
|
|
95
95
|
const IF_LIKE_NODE_TYPES: &[&str] = &["if_statement", "if_expression", "if", "unless"];
|
|
96
96
|
|
|
97
97
|
pub struct FunctionBodyMetrics {
|
|
98
|
+
pub cyclomatic_complexity: u64,
|
|
98
99
|
pub cognitive_complexity: u64,
|
|
99
100
|
pub nesting_depth: u64,
|
|
100
101
|
pub ncss: u64,
|
|
@@ -102,6 +103,7 @@ pub struct FunctionBodyMetrics {
|
|
|
102
103
|
|
|
103
104
|
/// Accumulator for one function body during measure_function_body_metrics' post-order pass.
|
|
104
105
|
struct FunctionBodyFrame {
|
|
106
|
+
cyclomatic_complexity: u64,
|
|
105
107
|
cognitive_complexity: u64,
|
|
106
108
|
/// Count of `1 + nesting` cognitive increments, for re-basing on hoist into the parent frame.
|
|
107
109
|
nesting_sensitive_count: u64,
|
|
@@ -117,6 +119,7 @@ struct FunctionBodyFrame {
|
|
|
117
119
|
impl FunctionBodyFrame {
|
|
118
120
|
fn new(entry_cognitive_nesting: u64, entry_structural_nesting: u64) -> Self {
|
|
119
121
|
FunctionBodyFrame {
|
|
122
|
+
cyclomatic_complexity: 1,
|
|
120
123
|
cognitive_complexity: 0,
|
|
121
124
|
nesting_sensitive_count: 0,
|
|
122
125
|
nesting_depth: 0,
|
|
@@ -141,8 +144,8 @@ struct FunctionBodyPass<'sets, 'code, 'source> {
|
|
|
141
144
|
/// already-computed totals: NCSS hoists as-is; cognitive complexity re-bases the nested function's
|
|
142
145
|
/// nesting-sensitive increments (each worth `1 + nesting`) by the nesting offset at the embedding
|
|
143
146
|
/// site, while flat increments (else branches, boolean-operator sequences, chain continuations,
|
|
144
|
-
/// jumps, guards) hoist unchanged; nesting depth
|
|
145
|
-
///
|
|
147
|
+
/// jumps, guards) hoist unchanged; cyclomatic complexity and nesting depth describe the own body
|
|
148
|
+
/// only, so nothing hoists.
|
|
146
149
|
pub fn measure_function_body_metrics(
|
|
147
150
|
root: Node<'_>,
|
|
148
151
|
sets: &LanguageSets,
|
|
@@ -186,7 +189,7 @@ impl FunctionBodyPass<'_, '_, '_> {
|
|
|
186
189
|
}
|
|
187
190
|
// The node's own increments target the frame it is embedded in, not the one it opens; a
|
|
188
191
|
// frame-opening or charged-class-body node contributes nothing to that frame's own body
|
|
189
|
-
// (nesting), matching the per-function traversal this pass replaces.
|
|
192
|
+
// (cyclomatic/nesting), matching the per-function traversal this pass replaces.
|
|
190
193
|
let entry_cognitive_nesting = self.top_frame().entry_cognitive_nesting;
|
|
191
194
|
let entry_structural_nesting = self.top_frame().entry_structural_nesting;
|
|
192
195
|
let relative_nesting = current_nesting + function_nesting_bonus - entry_cognitive_nesting;
|
|
@@ -196,7 +199,7 @@ impl FunctionBodyPass<'_, '_, '_> {
|
|
|
196
199
|
// `if` keyword token), so only named nodes count as decisions.
|
|
197
200
|
let is_decision = current.is_named()
|
|
198
201
|
&& self.sets.decision_nodes.contains(current.kind())
|
|
199
|
-
&& !
|
|
202
|
+
&& !is_pathless_switch_branch(current, self.code);
|
|
200
203
|
let is_case_clause = current.is_named() && CASE_CLAUSE_NODE_TYPES.contains(¤t.kind());
|
|
201
204
|
// Ruby's `case ... else` arm is an `else` node; like every other language's default branch
|
|
202
205
|
// it nests its contents inside the switch (it cannot go in the Ruby nesting set because
|
|
@@ -211,6 +214,13 @@ impl FunctionBodyPass<'_, '_, '_> {
|
|
|
211
214
|
// surcharge (Sonar cognitive-complexity semantics).
|
|
212
215
|
let is_continuation = is_decision && is_flat_chain_continuation(current);
|
|
213
216
|
|
|
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;
|
|
223
|
+
}
|
|
214
224
|
if is_decision && !is_case_clause {
|
|
215
225
|
if is_continuation {
|
|
216
226
|
self.top_frame().cognitive_complexity += 1;
|
|
@@ -295,6 +305,7 @@ impl FunctionBodyPass<'_, '_, '_> {
|
|
|
295
305
|
self.results.insert(
|
|
296
306
|
current.id(),
|
|
297
307
|
FunctionBodyMetrics {
|
|
308
|
+
cyclomatic_complexity: closed.cyclomatic_complexity,
|
|
298
309
|
cognitive_complexity: closed.cognitive_complexity,
|
|
299
310
|
nesting_depth: closed.nesting_depth,
|
|
300
311
|
// A function node without a countable declaration of its own (arrow functions,
|
|
@@ -361,7 +372,7 @@ pub fn measure_complexity(
|
|
|
361
372
|
// `if` keyword token), so only named nodes count as decisions.
|
|
362
373
|
let is_decision = current.is_named()
|
|
363
374
|
&& sets.decision_nodes.contains(current.kind())
|
|
364
|
-
&& !
|
|
375
|
+
&& !is_pathless_switch_branch(current, code);
|
|
365
376
|
let is_case_clause = current.is_named() && CASE_CLAUSE_NODE_TYPES.contains(¤t.kind());
|
|
366
377
|
// Ruby's `case ... else` arm is an `else` node; like every other language's default branch
|
|
367
378
|
// it nests its contents inside the switch (it cannot go in the Ruby nesting set because
|
|
@@ -614,46 +625,138 @@ fn is_flat_chain_continuation(node: Node<'_>) -> bool {
|
|
|
614
625
|
.is_some_and(|alternative| alternative.id() == node.id())
|
|
615
626
|
}
|
|
616
627
|
|
|
617
|
-
///
|
|
618
|
-
|
|
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 {
|
|
619
692
|
let kind = node.kind();
|
|
693
|
+
if kind == "switch_default" {
|
|
694
|
+
return true;
|
|
695
|
+
}
|
|
620
696
|
if kind == "case_statement" {
|
|
621
697
|
return node.child_by_field_name("value").is_none();
|
|
622
698
|
}
|
|
623
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.
|
|
624
702
|
if kind == "switch_block_statement_group" || kind == "switch_rule" {
|
|
625
|
-
|
|
703
|
+
return non_comment_children(node)
|
|
626
704
|
.into_iter()
|
|
627
|
-
.
|
|
628
|
-
|
|
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
|
+
|
|
715
|
+
// C# `default:` sections and catch-all (`_`, `var x`) labels and arms, and Kotlin `else ->`
|
|
716
|
+
// entries. A guarded catch-all (`_ when cond =>`) is still a default arm: only its guard
|
|
717
|
+
// branches, which is_pattern_guard charges, like Python's `case _ if cond:` and Rust's
|
|
718
|
+
// `_ if cond =>`.
|
|
719
|
+
if kind == "switch_section" {
|
|
720
|
+
return node.child(0).is_some_and(|first| first.kind() == "default")
|
|
721
|
+
|| crate::util::named_children(node)
|
|
722
|
+
.into_iter()
|
|
723
|
+
.any(is_csharp_catch_all_pattern);
|
|
724
|
+
}
|
|
725
|
+
if kind == "switch_expression_arm" {
|
|
726
|
+
return crate::util::named_children(node)
|
|
727
|
+
.first()
|
|
728
|
+
.is_some_and(|first| is_csharp_catch_all_pattern(*first));
|
|
729
|
+
}
|
|
730
|
+
if kind == "when_entry" {
|
|
731
|
+
return !crate::util::named_children(node)
|
|
732
|
+
.iter()
|
|
733
|
+
.any(|child| child.kind() == "when_condition");
|
|
629
734
|
}
|
|
630
735
|
|
|
631
|
-
// Python
|
|
632
|
-
|
|
633
|
-
|
|
736
|
+
// Python arms with an irrefutable pattern are unconditional like `default`.
|
|
737
|
+
// A bare `case y, z:` or `case y,:` is a sequence pattern: its elements are direct
|
|
738
|
+
// case_pattern children separated by comma tokens of the clause itself.
|
|
739
|
+
if kind == "case_clause" {
|
|
740
|
+
let patterns: Vec<Node<'_>> = crate::util::named_children(node)
|
|
634
741
|
.into_iter()
|
|
635
|
-
.
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
&& child.named_child_count() == 1
|
|
654
|
-
&& child
|
|
655
|
-
.named_child(0)
|
|
656
|
-
.is_some_and(|inner| inner.kind() == "identifier")
|
|
742
|
+
.filter(|child| child.kind() == "case_pattern")
|
|
743
|
+
.collect();
|
|
744
|
+
return !all_children(node).iter().any(|child| child.kind() == ",")
|
|
745
|
+
&& matches!(patterns[..], [pattern] if is_python_irrefutable_pattern(pattern));
|
|
746
|
+
}
|
|
747
|
+
// Rust `_ =>` (optionally guarded) fallback arms.
|
|
748
|
+
if kind == "match_arm" {
|
|
749
|
+
return crate::util::named_children(node)
|
|
750
|
+
.into_iter()
|
|
751
|
+
.find(|child| child.kind() == "match_pattern")
|
|
752
|
+
.is_some_and(|pattern| {
|
|
753
|
+
// The guard keyword is anonymous, so filter all children, not just named ones.
|
|
754
|
+
let parts: Vec<Node<'_>> = all_children(pattern)
|
|
755
|
+
.into_iter()
|
|
756
|
+
.filter(|child| !crate::ncss::COMMENT_NODE_TYPES.contains(&child.kind()))
|
|
757
|
+
.collect();
|
|
758
|
+
matches!(parts[..], [first, ..] if first.kind() == "_")
|
|
759
|
+
&& parts.get(1).is_none_or(|second| second.kind() == "if")
|
|
657
760
|
});
|
|
658
761
|
}
|
|
659
762
|
|
|
@@ -667,6 +770,64 @@ fn is_default_switch_branch(node: Node<'_>) -> bool {
|
|
|
667
770
|
false
|
|
668
771
|
}
|
|
669
772
|
|
|
773
|
+
/// PEP 634's irrefutable patterns: the wildcard `_`, a capture `y`, a group `(p)`, `p as y`, and
|
|
774
|
+
/// `p | q` when `p` (or, for `|`, any alternative) is irrefutable. A group parses as a one-element
|
|
775
|
+
/// tuple pattern that differs from the real tuple `(p,)` only by the comma token.
|
|
776
|
+
fn is_python_irrefutable_pattern(node: Node<'_>) -> bool {
|
|
777
|
+
match node.kind() {
|
|
778
|
+
"_" => true,
|
|
779
|
+
"case_pattern" => match non_comment_children(node)[..] {
|
|
780
|
+
[] => all_children(node).iter().any(|child| child.kind() == "_"),
|
|
781
|
+
[inner] => is_python_irrefutable_pattern(inner),
|
|
782
|
+
_ => false,
|
|
783
|
+
},
|
|
784
|
+
"dotted_name" => matches!(
|
|
785
|
+
non_comment_children(node)[..],
|
|
786
|
+
[name] if name.kind() == "identifier"
|
|
787
|
+
),
|
|
788
|
+
"tuple_pattern" => {
|
|
789
|
+
!all_children(node).iter().any(|child| child.kind() == ",")
|
|
790
|
+
&& matches!(
|
|
791
|
+
non_comment_children(node)[..],
|
|
792
|
+
[inner] if is_python_irrefutable_pattern(inner)
|
|
793
|
+
)
|
|
794
|
+
}
|
|
795
|
+
"as_pattern" => non_comment_children(node)
|
|
796
|
+
.first()
|
|
797
|
+
.is_some_and(|pattern| is_python_irrefutable_pattern(*pattern)),
|
|
798
|
+
"union_pattern" => all_children(node)
|
|
799
|
+
.into_iter()
|
|
800
|
+
.any(is_python_irrefutable_pattern),
|
|
801
|
+
_ => false,
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
fn non_comment_children<'t>(node: Node<'t>) -> Vec<Node<'t>> {
|
|
806
|
+
crate::util::named_children(node)
|
|
807
|
+
.into_iter()
|
|
808
|
+
.filter(|child| !crate::ncss::COMMENT_NODE_TYPES.contains(&child.kind()))
|
|
809
|
+
.collect()
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/// C# patterns that match every value: the discard `_` and `var x`/`var _`, possibly parenthesized
|
|
813
|
+
/// (but not a `var (a, b)` deconstruction, which requires a deconstructible value).
|
|
814
|
+
fn is_csharp_catch_all_pattern(node: Node<'_>) -> bool {
|
|
815
|
+
if node.kind() == "parenthesized_pattern" {
|
|
816
|
+
return crate::util::named_children(node)
|
|
817
|
+
.into_iter()
|
|
818
|
+
.find(|child| !crate::ncss::COMMENT_NODE_TYPES.contains(&child.kind()))
|
|
819
|
+
.is_some_and(is_csharp_catch_all_pattern);
|
|
820
|
+
}
|
|
821
|
+
node.kind() == "discard"
|
|
822
|
+
|| (node.kind() == "declaration_pattern"
|
|
823
|
+
&& node
|
|
824
|
+
.child_by_field_name("type")
|
|
825
|
+
.is_some_and(|ty| ty.kind() == "implicit_type")
|
|
826
|
+
&& !crate::util::named_children(node)
|
|
827
|
+
.iter()
|
|
828
|
+
.any(|child| child.kind() == "parenthesized_variable_designation"))
|
|
829
|
+
}
|
|
830
|
+
|
|
670
831
|
/// The parent guard is required because the same tokens appear in non-boolean syntax (C++ `int&&`,
|
|
671
832
|
/// `operator&&`, Rust's empty closure parameter list `|| 5`).
|
|
672
833
|
fn is_boolean_operator(node: Node<'_>, code: &Source<'_>) -> bool {
|
package/native/src/functions.rs
CHANGED
|
@@ -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
|
-
///
|
|
7
|
-
///
|
|
8
|
-
///
|
|
9
|
-
///
|
|
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
|
|
39
|
-
|
|
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");
|
package/native/src/lib.rs
CHANGED
package/native/src/measure.rs
CHANGED
|
@@ -53,6 +53,8 @@ pub fn measure(
|
|
|
53
53
|
// yields the JavaScript string (UTF-16 code unit) column.
|
|
54
54
|
start_column: node.start_position().column / 2,
|
|
55
55
|
end_line: node.end_position().row + 1,
|
|
56
|
+
end_column: node.end_position().column / 2,
|
|
57
|
+
cyclomatic_complexity: body_metrics.cyclomatic_complexity,
|
|
56
58
|
// Sonar's written spec adds +1 cognitive complexity per function in a recursion
|
|
57
59
|
// cycle, but this is intentionally not implemented (issue #22): mainstream
|
|
58
60
|
// implementations (PMD, SonarQube analyzers) omit it.
|
package/native/src/types.rs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-gauge",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.1",
|
|
4
4
|
"description": "Measure code metrics with tree-sitter.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -65,16 +65,16 @@
|
|
|
65
65
|
"@tsconfig/bun": "1.0.10",
|
|
66
66
|
"@tsconfig/node-lts": "24.0.0",
|
|
67
67
|
"@tsconfig/node-ts": "23.6.4",
|
|
68
|
-
"@types/bun": "1.4.
|
|
68
|
+
"@types/bun": "1.4.2",
|
|
69
69
|
"@types/node": "25.9.4",
|
|
70
70
|
"@willbooster/oxfmt-config": "1.2.2",
|
|
71
71
|
"@willbooster/oxlint-config": "1.4.8",
|
|
72
|
-
"@willbooster/wb": "22.
|
|
73
|
-
"build-ts": "21.0.
|
|
72
|
+
"@willbooster/wb": "22.7.0",
|
|
73
|
+
"build-ts": "21.0.16",
|
|
74
74
|
"conventional-changelog-conventionalcommits": "9.3.1",
|
|
75
|
-
"lefthook": "2.1.
|
|
76
|
-
"oxfmt": "0.
|
|
77
|
-
"oxlint": "1.
|
|
75
|
+
"lefthook": "2.1.14",
|
|
76
|
+
"oxfmt": "0.68.0",
|
|
77
|
+
"oxlint": "1.83.0",
|
|
78
78
|
"oxlint-tsgolint": "7.0.2001",
|
|
79
79
|
"semantic-release": "25.0.5",
|
|
80
80
|
"sort-package-json": "4.0.0",
|
|
@@ -89,12 +89,12 @@
|
|
|
89
89
|
"registry": "https://registry.npmjs.org/"
|
|
90
90
|
},
|
|
91
91
|
"optionalDependencies": {
|
|
92
|
-
"code-gauge-linux-x64-gnu": "4.
|
|
93
|
-
"code-gauge-linux-arm64-gnu": "4.
|
|
94
|
-
"code-gauge-linux-x64-musl": "4.
|
|
95
|
-
"code-gauge-linux-arm64-musl": "4.
|
|
96
|
-
"code-gauge-darwin-x64": "4.
|
|
97
|
-
"code-gauge-darwin-arm64": "4.
|
|
98
|
-
"code-gauge-win32-x64-msvc": "4.
|
|
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
|
}
|