code-gauge 4.1.3 → 4.2.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 +6 -6
- 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/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 +1 -1
- package/native/Cargo.lock +22 -0
- package/native/Cargo.toml +2 -0
- package/native/src/complexity.rs +55 -6
- package/native/src/dep_degree.rs +103 -9
- package/native/src/duplication.rs +197 -18
- package/native/src/functions.rs +194 -8
- package/native/src/languages.rs +142 -0
- package/native/src/measure.rs +76 -10
- package/native/src/ncss.rs +31 -6
- package/native/src/util.rs +59 -0
- package/package.json +10 -8
package/native/src/functions.rs
CHANGED
|
@@ -6,12 +6,20 @@ use crate::util::{all_children, find_children_by_field_name, named_children, nod
|
|
|
6
6
|
/// C++ `function_definition` also covers pure-virtual/`= default`/`= delete` members; those have no
|
|
7
7
|
/// `body` and are signatures, not implementations, matching how TypeScript method signatures are
|
|
8
8
|
/// excluded. Java `method_declaration` is NOT here: PMD reports abstract/interface methods as
|
|
9
|
-
/// methods (NCSS 1), so bodyless Java methods stay in the function list.
|
|
9
|
+
/// methods (NCSS 1), so bodyless Java methods stay in the function list (as do C#'s and Kotlin's).
|
|
10
|
+
/// C# auto-property accessors (`{ get; set; }`) and Kotlin visibility-only accessors (`private
|
|
11
|
+
/// set`) hold no code, so they need a body too; a C# property or indexer is a function only in its
|
|
12
|
+
/// expression-bodied form (`int X => ...`), otherwise its accessors are the functions.
|
|
10
13
|
const BODY_REQUIRED_FUNCTION_TYPES: &[&str] = &[
|
|
11
14
|
"function_definition",
|
|
12
15
|
"constructor_declaration",
|
|
13
16
|
"compact_constructor_declaration",
|
|
14
17
|
"function_signature_item",
|
|
18
|
+
"accessor_declaration",
|
|
19
|
+
"getter",
|
|
20
|
+
"setter",
|
|
21
|
+
"property_declaration",
|
|
22
|
+
"indexer_declaration",
|
|
15
23
|
];
|
|
16
24
|
|
|
17
25
|
pub fn is_implemented_function(node: Node<'_>) -> bool {
|
|
@@ -21,6 +29,19 @@ pub fn is_implemented_function(node: Node<'_>) -> bool {
|
|
|
21
29
|
return true;
|
|
22
30
|
}
|
|
23
31
|
|
|
32
|
+
if node.kind() == "property_declaration" || node.kind() == "indexer_declaration" {
|
|
33
|
+
return node
|
|
34
|
+
.child_by_field_name("value")
|
|
35
|
+
.is_some_and(|value| value.kind() == "arrow_expression_clause");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// The Kotlin grammar has no fields; an implemented accessor holds a `function_body` child.
|
|
39
|
+
if node.kind() == "getter" || node.kind() == "setter" {
|
|
40
|
+
return named_children(node)
|
|
41
|
+
.iter()
|
|
42
|
+
.any(|child| child.kind() == "function_body");
|
|
43
|
+
}
|
|
44
|
+
|
|
24
45
|
// C++ constructor/destructor function-try-blocks carry their `try_statement` outside the
|
|
25
46
|
// `body` field; they are implementations, unlike `= 0`/`= default`/`= delete` members.
|
|
26
47
|
named_children(node)
|
|
@@ -39,10 +60,28 @@ pub fn count_parameters(node: Node<'_>, code: &Source<'_>) -> usize {
|
|
|
39
60
|
return 0;
|
|
40
61
|
};
|
|
41
62
|
|
|
42
|
-
// A Java bare lambda parameter (`x -> x + 1`) puts a lone identifier in the `parameters` field
|
|
43
|
-
|
|
63
|
+
// A Java bare lambda parameter (`x -> x + 1`) puts a lone identifier in the `parameters` field;
|
|
64
|
+
// a C# one (`x => x + 1`) is an `implicit_parameter` leaf.
|
|
65
|
+
if parameters_node.kind() == "identifier" || parameters_node.kind() == "implicit_parameter" {
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
68
|
+
// Kotlin default values (`x: Int = 0`) are siblings of their `parameter`, not children.
|
|
69
|
+
if parameters_node.kind() == "function_value_parameters" {
|
|
70
|
+
return named_children(parameters_node)
|
|
71
|
+
.iter()
|
|
72
|
+
.filter(|child| child.kind() == "parameter")
|
|
73
|
+
.count();
|
|
74
|
+
}
|
|
75
|
+
// A Kotlin setter declares its single parameter directly (`set(value) { ... }`).
|
|
76
|
+
if parameters_node.kind() == "setter" {
|
|
44
77
|
return 1;
|
|
45
78
|
}
|
|
79
|
+
// A C# `params` array is spelled out as `type`/`name` fields of the parameter list itself.
|
|
80
|
+
let csharp_params_array_ids: HashSet<usize> = ["type", "name"]
|
|
81
|
+
.iter()
|
|
82
|
+
.flat_map(|field| find_children_by_field_name(parameters_node, field))
|
|
83
|
+
.map(|child| child.id())
|
|
84
|
+
.collect();
|
|
46
85
|
|
|
47
86
|
// Ruby block-locals after `;` (`{ |x; memo| ... }`) occupy `locals` fields and receive no arguments.
|
|
48
87
|
let block_local_ids: HashSet<usize> = find_children_by_field_name(parameters_node, "locals")
|
|
@@ -54,7 +93,9 @@ pub fn count_parameters(node: Node<'_>, code: &Source<'_>) -> usize {
|
|
|
54
93
|
// C/C++ `f(void)` declares none, and a Ruby block parameter (`&blk`) binds the block, which
|
|
55
94
|
// call sites pass outside the argument list.
|
|
56
95
|
for child in named_children(parameters_node) {
|
|
57
|
-
if child.kind()
|
|
96
|
+
if crate::ncss::COMMENT_NODE_TYPES.contains(&child.kind())
|
|
97
|
+
|| child.kind() == "attribute_list"
|
|
98
|
+
|| csharp_params_array_ids.contains(&child.id())
|
|
58
99
|
|| child.kind() == "self_parameter"
|
|
59
100
|
|| child.kind() == "receiver_parameter"
|
|
60
101
|
|| child.kind() == "block_parameter"
|
|
@@ -78,7 +119,7 @@ pub fn count_parameters(node: Node<'_>, code: &Source<'_>) -> usize {
|
|
|
78
119
|
.iter()
|
|
79
120
|
.filter(|child| !child.is_named() && node_text(**child, code) == "...")
|
|
80
121
|
.count();
|
|
81
|
-
count + anonymous_variadic_count
|
|
122
|
+
count + anonymous_variadic_count + usize::from(!csharp_params_array_ids.is_empty())
|
|
82
123
|
}
|
|
83
124
|
|
|
84
125
|
/// C/C++ `int f(void)` has a `parameter_declaration` whose type is a bare `void` with no declarator.
|
|
@@ -95,6 +136,15 @@ fn find_parameters_node(node: Node<'_>) -> Option<Node<'_>> {
|
|
|
95
136
|
return Some(direct);
|
|
96
137
|
}
|
|
97
138
|
|
|
139
|
+
// A C# indexer accessor (`this[int i] { get { ... } }`) takes the indexer's parameters.
|
|
140
|
+
if node.kind() == "accessor_declaration" {
|
|
141
|
+
return node
|
|
142
|
+
.parent()
|
|
143
|
+
.and_then(|list| list.parent())
|
|
144
|
+
.filter(|owner| owner.kind() == "indexer_declaration")
|
|
145
|
+
.and_then(|owner| owner.child_by_field_name("parameters"));
|
|
146
|
+
}
|
|
147
|
+
|
|
98
148
|
// A Java compact constructor implicitly takes the record's components, declared on the
|
|
99
149
|
// `record_declaration` two levels up (via `class_body`).
|
|
100
150
|
if node.kind() == "compact_constructor_declaration" {
|
|
@@ -113,9 +163,24 @@ fn find_parameters_node(node: Node<'_>) -> Option<Node<'_>> {
|
|
|
113
163
|
declarator = next_declarator(current);
|
|
114
164
|
}
|
|
115
165
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
166
|
+
// A Kotlin setter's parameter (`set(value)`) sits directly under the setter node.
|
|
167
|
+
if node.kind() == "setter"
|
|
168
|
+
&& named_children(node)
|
|
169
|
+
.iter()
|
|
170
|
+
.any(|child| child.kind() == "parameter_with_optional_type")
|
|
171
|
+
{
|
|
172
|
+
return Some(node);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
named_children(node).into_iter().find(|child| {
|
|
176
|
+
matches!(
|
|
177
|
+
child.kind(),
|
|
178
|
+
"formal_parameters"
|
|
179
|
+
| "parameter_list"
|
|
180
|
+
| "function_value_parameters"
|
|
181
|
+
| "lambda_parameters"
|
|
182
|
+
)
|
|
183
|
+
})
|
|
119
184
|
}
|
|
120
185
|
|
|
121
186
|
pub fn collect_nodes<'t>(root: Node<'t>, node_types: &HashSet<&'static str>) -> Vec<Node<'t>> {
|
|
@@ -143,6 +208,10 @@ pub fn find_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
|
143
208
|
return Some(wrapped_name);
|
|
144
209
|
}
|
|
145
210
|
|
|
211
|
+
if let Some(member_name) = find_member_function_name(node, code) {
|
|
212
|
+
return Some(member_name);
|
|
213
|
+
}
|
|
214
|
+
|
|
146
215
|
if let Some(name_node) = node.child_by_field_name("name") {
|
|
147
216
|
return Some(node_text(name_node, code).to_string());
|
|
148
217
|
}
|
|
@@ -183,6 +252,18 @@ pub fn find_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
|
183
252
|
if node.kind() == "lambda" && parent.kind() == "assignment" {
|
|
184
253
|
return find_ruby_assignment_name(parent, code);
|
|
185
254
|
}
|
|
255
|
+
|
|
256
|
+
// A Kotlin lambda or anonymous function initializing a property (`val f = { ... }`, also through
|
|
257
|
+
// a label or annotation prefix) takes the property name.
|
|
258
|
+
if node.kind() == "lambda_literal" || node.kind() == "anonymous_function" {
|
|
259
|
+
let mut holder = parent;
|
|
260
|
+
while holder.kind() == "prefix_expression" {
|
|
261
|
+
holder = holder.parent()?;
|
|
262
|
+
}
|
|
263
|
+
if holder.kind() == "property_declaration" {
|
|
264
|
+
return find_kotlin_property_name(holder, code);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
186
267
|
if (node.kind() == "block" || node.kind() == "do_block") && is_ruby_lambda_call(parent, code) {
|
|
187
268
|
return match parent.parent() {
|
|
188
269
|
Some(grandparent) if grandparent.kind() == "assignment" => {
|
|
@@ -197,6 +278,111 @@ pub fn find_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
|
197
278
|
.map(|name| node_text(name, code).to_string())
|
|
198
279
|
}
|
|
199
280
|
|
|
281
|
+
/// Names of C# and Kotlin members whose grammars carry no usable `name` field: accessors are
|
|
282
|
+
/// named after their property (`Count.get`; an expression-bodied property is its own getter),
|
|
283
|
+
/// Kotlin functions by their identifier child, Kotlin secondary constructors and C# destructors
|
|
284
|
+
/// after their class, and C# operators like C++ ones.
|
|
285
|
+
fn find_member_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
286
|
+
match node.kind() {
|
|
287
|
+
"accessor_declaration" => {
|
|
288
|
+
let keyword = node_text(node.child_by_field_name("name")?, code);
|
|
289
|
+
let owner = node.parent()?.parent()?;
|
|
290
|
+
Some(format!("{}.{keyword}", csharp_property_name(owner, code)?))
|
|
291
|
+
}
|
|
292
|
+
"property_declaration" | "indexer_declaration" => {
|
|
293
|
+
Some(format!("{}.get", csharp_property_name(node, code)?))
|
|
294
|
+
}
|
|
295
|
+
"getter" | "setter" => {
|
|
296
|
+
let keyword = if node.kind() == "getter" {
|
|
297
|
+
"get"
|
|
298
|
+
} else {
|
|
299
|
+
"set"
|
|
300
|
+
};
|
|
301
|
+
Some(format!(
|
|
302
|
+
"{}.{keyword}",
|
|
303
|
+
find_kotlin_accessor_owner_name(node, code)?
|
|
304
|
+
))
|
|
305
|
+
}
|
|
306
|
+
"function_declaration" if node.child_by_field_name("name").is_none() => {
|
|
307
|
+
first_named_child_of_kind(node, "simple_identifier")
|
|
308
|
+
.map(|name| node_text(name, code).to_string())
|
|
309
|
+
}
|
|
310
|
+
"secondary_constructor" => {
|
|
311
|
+
let mut ancestor = node.parent();
|
|
312
|
+
while let Some(current) = ancestor {
|
|
313
|
+
if current.kind() == "class_declaration" || current.kind() == "object_declaration" {
|
|
314
|
+
return first_named_child_of_kind(current, "type_identifier")
|
|
315
|
+
.map(|name| node_text(name, code).to_string());
|
|
316
|
+
}
|
|
317
|
+
ancestor = current.parent();
|
|
318
|
+
}
|
|
319
|
+
None
|
|
320
|
+
}
|
|
321
|
+
"destructor_declaration" => Some(format!(
|
|
322
|
+
"~{}",
|
|
323
|
+
node_text(node.child_by_field_name("name")?, code)
|
|
324
|
+
)),
|
|
325
|
+
"operator_declaration" => Some(format!(
|
|
326
|
+
"operator {}",
|
|
327
|
+
node_text(node.child_by_field_name("operator")?, code)
|
|
328
|
+
)),
|
|
329
|
+
"conversion_operator_declaration" => Some(format!(
|
|
330
|
+
"operator {}",
|
|
331
|
+
node_text(node.child_by_field_name("type")?, code)
|
|
332
|
+
)),
|
|
333
|
+
_ => None,
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/// A C# property or indexer (`this`) name.
|
|
338
|
+
fn csharp_property_name(owner: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
339
|
+
match owner.kind() {
|
|
340
|
+
"indexer_declaration" => Some("this".to_string()),
|
|
341
|
+
_ => Some(node_text(owner.child_by_field_name("name")?, code).to_string()),
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/// The property a Kotlin accessor belongs to: its parent when the accessor follows the initializer
|
|
346
|
+
/// on the same line, otherwise (accessor on its own line) the grammar emits it as a class-body
|
|
347
|
+
/// sibling after the property, any preceding accessor, and any comments between them.
|
|
348
|
+
fn find_kotlin_accessor_owner_name(accessor: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
349
|
+
if let Some(name) = accessor
|
|
350
|
+
.parent()
|
|
351
|
+
.and_then(|parent| find_kotlin_property_name(parent, code))
|
|
352
|
+
{
|
|
353
|
+
return Some(name);
|
|
354
|
+
}
|
|
355
|
+
let mut sibling = accessor.prev_named_sibling();
|
|
356
|
+
while let Some(current) = sibling {
|
|
357
|
+
if current.kind() == "property_declaration" {
|
|
358
|
+
return find_kotlin_property_name(current, code);
|
|
359
|
+
}
|
|
360
|
+
if !matches!(current.kind(), "getter" | "setter")
|
|
361
|
+
&& !crate::ncss::COMMENT_NODE_TYPES.contains(¤t.kind())
|
|
362
|
+
{
|
|
363
|
+
return None;
|
|
364
|
+
}
|
|
365
|
+
sibling = current.prev_named_sibling();
|
|
366
|
+
}
|
|
367
|
+
None
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/// The declared name of a Kotlin `property_declaration` (`val name: T`), if it declares one.
|
|
371
|
+
fn find_kotlin_property_name(property: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
372
|
+
if property.kind() != "property_declaration" {
|
|
373
|
+
return None;
|
|
374
|
+
}
|
|
375
|
+
let declaration = first_named_child_of_kind(property, "variable_declaration")?;
|
|
376
|
+
first_named_child_of_kind(declaration, "simple_identifier")
|
|
377
|
+
.map(|name| node_text(name, code).to_string())
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
fn first_named_child_of_kind<'t>(node: Node<'t>, kind: &str) -> Option<Node<'t>> {
|
|
381
|
+
named_children(node)
|
|
382
|
+
.into_iter()
|
|
383
|
+
.find(|child| child.kind() == kind)
|
|
384
|
+
}
|
|
385
|
+
|
|
200
386
|
fn find_ruby_assignment_name(assignment: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
201
387
|
let left_node = assignment.child_by_field_name("left")?;
|
|
202
388
|
if left_node.kind() == "identifier" || left_node.kind() == "constant" {
|
package/native/src/languages.rs
CHANGED
|
@@ -16,9 +16,11 @@ pub struct LanguageDefinition {
|
|
|
16
16
|
enum GrammarId {
|
|
17
17
|
C,
|
|
18
18
|
Cpp,
|
|
19
|
+
CSharp,
|
|
19
20
|
Go,
|
|
20
21
|
Java,
|
|
21
22
|
JavaScript,
|
|
23
|
+
Kotlin,
|
|
22
24
|
Python,
|
|
23
25
|
Ruby,
|
|
24
26
|
Rust,
|
|
@@ -31,9 +33,11 @@ impl LanguageDefinition {
|
|
|
31
33
|
match self.grammar_id {
|
|
32
34
|
GrammarId::C => tree_sitter_c::language(),
|
|
33
35
|
GrammarId::Cpp => tree_sitter_cpp::language(),
|
|
36
|
+
GrammarId::CSharp => tree_sitter_c_sharp::language(),
|
|
34
37
|
GrammarId::Go => tree_sitter_go::language(),
|
|
35
38
|
GrammarId::Java => tree_sitter_java::language(),
|
|
36
39
|
GrammarId::JavaScript => tree_sitter_javascript::language(),
|
|
40
|
+
GrammarId::Kotlin => tree_sitter_kotlin::language(),
|
|
37
41
|
GrammarId::Python => tree_sitter_python::language(),
|
|
38
42
|
GrammarId::Ruby => tree_sitter_ruby::language(),
|
|
39
43
|
GrammarId::Rust => tree_sitter_rust::language(),
|
|
@@ -527,6 +531,124 @@ const CPP_NCSS_NODES: &[&str] = &[
|
|
|
527
531
|
"co_yield_statement",
|
|
528
532
|
];
|
|
529
533
|
|
|
534
|
+
// C# members mirror Java's: every member kind with a body is a function, and accessors
|
|
535
|
+
// (`get { ... }`) are functions of their own so property logic is measured per accessor; an
|
|
536
|
+
// expression-bodied property or indexer (`int X => ...`) is its own getter (see functions.rs).
|
|
537
|
+
const CSHARP_FUNCTION_NODES: &[&str] = &[
|
|
538
|
+
"method_declaration",
|
|
539
|
+
"constructor_declaration",
|
|
540
|
+
"destructor_declaration",
|
|
541
|
+
"operator_declaration",
|
|
542
|
+
"conversion_operator_declaration",
|
|
543
|
+
"accessor_declaration",
|
|
544
|
+
"property_declaration",
|
|
545
|
+
"indexer_declaration",
|
|
546
|
+
"local_function_statement",
|
|
547
|
+
"lambda_expression",
|
|
548
|
+
"anonymous_method_expression",
|
|
549
|
+
];
|
|
550
|
+
const CSHARP_DECISION_NODES: &[&str] = &[
|
|
551
|
+
"if_statement",
|
|
552
|
+
"for_statement",
|
|
553
|
+
"foreach_statement",
|
|
554
|
+
"while_statement",
|
|
555
|
+
"do_statement",
|
|
556
|
+
"catch_clause",
|
|
557
|
+
"switch_section",
|
|
558
|
+
"switch_expression_arm",
|
|
559
|
+
"conditional_expression",
|
|
560
|
+
];
|
|
561
|
+
const CSHARP_NCSS_NODES: &[&str] = &[
|
|
562
|
+
"extern_alias_directive",
|
|
563
|
+
"using_directive",
|
|
564
|
+
"namespace_declaration",
|
|
565
|
+
"file_scoped_namespace_declaration",
|
|
566
|
+
"class_declaration",
|
|
567
|
+
"struct_declaration",
|
|
568
|
+
"interface_declaration",
|
|
569
|
+
"enum_declaration",
|
|
570
|
+
"record_declaration",
|
|
571
|
+
"delegate_declaration",
|
|
572
|
+
"field_declaration",
|
|
573
|
+
"event_field_declaration",
|
|
574
|
+
"event_declaration",
|
|
575
|
+
"property_declaration",
|
|
576
|
+
"indexer_declaration",
|
|
577
|
+
"method_declaration",
|
|
578
|
+
"constructor_declaration",
|
|
579
|
+
"destructor_declaration",
|
|
580
|
+
"operator_declaration",
|
|
581
|
+
"conversion_operator_declaration",
|
|
582
|
+
"accessor_declaration",
|
|
583
|
+
// An expression body (`=> expr`) stands for the single statement a block body would hold.
|
|
584
|
+
"arrow_expression_clause",
|
|
585
|
+
"local_function_statement",
|
|
586
|
+
"local_declaration_statement",
|
|
587
|
+
"expression_statement",
|
|
588
|
+
"if_statement",
|
|
589
|
+
"while_statement",
|
|
590
|
+
"do_statement",
|
|
591
|
+
"for_statement",
|
|
592
|
+
"foreach_statement",
|
|
593
|
+
"switch_statement",
|
|
594
|
+
"switch_expression",
|
|
595
|
+
"switch_section",
|
|
596
|
+
"switch_expression_arm",
|
|
597
|
+
"break_statement",
|
|
598
|
+
"continue_statement",
|
|
599
|
+
"return_statement",
|
|
600
|
+
"throw_statement",
|
|
601
|
+
"yield_statement",
|
|
602
|
+
"goto_statement",
|
|
603
|
+
"labeled_statement",
|
|
604
|
+
"lock_statement",
|
|
605
|
+
"using_statement",
|
|
606
|
+
"fixed_statement",
|
|
607
|
+
"checked_statement",
|
|
608
|
+
"unsafe_statement",
|
|
609
|
+
"catch_clause",
|
|
610
|
+
"finally_clause",
|
|
611
|
+
];
|
|
612
|
+
|
|
613
|
+
// Kotlin's grammar wraps neither statements nor members, so bodies are counted positionally like
|
|
614
|
+
// Ruby's (see KOTLIN_NCSS_CONTAINERS); only clauses hanging off non-container parents are listed.
|
|
615
|
+
const KOTLIN_FUNCTION_NODES: &[&str] = &[
|
|
616
|
+
"function_declaration",
|
|
617
|
+
"secondary_constructor",
|
|
618
|
+
"getter",
|
|
619
|
+
"setter",
|
|
620
|
+
"anonymous_function",
|
|
621
|
+
"lambda_literal",
|
|
622
|
+
];
|
|
623
|
+
const KOTLIN_DECISION_NODES: &[&str] = &[
|
|
624
|
+
"if_expression",
|
|
625
|
+
"for_statement",
|
|
626
|
+
"while_statement",
|
|
627
|
+
"do_while_statement",
|
|
628
|
+
"catch_block",
|
|
629
|
+
"when_entry",
|
|
630
|
+
];
|
|
631
|
+
// Accessors count like C# accessor declarations; a visibility-only `private set` is skipped in
|
|
632
|
+
// ncss.rs because it declares nothing.
|
|
633
|
+
const KOTLIN_NCSS_NODES: &[&str] = &[
|
|
634
|
+
"when_entry",
|
|
635
|
+
"catch_block",
|
|
636
|
+
"finally_block",
|
|
637
|
+
"getter",
|
|
638
|
+
"setter",
|
|
639
|
+
];
|
|
640
|
+
// `control_structure_body` holds a braceless branch/loop body (`if (x) foo()`), which counts like
|
|
641
|
+
// the single statement of a braced one; `function_body` holds an expression body (`fun f() = x`).
|
|
642
|
+
const KOTLIN_NCSS_CONTAINERS: &[&str] = &[
|
|
643
|
+
"source_file",
|
|
644
|
+
"import_list",
|
|
645
|
+
"statements",
|
|
646
|
+
"class_body",
|
|
647
|
+
"enum_class_body",
|
|
648
|
+
"control_structure_body",
|
|
649
|
+
"function_body",
|
|
650
|
+
];
|
|
651
|
+
|
|
530
652
|
pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
531
653
|
LanguageDefinition {
|
|
532
654
|
name: "javascript",
|
|
@@ -638,6 +760,26 @@ pub const LANGUAGES: &[LanguageDefinition] = &[
|
|
|
638
760
|
ncss_node_types: CPP_NCSS_NODES,
|
|
639
761
|
ncss_container_node_types: &[],
|
|
640
762
|
},
|
|
763
|
+
LanguageDefinition {
|
|
764
|
+
name: "csharp",
|
|
765
|
+
aliases: &["cs", "c#"],
|
|
766
|
+
grammar_id: GrammarId::CSharp,
|
|
767
|
+
function_node_types: CSHARP_FUNCTION_NODES,
|
|
768
|
+
decision_node_types: CSHARP_DECISION_NODES,
|
|
769
|
+
nesting_node_types: CSHARP_DECISION_NODES,
|
|
770
|
+
ncss_node_types: CSHARP_NCSS_NODES,
|
|
771
|
+
ncss_container_node_types: &[],
|
|
772
|
+
},
|
|
773
|
+
LanguageDefinition {
|
|
774
|
+
name: "kotlin",
|
|
775
|
+
aliases: &["kt", "kts"],
|
|
776
|
+
grammar_id: GrammarId::Kotlin,
|
|
777
|
+
function_node_types: KOTLIN_FUNCTION_NODES,
|
|
778
|
+
decision_node_types: KOTLIN_DECISION_NODES,
|
|
779
|
+
nesting_node_types: KOTLIN_DECISION_NODES,
|
|
780
|
+
ncss_node_types: KOTLIN_NCSS_NODES,
|
|
781
|
+
ncss_container_node_types: KOTLIN_NCSS_CONTAINERS,
|
|
782
|
+
},
|
|
641
783
|
];
|
|
642
784
|
|
|
643
785
|
pub fn find_language(name: &str) -> Option<&'static LanguageDefinition> {
|
package/native/src/measure.rs
CHANGED
|
@@ -16,7 +16,10 @@ use crate::languages::LanguageDefinition;
|
|
|
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_identifier_leaf, is_js_whitespace, named_children, node_text, split_lines,
|
|
21
|
+
Source,
|
|
22
|
+
};
|
|
20
23
|
|
|
21
24
|
pub fn measure(
|
|
22
25
|
code: &str,
|
|
@@ -115,6 +118,9 @@ pub fn collect_cross_file_data(
|
|
|
115
118
|
/// Name-carrying leaf types anonymized by tokenize_function so consistent renames still match.
|
|
116
119
|
const IDENTIFIER_LEAF_NODE_TYPES: &[&str] = &[
|
|
117
120
|
"identifier",
|
|
121
|
+
"simple_identifier",
|
|
122
|
+
"interpolated_identifier",
|
|
123
|
+
"implicit_parameter",
|
|
118
124
|
"property_identifier",
|
|
119
125
|
"field_identifier",
|
|
120
126
|
"type_identifier",
|
|
@@ -152,14 +158,17 @@ fn collect_token_symbols(
|
|
|
152
158
|
symbols: &mut Vec<i32>,
|
|
153
159
|
id_index_by_name: &mut HashMap<String, usize>,
|
|
154
160
|
) {
|
|
155
|
-
if matches!(
|
|
161
|
+
if matches!(
|
|
162
|
+
node.kind(),
|
|
163
|
+
"comment" | "line_comment" | "block_comment" | "multiline_comment"
|
|
164
|
+
) {
|
|
156
165
|
return;
|
|
157
166
|
}
|
|
158
167
|
if atomic_operand_node_types().contains(node.kind()) {
|
|
159
168
|
symbols.push(hash_text(node.kind()));
|
|
160
169
|
return;
|
|
161
170
|
}
|
|
162
|
-
if node
|
|
171
|
+
if !is_identifier_leaf(node) {
|
|
163
172
|
for child in all_children(node) {
|
|
164
173
|
collect_token_symbols(child, code, symbols, id_index_by_name);
|
|
165
174
|
}
|
|
@@ -279,7 +288,10 @@ fn collect_comment_spans(root: Node<'_>) -> Vec<CommentSpan> {
|
|
|
279
288
|
let mut spans = Vec::new();
|
|
280
289
|
|
|
281
290
|
fn visit(node: Node<'_>, spans: &mut Vec<CommentSpan>) {
|
|
282
|
-
if matches!(
|
|
291
|
+
if matches!(
|
|
292
|
+
node.kind(),
|
|
293
|
+
"comment" | "line_comment" | "block_comment" | "multiline_comment"
|
|
294
|
+
) {
|
|
283
295
|
for row in node.start_position().row..=node.end_position().row {
|
|
284
296
|
// Node columns are UTF-16 code units x 2 (the tree is parsed from UTF-16);
|
|
285
297
|
// halving matches the code-unit columns the line scan below counts.
|
|
@@ -392,6 +404,18 @@ const OPERATOR_TEXTS: &[&str] = &[
|
|
|
392
404
|
"&^",
|
|
393
405
|
"&^=",
|
|
394
406
|
"&.",
|
|
407
|
+
// Kotlin elvis, not-null assertion, negated containment/type checks, safe cast, and labeled
|
|
408
|
+
// jumps (single tokens in the grammar: `break@`, `continue@`, `return@`).
|
|
409
|
+
"?:",
|
|
410
|
+
"!!",
|
|
411
|
+
"!in",
|
|
412
|
+
"!is",
|
|
413
|
+
"as?",
|
|
414
|
+
"break@",
|
|
415
|
+
"continue@",
|
|
416
|
+
"return@",
|
|
417
|
+
// C# `default(T)`/`default`; the same token labels switch sections, which do not count.
|
|
418
|
+
"default",
|
|
395
419
|
// Member access/qualification are classical Halstead operators; `->` also captures
|
|
396
420
|
// Python/Rust return-type arrows, consistent with the counted `=>`.
|
|
397
421
|
".",
|
|
@@ -435,6 +459,9 @@ const OPERATOR_TEXTS: &[&str] = &[
|
|
|
435
459
|
|
|
436
460
|
const OPERAND_NODE_TYPES: &[&str] = &[
|
|
437
461
|
"identifier",
|
|
462
|
+
"simple_identifier",
|
|
463
|
+
"interpolated_identifier",
|
|
464
|
+
"implicit_parameter",
|
|
438
465
|
"property_identifier",
|
|
439
466
|
"field_identifier",
|
|
440
467
|
"type_identifier",
|
|
@@ -445,9 +472,13 @@ const OPERAND_NODE_TYPES: &[&str] = &[
|
|
|
445
472
|
"simple_symbol",
|
|
446
473
|
"self",
|
|
447
474
|
"this",
|
|
475
|
+
"this_expression",
|
|
448
476
|
"super",
|
|
449
|
-
|
|
477
|
+
"super_expression",
|
|
478
|
+
"base",
|
|
479
|
+
// C/C++/Rust/C# built-in types are leaves of their own node type, unlike Go's `type_identifier`.
|
|
450
480
|
"primitive_type",
|
|
481
|
+
"predefined_type",
|
|
451
482
|
"boolean_type",
|
|
452
483
|
"void_type",
|
|
453
484
|
"auto",
|
|
@@ -456,6 +487,9 @@ const OPERAND_NODE_TYPES: &[&str] = &[
|
|
|
456
487
|
"float",
|
|
457
488
|
"integer_literal",
|
|
458
489
|
"float_literal",
|
|
490
|
+
"real_literal",
|
|
491
|
+
"hex_literal",
|
|
492
|
+
"bin_literal",
|
|
459
493
|
"int_literal",
|
|
460
494
|
"rune_literal",
|
|
461
495
|
"imaginary_literal",
|
|
@@ -470,16 +504,18 @@ const OPERAND_NODE_TYPES: &[&str] = &[
|
|
|
470
504
|
"string_literal",
|
|
471
505
|
// Go raw strings are leaves with no content child, unlike Rust/C++ `raw_string_literal`s.
|
|
472
506
|
"raw_string_literal",
|
|
507
|
+
"verbatim_string_literal",
|
|
473
508
|
"string_fragment",
|
|
474
509
|
"multiline_string_fragment",
|
|
475
510
|
"string_content",
|
|
511
|
+
"string_literal_content",
|
|
476
512
|
"raw_string_content",
|
|
477
513
|
"template_string",
|
|
478
|
-
"character_literal",
|
|
479
514
|
"char_literal",
|
|
480
515
|
"character",
|
|
481
516
|
"true",
|
|
482
517
|
"false",
|
|
518
|
+
"boolean_literal",
|
|
483
519
|
"null",
|
|
484
520
|
"null_literal",
|
|
485
521
|
"undefined",
|
|
@@ -488,8 +524,13 @@ const OPERAND_NODE_TYPES: &[&str] = &[
|
|
|
488
524
|
];
|
|
489
525
|
|
|
490
526
|
/// Non-leaf literals counted as one Halstead operand without descending; see metrics.ts.
|
|
527
|
+
/// `character_literal` is a leaf in Java and Kotlin but wraps a content node in C#; Kotlin's
|
|
528
|
+
/// suffixed numbers (`1L`, `1u`) wrap the bare literal, so `1` and `1L` stay distinct.
|
|
491
529
|
const ATOMIC_OPERAND_NODE_TYPES: &[&str] = &[
|
|
492
530
|
"interpreted_string_literal",
|
|
531
|
+
"character_literal",
|
|
532
|
+
"long_literal",
|
|
533
|
+
"unsigned_literal",
|
|
493
534
|
"regex",
|
|
494
535
|
"user_defined_literal",
|
|
495
536
|
"integral_type",
|
|
@@ -523,7 +564,10 @@ fn measure_halstead(root: Node<'_>, code: &Source<'_>) -> HalsteadCounts {
|
|
|
523
564
|
operators: &mut HashMap<String, u64>,
|
|
524
565
|
operands: &mut HashMap<String, u64>,
|
|
525
566
|
) {
|
|
526
|
-
if matches!(
|
|
567
|
+
if matches!(
|
|
568
|
+
node.kind(),
|
|
569
|
+
"comment" | "line_comment" | "block_comment" | "multiline_comment"
|
|
570
|
+
) {
|
|
527
571
|
return;
|
|
528
572
|
}
|
|
529
573
|
|
|
@@ -536,10 +580,13 @@ fn measure_halstead(root: Node<'_>, code: &Source<'_>) -> HalsteadCounts {
|
|
|
536
580
|
|
|
537
581
|
// Operators are counted from leaf tokens only: keyword-named nodes always contain a
|
|
538
582
|
// same-text anonymous keyword leaf, so counting the named node as well would double-count.
|
|
539
|
-
if node
|
|
583
|
+
if is_identifier_leaf(node) {
|
|
540
584
|
let text = node_text(node, code);
|
|
541
|
-
// Operands win over text matches so identifiers spelled like word operators stay operands
|
|
542
|
-
|
|
585
|
+
// Operands win over text matches so identifiers spelled like word operators stay operands;
|
|
586
|
+
// C# `nameof(x)` is the one keyword operator the grammar parses as a plain callee.
|
|
587
|
+
if is_csharp_nameof_callee(node, text) {
|
|
588
|
+
*operators.entry(text.to_string()).or_insert(0) += 1;
|
|
589
|
+
} else if operand_node_types().contains(node.kind()) {
|
|
543
590
|
*operands.entry(text.to_string()).or_insert(0) += 1;
|
|
544
591
|
} else if (operator_texts().contains(text) || operator_texts().contains(node.kind()))
|
|
545
592
|
&& is_countable_contextual_token(node, text)
|
|
@@ -565,6 +612,18 @@ fn measure_halstead(root: Node<'_>, code: &Source<'_>) -> HalsteadCounts {
|
|
|
565
612
|
}
|
|
566
613
|
}
|
|
567
614
|
|
|
615
|
+
/// tree-sitter-c-sharp parses `nameof(x)` as an invocation of an identifier named `nameof`.
|
|
616
|
+
fn is_csharp_nameof_callee(node: Node<'_>, text: &str) -> bool {
|
|
617
|
+
text == "nameof"
|
|
618
|
+
&& node.kind() == "identifier"
|
|
619
|
+
&& node.parent().is_some_and(|parent| {
|
|
620
|
+
parent.kind() == "invocation_expression"
|
|
621
|
+
&& parent
|
|
622
|
+
.child_by_field_name("function")
|
|
623
|
+
.is_some_and(|callee| callee.id() == node.id())
|
|
624
|
+
})
|
|
625
|
+
}
|
|
626
|
+
|
|
568
627
|
/// Ternary/conditional and Rust try parents make `?` an operator; TS optional markers do not.
|
|
569
628
|
const QUESTION_OPERATOR_PARENT_TYPES: &[&str] = &[
|
|
570
629
|
"ternary_expression",
|
|
@@ -573,6 +632,8 @@ const QUESTION_OPERATOR_PARENT_TYPES: &[&str] = &[
|
|
|
573
632
|
"try_expression",
|
|
574
633
|
// TypeScript conditional types (`T extends U ? X : Y`) select like a ternary.
|
|
575
634
|
"conditional_type",
|
|
635
|
+
// C# null-conditional access (`a?.b`).
|
|
636
|
+
"conditional_access_expression",
|
|
576
637
|
];
|
|
577
638
|
|
|
578
639
|
fn is_countable_contextual_token(node: Node<'_>, text: &str) -> bool {
|
|
@@ -582,6 +643,11 @@ fn is_countable_contextual_token(node: Node<'_>, text: &str) -> bool {
|
|
|
582
643
|
return parent_type == Some("binary_operator")
|
|
583
644
|
|| parent_type == Some("augmented_assignment");
|
|
584
645
|
}
|
|
646
|
+
if text == "default" {
|
|
647
|
+
return node
|
|
648
|
+
.parent()
|
|
649
|
+
.is_some_and(|parent| parent.kind() == "default_expression");
|
|
650
|
+
}
|
|
585
651
|
if text != "?" {
|
|
586
652
|
return true;
|
|
587
653
|
}
|