code-gauge 4.7.1 → 4.8.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/README.md +1 -1
- package/THIRD-PARTY-NOTICES.txt +595 -29
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- 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/crossFileDuplication.cjs +1 -1
- package/dist/crossFileDuplication.cjs.map +1 -1
- package/dist/crossFileDuplication.js +1 -1
- package/dist/crossFileDuplication.js.map +1 -1
- package/dist/crossFileNearMiss.cjs +1 -1
- package/dist/crossFileNearMiss.cjs.map +1 -1
- package/dist/crossFileNearMiss.js +1 -1
- package/dist/crossFileNearMiss.js.map +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/duplicateSelection.cjs +1 -1
- package/dist/duplicateSelection.cjs.map +1 -1
- package/dist/duplicateSelection.js +1 -1
- package/dist/duplicateSelection.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/git.cjs +1 -1
- package/dist/git.cjs.map +1 -1
- package/dist/git.js +1 -1
- package/dist/git.js.map +1 -1
- package/dist/languages.cjs +1 -1
- package/dist/languages.cjs.map +1 -1
- package/dist/languages.js +1 -1
- package/dist/languages.js.map +1 -1
- package/dist/regressionGate.cjs +1 -1
- package/dist/regressionGate.cjs.map +1 -1
- package/dist/regressionGate.js +1 -1
- package/dist/regressionGate.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/native/Cargo.lock +47 -15
- package/native/Cargo.toml +9 -9
- package/native/code-gauge.wasm +0 -0
- package/native/src/complexity.rs +26 -33
- package/native/src/dep_degree.rs +43 -32
- package/native/src/duplication.rs +55 -40
- package/native/src/functions.rs +20 -62
- package/native/src/languages.rs +28 -7
- package/native/src/measure.rs +16 -27
- package/native/src/ncss.rs +32 -23
- package/native/src/tree_index.rs +9 -0
- package/native/src/util.rs +12 -57
- package/package.json +10 -10
|
@@ -10,7 +10,7 @@ use crate::types::{
|
|
|
10
10
|
CrossFileCandidate, CrossFileToken, CrossFileTokenRange, DuplicateBlockOccurrence,
|
|
11
11
|
DuplicationMetrics,
|
|
12
12
|
};
|
|
13
|
-
use crate::util::{all_children,
|
|
13
|
+
use crate::util::{all_children, 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] = &[
|
|
@@ -62,10 +62,7 @@ const DUPLICATE_BLOCK_TYPES: &[&str] = &[
|
|
|
62
62
|
"do_while_statement",
|
|
63
63
|
"catch_block",
|
|
64
64
|
"finally_block",
|
|
65
|
-
"statements",
|
|
66
|
-
"control_structure_body",
|
|
67
65
|
"function_body",
|
|
68
|
-
"jump_expression",
|
|
69
66
|
"jsx_element",
|
|
70
67
|
"jsx_self_closing_element",
|
|
71
68
|
"if",
|
|
@@ -106,10 +103,9 @@ const STATEMENT_CONTAINER_TYPES: &[&str] = &[
|
|
|
106
103
|
"default_case",
|
|
107
104
|
"compilation_unit",
|
|
108
105
|
"switch_section",
|
|
109
|
-
"statements",
|
|
110
106
|
"enum_class_body",
|
|
111
|
-
"control_structure_body",
|
|
112
107
|
"function_body",
|
|
108
|
+
"lambda_literal",
|
|
113
109
|
];
|
|
114
110
|
|
|
115
111
|
/// C# type bodies are `declaration_list`s, a name Rust also uses for `mod`/`impl`/`trait` bodies;
|
|
@@ -122,8 +118,8 @@ const CSHARP_DECLARATION_LIST_PARENT_TYPES: &[&str] = &[
|
|
|
122
118
|
"namespace_declaration",
|
|
123
119
|
];
|
|
124
120
|
|
|
125
|
-
/// Whole-subtree duplicate candidates: DUPLICATE_BLOCK_TYPES plus Kotlin's `try_expression`,
|
|
126
|
-
///
|
|
121
|
+
/// Whole-subtree duplicate candidates: DUPLICATE_BLOCK_TYPES plus Kotlin's `try_expression`, which
|
|
122
|
+
/// shares its name with Rust's `?` operator (see is_kotlin_try_expression).
|
|
127
123
|
fn is_duplicate_block(node: Node<'_>) -> bool {
|
|
128
124
|
if !node.is_named() {
|
|
129
125
|
return false;
|
|
@@ -149,8 +145,6 @@ fn is_statement_container(node: Node<'_>) -> bool {
|
|
|
149
145
|
/// Identifier leaves anonymized by occurrence order so consistently renamed copies still match.
|
|
150
146
|
const ANONYMIZED_IDENTIFIER_TYPES: &[&str] = &[
|
|
151
147
|
"identifier",
|
|
152
|
-
"simple_identifier",
|
|
153
|
-
"interpolated_identifier",
|
|
154
148
|
"implicit_parameter",
|
|
155
149
|
"constant",
|
|
156
150
|
"instance_variable",
|
|
@@ -172,8 +166,6 @@ const LITERAL_KIND_BY_TYPE: &[(&str, &str)] = &[
|
|
|
172
166
|
("integer_literal", "#num"),
|
|
173
167
|
("float_literal", "#num"),
|
|
174
168
|
("real_literal", "#num"),
|
|
175
|
-
("hex_literal", "#num"),
|
|
176
|
-
("bin_literal", "#num"),
|
|
177
169
|
("int_literal", "#num"),
|
|
178
170
|
("rune_literal", "#char"),
|
|
179
171
|
("imaginary_literal", "#num"),
|
|
@@ -188,7 +180,6 @@ const LITERAL_KIND_BY_TYPE: &[(&str, &str)] = &[
|
|
|
188
180
|
("string_content", "#str"),
|
|
189
181
|
("string_literal_content", "#str"),
|
|
190
182
|
("character_literal_content", "#char"),
|
|
191
|
-
("character_escape_seq", "#char"),
|
|
192
183
|
("verbatim_string_literal", "#str"),
|
|
193
184
|
("interpolated_string_expression", "#str"),
|
|
194
185
|
("raw_string_content", "#str"),
|
|
@@ -208,12 +199,7 @@ const LITERAL_KIND_BY_TYPE: &[(&str, &str)] = &[
|
|
|
208
199
|
("regex_pattern", "#regex"),
|
|
209
200
|
];
|
|
210
201
|
|
|
211
|
-
const COMMENT_TYPES: &[&str] = &[
|
|
212
|
-
"comment",
|
|
213
|
-
"line_comment",
|
|
214
|
-
"block_comment",
|
|
215
|
-
"multiline_comment",
|
|
216
|
-
];
|
|
202
|
+
const COMMENT_TYPES: &[&str] = &["comment", "line_comment", "block_comment"];
|
|
217
203
|
|
|
218
204
|
/// Children of a string node that carry only literal content; anything else is interpolation.
|
|
219
205
|
const STRING_FRAGMENT_TYPES: &[&str] = &[
|
|
@@ -222,7 +208,6 @@ const STRING_FRAGMENT_TYPES: &[&str] = &[
|
|
|
222
208
|
"string_content",
|
|
223
209
|
"string_literal_content",
|
|
224
210
|
"character_literal_content",
|
|
225
|
-
"character_escape_seq",
|
|
226
211
|
"raw_string_content",
|
|
227
212
|
"raw_string_start",
|
|
228
213
|
"raw_string_end",
|
|
@@ -286,7 +271,6 @@ const STRING_CONTENT_FRAGMENT_TYPES: &[&str] = &[
|
|
|
286
271
|
"string_content",
|
|
287
272
|
"string_literal_content",
|
|
288
273
|
"character_literal_content",
|
|
289
|
-
"character_escape_seq",
|
|
290
274
|
"raw_string_content",
|
|
291
275
|
"escape_sequence",
|
|
292
276
|
"heredoc_content",
|
|
@@ -552,12 +536,12 @@ fn collect_tokens<'a>(
|
|
|
552
536
|
container_statement_ranges: &mut Vec<Vec<TokenRange>>,
|
|
553
537
|
) -> TokenRange {
|
|
554
538
|
let start_token_index = tokens.len();
|
|
555
|
-
let atomic_kind = if
|
|
539
|
+
let atomic_kind = if node.child_count() == 0 {
|
|
556
540
|
None
|
|
557
541
|
} else {
|
|
558
542
|
atomic_literal_kind(node)
|
|
559
543
|
};
|
|
560
|
-
if
|
|
544
|
+
if node.child_count() == 0 {
|
|
561
545
|
append_leaf_token(node, code, tokens);
|
|
562
546
|
} else if let Some(atomic_kind) = atomic_kind {
|
|
563
547
|
// Interpolation-free strings collapse to their kind tag so copies differing only in
|
|
@@ -580,7 +564,12 @@ fn collect_tokens<'a>(
|
|
|
580
564
|
block_ranges,
|
|
581
565
|
container_statement_ranges,
|
|
582
566
|
);
|
|
583
|
-
|
|
567
|
+
// A Kotlin lambda's parameters sit beside its statements.
|
|
568
|
+
if is_container
|
|
569
|
+
&& child.is_named()
|
|
570
|
+
&& !COMMENT_TYPES.contains(&child.kind_name())
|
|
571
|
+
&& child.kind_name() != "lambda_parameters"
|
|
572
|
+
{
|
|
584
573
|
statement_ranges.push(child_range);
|
|
585
574
|
}
|
|
586
575
|
}
|
|
@@ -664,14 +653,19 @@ fn append_leaf_token<'a>(node: Node<'_>, code: &Source<'a>, tokens: &mut Vec<Tok
|
|
|
664
653
|
return;
|
|
665
654
|
}
|
|
666
655
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
656
|
+
if crate::util::is_kotlin_keyword_literal(node, code) {
|
|
657
|
+
tokens.push(make_text_token(
|
|
658
|
+
Cow::Borrowed(node_text(node, code)),
|
|
659
|
+
None,
|
|
660
|
+
false,
|
|
661
|
+
start_row,
|
|
662
|
+
end_row,
|
|
663
|
+
));
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
671
666
|
if node.is_named()
|
|
672
|
-
&& (
|
|
673
|
-
|
|
674
|
-
&& !is_semantic_name_leaf(node, code)))
|
|
667
|
+
&& ANONYMIZED_IDENTIFIER_TYPES.contains(&node.kind_name())
|
|
668
|
+
&& !is_semantic_name_leaf(node, code)
|
|
675
669
|
{
|
|
676
670
|
tokens.push(Token {
|
|
677
671
|
is_id: true,
|
|
@@ -795,8 +789,15 @@ fn is_semantic_name_leaf(node: Node<'_>, code: &Source<'_>) -> bool {
|
|
|
795
789
|
};
|
|
796
790
|
|
|
797
791
|
// Java method references (`Foo::bar`) and Kotlin callable references (`::bar`) name their
|
|
798
|
-
// identifiers without grammar fields.
|
|
799
|
-
if
|
|
792
|
+
// identifiers without grammar fields, and Kotlin spells type names as identifiers.
|
|
793
|
+
if matches!(
|
|
794
|
+
parent.kind_name(),
|
|
795
|
+
"method_reference" | "callable_reference" | "user_type"
|
|
796
|
+
) {
|
|
797
|
+
return true;
|
|
798
|
+
}
|
|
799
|
+
// A Kotlin type parameter's name has no field (C# puts its own in `name`).
|
|
800
|
+
if parent.kind_name() == "type_parameter" && parent.child_by_field_name("name").is_none() {
|
|
800
801
|
return true;
|
|
801
802
|
}
|
|
802
803
|
|
|
@@ -823,16 +824,30 @@ fn is_semantic_name_leaf(node: Node<'_>, code: &Source<'_>) -> bool {
|
|
|
823
824
|
}
|
|
824
825
|
}
|
|
825
826
|
|
|
826
|
-
// Kotlin (no
|
|
827
|
-
//
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
return true;
|
|
831
|
-
}
|
|
827
|
+
// Kotlin (no callee or member fields): a callee (`foo(...)`, whose call ends in Kotlin's own
|
|
828
|
+
// argument nodes), a member name (`a.foo`), an infix function (`a shl b`), and a named
|
|
829
|
+
// argument (`foo(name = x)`) are API names.
|
|
830
|
+
if node.kind_name() == "identifier" {
|
|
832
831
|
let is_first_named = parent
|
|
833
832
|
.named_child(0)
|
|
834
833
|
.is_some_and(|first| first.id() == node.id());
|
|
835
|
-
|
|
834
|
+
let is_last_named = parent
|
|
835
|
+
.named_child(parent.named_child_count().saturating_sub(1))
|
|
836
|
+
.is_some_and(|last| last.id() == node.id());
|
|
837
|
+
if parent.kind_name() == "navigation_expression" && is_last_named && !is_first_named {
|
|
838
|
+
return true;
|
|
839
|
+
}
|
|
840
|
+
if parent.kind_name() == "call_expression"
|
|
841
|
+
&& is_first_named
|
|
842
|
+
&& parent
|
|
843
|
+
.named_child(parent.named_child_count().saturating_sub(1))
|
|
844
|
+
.is_some_and(|arguments| {
|
|
845
|
+
matches!(
|
|
846
|
+
arguments.kind_name(),
|
|
847
|
+
"value_arguments" | "annotated_lambda"
|
|
848
|
+
)
|
|
849
|
+
})
|
|
850
|
+
{
|
|
836
851
|
return true;
|
|
837
852
|
}
|
|
838
853
|
if parent.kind_name() == "infix_expression"
|
package/native/src/functions.rs
CHANGED
|
@@ -37,8 +37,8 @@ pub fn is_implemented_function(node: Node<'_>) -> bool {
|
|
|
37
37
|
.is_some_and(|value| value.kind_name() == "arrow_expression_clause");
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
//
|
|
41
|
-
//
|
|
40
|
+
// Kotlin has no `body` field; an implemented function or accessor holds a `function_body`
|
|
41
|
+
// child.
|
|
42
42
|
if node.kind_name() == "getter"
|
|
43
43
|
|| node.kind_name() == "setter"
|
|
44
44
|
|| node.kind_name() == "function_declaration"
|
|
@@ -175,7 +175,7 @@ fn find_parameters_node(node: Node<'_>) -> Option<Node<'_>> {
|
|
|
175
175
|
if node.kind_name() == "setter"
|
|
176
176
|
&& named_children(node)
|
|
177
177
|
.iter()
|
|
178
|
-
.any(|child| child.kind_name() == "
|
|
178
|
+
.any(|child| child.kind_name() == "identifier")
|
|
179
179
|
{
|
|
180
180
|
return Some(node);
|
|
181
181
|
}
|
|
@@ -343,14 +343,17 @@ pub fn find_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
|
343
343
|
// (`run = { ... }`, `obj.run = { ... }`) takes the assigned name.
|
|
344
344
|
if node.kind_name() == "lambda_literal" || node.kind_name() == "anonymous_function" {
|
|
345
345
|
let mut holder = parent;
|
|
346
|
-
while
|
|
346
|
+
while matches!(
|
|
347
|
+
holder.kind_name(),
|
|
348
|
+
"labeled_expression" | "annotated_expression"
|
|
349
|
+
) {
|
|
347
350
|
holder = holder.parent_node()?;
|
|
348
351
|
}
|
|
349
352
|
if holder.kind_name() == "property_declaration" {
|
|
350
353
|
return find_kotlin_property_name(holder, code);
|
|
351
354
|
}
|
|
352
355
|
if holder.kind_name() == "assignment" {
|
|
353
|
-
return
|
|
356
|
+
return find_assignment_target_name(holder, code);
|
|
354
357
|
}
|
|
355
358
|
}
|
|
356
359
|
// A `lambda { }` / `proc { }` block is measured, but the call around it is what gets bound.
|
|
@@ -852,8 +855,8 @@ fn find_string_literal_content(literal: Node<'_>, code: &Source<'_>) -> Option<S
|
|
|
852
855
|
}
|
|
853
856
|
|
|
854
857
|
/// A compound assignment (`x += f`) does not bind the function to its target, so only a plain `=`
|
|
855
|
-
/// names it. Grammars with an `operator` field (C/C++, C#, Java) expose it directly; Go
|
|
856
|
-
///
|
|
858
|
+
/// names it. Grammars with an `operator` field (C/C++, C#, Java, Kotlin) expose it directly; Go has
|
|
859
|
+
/// none, so the operator is the assignment's own anonymous token child.
|
|
857
860
|
fn is_plain_assignment(assignment: Node<'_>, code: &Source<'_>) -> bool {
|
|
858
861
|
match assignment.child_by_field_name("operator") {
|
|
859
862
|
Some(operator) => node_text(operator, code) == "=",
|
|
@@ -863,9 +866,9 @@ fn is_plain_assignment(assignment: Node<'_>, code: &Source<'_>) -> bool {
|
|
|
863
866
|
}
|
|
864
867
|
}
|
|
865
868
|
|
|
866
|
-
/// The assigned identifier, or the member name of a JS member, Rust/C++ field, C# member,
|
|
867
|
-
/// field access (`a.b.run` names `run`) or a C++ qualified name (`N::run`);
|
|
868
|
-
/// name nothing.
|
|
869
|
+
/// The assigned identifier, or the member name of a JS member, Rust/C++ field, C# member, Java
|
|
870
|
+
/// field access, or Kotlin navigation (`a.b.run` names `run`) or a C++ qualified name (`N::run`);
|
|
871
|
+
/// subscripts (`o["run"]`) name nothing.
|
|
869
872
|
fn find_assignment_target_name(assignment: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
870
873
|
if !is_plain_assignment(assignment, code) {
|
|
871
874
|
return None;
|
|
@@ -877,34 +880,17 @@ fn find_assignment_target_name(assignment: Node<'_>, code: &Source<'_>) -> Optio
|
|
|
877
880
|
"field_expression" => target.child_by_field_name("field")?,
|
|
878
881
|
"member_access_expression" => target.child_by_field_name("name")?,
|
|
879
882
|
"field_access" => target.child_by_field_name("field")?,
|
|
883
|
+
"navigation_expression" => target.named_child(target.named_child_count() - 1)?,
|
|
880
884
|
"qualified_identifier" => return unwrap_declarator_name(Some(target), code),
|
|
881
885
|
_ => return None,
|
|
882
886
|
};
|
|
883
887
|
Some(node_text(name, code).to_string())
|
|
884
888
|
}
|
|
885
889
|
|
|
886
|
-
/// The Kotlin assignment target: the variable itself or the member of a trailing navigation suffix
|
|
887
|
-
/// (`obj.run` names `run`); a trailing indexing suffix (`arr[0] = { }`) names nothing, like
|
|
888
|
-
/// subscripts in the other languages.
|
|
889
|
-
fn find_kotlin_assignment_name(assignment: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
890
|
-
if !is_plain_assignment(assignment, code) {
|
|
891
|
-
return None;
|
|
892
|
-
}
|
|
893
|
-
let target = first_named_child_of_kind(assignment, "directly_assignable_expression")?;
|
|
894
|
-
let children = named_children(target);
|
|
895
|
-
let holder = match children.last()? {
|
|
896
|
-
last if last.kind_name() == "navigation_suffix" => *last,
|
|
897
|
-
last if last.kind_name() == "simple_identifier" && children.len() == 1 => target,
|
|
898
|
-
_ => return None,
|
|
899
|
-
};
|
|
900
|
-
first_named_child_of_kind(holder, "simple_identifier")
|
|
901
|
-
.map(|name| node_text(name, code).to_string())
|
|
902
|
-
}
|
|
903
|
-
|
|
904
890
|
/// Names of C# and Kotlin members whose grammars carry no usable `name` field: accessors are
|
|
905
891
|
/// named after their property (`Count.get`; an expression-bodied property is its own getter),
|
|
906
|
-
/// Kotlin
|
|
907
|
-
///
|
|
892
|
+
/// Kotlin secondary constructors and C# destructors after their class, and C# operators like C++
|
|
893
|
+
/// ones.
|
|
908
894
|
fn find_member_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
909
895
|
match node.kind_name() {
|
|
910
896
|
"accessor_declaration" => {
|
|
@@ -923,20 +909,17 @@ fn find_member_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String
|
|
|
923
909
|
};
|
|
924
910
|
Some(format!(
|
|
925
911
|
"{}.{keyword}",
|
|
926
|
-
|
|
912
|
+
find_kotlin_property_name(node.parent_node()?, code)?
|
|
927
913
|
))
|
|
928
914
|
}
|
|
929
|
-
"function_declaration" if node.child_by_field_name("name").is_none() => {
|
|
930
|
-
first_named_child_of_kind(node, "simple_identifier")
|
|
931
|
-
.map(|name| node_text(name, code).to_string())
|
|
932
|
-
}
|
|
933
915
|
"secondary_constructor" => {
|
|
934
916
|
let mut ancestor = node.parent_node();
|
|
935
917
|
while let Some(current) = ancestor {
|
|
936
918
|
if current.kind_name() == "class_declaration"
|
|
937
919
|
|| current.kind_name() == "object_declaration"
|
|
938
920
|
{
|
|
939
|
-
return
|
|
921
|
+
return current
|
|
922
|
+
.child_by_field_name("name")
|
|
940
923
|
.map(|name| node_text(name, code).to_string());
|
|
941
924
|
}
|
|
942
925
|
ancestor = current.parent_node();
|
|
@@ -967,38 +950,13 @@ fn csharp_property_name(owner: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
|
967
950
|
}
|
|
968
951
|
}
|
|
969
952
|
|
|
970
|
-
/// The property a Kotlin accessor belongs to: its parent when the accessor follows the initializer
|
|
971
|
-
/// on the same line, otherwise (accessor on its own line) the grammar emits it as a class-body
|
|
972
|
-
/// sibling after the property, any preceding accessor, and any comments between them.
|
|
973
|
-
fn find_kotlin_accessor_owner_name(accessor: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
974
|
-
if let Some(name) = accessor
|
|
975
|
-
.parent_node()
|
|
976
|
-
.and_then(|parent| find_kotlin_property_name(parent, code))
|
|
977
|
-
{
|
|
978
|
-
return Some(name);
|
|
979
|
-
}
|
|
980
|
-
let mut sibling = accessor.prev_named_sibling();
|
|
981
|
-
while let Some(current) = sibling {
|
|
982
|
-
if current.kind_name() == "property_declaration" {
|
|
983
|
-
return find_kotlin_property_name(current, code);
|
|
984
|
-
}
|
|
985
|
-
if !matches!(current.kind_name(), "getter" | "setter")
|
|
986
|
-
&& !crate::ncss::COMMENT_NODE_TYPES.contains(¤t.kind_name())
|
|
987
|
-
{
|
|
988
|
-
return None;
|
|
989
|
-
}
|
|
990
|
-
sibling = current.prev_named_sibling();
|
|
991
|
-
}
|
|
992
|
-
None
|
|
993
|
-
}
|
|
994
|
-
|
|
995
953
|
/// The declared name of a Kotlin `property_declaration` (`val name: T`), if it declares one.
|
|
996
954
|
fn find_kotlin_property_name(property: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
997
955
|
if property.kind_name() != "property_declaration" {
|
|
998
956
|
return None;
|
|
999
957
|
}
|
|
1000
958
|
let declaration = first_named_child_of_kind(property, "variable_declaration")?;
|
|
1001
|
-
first_named_child_of_kind(declaration, "
|
|
959
|
+
first_named_child_of_kind(declaration, "identifier")
|
|
1002
960
|
.map(|name| node_text(name, code).to_string())
|
|
1003
961
|
}
|
|
1004
962
|
|
package/native/src/languages.rs
CHANGED
|
@@ -15,6 +15,9 @@ pub struct LanguageDefinition {
|
|
|
15
15
|
pub nesting_node_types: &'static [&'static str],
|
|
16
16
|
pub ncss_node_types: &'static [&'static str],
|
|
17
17
|
pub ncss_container_node_types: &'static [&'static str],
|
|
18
|
+
/// Parents whose braceless `consequence`/`alternative`/`body` child counts like the single
|
|
19
|
+
/// statement of a braced body, for grammars that wrap it in no statement node.
|
|
20
|
+
pub ncss_bare_body_parent_node_types: &'static [&'static str],
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
#[derive(Clone, Copy)]
|
|
@@ -43,8 +46,8 @@ impl LanguageDefinition {
|
|
|
43
46
|
GrammarId::CSharp => tree_sitter_c_sharp::language(),
|
|
44
47
|
GrammarId::Go => tree_sitter_go::language(),
|
|
45
48
|
GrammarId::Java => tree_sitter_java::language(),
|
|
46
|
-
GrammarId::JavaScript => tree_sitter_javascript::
|
|
47
|
-
GrammarId::Kotlin => tree_sitter_kotlin::
|
|
49
|
+
GrammarId::JavaScript => tree_sitter_javascript::LANGUAGE.into(),
|
|
50
|
+
GrammarId::Kotlin => tree_sitter_kotlin::LANGUAGE.into(),
|
|
48
51
|
GrammarId::Python => tree_sitter_python::language(),
|
|
49
52
|
GrammarId::Ruby => tree_sitter_ruby::language(),
|
|
50
53
|
GrammarId::Rust => tree_sitter_rust::language(),
|
|
@@ -661,16 +664,21 @@ const KOTLIN_NCSS_NODES: &[&str] = &[
|
|
|
661
664
|
"getter",
|
|
662
665
|
"setter",
|
|
663
666
|
];
|
|
664
|
-
// `
|
|
665
|
-
// the single statement of a braced one; `function_body` holds an expression body (`fun f() = x`).
|
|
667
|
+
// `function_body` holds an expression body (`fun f() = x`).
|
|
666
668
|
const KOTLIN_NCSS_CONTAINERS: &[&str] = &[
|
|
667
669
|
"source_file",
|
|
668
|
-
"
|
|
669
|
-
"statements",
|
|
670
|
+
"block",
|
|
670
671
|
"class_body",
|
|
671
672
|
"enum_class_body",
|
|
672
|
-
"control_structure_body",
|
|
673
673
|
"function_body",
|
|
674
|
+
"lambda_literal",
|
|
675
|
+
];
|
|
676
|
+
const KOTLIN_NCSS_BARE_BODY_PARENTS: &[&str] = &[
|
|
677
|
+
"if_expression",
|
|
678
|
+
"for_statement",
|
|
679
|
+
"while_statement",
|
|
680
|
+
"do_while_statement",
|
|
681
|
+
"when_entry",
|
|
674
682
|
];
|
|
675
683
|
|
|
676
684
|
pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
@@ -684,6 +692,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
684
692
|
nesting_node_types: COMMON_NESTING_NODES,
|
|
685
693
|
ncss_node_types: JS_NCSS_NODES,
|
|
686
694
|
ncss_container_node_types: &[],
|
|
695
|
+
ncss_bare_body_parent_node_types: &[],
|
|
687
696
|
},
|
|
688
697
|
LanguageDefinition {
|
|
689
698
|
name: "jsx",
|
|
@@ -695,6 +704,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
695
704
|
nesting_node_types: COMMON_NESTING_NODES,
|
|
696
705
|
ncss_node_types: JS_NCSS_NODES,
|
|
697
706
|
ncss_container_node_types: &[],
|
|
707
|
+
ncss_bare_body_parent_node_types: &[],
|
|
698
708
|
},
|
|
699
709
|
LanguageDefinition {
|
|
700
710
|
name: "typescript",
|
|
@@ -706,6 +716,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
706
716
|
nesting_node_types: COMMON_NESTING_NODES,
|
|
707
717
|
ncss_node_types: JS_NCSS_NODES,
|
|
708
718
|
ncss_container_node_types: &[],
|
|
719
|
+
ncss_bare_body_parent_node_types: &[],
|
|
709
720
|
},
|
|
710
721
|
LanguageDefinition {
|
|
711
722
|
name: "tsx",
|
|
@@ -717,6 +728,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
717
728
|
nesting_node_types: COMMON_NESTING_NODES,
|
|
718
729
|
ncss_node_types: JS_NCSS_NODES,
|
|
719
730
|
ncss_container_node_types: &[],
|
|
731
|
+
ncss_bare_body_parent_node_types: &[],
|
|
720
732
|
},
|
|
721
733
|
LanguageDefinition {
|
|
722
734
|
name: "python",
|
|
@@ -728,6 +740,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
728
740
|
nesting_node_types: COMMON_NESTING_NODES,
|
|
729
741
|
ncss_node_types: PYTHON_NCSS_NODES,
|
|
730
742
|
ncss_container_node_types: &[],
|
|
743
|
+
ncss_bare_body_parent_node_types: &[],
|
|
731
744
|
},
|
|
732
745
|
LanguageDefinition {
|
|
733
746
|
name: "go",
|
|
@@ -739,6 +752,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
739
752
|
nesting_node_types: GO_NESTING_NODES,
|
|
740
753
|
ncss_node_types: GO_NCSS_NODES,
|
|
741
754
|
ncss_container_node_types: &[],
|
|
755
|
+
ncss_bare_body_parent_node_types: &[],
|
|
742
756
|
},
|
|
743
757
|
LanguageDefinition {
|
|
744
758
|
name: "rust",
|
|
@@ -750,6 +764,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
750
764
|
nesting_node_types: COMMON_NESTING_NODES,
|
|
751
765
|
ncss_node_types: RUST_NCSS_NODES,
|
|
752
766
|
ncss_container_node_types: RUST_NCSS_CONTAINERS,
|
|
767
|
+
ncss_bare_body_parent_node_types: &[],
|
|
753
768
|
},
|
|
754
769
|
LanguageDefinition {
|
|
755
770
|
name: "java",
|
|
@@ -761,6 +776,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
761
776
|
nesting_node_types: JAVA_DECISION_NODES,
|
|
762
777
|
ncss_node_types: JAVA_NCSS_NODES,
|
|
763
778
|
ncss_container_node_types: &[],
|
|
779
|
+
ncss_bare_body_parent_node_types: &[],
|
|
764
780
|
},
|
|
765
781
|
LanguageDefinition {
|
|
766
782
|
name: "ruby",
|
|
@@ -772,6 +788,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
772
788
|
nesting_node_types: RUBY_DECISION_NODES,
|
|
773
789
|
ncss_node_types: RUBY_NCSS_NODES,
|
|
774
790
|
ncss_container_node_types: RUBY_NCSS_CONTAINERS,
|
|
791
|
+
ncss_bare_body_parent_node_types: &[],
|
|
775
792
|
},
|
|
776
793
|
LanguageDefinition {
|
|
777
794
|
name: "c",
|
|
@@ -783,6 +800,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
783
800
|
nesting_node_types: C_DECISION_NODES,
|
|
784
801
|
ncss_node_types: C_NCSS_NODES,
|
|
785
802
|
ncss_container_node_types: &[],
|
|
803
|
+
ncss_bare_body_parent_node_types: &[],
|
|
786
804
|
},
|
|
787
805
|
LanguageDefinition {
|
|
788
806
|
name: "cpp",
|
|
@@ -794,6 +812,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
794
812
|
nesting_node_types: CPP_DECISION_NODES,
|
|
795
813
|
ncss_node_types: CPP_NCSS_NODES,
|
|
796
814
|
ncss_container_node_types: &[],
|
|
815
|
+
ncss_bare_body_parent_node_types: &[],
|
|
797
816
|
},
|
|
798
817
|
LanguageDefinition {
|
|
799
818
|
name: "csharp",
|
|
@@ -805,6 +824,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
805
824
|
nesting_node_types: CSHARP_DECISION_NODES,
|
|
806
825
|
ncss_node_types: CSHARP_NCSS_NODES,
|
|
807
826
|
ncss_container_node_types: &[],
|
|
827
|
+
ncss_bare_body_parent_node_types: &[],
|
|
808
828
|
},
|
|
809
829
|
LanguageDefinition {
|
|
810
830
|
name: "kotlin",
|
|
@@ -816,6 +836,7 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
816
836
|
nesting_node_types: KOTLIN_DECISION_NODES,
|
|
817
837
|
ncss_node_types: KOTLIN_NCSS_NODES,
|
|
818
838
|
ncss_container_node_types: KOTLIN_NCSS_CONTAINERS,
|
|
839
|
+
ncss_bare_body_parent_node_types: KOTLIN_NCSS_BARE_BODY_PARENTS,
|
|
819
840
|
},
|
|
820
841
|
];
|
|
821
842
|
|
package/native/src/measure.rs
CHANGED
|
@@ -16,10 +16,7 @@ use crate::tree_index::{NodeExt, TreeIndex};
|
|
|
16
16
|
use crate::types::{
|
|
17
17
|
CrossFileFileData, FunctionMetrics, HalsteadCounts, LineMetrics, NativeMetrics,
|
|
18
18
|
};
|
|
19
|
-
use crate::util::{
|
|
20
|
-
all_children, is_identifier_leaf, is_js_whitespace, named_children, node_text, split_lines,
|
|
21
|
-
Source,
|
|
22
|
-
};
|
|
19
|
+
use crate::util::{all_children, is_js_whitespace, named_children, node_text, split_lines, Source};
|
|
23
20
|
|
|
24
21
|
pub fn measure(
|
|
25
22
|
code: &str,
|
|
@@ -166,23 +163,20 @@ fn is_csharp_top_level_statement(node: Node<'_>) -> bool {
|
|
|
166
163
|
fn has_top_level_statements(root: Node<'_>, language: &LanguageDefinition) -> bool {
|
|
167
164
|
const KOTLIN_DECLARATIONS: &[&str] = &[
|
|
168
165
|
"package_header",
|
|
169
|
-
"
|
|
166
|
+
"import",
|
|
170
167
|
"class_declaration",
|
|
171
168
|
"object_declaration",
|
|
172
169
|
"function_declaration",
|
|
173
170
|
"property_declaration",
|
|
174
171
|
"type_alias",
|
|
175
|
-
"
|
|
172
|
+
"shebang",
|
|
176
173
|
"file_annotation",
|
|
177
|
-
"getter",
|
|
178
|
-
"setter",
|
|
179
174
|
];
|
|
180
175
|
let children = named_children(root);
|
|
181
176
|
match language.name {
|
|
182
177
|
"csharp" => children.into_iter().any(is_csharp_top_level_statement),
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
// never taken for a script.
|
|
178
|
+
// Error recovery leaves residue at the top level, so a file with parse errors is never taken
|
|
179
|
+
// for a script.
|
|
186
180
|
"kotlin" => {
|
|
187
181
|
!root.has_error()
|
|
188
182
|
&& children.iter().any(|child| {
|
|
@@ -233,8 +227,6 @@ fn to_cross_file_data(
|
|
|
233
227
|
/// Name-carrying leaf types anonymized by tokenize_function so consistent renames still match.
|
|
234
228
|
const IDENTIFIER_LEAF_NODE_TYPES: &[&str] = &[
|
|
235
229
|
"identifier",
|
|
236
|
-
"simple_identifier",
|
|
237
|
-
"interpolated_identifier",
|
|
238
230
|
"implicit_parameter",
|
|
239
231
|
"property_identifier",
|
|
240
232
|
"field_identifier",
|
|
@@ -278,7 +270,7 @@ fn collect_token_symbols(
|
|
|
278
270
|
) {
|
|
279
271
|
if matches!(
|
|
280
272
|
node.kind_name(),
|
|
281
|
-
"comment" | "line_comment" | "block_comment"
|
|
273
|
+
"comment" | "line_comment" | "block_comment"
|
|
282
274
|
) {
|
|
283
275
|
return;
|
|
284
276
|
}
|
|
@@ -286,12 +278,16 @@ fn collect_token_symbols(
|
|
|
286
278
|
symbols.push(hash_text(node.kind_name()));
|
|
287
279
|
return;
|
|
288
280
|
}
|
|
289
|
-
if
|
|
281
|
+
if node.child_count() != 0 {
|
|
290
282
|
for child in all_children(node) {
|
|
291
283
|
collect_token_symbols(child, code, symbols, id_index_by_name);
|
|
292
284
|
}
|
|
293
285
|
return;
|
|
294
286
|
}
|
|
287
|
+
if crate::util::is_kotlin_keyword_literal(node, code) {
|
|
288
|
+
symbols.push(hash_text(node_text(node, code)));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
295
291
|
if IDENTIFIER_LEAF_NODE_TYPES.contains(&node.kind_name()) {
|
|
296
292
|
let next_index = id_index_by_name.len();
|
|
297
293
|
let index = *id_index_by_name
|
|
@@ -322,7 +318,7 @@ fn parse_source(
|
|
|
322
318
|
.set_language(&language.grammar())
|
|
323
319
|
.map_err(|error| error.to_string())?;
|
|
324
320
|
parser
|
|
325
|
-
.
|
|
321
|
+
.parse_utf16_le(source.to_utf16(), None)
|
|
326
322
|
.ok_or_else(|| "parse failed".to_string())
|
|
327
323
|
}
|
|
328
324
|
|
|
@@ -380,7 +376,7 @@ fn collect_comment_spans(root: Node<'_>) -> Vec<CommentSpan> {
|
|
|
380
376
|
fn visit(node: Node<'_>, spans: &mut Vec<CommentSpan>) {
|
|
381
377
|
if matches!(
|
|
382
378
|
node.kind_name(),
|
|
383
|
-
"comment" | "line_comment" | "block_comment"
|
|
379
|
+
"comment" | "line_comment" | "block_comment"
|
|
384
380
|
) {
|
|
385
381
|
for row in node.start_position().row..=node.end_position().row {
|
|
386
382
|
// Node columns are UTF-16 code units x 2 (the tree is parsed from UTF-16);
|
|
@@ -549,8 +545,6 @@ const OPERATOR_TEXTS: &[&str] = &[
|
|
|
549
545
|
|
|
550
546
|
const OPERAND_NODE_TYPES: &[&str] = &[
|
|
551
547
|
"identifier",
|
|
552
|
-
"simple_identifier",
|
|
553
|
-
"interpolated_identifier",
|
|
554
548
|
"implicit_parameter",
|
|
555
549
|
"property_identifier",
|
|
556
550
|
"field_identifier",
|
|
@@ -578,8 +572,6 @@ const OPERAND_NODE_TYPES: &[&str] = &[
|
|
|
578
572
|
"integer_literal",
|
|
579
573
|
"float_literal",
|
|
580
574
|
"real_literal",
|
|
581
|
-
"hex_literal",
|
|
582
|
-
"bin_literal",
|
|
583
575
|
"int_literal",
|
|
584
576
|
"rune_literal",
|
|
585
577
|
"imaginary_literal",
|
|
@@ -614,13 +606,10 @@ const OPERAND_NODE_TYPES: &[&str] = &[
|
|
|
614
606
|
];
|
|
615
607
|
|
|
616
608
|
/// Non-leaf literals counted as one Halstead operand without descending.
|
|
617
|
-
/// `character_literal` is a leaf in Java
|
|
618
|
-
/// suffixed numbers (`1L`, `1u`) wrap the bare literal, so `1` and `1L` stay distinct.
|
|
609
|
+
/// `character_literal` is a leaf in Java but wraps its content in C# and Kotlin.
|
|
619
610
|
const ATOMIC_OPERAND_NODE_TYPES: &[&str] = &[
|
|
620
611
|
"interpreted_string_literal",
|
|
621
612
|
"character_literal",
|
|
622
|
-
"long_literal",
|
|
623
|
-
"unsigned_literal",
|
|
624
613
|
"regex",
|
|
625
614
|
"user_defined_literal",
|
|
626
615
|
"integral_type",
|
|
@@ -656,7 +645,7 @@ fn measure_halstead(root: Node<'_>, code: &Source<'_>) -> HalsteadCounts {
|
|
|
656
645
|
) {
|
|
657
646
|
if matches!(
|
|
658
647
|
node.kind_name(),
|
|
659
|
-
"comment" | "line_comment" | "block_comment"
|
|
648
|
+
"comment" | "line_comment" | "block_comment"
|
|
660
649
|
) {
|
|
661
650
|
return;
|
|
662
651
|
}
|
|
@@ -668,7 +657,7 @@ fn measure_halstead(root: Node<'_>, code: &Source<'_>) -> HalsteadCounts {
|
|
|
668
657
|
|
|
669
658
|
// Operators are counted from leaf tokens only: keyword-named nodes always contain a
|
|
670
659
|
// same-text anonymous keyword leaf, so counting the named node as well would double-count.
|
|
671
|
-
if
|
|
660
|
+
if node.child_count() == 0 {
|
|
672
661
|
let text = node_text(node, code);
|
|
673
662
|
// Operands win over text matches so identifiers spelled like word operators stay operands;
|
|
674
663
|
// C# `nameof(x)` is the one keyword operator the grammar parses as a plain callee.
|