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/THIRD-PARTY-NOTICES.txt +1152 -798
- package/dist/cli.cjs +3 -3
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/cli.js.map +1 -1
- package/dist/cliConfig.cjs +1 -1
- package/dist/cliConfig.cjs.map +1 -1
- package/dist/cliConfig.js +1 -1
- package/dist/cliConfig.js.map +1 -1
- package/dist/diffCommand.cjs +2 -2
- package/dist/diffCommand.cjs.map +1 -1
- package/dist/diffCommand.js +3 -3
- 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/nativeMetrics.cjs +3 -3
- package/dist/nativeMetrics.cjs.map +1 -1
- package/dist/nativeMetrics.js +3 -3
- package/dist/nativeMetrics.js.map +1 -1
- package/dist/scan.cjs +1 -1
- package/dist/scan.cjs.map +1 -1
- package/dist/scan.d.ts +0 -1
- package/dist/scan.js +1 -1
- package/dist/scan.js.map +1 -1
- package/native/Cargo.lock +73 -73
- package/native/Cargo.toml +16 -21
- package/native/code-gauge.wasm +0 -0
- package/native/src/dep_degree.rs +6 -6
- package/native/src/duplication.rs +38 -12
- package/native/src/functions.rs +10 -7
- package/native/src/languages.rs +14 -13
- package/native/src/measure.rs +16 -8
- package/native/src/tree_index.rs +4 -4
- package/native/src/util.rs +9 -2
- package/package.json +9 -8
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
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
# `
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
tree-sitter = "=0.
|
|
26
|
-
tree-sitter-
|
|
27
|
-
tree-sitter-
|
|
28
|
-
tree-sitter-
|
|
29
|
-
tree-sitter-
|
|
30
|
-
tree-sitter-
|
|
31
|
-
tree-sitter-
|
|
32
|
-
tree-sitter-
|
|
33
|
-
tree-sitter-
|
|
34
|
-
tree-sitter-
|
|
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]
|
package/native/code-gauge.wasm
CHANGED
|
Binary file
|
package/native/src/dep_degree.rs
CHANGED
|
@@ -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<&'
|
|
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<&'
|
|
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
|
|
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<&'
|
|
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<'
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
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(
|
|
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(),
|
package/native/src/functions.rs
CHANGED
|
@@ -821,13 +821,16 @@ fn find_string_literal_content(literal: Node<'_>, code: &Source<'_>) -> Option<S
|
|
|
821
821
|
{
|
|
822
822
|
return None;
|
|
823
823
|
}
|
|
824
|
-
//
|
|
825
|
-
//
|
|
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"
|
|
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
|
|
842
|
-
//
|
|
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
|
|
981
|
+
parent.field_name_for_child(index),
|
|
979
982
|
None | Some("value")
|
|
980
983
|
);
|
|
981
984
|
}
|
package/native/src/languages.rs
CHANGED
|
@@ -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::
|
|
45
|
-
GrammarId::Cpp => tree_sitter_cpp::
|
|
46
|
-
GrammarId::CSharp => tree_sitter_c_sharp::
|
|
47
|
-
GrammarId::Go => tree_sitter_go::
|
|
48
|
-
GrammarId::Java => tree_sitter_java::
|
|
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::
|
|
52
|
-
GrammarId::Ruby => tree_sitter_ruby::
|
|
53
|
-
GrammarId::Rust => tree_sitter_rust::
|
|
54
|
-
GrammarId::TypeScript => tree_sitter_typescript::
|
|
55
|
-
GrammarId::Tsx => tree_sitter_typescript::
|
|
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 [
|
|
61
|
-
static KIND_NAMES: [OnceLock<Vec
|
|
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",
|
package/native/src/measure.rs
CHANGED
|
@@ -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::{
|
|
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
|
|
279
|
+
if is_atomic_operand(node) {
|
|
278
280
|
symbols.push(hash_text(node.kind_name()));
|
|
279
281
|
return;
|
|
280
282
|
}
|
|
281
|
-
if node
|
|
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
|
|
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
|
|
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"
|
package/native/src/tree_index.rs
CHANGED
|
@@ -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 [
|
|
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) -> &'
|
|
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) -> &'
|
|
112
|
+
fn kind_name(self) -> &'t str {
|
|
113
113
|
KIND_NAMES
|
|
114
114
|
.get()
|
|
115
115
|
.get(usize::from(self.kind_id()))
|
|
116
|
-
.
|
|
116
|
+
.map(String::as_str)
|
|
117
117
|
.unwrap_or_else(|| self.kind())
|
|
118
118
|
}
|
|
119
119
|
}
|
package/native/src/util.rs
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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.
|
|
98
|
-
"code-gauge-linux-arm64-gnu": "5.
|
|
99
|
-
"code-gauge-linux-x64-musl": "5.
|
|
100
|
-
"code-gauge-linux-arm64-musl": "5.
|
|
101
|
-
"code-gauge-darwin-x64": "5.
|
|
102
|
-
"code-gauge-darwin-arm64": "5.
|
|
103
|
-
"code-gauge-win32-x64-msvc": "5.
|
|
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
|
}
|