code-gauge 4.7.1 → 5.0.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 +35 -10
- package/THIRD-PARTY-NOTICES.txt +595 -29
- 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.d.ts +13 -3
- 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 +3 -3
- package/dist/diffCommand.cjs.map +1 -1
- package/dist/diffCommand.js +3 -3
- 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/exclusion.cjs +2 -0
- package/dist/exclusion.cjs.map +1 -0
- package/dist/exclusion.d.ts +30 -0
- package/dist/exclusion.js +2 -0
- package/dist/exclusion.js.map +1 -0
- package/dist/git.cjs +1 -1
- package/dist/git.cjs.map +1 -1
- package/dist/git.d.ts +16 -0
- package/dist/git.js +1 -1
- package/dist/git.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +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/newDuplication.cjs +2 -0
- package/dist/newDuplication.cjs.map +1 -0
- package/dist/newDuplication.d.ts +40 -0
- package/dist/newDuplication.js +2 -0
- package/dist/newDuplication.js.map +1 -0
- package/dist/regressionGate.cjs +1 -1
- package/dist/regressionGate.cjs.map +1 -1
- package/dist/regressionGate.d.ts +8 -6
- 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.d.ts +12 -3
- package/dist/scan.js +1 -1
- package/dist/scan.js.map +1 -1
- package/dist/worker.cjs +1 -1
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.js +1 -1
- package/dist/worker.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 +158 -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
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.
|
package/native/src/ncss.rs
CHANGED
|
@@ -4,21 +4,14 @@ use tree_sitter::Node;
|
|
|
4
4
|
use crate::tree_index::NodeExt;
|
|
5
5
|
use crate::util::find_children_by_field_name;
|
|
6
6
|
|
|
7
|
-
pub const COMMENT_NODE_TYPES: &[&str] = &[
|
|
8
|
-
"comment",
|
|
9
|
-
"line_comment",
|
|
10
|
-
"block_comment",
|
|
11
|
-
"multiline_comment",
|
|
12
|
-
];
|
|
7
|
+
pub const COMMENT_NODE_TYPES: &[&str] = &["comment", "line_comment", "block_comment"];
|
|
13
8
|
|
|
14
9
|
/// Nodes never counted positionally inside NCSS containers: metadata, empty statements, Ruby
|
|
15
10
|
/// heredoc bodies (tree-sitter emits them as siblings of the statement that opened the heredoc),
|
|
16
|
-
/// Ruby statement parentheses (transparent wrappers whose children count instead), Kotlin
|
|
17
|
-
///
|
|
18
|
-
///
|
|
19
|
-
///
|
|
20
|
-
/// expression) and `label` (a Rust block label `'outer:` stands for the labeled statement the
|
|
21
|
-
/// other languages count, while a Kotlin `outer@` is a childless token beside its statement).
|
|
11
|
+
/// Ruby statement parentheses (transparent wrappers whose children count instead), Kotlin enum
|
|
12
|
+
/// entries (Java enum constants are not counted either), and Kotlin lambda parameters (siblings of
|
|
13
|
+
/// the lambda's statements). Kotlin's `try_expression` is excluded contextually below because Rust
|
|
14
|
+
/// shares its name: Rust's `?` operator must keep counting as a tail expression.
|
|
22
15
|
const POSITIONAL_EXCLUSION_TYPES: &[&str] = &[
|
|
23
16
|
"attribute_item",
|
|
24
17
|
"inner_attribute_item",
|
|
@@ -27,8 +20,9 @@ const POSITIONAL_EXCLUSION_TYPES: &[&str] = &[
|
|
|
27
20
|
"parenthesized_statements",
|
|
28
21
|
"annotation",
|
|
29
22
|
"file_annotation",
|
|
30
|
-
"
|
|
23
|
+
"shebang",
|
|
31
24
|
"enum_entry",
|
|
25
|
+
"lambda_parameters",
|
|
32
26
|
];
|
|
33
27
|
|
|
34
28
|
/// TypeScript interface members count like Java interface members, but the same node types appear
|
|
@@ -66,6 +60,7 @@ pub fn ncss_contribution(
|
|
|
66
60
|
parent: Option<Node<'_>>,
|
|
67
61
|
countable: &FxHashSet<&'static str>,
|
|
68
62
|
containers: &FxHashSet<&'static str>,
|
|
63
|
+
bare_body_parents: &FxHashSet<&'static str>,
|
|
69
64
|
) -> u64 {
|
|
70
65
|
if !node.is_named()
|
|
71
66
|
|| COMMENT_NODE_TYPES.contains(&node.kind_name())
|
|
@@ -74,7 +69,7 @@ pub fn ncss_contribution(
|
|
|
74
69
|
return 0;
|
|
75
70
|
}
|
|
76
71
|
// A Kotlin accessor without a body (`private set`) only changes visibility and declares
|
|
77
|
-
// nothing of its own
|
|
72
|
+
// nothing of its own, so it must not count through its node type.
|
|
78
73
|
if (node.kind_name() == "getter" || node.kind_name() == "setter")
|
|
79
74
|
&& !crate::functions::is_implemented_function(node)
|
|
80
75
|
{
|
|
@@ -82,11 +77,13 @@ pub fn ncss_contribution(
|
|
|
82
77
|
}
|
|
83
78
|
|
|
84
79
|
let mut contribution = 0;
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
// A Kotlin lambda holds its statements directly, so it is a container, yet it still counts where
|
|
81
|
+
// it stands like any other expression.
|
|
82
|
+
let positional = (is_in_container_position(parent, containers)
|
|
83
|
+
|| is_bare_body(node, parent, bare_body_parents))
|
|
84
|
+
&& (!containers.contains(node.kind_name()) || node.kind_name() == "lambda_literal")
|
|
87
85
|
&& !POSITIONAL_EXCLUSION_TYPES.contains(&node.kind_name())
|
|
88
|
-
&& !crate::util::is_kotlin_try_expression(node)
|
|
89
|
-
&& !(node.kind_name() == "label" && node.child_count() == 0);
|
|
86
|
+
&& !crate::util::is_kotlin_try_expression(node);
|
|
90
87
|
if (counts_through_node_type(node, countable)
|
|
91
88
|
|| positional
|
|
92
89
|
|| counts_contextually(node, parent))
|
|
@@ -95,12 +92,11 @@ pub fn ncss_contribution(
|
|
|
95
92
|
contribution += 1;
|
|
96
93
|
}
|
|
97
94
|
|
|
98
|
-
// A bare else branch (Java/Go `alternative:` without an else-clause wrapper
|
|
99
|
-
//
|
|
100
|
-
//
|
|
95
|
+
// A bare else branch (Java/Go/Kotlin `alternative:` without an else-clause wrapper) counts 1
|
|
96
|
+
// like the `else` keyword does in PMD; an `else if` chain charges the nested if separately on
|
|
97
|
+
// top.
|
|
101
98
|
if IF_NODE_TYPES.contains(&node.kind_name()) {
|
|
102
|
-
contribution += count_bare_alternatives(node)
|
|
103
|
-
+ u64::from(crate::util::kotlin_else_body(node).is_some());
|
|
99
|
+
contribution += count_bare_alternatives(node);
|
|
104
100
|
}
|
|
105
101
|
|
|
106
102
|
contribution
|
|
@@ -137,6 +133,19 @@ fn is_in_container_position(
|
|
|
137
133
|
false
|
|
138
134
|
}
|
|
139
135
|
|
|
136
|
+
/// A braceless branch or loop body (`if (x) foo()`) stands for the single statement a braced body
|
|
137
|
+
/// would hold.
|
|
138
|
+
fn is_bare_body(
|
|
139
|
+
node: Node<'_>,
|
|
140
|
+
parent: Option<Node<'_>>,
|
|
141
|
+
bare_body_parents: &FxHashSet<&'static str>,
|
|
142
|
+
) -> bool {
|
|
143
|
+
parent.is_some_and(|parent| bare_body_parents.contains(parent.kind_name()))
|
|
144
|
+
&& ["consequence", "alternative", "body"]
|
|
145
|
+
.iter()
|
|
146
|
+
.any(|field_name| is_field_of_parent(node, parent, field_name))
|
|
147
|
+
}
|
|
148
|
+
|
|
140
149
|
/// `export const x = 1` nests a countable declaration inside `export_statement`; only the inner
|
|
141
150
|
/// declaration counts, mirroring how PMD counts one statement per declared entity.
|
|
142
151
|
fn is_declaration_wrapper(node: Node<'_>, countable: &FxHashSet<&'static str>) -> bool {
|
package/native/src/tree_index.rs
CHANGED
|
@@ -12,6 +12,13 @@ thread_local! {
|
|
|
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
14
|
static KIND_NAMES: Cell<&'static [&'static str]> = const { Cell::new(&[]) };
|
|
15
|
+
/// Name of the indexed tree's language.
|
|
16
|
+
static LANGUAGE_NAME: Cell<&'static str> = const { Cell::new("") };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// The language of the tree indexed on this thread, or "" when none is.
|
|
20
|
+
pub fn language_name() -> &'static str {
|
|
21
|
+
LANGUAGE_NAME.get()
|
|
15
22
|
}
|
|
16
23
|
|
|
17
24
|
/// Makes NodeExt lookups O(1) while the tree is being measured: tree-sitter's `Node::parent`
|
|
@@ -30,6 +37,7 @@ impl<'t> TreeIndex<'t> {
|
|
|
30
37
|
// Constructed first so that the depth error below also clears the partial index on drop.
|
|
31
38
|
let index = TreeIndex { tree: PhantomData };
|
|
32
39
|
KIND_NAMES.set(language.kind_names());
|
|
40
|
+
LANGUAGE_NAME.set(language.name);
|
|
33
41
|
PARENTS.with_borrow_mut(|parents| {
|
|
34
42
|
assert!(
|
|
35
43
|
parents.is_empty(),
|
|
@@ -65,6 +73,7 @@ impl Drop for TreeIndex<'_> {
|
|
|
65
73
|
fn drop(&mut self) {
|
|
66
74
|
PARENTS.with_borrow_mut(|parents| parents.clear());
|
|
67
75
|
KIND_NAMES.set(&[]);
|
|
76
|
+
LANGUAGE_NAME.set("");
|
|
68
77
|
}
|
|
69
78
|
}
|
|
70
79
|
|