code-gauge 4.6.0 → 4.7.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 +25 -0
- package/THIRD-PARTY-NOTICES.txt +8911 -0
- package/dist/cliConfig.cjs +1 -1
- package/dist/cliConfig.cjs.map +1 -1
- package/dist/cliConfig.js +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/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/metrics.cjs +1 -1
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.ts +8 -3
- package/dist/metrics.js +1 -1
- package/dist/metrics.js.map +1 -1
- package/dist/nativeMetrics.cjs +3 -3
- package/dist/nativeMetrics.cjs.map +1 -1
- package/dist/nativeMetrics.d.ts +30 -0
- package/dist/nativeMetrics.js +3 -3
- package/dist/nativeMetrics.js.map +1 -1
- package/dist/scan.cjs +1 -1
- package/dist/scan.cjs.map +1 -1
- package/dist/scan.d.ts +2 -2
- package/dist/scan.js +1 -1
- package/dist/scan.js.map +1 -1
- package/dist/wasmBinding.cjs +2 -0
- package/dist/wasmBinding.cjs.map +1 -0
- package/dist/wasmBinding.d.ts +9 -0
- package/dist/wasmBinding.js +2 -0
- package/dist/wasmBinding.js.map +1 -0
- package/dist/worker.cjs +2 -0
- package/dist/worker.cjs.map +1 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +2 -0
- package/dist/worker.js.map +1 -0
- package/native/Cargo.lock +1 -0
- package/native/Cargo.toml +6 -2
- package/native/build.rs +4 -1
- package/native/code-gauge.wasm +0 -0
- package/native/src/complexity.rs +134 -230
- package/native/src/dep_degree.rs +42 -39
- package/native/src/duplication.rs +65 -63
- package/native/src/functions.rs +169 -152
- package/native/src/languages.rs +20 -0
- package/native/src/lib.rs +41 -42
- package/native/src/measure.rs +109 -123
- package/native/src/napi.rs +104 -0
- package/native/src/ncss.rs +79 -84
- package/native/src/near_miss.rs +7 -7
- package/native/src/tree_index.rs +110 -0
- package/native/src/util.rs +17 -12
- package/native/src/wasm.rs +128 -0
- package/native/src/worker_pool.rs +53 -0
- package/package.json +16 -11
package/native/src/dep_degree.rs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
use
|
|
1
|
+
use rustc_hash::{FxHashMap, FxHashSet};
|
|
2
2
|
use tree_sitter::Node;
|
|
3
3
|
|
|
4
4
|
use crate::complexity::is_function_boundary;
|
|
5
|
+
use crate::tree_index::NodeExt;
|
|
5
6
|
use crate::util::{is_identifier_leaf, node_text, Source};
|
|
6
7
|
|
|
7
8
|
/// Leaf node types treated as variable references by the def-use approximation.
|
|
@@ -112,7 +113,7 @@ struct DepDegreeLeaf<'t> {
|
|
|
112
113
|
pub fn measure_dep_degree(
|
|
113
114
|
function_node: Node<'_>,
|
|
114
115
|
code: &Source<'_>,
|
|
115
|
-
function_nodes: &
|
|
116
|
+
function_nodes: &FxHashSet<&'static str>,
|
|
116
117
|
) -> u64 {
|
|
117
118
|
let mut leaves = Vec::new();
|
|
118
119
|
let mut next_scope_id = 0usize;
|
|
@@ -125,7 +126,7 @@ pub fn measure_dep_degree(
|
|
|
125
126
|
&mut leaves,
|
|
126
127
|
true,
|
|
127
128
|
);
|
|
128
|
-
let mut definition_scopes_by_name:
|
|
129
|
+
let mut definition_scopes_by_name: FxHashMap<&str, Vec<String>> = FxHashMap::default();
|
|
129
130
|
for name in implicit_accessor_definitions(function_node, code) {
|
|
130
131
|
add_definition(&mut definition_scopes_by_name, name, "");
|
|
131
132
|
}
|
|
@@ -162,20 +163,20 @@ pub fn measure_dep_degree(
|
|
|
162
163
|
/// `type_identifier` under the `pointer_type_declarator` spelling `C::*`, possibly through further
|
|
163
164
|
/// declarator wrappers; every other `type_identifier` names a type, not a variable.
|
|
164
165
|
fn is_variable_leaf(node: Node<'_>) -> bool {
|
|
165
|
-
VARIABLE_NODE_TYPES.contains(&node.
|
|
166
|
+
VARIABLE_NODE_TYPES.contains(&node.kind_name())
|
|
166
167
|
|| crate::util::is_kotlin_callable_receiver(node)
|
|
167
|
-
|| (node.
|
|
168
|
+
|| (node.kind_name() == "type_identifier" && is_member_pointer_name(node))
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
fn is_member_pointer_name(node: Node<'_>) -> bool {
|
|
171
172
|
let mut current = node;
|
|
172
|
-
while let Some(parent) = current.
|
|
173
|
-
if parent.
|
|
173
|
+
while let Some(parent) = current.parent_node() {
|
|
174
|
+
if parent.kind_name() == "pointer_type_declarator" {
|
|
174
175
|
return true;
|
|
175
176
|
}
|
|
176
177
|
// The climb follows the declared-name position only, exactly like unwrap_declarator_wrappers.
|
|
177
|
-
if !DECLARATOR_WRAPPER_TYPES.contains(&parent.
|
|
178
|
-
|| parent.
|
|
178
|
+
if !DECLARATOR_WRAPPER_TYPES.contains(&parent.kind_name())
|
|
179
|
+
|| parent.kind_name() == "qualified_identifier"
|
|
179
180
|
|| field_name_in_parent(current, parent).is_some_and(|field| field != "declarator")
|
|
180
181
|
{
|
|
181
182
|
return false;
|
|
@@ -189,7 +190,7 @@ fn is_member_pointer_name(node: Node<'_>) -> bool {
|
|
|
189
190
|
/// indexer's parameters (including a `params` array, which the grammar names directly on the
|
|
190
191
|
/// parameter list) and, in a setter, initializer, or event accessor, the implicit `value`.
|
|
191
192
|
fn implicit_accessor_definitions<'a>(function_node: Node<'_>, code: &Source<'a>) -> Vec<&'a str> {
|
|
192
|
-
if function_node.
|
|
193
|
+
if function_node.kind_name() != "accessor_declaration" {
|
|
193
194
|
return Vec::new();
|
|
194
195
|
}
|
|
195
196
|
let mut names = Vec::new();
|
|
@@ -201,9 +202,11 @@ fn implicit_accessor_definitions<'a>(function_node: Node<'_>, code: &Source<'a>)
|
|
|
201
202
|
{
|
|
202
203
|
names.push("value");
|
|
203
204
|
}
|
|
204
|
-
let owner = function_node
|
|
205
|
+
let owner = function_node
|
|
206
|
+
.parent_node()
|
|
207
|
+
.and_then(|list| list.parent_node());
|
|
205
208
|
if let Some(parameters) = owner
|
|
206
|
-
.filter(|owner| owner.
|
|
209
|
+
.filter(|owner| owner.kind_name() == "indexer_declaration")
|
|
207
210
|
.and_then(|owner| owner.child_by_field_name("parameters"))
|
|
208
211
|
{
|
|
209
212
|
let declared = crate::util::named_children(parameters)
|
|
@@ -223,12 +226,12 @@ fn collect_dep_degree_leaves<'t>(
|
|
|
223
226
|
field_name: Option<&'static str>,
|
|
224
227
|
scope: &str,
|
|
225
228
|
next_scope_id: &mut usize,
|
|
226
|
-
function_nodes: &
|
|
229
|
+
function_nodes: &FxHashSet<&'static str>,
|
|
227
230
|
leaves: &mut Vec<DepDegreeLeaf<'t>>,
|
|
228
231
|
is_measured_root: bool,
|
|
229
232
|
) {
|
|
230
233
|
if matches!(
|
|
231
|
-
node.
|
|
234
|
+
node.kind_name(),
|
|
232
235
|
"comment" | "line_comment" | "block_comment" | "multiline_comment"
|
|
233
236
|
) {
|
|
234
237
|
return;
|
|
@@ -268,7 +271,7 @@ fn collect_dep_degree_leaves<'t>(
|
|
|
268
271
|
}
|
|
269
272
|
|
|
270
273
|
fn add_definition<'a>(
|
|
271
|
-
definition_scopes_by_name: &mut
|
|
274
|
+
definition_scopes_by_name: &mut FxHashMap<&'a str, Vec<String>>,
|
|
272
275
|
name: &'a str,
|
|
273
276
|
scope: &str,
|
|
274
277
|
) {
|
|
@@ -291,37 +294,37 @@ fn is_definition_visible(definition_scopes: Option<&Vec<String>>, scope: &str) -
|
|
|
291
294
|
}
|
|
292
295
|
|
|
293
296
|
fn is_structural_definition(leaf: &DepDegreeLeaf<'_>) -> bool {
|
|
294
|
-
let Some(parent) = leaf.node.
|
|
297
|
+
let Some(parent) = leaf.node.parent_node() else {
|
|
295
298
|
return false;
|
|
296
299
|
};
|
|
297
|
-
if leaf.node.
|
|
298
|
-
&& KOTLIN_DEFINITION_PARENT_TYPES.contains(&parent.
|
|
300
|
+
if leaf.node.kind_name() == "simple_identifier"
|
|
301
|
+
&& KOTLIN_DEFINITION_PARENT_TYPES.contains(&parent.kind_name())
|
|
299
302
|
{
|
|
300
303
|
return true;
|
|
301
304
|
}
|
|
302
|
-
if leaf.node.
|
|
303
|
-
&& CSHARP_QUERY_BINDING_PARENT_TYPES.contains(&parent.
|
|
305
|
+
if leaf.node.kind_name() == "identifier"
|
|
306
|
+
&& CSHARP_QUERY_BINDING_PARENT_TYPES.contains(&parent.kind_name())
|
|
304
307
|
&& crate::util::named_children(parent)
|
|
305
308
|
.into_iter()
|
|
306
|
-
.find(|child| child.
|
|
309
|
+
.find(|child| child.kind_name() == "identifier")
|
|
307
310
|
.is_some_and(|first| first.id() == leaf.node.id())
|
|
308
311
|
{
|
|
309
312
|
return true;
|
|
310
313
|
}
|
|
311
314
|
let (declared, declared_field) = unwrap_declarator_wrappers(leaf);
|
|
312
315
|
if declared
|
|
313
|
-
.
|
|
316
|
+
.parent_node()
|
|
314
317
|
.is_some_and(|holder| is_definition_field(holder, declared_field))
|
|
315
318
|
{
|
|
316
319
|
return true;
|
|
317
320
|
}
|
|
318
|
-
if !DEFINITION_LIST_NODE_TYPES.contains(&parent.
|
|
321
|
+
if !DEFINITION_LIST_NODE_TYPES.contains(&parent.kind_name()) {
|
|
319
322
|
return false;
|
|
320
323
|
}
|
|
321
|
-
let Some(holder) = parent.
|
|
324
|
+
let Some(holder) = parent.parent_node() else {
|
|
322
325
|
return false;
|
|
323
326
|
};
|
|
324
|
-
DEFINITION_LIST_HOLDER_TYPES.contains(&holder.
|
|
327
|
+
DEFINITION_LIST_HOLDER_TYPES.contains(&holder.kind_name())
|
|
325
328
|
&& field_name_in_parent(parent, holder) == Some("left")
|
|
326
329
|
}
|
|
327
330
|
|
|
@@ -329,7 +332,7 @@ fn is_definition_field(holder: Node<'_>, field_name: Option<&str>) -> bool {
|
|
|
329
332
|
DEFINITION_FIELD_BY_PARENT_TYPE
|
|
330
333
|
.iter()
|
|
331
334
|
.any(|(parent_type, definition_field)| {
|
|
332
|
-
*parent_type == holder.
|
|
335
|
+
*parent_type == holder.kind_name() && field_name == Some(definition_field)
|
|
333
336
|
})
|
|
334
337
|
}
|
|
335
338
|
|
|
@@ -342,10 +345,10 @@ fn unwrap_declarator_wrappers<'t>(leaf: &DepDegreeLeaf<'t>) -> (Node<'t>, Option
|
|
|
342
345
|
let mut current = leaf.node;
|
|
343
346
|
let mut field_name = leaf.field_name;
|
|
344
347
|
while let Some(parent) = current
|
|
345
|
-
.
|
|
346
|
-
.filter(|parent| DECLARATOR_WRAPPER_TYPES.contains(&parent.
|
|
348
|
+
.parent_node()
|
|
349
|
+
.filter(|parent| DECLARATOR_WRAPPER_TYPES.contains(&parent.kind_name()))
|
|
347
350
|
{
|
|
348
|
-
let declared_field = if parent.
|
|
351
|
+
let declared_field = if parent.kind_name() == "qualified_identifier" {
|
|
349
352
|
"name"
|
|
350
353
|
} else {
|
|
351
354
|
"declarator"
|
|
@@ -354,7 +357,7 @@ fn unwrap_declarator_wrappers<'t>(leaf: &DepDegreeLeaf<'t>) -> (Node<'t>, Option
|
|
|
354
357
|
break;
|
|
355
358
|
}
|
|
356
359
|
field_name = parent
|
|
357
|
-
.
|
|
360
|
+
.parent_node()
|
|
358
361
|
.and_then(|grandparent| field_name_in_parent(parent, grandparent));
|
|
359
362
|
current = parent;
|
|
360
363
|
}
|
|
@@ -368,12 +371,12 @@ fn is_parameter_definition(leaf: &DepDegreeLeaf<'_>) -> bool {
|
|
|
368
371
|
// Kotlin has no `type`/`value` fields to veto default values: a function parameter's default
|
|
369
372
|
// sits in the parameter list (`fun f(b: Int = a)`), a class parameter's inside the parameter
|
|
370
373
|
// node (`class A(val y: Int = a)`), so only a parameter node's first identifier child binds.
|
|
371
|
-
if leaf.node.
|
|
372
|
-
return leaf.node.
|
|
373
|
-
KOTLIN_PARAMETER_TYPES.contains(&parent.
|
|
374
|
+
if leaf.node.kind_name() == "simple_identifier" {
|
|
375
|
+
return leaf.node.parent_node().is_some_and(|parent| {
|
|
376
|
+
KOTLIN_PARAMETER_TYPES.contains(&parent.kind_name())
|
|
374
377
|
&& crate::util::named_children(parent)
|
|
375
378
|
.into_iter()
|
|
376
|
-
.find(|child| child.
|
|
379
|
+
.find(|child| child.kind_name() == "simple_identifier")
|
|
377
380
|
.is_some_and(|first| first.id() == leaf.node.id())
|
|
378
381
|
});
|
|
379
382
|
}
|
|
@@ -384,10 +387,10 @@ fn is_parameter_definition(leaf: &DepDegreeLeaf<'_>) -> bool {
|
|
|
384
387
|
let declares_member_pointer = is_member_pointer_name(leaf.node);
|
|
385
388
|
let mut in_member_pointer = false;
|
|
386
389
|
loop {
|
|
387
|
-
let Some(parent) = current.
|
|
390
|
+
let Some(parent) = current.parent_node() else {
|
|
388
391
|
return false;
|
|
389
392
|
};
|
|
390
|
-
let parent_is_parameterish = parent.
|
|
393
|
+
let parent_is_parameterish = parent.kind_name().contains("parameter");
|
|
391
394
|
// Beyond the grandparent, only declarator wrappers keep climbing, plus the
|
|
392
395
|
// `qualified_identifier` nodes that spell a member-pointer parameter's class (`int C::* q`,
|
|
393
396
|
// `int N::C::* q`) — entered from the pointer declarator and continued through the `name`
|
|
@@ -395,13 +398,13 @@ fn is_parameter_definition(leaf: &DepDegreeLeaf<'_>) -> bool {
|
|
|
395
398
|
// a read. Checking this before the field lookup also keeps reads inside high-arity nodes
|
|
396
399
|
// O(1).
|
|
397
400
|
let continues_member_pointer = declares_member_pointer
|
|
398
|
-
&& parent.
|
|
399
|
-
&& (current.
|
|
401
|
+
&& parent.kind_name() == "qualified_identifier"
|
|
402
|
+
&& (current.kind_name() == "pointer_type_declarator" || in_member_pointer)
|
|
400
403
|
&& field_name_in_parent(current, parent) == Some("name");
|
|
401
404
|
in_member_pointer = continues_member_pointer;
|
|
402
405
|
if depth >= 1
|
|
403
406
|
&& !parent_is_parameterish
|
|
404
|
-
&& !parent.
|
|
407
|
+
&& !parent.kind_name().contains("declarator")
|
|
405
408
|
&& !continues_member_pointer
|
|
406
409
|
{
|
|
407
410
|
return false;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
use indexmap::IndexMap;
|
|
2
|
+
use rustc_hash::{FxHashMap, FxHashSet};
|
|
2
3
|
use std::borrow::Cow;
|
|
3
|
-
use std::collections::{HashMap, HashSet};
|
|
4
4
|
use std::sync::OnceLock;
|
|
5
5
|
use tree_sitter::Node;
|
|
6
6
|
|
|
7
7
|
use crate::near_miss::{Block, Matcher, PairMatch, FILTRATION_PERCENT, MAX_LENGTH_RATIO};
|
|
8
|
+
use crate::tree_index::NodeExt;
|
|
8
9
|
use crate::types::{
|
|
9
10
|
CrossFileCandidate, CrossFileToken, CrossFileTokenRange, DuplicateBlockOccurrence,
|
|
10
11
|
DuplicationMetrics,
|
|
@@ -127,22 +128,22 @@ fn is_duplicate_block(node: Node<'_>) -> bool {
|
|
|
127
128
|
if !node.is_named() {
|
|
128
129
|
return false;
|
|
129
130
|
}
|
|
130
|
-
if node.
|
|
131
|
+
if node.kind_name() == "try_expression" {
|
|
131
132
|
return crate::util::is_kotlin_try_expression(node);
|
|
132
133
|
}
|
|
133
|
-
DUPLICATE_BLOCK_TYPES.contains(&node.
|
|
134
|
+
DUPLICATE_BLOCK_TYPES.contains(&node.kind_name())
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
fn is_statement_container(node: Node<'_>) -> bool {
|
|
137
138
|
if !node.is_named() {
|
|
138
139
|
return false;
|
|
139
140
|
}
|
|
140
|
-
if node.
|
|
141
|
-
return node
|
|
142
|
-
.parent()
|
|
143
|
-
|
|
141
|
+
if node.kind_name() == "declaration_list" {
|
|
142
|
+
return node.parent_node().is_some_and(|parent| {
|
|
143
|
+
CSHARP_DECLARATION_LIST_PARENT_TYPES.contains(&parent.kind_name())
|
|
144
|
+
});
|
|
144
145
|
}
|
|
145
|
-
STATEMENT_CONTAINER_TYPES.contains(&node.
|
|
146
|
+
STATEMENT_CONTAINER_TYPES.contains(&node.kind_name())
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
/// Identifier leaves anonymized by occurrence order so consistently renamed copies still match.
|
|
@@ -320,13 +321,13 @@ fn is_literal_dense(literal_count: usize, token_count: usize) -> bool {
|
|
|
320
321
|
literal_count * 5 >= token_count
|
|
321
322
|
}
|
|
322
323
|
|
|
323
|
-
fn literal_kind_by_type() -> &'static
|
|
324
|
-
static MAP: OnceLock<
|
|
324
|
+
fn literal_kind_by_type() -> &'static FxHashMap<&'static str, &'static str> {
|
|
325
|
+
static MAP: OnceLock<FxHashMap<&'static str, &'static str>> = OnceLock::new();
|
|
325
326
|
MAP.get_or_init(|| LITERAL_KIND_BY_TYPE.iter().copied().collect())
|
|
326
327
|
}
|
|
327
328
|
|
|
328
|
-
fn semantic_name_field_by_parent_type() -> &'static
|
|
329
|
-
static MAP: OnceLock<
|
|
329
|
+
fn semantic_name_field_by_parent_type() -> &'static FxHashMap<&'static str, &'static str> {
|
|
330
|
+
static MAP: OnceLock<FxHashMap<&'static str, &'static str>> = OnceLock::new();
|
|
330
331
|
MAP.get_or_init(|| SEMANTIC_NAME_FIELD_BY_PARENT_TYPE.iter().copied().collect())
|
|
331
332
|
}
|
|
332
333
|
|
|
@@ -405,7 +406,7 @@ pub fn tokenize<'a>(root: Node<'_>, code: &Source<'a>) -> TokenizedSource<'a> {
|
|
|
405
406
|
/// candidates cross-file matching fingerprints in TypeScript.
|
|
406
407
|
pub fn measure_duplication(
|
|
407
408
|
source: &TokenizedSource<'_>,
|
|
408
|
-
code_line_numbers: &
|
|
409
|
+
code_line_numbers: &FxHashSet<usize>,
|
|
409
410
|
settings: &DuplicationSettings,
|
|
410
411
|
) -> DuplicationMetrics {
|
|
411
412
|
let tokens = &source.tokens;
|
|
@@ -568,7 +569,7 @@ fn collect_tokens<'a>(
|
|
|
568
569
|
node.start_position().row,
|
|
569
570
|
node.end_position().row,
|
|
570
571
|
));
|
|
571
|
-
} else if !COMMENT_TYPES.contains(&node.
|
|
572
|
+
} else if !COMMENT_TYPES.contains(&node.kind_name()) {
|
|
572
573
|
let mut statement_ranges: Vec<TokenRange> = Vec::new();
|
|
573
574
|
let is_container = is_statement_container(node);
|
|
574
575
|
for child in all_children(node) {
|
|
@@ -579,7 +580,7 @@ fn collect_tokens<'a>(
|
|
|
579
580
|
block_ranges,
|
|
580
581
|
container_statement_ranges,
|
|
581
582
|
);
|
|
582
|
-
if is_container && child.is_named() && !COMMENT_TYPES.contains(&child.
|
|
583
|
+
if is_container && child.is_named() && !COMMENT_TYPES.contains(&child.kind_name()) {
|
|
583
584
|
statement_ranges.push(child_range);
|
|
584
585
|
}
|
|
585
586
|
}
|
|
@@ -611,14 +612,14 @@ fn collect_tokens<'a>(
|
|
|
611
612
|
/// The kind tag of a string-like node with no interpolation, or None to descend normally.
|
|
612
613
|
fn atomic_literal_kind(node: Node<'_>) -> Option<&'static str> {
|
|
613
614
|
let kind = if node.is_named() {
|
|
614
|
-
literal_kind_by_type().get(node.
|
|
615
|
+
literal_kind_by_type().get(node.kind_name()).copied()
|
|
615
616
|
} else {
|
|
616
617
|
None
|
|
617
618
|
};
|
|
618
619
|
let kind = kind?;
|
|
619
620
|
if named_children(node)
|
|
620
621
|
.iter()
|
|
621
|
-
.all(|child| STRING_FRAGMENT_TYPES.contains(&child.
|
|
622
|
+
.all(|child| STRING_FRAGMENT_TYPES.contains(&child.kind_name()))
|
|
622
623
|
{
|
|
623
624
|
Some(kind)
|
|
624
625
|
} else {
|
|
@@ -627,13 +628,13 @@ fn atomic_literal_kind(node: Node<'_>) -> Option<&'static str> {
|
|
|
627
628
|
}
|
|
628
629
|
|
|
629
630
|
fn append_leaf_token<'a>(node: Node<'_>, code: &Source<'a>, tokens: &mut Vec<Token<'a>>) {
|
|
630
|
-
if COMMENT_TYPES.contains(&node.
|
|
631
|
+
if COMMENT_TYPES.contains(&node.kind_name()) {
|
|
631
632
|
return;
|
|
632
633
|
}
|
|
633
634
|
|
|
634
635
|
let start_row = node.start_position().row;
|
|
635
636
|
let end_row = node.end_position().row;
|
|
636
|
-
if node.is_named() && SHORTHAND_PROPERTY_TYPES.contains(&node.
|
|
637
|
+
if node.is_named() && SHORTHAND_PROPERTY_TYPES.contains(&node.kind_name()) {
|
|
637
638
|
let text = node_text(node, code);
|
|
638
639
|
tokens.push(make_text_token(
|
|
639
640
|
Cow::Borrowed(text),
|
|
@@ -669,7 +670,7 @@ fn append_leaf_token<'a>(node: Node<'_>, code: &Source<'a>, tokens: &mut Vec<Tok
|
|
|
669
670
|
&& !pascal_case_regex().is_match(node_text(node, code));
|
|
670
671
|
if node.is_named()
|
|
671
672
|
&& (is_variable_receiver
|
|
672
|
-
|| (ANONYMIZED_IDENTIFIER_TYPES.contains(&node.
|
|
673
|
+
|| (ANONYMIZED_IDENTIFIER_TYPES.contains(&node.kind_name())
|
|
673
674
|
&& !is_semantic_name_leaf(node, code)))
|
|
674
675
|
{
|
|
675
676
|
tokens.push(Token {
|
|
@@ -688,7 +689,7 @@ fn append_leaf_token<'a>(node: Node<'_>, code: &Source<'a>, tokens: &mut Vec<Tok
|
|
|
688
689
|
|
|
689
690
|
// Anything else keeps its text: keywords, operators, punctuation, and semantic names.
|
|
690
691
|
let literal_kind = if node.is_named() {
|
|
691
|
-
literal_kind_by_type().get(node.
|
|
692
|
+
literal_kind_by_type().get(node.kind_name()).copied()
|
|
692
693
|
} else {
|
|
693
694
|
None
|
|
694
695
|
};
|
|
@@ -745,12 +746,12 @@ fn literal_value_text<'a>(node: Node<'_>, kind: &str, code: &Source<'a>) -> Cow<
|
|
|
745
746
|
return Cow::Borrowed(node_text(node, code));
|
|
746
747
|
}
|
|
747
748
|
// Fragment leaves already carry bare content; a quote appearing there is content.
|
|
748
|
-
if STRING_CONTENT_FRAGMENT_TYPES.contains(&node.
|
|
749
|
+
if STRING_CONTENT_FRAGMENT_TYPES.contains(&node.kind_name()) {
|
|
749
750
|
return Cow::Borrowed(node_text(node, code));
|
|
750
751
|
}
|
|
751
752
|
let fragments: Vec<&str> = named_children(node)
|
|
752
753
|
.iter()
|
|
753
|
-
.filter(|child| STRING_CONTENT_FRAGMENT_TYPES.contains(&child.
|
|
754
|
+
.filter(|child| STRING_CONTENT_FRAGMENT_TYPES.contains(&child.kind_name()))
|
|
754
755
|
.map(|child| node_text(*child, code))
|
|
755
756
|
.collect();
|
|
756
757
|
if !fragments.is_empty() {
|
|
@@ -758,7 +759,7 @@ fn literal_value_text<'a>(node: Node<'_>, kind: &str, code: &Source<'a>) -> Cow<
|
|
|
758
759
|
}
|
|
759
760
|
// A C# verbatim string (`@"..."`) carries the same value as its ordinary spelling.
|
|
760
761
|
let text = node_text(node, code);
|
|
761
|
-
let text = if node.
|
|
762
|
+
let text = if node.kind_name() == "verbatim_string_literal" {
|
|
762
763
|
text.strip_prefix('@').unwrap_or(text)
|
|
763
764
|
} else {
|
|
764
765
|
text
|
|
@@ -789,13 +790,13 @@ fn build_literal_count_prefix(tokens: &[Token<'_>]) -> Vec<usize> {
|
|
|
789
790
|
}
|
|
790
791
|
|
|
791
792
|
fn is_semantic_name_leaf(node: Node<'_>, code: &Source<'_>) -> bool {
|
|
792
|
-
let Some(parent) = node.
|
|
793
|
+
let Some(parent) = node.parent_node() else {
|
|
793
794
|
return false;
|
|
794
795
|
};
|
|
795
796
|
|
|
796
797
|
// Java method references (`Foo::bar`) and Kotlin callable references (`::bar`) name their
|
|
797
798
|
// identifiers without grammar fields.
|
|
798
|
-
if parent.
|
|
799
|
+
if parent.kind_name() == "method_reference" || parent.kind_name() == "callable_reference" {
|
|
799
800
|
return true;
|
|
800
801
|
}
|
|
801
802
|
|
|
@@ -803,18 +804,19 @@ fn is_semantic_name_leaf(node: Node<'_>, code: &Source<'_>) -> bool {
|
|
|
803
804
|
// `new Foo()`) and attribute names (`[Obsolete]`, an `attribute` inside an `attribute_list`, or
|
|
804
805
|
// `[assembly: Foo]` inside a `global_attribute`; C/C++ attributes hang off other parents and
|
|
805
806
|
// Python's `attribute` has no `name` field).
|
|
806
|
-
if node.
|
|
807
|
+
if node.kind_name() == "identifier" {
|
|
807
808
|
let occupies = |field: &str| {
|
|
808
809
|
parent
|
|
809
810
|
.child_by_field_name(field)
|
|
810
811
|
.is_some_and(|field_node| field_node.id() == node.id())
|
|
811
812
|
};
|
|
812
|
-
let is_csharp_attribute = parent.
|
|
813
|
-
&& parent
|
|
814
|
-
.
|
|
815
|
-
|
|
816
|
-
if CSHARP_TYPE_PARENT_TYPES.contains(&parent.
|
|
817
|
-
|| (occupies("type")
|
|
813
|
+
let is_csharp_attribute = parent.kind_name() == "attribute"
|
|
814
|
+
&& parent.parent_node().is_some_and(|list| {
|
|
815
|
+
matches!(list.kind_name(), "attribute_list" | "global_attribute")
|
|
816
|
+
});
|
|
817
|
+
if CSHARP_TYPE_PARENT_TYPES.contains(&parent.kind_name())
|
|
818
|
+
|| (occupies("type")
|
|
819
|
+
&& !NON_CSHARP_TYPE_FIELD_PARENT_TYPES.contains(&parent.kind_name()))
|
|
818
820
|
|| (is_csharp_attribute && occupies("name"))
|
|
819
821
|
{
|
|
820
822
|
return true;
|
|
@@ -823,35 +825,35 @@ fn is_semantic_name_leaf(node: Node<'_>, code: &Source<'_>) -> bool {
|
|
|
823
825
|
|
|
824
826
|
// Kotlin (no grammar fields): a callee (`foo(...)`), a member name (`a.foo`), an infix function
|
|
825
827
|
// (`a shl b`), and a named argument (`foo(name = x)`) are API names.
|
|
826
|
-
if node.
|
|
827
|
-
if parent.
|
|
828
|
+
if node.kind_name() == "simple_identifier" {
|
|
829
|
+
if parent.kind_name() == "navigation_suffix" {
|
|
828
830
|
return true;
|
|
829
831
|
}
|
|
830
832
|
let is_first_named = parent
|
|
831
833
|
.named_child(0)
|
|
832
834
|
.is_some_and(|first| first.id() == node.id());
|
|
833
|
-
if parent.
|
|
835
|
+
if parent.kind_name() == "call_expression" && is_first_named {
|
|
834
836
|
return true;
|
|
835
837
|
}
|
|
836
|
-
if parent.
|
|
838
|
+
if parent.kind_name() == "infix_expression"
|
|
837
839
|
&& parent
|
|
838
840
|
.named_child(1)
|
|
839
841
|
.is_some_and(|operator| operator.id() == node.id())
|
|
840
842
|
{
|
|
841
843
|
return true;
|
|
842
844
|
}
|
|
843
|
-
if parent.
|
|
845
|
+
if parent.kind_name() == "value_argument"
|
|
844
846
|
&& is_first_named
|
|
845
847
|
&& node
|
|
846
848
|
.next_sibling()
|
|
847
|
-
.is_some_and(|next| !next.is_named() && next.
|
|
849
|
+
.is_some_and(|next| !next.is_named() && next.kind_name() == "=")
|
|
848
850
|
{
|
|
849
851
|
return true;
|
|
850
852
|
}
|
|
851
853
|
}
|
|
852
854
|
|
|
853
855
|
// `call` names its callee `method` in Ruby but `function` in Python; accept both fields.
|
|
854
|
-
if parent.
|
|
856
|
+
if parent.kind_name() == "call"
|
|
855
857
|
&& parent
|
|
856
858
|
.child_by_field_name("function")
|
|
857
859
|
.is_some_and(|function| function.id() == node.id())
|
|
@@ -860,8 +862,8 @@ fn is_semantic_name_leaf(node: Node<'_>, code: &Source<'_>) -> bool {
|
|
|
860
862
|
}
|
|
861
863
|
|
|
862
864
|
// A Ruby constant receiving a call (`Alpha.new(...)`) names the invoked API.
|
|
863
|
-
if node.
|
|
864
|
-
&& parent.
|
|
865
|
+
if node.kind_name() == "constant"
|
|
866
|
+
&& parent.kind_name() == "call"
|
|
865
867
|
&& parent
|
|
866
868
|
.child_by_field_name("receiver")
|
|
867
869
|
.is_some_and(|receiver| receiver.id() == node.id())
|
|
@@ -871,7 +873,7 @@ fn is_semantic_name_leaf(node: Node<'_>, code: &Source<'_>) -> bool {
|
|
|
871
873
|
|
|
872
874
|
// Java/C# static receivers (`Alpha.run(...)`, `Console.WriteLine(...)`) name the invoked type;
|
|
873
875
|
// PascalCase is the discriminator because the tokenizer has no symbol table.
|
|
874
|
-
let is_static_receiver = match parent.
|
|
876
|
+
let is_static_receiver = match parent.kind_name() {
|
|
875
877
|
"method_invocation" => parent
|
|
876
878
|
.child_by_field_name("object")
|
|
877
879
|
.is_some_and(|object| object.id() == node.id()),
|
|
@@ -889,7 +891,7 @@ fn is_semantic_name_leaf(node: Node<'_>, code: &Source<'_>) -> bool {
|
|
|
889
891
|
}
|
|
890
892
|
|
|
891
893
|
// Qualified/generic callees are semantic in call position only.
|
|
892
|
-
if (parent.
|
|
894
|
+
if (parent.kind_name() == "scoped_identifier" || parent.kind_name() == "qualified_identifier")
|
|
893
895
|
&& (parent
|
|
894
896
|
.child_by_field_name("name")
|
|
895
897
|
.is_some_and(|name| name.id() == node.id())
|
|
@@ -898,9 +900,9 @@ fn is_semantic_name_leaf(node: Node<'_>, code: &Source<'_>) -> bool {
|
|
|
898
900
|
.is_some_and(|path| path.id() == node.id()))
|
|
899
901
|
{
|
|
900
902
|
let mut outer = parent;
|
|
901
|
-
while let Some(outer_parent) = outer.
|
|
903
|
+
while let Some(outer_parent) = outer.parent_node() {
|
|
902
904
|
if matches!(
|
|
903
|
-
outer_parent.
|
|
905
|
+
outer_parent.kind_name(),
|
|
904
906
|
"scoped_identifier"
|
|
905
907
|
| "qualified_identifier"
|
|
906
908
|
| "generic_function"
|
|
@@ -911,8 +913,8 @@ fn is_semantic_name_leaf(node: Node<'_>, code: &Source<'_>) -> bool {
|
|
|
911
913
|
break;
|
|
912
914
|
}
|
|
913
915
|
}
|
|
914
|
-
if outer.
|
|
915
|
-
call.
|
|
916
|
+
if outer.parent_node().is_some_and(|call| {
|
|
917
|
+
call.kind_name() == "call_expression"
|
|
916
918
|
&& call
|
|
917
919
|
.child_by_field_name("function")
|
|
918
920
|
.is_some_and(|function| function.id() == outer.id())
|
|
@@ -922,9 +924,9 @@ fn is_semantic_name_leaf(node: Node<'_>, code: &Source<'_>) -> bool {
|
|
|
922
924
|
}
|
|
923
925
|
|
|
924
926
|
// Go struct-literal keys (`Config{Timeout: ...}`) have no `key` field in the grammar.
|
|
925
|
-
if parent.
|
|
926
|
-
&& parent.
|
|
927
|
-
grandparent.
|
|
927
|
+
if parent.kind_name() == "literal_element"
|
|
928
|
+
&& parent.parent_node().is_some_and(|grandparent| {
|
|
929
|
+
grandparent.kind_name() == "keyed_element"
|
|
928
930
|
&& grandparent
|
|
929
931
|
.named_child(0)
|
|
930
932
|
.is_some_and(|first| first.id() == parent.id())
|
|
@@ -934,7 +936,7 @@ fn is_semantic_name_leaf(node: Node<'_>, code: &Source<'_>) -> bool {
|
|
|
934
936
|
}
|
|
935
937
|
|
|
936
938
|
semantic_name_field_by_parent_type()
|
|
937
|
-
.get(parent.
|
|
939
|
+
.get(parent.kind_name())
|
|
938
940
|
.is_some_and(|field| {
|
|
939
941
|
parent
|
|
940
942
|
.child_by_field_name(*field)
|
|
@@ -1005,7 +1007,7 @@ fn collect_sequence_candidates(
|
|
|
1005
1007
|
min_tokens: usize,
|
|
1006
1008
|
) -> Vec<DuplicateCandidate> {
|
|
1007
1009
|
let mut candidates = Vec::new();
|
|
1008
|
-
let mut occurrences_by_window_key:
|
|
1010
|
+
let mut occurrences_by_window_key: FxHashMap<i64, WindowOccurrences> = FxHashMap::default();
|
|
1009
1011
|
let container_windows: Vec<ContainerWindows> = containers
|
|
1010
1012
|
.iter()
|
|
1011
1013
|
.map(|statements| enumerate_container_windows(tokens, statements, min_tokens))
|
|
@@ -1111,7 +1113,7 @@ fn collect_sequence_candidates(
|
|
|
1111
1113
|
|
|
1112
1114
|
// Every emitted window exposes its repeating, unvisited sub-windows; lengths strictly
|
|
1113
1115
|
// decrease, so the worklist terminates.
|
|
1114
|
-
let mut visited:
|
|
1116
|
+
let mut visited: FxHashSet<SequenceWindow> = maximal_windows.iter().copied().collect();
|
|
1115
1117
|
let mut frontier = maximal_windows;
|
|
1116
1118
|
while !frontier.is_empty() {
|
|
1117
1119
|
let mut emitted: Vec<SequenceWindow> = Vec::new();
|
|
@@ -1269,7 +1271,7 @@ fn fingerprint_hash_pair(
|
|
|
1269
1271
|
fold_literal_values: bool,
|
|
1270
1272
|
) -> (i32, i32) {
|
|
1271
1273
|
let clamped_end = end_token_index.min(tokens.len());
|
|
1272
|
-
let mut index_by_identifier:
|
|
1274
|
+
let mut index_by_identifier: FxHashMap<&str, usize> = FxHashMap::default();
|
|
1273
1275
|
let mut index_hashes: Vec<(i32, i32)> = Vec::new();
|
|
1274
1276
|
let mut primary: i32 = 5381;
|
|
1275
1277
|
let mut secondary: i32 = 52_711;
|
|
@@ -1344,7 +1346,7 @@ fn select_maximal_duplicates(
|
|
|
1344
1346
|
.filter(|group| group.len() >= 2)
|
|
1345
1347
|
.collect();
|
|
1346
1348
|
// Greedy order ranks by total coverage (region size × copies).
|
|
1347
|
-
let group_size_by_fingerprint:
|
|
1349
|
+
let group_size_by_fingerprint: FxHashMap<std::rc::Rc<str>, usize> = groups
|
|
1348
1350
|
.iter()
|
|
1349
1351
|
.map(|group| {
|
|
1350
1352
|
(
|
|
@@ -1925,7 +1927,7 @@ fn collect_near_miss_groups(
|
|
|
1925
1927
|
// Rebuild the component as ONE group with one coalesced occurrence per member block:
|
|
1926
1928
|
// the fragments every node of a block overlaps are collected together, since a block's
|
|
1927
1929
|
// cores are parts of one copy.
|
|
1928
|
-
let mut consumed:
|
|
1930
|
+
let mut consumed: FxHashSet<(usize, usize)> = FxHashSet::default();
|
|
1929
1931
|
let mut merged: Vec<CountedOccurrence> = Vec::new();
|
|
1930
1932
|
let mut unanchored_nodes: Vec<usize> = Vec::new();
|
|
1931
1933
|
let mut nodes_by_block: IndexMap<usize, Vec<usize>> = IndexMap::new();
|
|
@@ -1974,7 +1976,7 @@ fn collect_near_miss_groups(
|
|
|
1974
1976
|
(occurrence.start_token_index, occurrence.end_token_index)
|
|
1975
1977
|
});
|
|
1976
1978
|
let mut copy_parts: Vec<CountedOccurrence> = Vec::new();
|
|
1977
|
-
let mut copy_groups:
|
|
1979
|
+
let mut copy_groups: FxHashSet<usize> = FxHashSet::default();
|
|
1978
1980
|
for (occurrence, group_index) in fragments {
|
|
1979
1981
|
if copy_groups.contains(&group_index) {
|
|
1980
1982
|
merged.push(coalesce_occurrences(std::mem::take(&mut copy_parts)));
|
|
@@ -2156,8 +2158,8 @@ fn merge_overlapping_cores(cores: &[(usize, usize)]) -> Vec<(usize, usize)> {
|
|
|
2156
2158
|
/// Interned per call so a file's symbol ids (and thus its n-gram hashes) never depend on which
|
|
2157
2159
|
/// other files the process measured before it.
|
|
2158
2160
|
fn to_symbol_stream(tokens: &[Token<'_>]) -> (Vec<i32>, Vec<bool>) {
|
|
2159
|
-
let mut symbol_by_token_hashes:
|
|
2160
|
-
let mut id_by_identifier:
|
|
2161
|
+
let mut symbol_by_token_hashes: FxHashMap<(i32, i32, i32, i32), i32> = FxHashMap::default();
|
|
2162
|
+
let mut id_by_identifier: FxHashMap<&str, i32> = FxHashMap::default();
|
|
2161
2163
|
tokens
|
|
2162
2164
|
.iter()
|
|
2163
2165
|
.map(|token| {
|
|
@@ -2222,7 +2224,7 @@ fn for_each_candidate_pair(
|
|
|
2222
2224
|
) {
|
|
2223
2225
|
let mut order: Vec<usize> = (0..blocks.len()).collect();
|
|
2224
2226
|
order.sort_by_key(|&index| blocks[index].len());
|
|
2225
|
-
let mut postings:
|
|
2227
|
+
let mut postings: FxHashMap<i32, Vec<usize>> = FxHashMap::default();
|
|
2226
2228
|
let mut shared_counts = vec![0usize; blocks.len()];
|
|
2227
2229
|
let mut touched: Vec<usize> = Vec::new();
|
|
2228
2230
|
for right in order {
|
|
@@ -2284,13 +2286,13 @@ fn count_redundant_fragments(group: &[CountedOccurrence]) -> usize {
|
|
|
2284
2286
|
|
|
2285
2287
|
fn summarize_duplicates(
|
|
2286
2288
|
groups: &[Vec<CountedOccurrence>],
|
|
2287
|
-
code_line_numbers: &
|
|
2289
|
+
code_line_numbers: &FxHashSet<usize>,
|
|
2288
2290
|
tokens: &[Token<'_>],
|
|
2289
2291
|
) -> DuplicationMetrics {
|
|
2290
2292
|
let mut duplicate_block_count = 0;
|
|
2291
2293
|
let mut max_duplicate_block_size = 0;
|
|
2292
2294
|
let mut duplicate_block_groups: Vec<Vec<DuplicateBlockOccurrence>> = Vec::new();
|
|
2293
|
-
let mut duplicated_lines:
|
|
2295
|
+
let mut duplicated_lines: FxHashSet<usize> = FxHashSet::default();
|
|
2294
2296
|
for group in groups {
|
|
2295
2297
|
duplicate_block_count += count_redundant_fragments(group);
|
|
2296
2298
|
for occurrence in group {
|