code-gauge 4.1.3 → 4.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +202 -9
- 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/dep_degree.rs
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
use std::collections::{HashMap, HashSet};
|
|
2
2
|
use tree_sitter::Node;
|
|
3
3
|
|
|
4
|
-
use crate::complexity::
|
|
5
|
-
use crate::util::{node_text, Source};
|
|
4
|
+
use crate::complexity::is_function_boundary;
|
|
5
|
+
use crate::util::{is_identifier_leaf, node_text, Source};
|
|
6
6
|
|
|
7
7
|
/// Leaf node types treated as variable references by the def-use approximation.
|
|
8
8
|
const VARIABLE_NODE_TYPES: &[&str] = &[
|
|
9
9
|
"identifier",
|
|
10
|
+
"simple_identifier",
|
|
11
|
+
"interpolated_identifier",
|
|
12
|
+
"implicit_parameter",
|
|
10
13
|
"instance_variable",
|
|
11
14
|
"class_variable",
|
|
12
15
|
"global_variable",
|
|
@@ -36,6 +39,37 @@ const DEFINITION_FIELD_BY_PARENT_TYPE: &[(&str, &str)] = &[
|
|
|
36
39
|
("for_in_clause", "left"),
|
|
37
40
|
("for_range_loop", "declarator"),
|
|
38
41
|
("for_expression", "pattern"),
|
|
42
|
+
("for", "pattern"),
|
|
43
|
+
("foreach_statement", "left"),
|
|
44
|
+
("catch_declaration", "name"),
|
|
45
|
+
("declaration_pattern", "name"),
|
|
46
|
+
("declaration_expression", "name"),
|
|
47
|
+
("recursive_pattern", "name"),
|
|
48
|
+
("var_pattern", "name"),
|
|
49
|
+
("tuple_pattern", "name"),
|
|
50
|
+
("parenthesized_variable_designation", "name"),
|
|
51
|
+
("from_clause", "name"),
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
/// Kotlin has no grammar fields: an identifier directly under one of these declares a binding
|
|
55
|
+
/// (`val x`, `for (x in xs)`, lambda parameters, and the `catch (e: T)` exception name).
|
|
56
|
+
const KOTLIN_DEFINITION_PARENT_TYPES: &[&str] = &["variable_declaration", "catch_block"];
|
|
57
|
+
|
|
58
|
+
/// Kotlin parameter nodes whose identifier child is the declared name; the parameter LIST nodes
|
|
59
|
+
/// (`function_value_parameters`) also hold default-value expressions, which are reads.
|
|
60
|
+
const KOTLIN_PARAMETER_TYPES: &[&str] = &[
|
|
61
|
+
"parameter",
|
|
62
|
+
"parameter_with_optional_type",
|
|
63
|
+
"class_parameter",
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
/// C# LINQ clauses that bind a range variable as their first identifier child (no grammar field):
|
|
67
|
+
/// `join y in ...`, `into ys`, `let z = ...`, and a query continuation `into g`.
|
|
68
|
+
const CSHARP_QUERY_BINDING_PARENT_TYPES: &[&str] = &[
|
|
69
|
+
"join_clause",
|
|
70
|
+
"join_into_clause",
|
|
71
|
+
"let_clause",
|
|
72
|
+
"query_expression",
|
|
39
73
|
];
|
|
40
74
|
|
|
41
75
|
/// Multi-target lists (`a, b = ...`, `a, b := ...`) whose holder's `left` field marks definitions.
|
|
@@ -79,10 +113,15 @@ pub fn measure_dep_degree(
|
|
|
79
113
|
true,
|
|
80
114
|
);
|
|
81
115
|
let mut definition_scopes_by_name: HashMap<&str, Vec<String>> = HashMap::new();
|
|
116
|
+
for name in implicit_accessor_definitions(function_node, code) {
|
|
117
|
+
add_definition(&mut definition_scopes_by_name, name, "");
|
|
118
|
+
}
|
|
82
119
|
let mut pairs = 0u64;
|
|
83
120
|
for index in 0..leaves.len() {
|
|
84
121
|
let leaf = &leaves[index];
|
|
85
|
-
if !VARIABLE_NODE_TYPES.contains(&leaf.node.kind())
|
|
122
|
+
if !VARIABLE_NODE_TYPES.contains(&leaf.node.kind())
|
|
123
|
+
&& !crate::util::is_kotlin_callable_receiver(leaf.node)
|
|
124
|
+
{
|
|
86
125
|
continue;
|
|
87
126
|
}
|
|
88
127
|
let name = node_text(leaf.node, code);
|
|
@@ -108,6 +147,36 @@ pub fn measure_dep_degree(
|
|
|
108
147
|
pairs
|
|
109
148
|
}
|
|
110
149
|
|
|
150
|
+
/// Names a C# accessor body can read without declaring them in its own subtree: the owning
|
|
151
|
+
/// indexer's parameters (including a `params` array, which the grammar names directly on the
|
|
152
|
+
/// parameter list) and, in a setter, initializer, or event accessor, the implicit `value`.
|
|
153
|
+
fn implicit_accessor_definitions<'a>(function_node: Node<'_>, code: &Source<'a>) -> Vec<&'a str> {
|
|
154
|
+
if function_node.kind() != "accessor_declaration" {
|
|
155
|
+
return Vec::new();
|
|
156
|
+
}
|
|
157
|
+
let mut names = Vec::new();
|
|
158
|
+
if function_node
|
|
159
|
+
.child_by_field_name("name")
|
|
160
|
+
.is_some_and(|keyword| {
|
|
161
|
+
matches!(node_text(keyword, code), "set" | "init" | "add" | "remove")
|
|
162
|
+
})
|
|
163
|
+
{
|
|
164
|
+
names.push("value");
|
|
165
|
+
}
|
|
166
|
+
let owner = function_node.parent().and_then(|list| list.parent());
|
|
167
|
+
if let Some(parameters) = owner
|
|
168
|
+
.filter(|owner| owner.kind() == "indexer_declaration")
|
|
169
|
+
.and_then(|owner| owner.child_by_field_name("parameters"))
|
|
170
|
+
{
|
|
171
|
+
let declared = crate::util::named_children(parameters)
|
|
172
|
+
.into_iter()
|
|
173
|
+
.filter_map(|parameter| parameter.child_by_field_name("name"))
|
|
174
|
+
.chain(parameters.child_by_field_name("name"));
|
|
175
|
+
names.extend(declared.map(|name| node_text(name, code)));
|
|
176
|
+
}
|
|
177
|
+
names
|
|
178
|
+
}
|
|
179
|
+
|
|
111
180
|
/// Collects non-comment leaves with their parent field (one cursor pass, so children of
|
|
112
181
|
/// high-arity nodes cost O(1)) and function-scope chain; mirrors collectDepDegreeLeaves.
|
|
113
182
|
#[allow(clippy::too_many_arguments)]
|
|
@@ -120,10 +189,13 @@ fn collect_dep_degree_leaves<'t>(
|
|
|
120
189
|
leaves: &mut Vec<DepDegreeLeaf<'t>>,
|
|
121
190
|
is_measured_root: bool,
|
|
122
191
|
) {
|
|
123
|
-
if matches!(
|
|
192
|
+
if matches!(
|
|
193
|
+
node.kind(),
|
|
194
|
+
"comment" | "line_comment" | "block_comment" | "multiline_comment"
|
|
195
|
+
) {
|
|
124
196
|
return;
|
|
125
197
|
}
|
|
126
|
-
if node
|
|
198
|
+
if is_identifier_leaf(node) {
|
|
127
199
|
leaves.push(DepDegreeLeaf {
|
|
128
200
|
node,
|
|
129
201
|
field_name,
|
|
@@ -157,10 +229,6 @@ fn collect_dep_degree_leaves<'t>(
|
|
|
157
229
|
}
|
|
158
230
|
}
|
|
159
231
|
|
|
160
|
-
fn is_function_boundary(node: Node<'_>, function_nodes: &HashSet<&'static str>) -> bool {
|
|
161
|
-
function_nodes.contains(node.kind()) && !is_lambda_body_block(node)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
232
|
fn add_definition<'a>(
|
|
165
233
|
definition_scopes_by_name: &mut HashMap<&'a str, Vec<String>>,
|
|
166
234
|
name: &'a str,
|
|
@@ -188,6 +256,20 @@ fn is_structural_definition(leaf: &DepDegreeLeaf<'_>) -> bool {
|
|
|
188
256
|
let Some(parent) = leaf.node.parent() else {
|
|
189
257
|
return false;
|
|
190
258
|
};
|
|
259
|
+
if leaf.node.kind() == "simple_identifier"
|
|
260
|
+
&& KOTLIN_DEFINITION_PARENT_TYPES.contains(&parent.kind())
|
|
261
|
+
{
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
if leaf.node.kind() == "identifier"
|
|
265
|
+
&& CSHARP_QUERY_BINDING_PARENT_TYPES.contains(&parent.kind())
|
|
266
|
+
&& crate::util::named_children(parent)
|
|
267
|
+
.into_iter()
|
|
268
|
+
.find(|child| child.kind() == "identifier")
|
|
269
|
+
.is_some_and(|first| first.id() == leaf.node.id())
|
|
270
|
+
{
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
191
273
|
if DEFINITION_FIELD_BY_PARENT_TYPE
|
|
192
274
|
.iter()
|
|
193
275
|
.any(|(parent_type, definition_field)| {
|
|
@@ -210,6 +292,18 @@ fn is_structural_definition(leaf: &DepDegreeLeaf<'_>) -> bool {
|
|
|
210
292
|
/// (C/C++ function-pointer or array parameters) is a parameter-ish node, or the identifier
|
|
211
293
|
/// directly occupies a parameter field; type annotations and default values bind nothing.
|
|
212
294
|
fn is_parameter_definition(leaf: &DepDegreeLeaf<'_>) -> bool {
|
|
295
|
+
// Kotlin has no `type`/`value` fields to veto default values: a function parameter's default
|
|
296
|
+
// sits in the parameter list (`fun f(b: Int = a)`), a class parameter's inside the parameter
|
|
297
|
+
// node (`class A(val y: Int = a)`), so only a parameter node's first identifier child binds.
|
|
298
|
+
if leaf.node.kind() == "simple_identifier" {
|
|
299
|
+
return leaf.node.parent().is_some_and(|parent| {
|
|
300
|
+
KOTLIN_PARAMETER_TYPES.contains(&parent.kind())
|
|
301
|
+
&& crate::util::named_children(parent)
|
|
302
|
+
.into_iter()
|
|
303
|
+
.find(|child| child.kind() == "simple_identifier")
|
|
304
|
+
.is_some_and(|first| first.id() == leaf.node.id())
|
|
305
|
+
});
|
|
306
|
+
}
|
|
213
307
|
let mut current = leaf.node;
|
|
214
308
|
let mut depth = 0usize;
|
|
215
309
|
loop {
|
|
@@ -8,7 +8,7 @@ use crate::types::{
|
|
|
8
8
|
CrossFileCandidate, CrossFileToken, CrossFileTokenRange, DuplicateBlockOccurrence,
|
|
9
9
|
DuplicationMetrics,
|
|
10
10
|
};
|
|
11
|
-
use crate::util::{all_children, named_children, node_text, to_int32, Source};
|
|
11
|
+
use crate::util::{all_children, is_identifier_leaf, named_children, node_text, to_int32, Source};
|
|
12
12
|
|
|
13
13
|
/// Block-like nodes considered as whole-subtree duplicate candidates; see duplication.ts.
|
|
14
14
|
const DUPLICATE_BLOCK_TYPES: &[&str] = &[
|
|
@@ -50,6 +50,20 @@ const DUPLICATE_BLOCK_TYPES: &[&str] = &[
|
|
|
50
50
|
"while_expression",
|
|
51
51
|
"loop_expression",
|
|
52
52
|
"match_expression",
|
|
53
|
+
"foreach_statement",
|
|
54
|
+
"switch_section",
|
|
55
|
+
"switch_expression_arm",
|
|
56
|
+
"using_statement",
|
|
57
|
+
"lock_statement",
|
|
58
|
+
"when_expression",
|
|
59
|
+
"when_entry",
|
|
60
|
+
"do_while_statement",
|
|
61
|
+
"catch_block",
|
|
62
|
+
"finally_block",
|
|
63
|
+
"statements",
|
|
64
|
+
"control_structure_body",
|
|
65
|
+
"function_body",
|
|
66
|
+
"jump_expression",
|
|
53
67
|
"jsx_element",
|
|
54
68
|
"jsx_self_closing_element",
|
|
55
69
|
"if",
|
|
@@ -88,11 +102,54 @@ const STATEMENT_CONTAINER_TYPES: &[&str] = &[
|
|
|
88
102
|
"type_case",
|
|
89
103
|
"communication_case",
|
|
90
104
|
"default_case",
|
|
105
|
+
"compilation_unit",
|
|
106
|
+
"switch_section",
|
|
107
|
+
"statements",
|
|
108
|
+
"enum_class_body",
|
|
109
|
+
"control_structure_body",
|
|
110
|
+
"function_body",
|
|
91
111
|
];
|
|
92
112
|
|
|
113
|
+
/// C# type bodies are `declaration_list`s, a name Rust also uses for `mod`/`impl`/`trait` bodies;
|
|
114
|
+
/// only the C# ones (under these parents) hold member runs scanned like Java's `class_body`.
|
|
115
|
+
const CSHARP_DECLARATION_LIST_PARENT_TYPES: &[&str] = &[
|
|
116
|
+
"class_declaration",
|
|
117
|
+
"struct_declaration",
|
|
118
|
+
"interface_declaration",
|
|
119
|
+
"record_declaration",
|
|
120
|
+
"namespace_declaration",
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
/// Whole-subtree duplicate candidates: DUPLICATE_BLOCK_TYPES plus Kotlin's `try_expression`,
|
|
124
|
+
/// distinguished by its clause children from Rust's `try_expression` (the `?` operator).
|
|
125
|
+
fn is_duplicate_block(node: Node<'_>) -> bool {
|
|
126
|
+
if !node.is_named() {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
if node.kind() == "try_expression" {
|
|
130
|
+
return crate::util::is_kotlin_try_expression(node);
|
|
131
|
+
}
|
|
132
|
+
DUPLICATE_BLOCK_TYPES.contains(&node.kind())
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
fn is_statement_container(node: Node<'_>) -> bool {
|
|
136
|
+
if !node.is_named() {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
if node.kind() == "declaration_list" {
|
|
140
|
+
return node
|
|
141
|
+
.parent()
|
|
142
|
+
.is_some_and(|parent| CSHARP_DECLARATION_LIST_PARENT_TYPES.contains(&parent.kind()));
|
|
143
|
+
}
|
|
144
|
+
STATEMENT_CONTAINER_TYPES.contains(&node.kind())
|
|
145
|
+
}
|
|
146
|
+
|
|
93
147
|
/// Identifier leaves anonymized by occurrence order so consistently renamed copies still match.
|
|
94
148
|
const ANONYMIZED_IDENTIFIER_TYPES: &[&str] = &[
|
|
95
149
|
"identifier",
|
|
150
|
+
"simple_identifier",
|
|
151
|
+
"interpolated_identifier",
|
|
152
|
+
"implicit_parameter",
|
|
96
153
|
"constant",
|
|
97
154
|
"instance_variable",
|
|
98
155
|
"class_variable",
|
|
@@ -112,6 +169,9 @@ const LITERAL_KIND_BY_TYPE: &[(&str, &str)] = &[
|
|
|
112
169
|
("float", "#num"),
|
|
113
170
|
("integer_literal", "#num"),
|
|
114
171
|
("float_literal", "#num"),
|
|
172
|
+
("real_literal", "#num"),
|
|
173
|
+
("hex_literal", "#num"),
|
|
174
|
+
("bin_literal", "#num"),
|
|
115
175
|
("int_literal", "#num"),
|
|
116
176
|
("rune_literal", "#char"),
|
|
117
177
|
("imaginary_literal", "#num"),
|
|
@@ -124,6 +184,11 @@ const LITERAL_KIND_BY_TYPE: &[(&str, &str)] = &[
|
|
|
124
184
|
("string_fragment", "#str"),
|
|
125
185
|
("multiline_string_fragment", "#str"),
|
|
126
186
|
("string_content", "#str"),
|
|
187
|
+
("string_literal_content", "#str"),
|
|
188
|
+
("character_literal_content", "#char"),
|
|
189
|
+
("character_escape_seq", "#char"),
|
|
190
|
+
("verbatim_string_literal", "#str"),
|
|
191
|
+
("interpolated_string_expression", "#str"),
|
|
127
192
|
("raw_string_content", "#str"),
|
|
128
193
|
("heredoc_content", "#str"),
|
|
129
194
|
("heredoc_beginning", "#heredoc"),
|
|
@@ -141,18 +206,31 @@ const LITERAL_KIND_BY_TYPE: &[(&str, &str)] = &[
|
|
|
141
206
|
("regex_pattern", "#regex"),
|
|
142
207
|
];
|
|
143
208
|
|
|
144
|
-
const COMMENT_TYPES: &[&str] = &[
|
|
209
|
+
const COMMENT_TYPES: &[&str] = &[
|
|
210
|
+
"comment",
|
|
211
|
+
"line_comment",
|
|
212
|
+
"block_comment",
|
|
213
|
+
"multiline_comment",
|
|
214
|
+
];
|
|
145
215
|
|
|
146
216
|
/// Children of a string node that carry only literal content; anything else is interpolation.
|
|
147
217
|
const STRING_FRAGMENT_TYPES: &[&str] = &[
|
|
148
218
|
"string_fragment",
|
|
149
219
|
"multiline_string_fragment",
|
|
150
220
|
"string_content",
|
|
221
|
+
"string_literal_content",
|
|
222
|
+
"character_literal_content",
|
|
223
|
+
"character_escape_seq",
|
|
151
224
|
"raw_string_content",
|
|
225
|
+
"raw_string_start",
|
|
226
|
+
"raw_string_end",
|
|
152
227
|
"escape_sequence",
|
|
153
228
|
"heredoc_content",
|
|
154
229
|
"string_start",
|
|
155
230
|
"string_end",
|
|
231
|
+
"string_literal_encoding",
|
|
232
|
+
"interpolation_start",
|
|
233
|
+
"interpolation_quote",
|
|
156
234
|
];
|
|
157
235
|
|
|
158
236
|
/// Grammar fields whose plain-`identifier` leaves are semantic API names, kept verbatim.
|
|
@@ -168,6 +246,32 @@ const SEMANTIC_NAME_FIELD_BY_PARENT_TYPE: &[(&str, &str)] = &[
|
|
|
168
246
|
("element_value_pair", "key"),
|
|
169
247
|
("generic_function", "function"),
|
|
170
248
|
("template_function", "name"),
|
|
249
|
+
("invocation_expression", "function"),
|
|
250
|
+
("member_access_expression", "name"),
|
|
251
|
+
("member_binding_expression", "name"),
|
|
252
|
+
("argument", "name"),
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
/// Rust's `Some(x)` variant patterns and Java's `uses Foo;` also put a plain identifier in a `type`
|
|
256
|
+
/// field; they stay anonymized as before C# support. Rust's `generic_type` head is normally a
|
|
257
|
+
/// `type_identifier` (kept verbatim like every type name); the entry covers the reserved-word
|
|
258
|
+
/// heads the grammar spells as a plain identifier.
|
|
259
|
+
const NON_CSHARP_TYPE_FIELD_PARENT_TYPES: &[&str] = &[
|
|
260
|
+
"tuple_struct_pattern",
|
|
261
|
+
"generic_type",
|
|
262
|
+
"uses_module_directive",
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
/// C# spells type names as plain `identifier`s (Java has `type_identifier`); an identifier under
|
|
266
|
+
/// one of these parents, or in any other parent's `type` field, names a type and stays verbatim.
|
|
267
|
+
const CSHARP_TYPE_PARENT_TYPES: &[&str] = &[
|
|
268
|
+
"generic_name",
|
|
269
|
+
"qualified_name",
|
|
270
|
+
"alias_qualified_name",
|
|
271
|
+
"type_argument_list",
|
|
272
|
+
"base_list",
|
|
273
|
+
"explicit_interface_specifier",
|
|
274
|
+
"using_directive",
|
|
171
275
|
];
|
|
172
276
|
|
|
173
277
|
/// Kind tags whose raw source text re-enters the fingerprint in literal-dense (data-like) regions.
|
|
@@ -178,6 +282,9 @@ const STRING_CONTENT_FRAGMENT_TYPES: &[&str] = &[
|
|
|
178
282
|
"string_fragment",
|
|
179
283
|
"multiline_string_fragment",
|
|
180
284
|
"string_content",
|
|
285
|
+
"string_literal_content",
|
|
286
|
+
"character_literal_content",
|
|
287
|
+
"character_escape_seq",
|
|
181
288
|
"raw_string_content",
|
|
182
289
|
"escape_sequence",
|
|
183
290
|
"heredoc_content",
|
|
@@ -438,12 +545,12 @@ fn collect_tokens<'a>(
|
|
|
438
545
|
container_statement_ranges: &mut Vec<Vec<TokenRange>>,
|
|
439
546
|
) -> TokenRange {
|
|
440
547
|
let start_token_index = tokens.len();
|
|
441
|
-
let atomic_kind = if node
|
|
548
|
+
let atomic_kind = if is_identifier_leaf(node) {
|
|
442
549
|
None
|
|
443
550
|
} else {
|
|
444
551
|
atomic_literal_kind(node)
|
|
445
552
|
};
|
|
446
|
-
if node
|
|
553
|
+
if is_identifier_leaf(node) {
|
|
447
554
|
append_leaf_token(node, code, tokens);
|
|
448
555
|
} else if let Some(atomic_kind) = atomic_kind {
|
|
449
556
|
// Interpolation-free strings collapse to their kind tag so copies differing only in
|
|
@@ -457,7 +564,7 @@ fn collect_tokens<'a>(
|
|
|
457
564
|
));
|
|
458
565
|
} else if !COMMENT_TYPES.contains(&node.kind()) {
|
|
459
566
|
let mut statement_ranges: Vec<TokenRange> = Vec::new();
|
|
460
|
-
let is_container =
|
|
567
|
+
let is_container = is_statement_container(node);
|
|
461
568
|
for child in all_children(node) {
|
|
462
569
|
let child_range = visit(
|
|
463
570
|
child,
|
|
@@ -485,7 +592,7 @@ fn collect_tokens<'a>(
|
|
|
485
592
|
start_line: node.start_position().row + 1,
|
|
486
593
|
end_line: node.end_position().row + 1,
|
|
487
594
|
};
|
|
488
|
-
if
|
|
595
|
+
if is_duplicate_block(node) {
|
|
489
596
|
block_ranges.push(TokenRange { ..range });
|
|
490
597
|
}
|
|
491
598
|
range
|
|
@@ -549,9 +656,14 @@ fn append_leaf_token<'a>(node: Node<'_>, code: &Source<'a>, tokens: &mut Vec<Tok
|
|
|
549
656
|
return;
|
|
550
657
|
}
|
|
551
658
|
|
|
659
|
+
// A Kotlin bound callable-reference receiver (`xs::size`) renames like a variable unless it is
|
|
660
|
+
// PascalCase, the discriminator used for static receivers everywhere else.
|
|
661
|
+
let is_variable_receiver = crate::util::is_kotlin_callable_receiver(node)
|
|
662
|
+
&& !pascal_case_regex().is_match(node_text(node, code));
|
|
552
663
|
if node.is_named()
|
|
553
|
-
&&
|
|
554
|
-
|
|
664
|
+
&& (is_variable_receiver
|
|
665
|
+
|| (ANONYMIZED_IDENTIFIER_TYPES.contains(&node.kind())
|
|
666
|
+
&& !is_semantic_name_leaf(node, code)))
|
|
555
667
|
{
|
|
556
668
|
tokens.push(Token {
|
|
557
669
|
is_id: true,
|
|
@@ -637,7 +749,14 @@ fn literal_value_text<'a>(node: Node<'_>, kind: &str, code: &Source<'a>) -> Cow<
|
|
|
637
749
|
if !fragments.is_empty() {
|
|
638
750
|
return Cow::Owned(fragments.concat());
|
|
639
751
|
}
|
|
640
|
-
|
|
752
|
+
// A C# verbatim string (`@"..."`) carries the same value as its ordinary spelling.
|
|
753
|
+
let text = node_text(node, code);
|
|
754
|
+
let text = if node.kind() == "verbatim_string_literal" {
|
|
755
|
+
text.strip_prefix('@').unwrap_or(text)
|
|
756
|
+
} else {
|
|
757
|
+
text
|
|
758
|
+
};
|
|
759
|
+
Cow::Borrowed(strip_matching_quotes(text))
|
|
641
760
|
}
|
|
642
761
|
|
|
643
762
|
/// Strips one matching pair of surrounding ASCII quotes, matching stripMatchingQuotes in
|
|
@@ -667,11 +786,63 @@ fn is_semantic_name_leaf(node: Node<'_>, code: &Source<'_>) -> bool {
|
|
|
667
786
|
return false;
|
|
668
787
|
};
|
|
669
788
|
|
|
670
|
-
// Java method references (`Foo::bar`)
|
|
671
|
-
|
|
789
|
+
// Java method references (`Foo::bar`) and Kotlin callable references (`::bar`) name their
|
|
790
|
+
// identifiers without grammar fields.
|
|
791
|
+
if parent.kind() == "method_reference" || parent.kind() == "callable_reference" {
|
|
672
792
|
return true;
|
|
673
793
|
}
|
|
674
794
|
|
|
795
|
+
// C# type positions (see CSHARP_TYPE_PARENT_TYPES, plus any parent's `type` field, e.g.
|
|
796
|
+
// `new Foo()`) and attribute names (`[Obsolete]`, an `attribute` inside an `attribute_list`, or
|
|
797
|
+
// `[assembly: Foo]` inside a `global_attribute`; C/C++ attributes hang off other parents and
|
|
798
|
+
// Python's `attribute` has no `name` field).
|
|
799
|
+
if node.kind() == "identifier" {
|
|
800
|
+
let occupies = |field: &str| {
|
|
801
|
+
parent
|
|
802
|
+
.child_by_field_name(field)
|
|
803
|
+
.is_some_and(|field_node| field_node.id() == node.id())
|
|
804
|
+
};
|
|
805
|
+
let is_csharp_attribute = parent.kind() == "attribute"
|
|
806
|
+
&& parent
|
|
807
|
+
.parent()
|
|
808
|
+
.is_some_and(|list| matches!(list.kind(), "attribute_list" | "global_attribute"));
|
|
809
|
+
if CSHARP_TYPE_PARENT_TYPES.contains(&parent.kind())
|
|
810
|
+
|| (occupies("type") && !NON_CSHARP_TYPE_FIELD_PARENT_TYPES.contains(&parent.kind()))
|
|
811
|
+
|| (is_csharp_attribute && occupies("name"))
|
|
812
|
+
{
|
|
813
|
+
return true;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// Kotlin (no grammar fields): a callee (`foo(...)`), a member name (`a.foo`), an infix function
|
|
818
|
+
// (`a shl b`), and a named argument (`foo(name = x)`) are API names.
|
|
819
|
+
if node.kind() == "simple_identifier" {
|
|
820
|
+
if parent.kind() == "navigation_suffix" {
|
|
821
|
+
return true;
|
|
822
|
+
}
|
|
823
|
+
let is_first_named = parent
|
|
824
|
+
.named_child(0)
|
|
825
|
+
.is_some_and(|first| first.id() == node.id());
|
|
826
|
+
if parent.kind() == "call_expression" && is_first_named {
|
|
827
|
+
return true;
|
|
828
|
+
}
|
|
829
|
+
if parent.kind() == "infix_expression"
|
|
830
|
+
&& parent
|
|
831
|
+
.named_child(1)
|
|
832
|
+
.is_some_and(|operator| operator.id() == node.id())
|
|
833
|
+
{
|
|
834
|
+
return true;
|
|
835
|
+
}
|
|
836
|
+
if parent.kind() == "value_argument"
|
|
837
|
+
&& is_first_named
|
|
838
|
+
&& node
|
|
839
|
+
.next_sibling()
|
|
840
|
+
.is_some_and(|next| !next.is_named() && next.kind() == "=")
|
|
841
|
+
{
|
|
842
|
+
return true;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
675
846
|
// `call` names its callee `method` in Ruby but `function` in Python; accept both fields.
|
|
676
847
|
if parent.kind() == "call"
|
|
677
848
|
&& parent
|
|
@@ -691,14 +862,22 @@ fn is_semantic_name_leaf(node: Node<'_>, code: &Source<'_>) -> bool {
|
|
|
691
862
|
return true;
|
|
692
863
|
}
|
|
693
864
|
|
|
694
|
-
// Java static receivers (`Alpha.run(...)`) name the invoked type;
|
|
695
|
-
// discriminator because the tokenizer has no symbol table.
|
|
696
|
-
|
|
697
|
-
|
|
865
|
+
// Java/C# static receivers (`Alpha.run(...)`, `Console.WriteLine(...)`) name the invoked type;
|
|
866
|
+
// PascalCase is the discriminator because the tokenizer has no symbol table.
|
|
867
|
+
let is_static_receiver = match parent.kind() {
|
|
868
|
+
"method_invocation" => parent
|
|
698
869
|
.child_by_field_name("object")
|
|
699
|
-
.is_some_and(|object| object.id() == node.id())
|
|
700
|
-
|
|
701
|
-
|
|
870
|
+
.is_some_and(|object| object.id() == node.id()),
|
|
871
|
+
"member_access_expression" => parent
|
|
872
|
+
.child_by_field_name("expression")
|
|
873
|
+
.is_some_and(|receiver| receiver.id() == node.id()),
|
|
874
|
+
// Kotlin has no fields: the receiver is the first child of `navigation_expression`.
|
|
875
|
+
"navigation_expression" => parent
|
|
876
|
+
.named_child(0)
|
|
877
|
+
.is_some_and(|first| first.id() == node.id()),
|
|
878
|
+
_ => false,
|
|
879
|
+
};
|
|
880
|
+
if is_static_receiver && pascal_case_regex().is_match(node_text(node, code)) {
|
|
702
881
|
return true;
|
|
703
882
|
}
|
|
704
883
|
|