code-gauge 4.2.1 → 4.2.2
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/dist/crossFileDuplication.cjs +1 -1
- package/dist/crossFileDuplication.cjs.map +1 -1
- package/dist/crossFileDuplication.d.ts +3 -1
- package/dist/crossFileDuplication.js +1 -1
- package/dist/crossFileDuplication.js.map +1 -1
- package/dist/duplicateSelection.cjs +1 -1
- package/dist/duplicateSelection.cjs.map +1 -1
- package/dist/duplicateSelection.d.ts +13 -4
- 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.d.ts +16 -7
- package/dist/duplication.js +1 -1
- package/dist/duplication.js.map +1 -1
- package/native/src/complexity.rs +11 -8
- package/native/src/dep_degree.rs +102 -11
- package/native/src/functions.rs +828 -22
- package/package.json +8 -8
package/native/src/functions.rs
CHANGED
|
@@ -200,6 +200,58 @@ pub fn collect_nodes<'t>(root: Node<'t>, node_types: &HashSet<&'static str>) ->
|
|
|
200
200
|
nodes
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
/// The value a transparent wrapper wraps, if this node is one. Grouping parentheses and type-only
|
|
204
|
+
/// wrappers do not change what a value is bound to, so naming looks through them. TypeScript's
|
|
205
|
+
/// angle-bracket assertion puts the type first (`<Fn>(f)`), so its value is its last child, as does
|
|
206
|
+
/// a C-style cast (`(Runnable) () -> 1`), which names it through a field; Ruby's
|
|
207
|
+
/// `parenthesized_statements` wraps a lone value only when it holds exactly one statement.
|
|
208
|
+
fn wrapped_transparent_value(wrapper: Node<'_>) -> Option<Node<'_>> {
|
|
209
|
+
// A C-style cast (Java, C#, C/C++) names its type first, so its value comes from the field.
|
|
210
|
+
if wrapper.kind() == "cast_expression" {
|
|
211
|
+
return wrapper.child_by_field_name("value");
|
|
212
|
+
}
|
|
213
|
+
if !matches!(
|
|
214
|
+
wrapper.kind(),
|
|
215
|
+
"type_assertion"
|
|
216
|
+
| "parenthesized_statements"
|
|
217
|
+
| "parenthesized_expression"
|
|
218
|
+
| "as_expression"
|
|
219
|
+
| "satisfies_expression"
|
|
220
|
+
| "non_null_expression"
|
|
221
|
+
| "type_cast_expression"
|
|
222
|
+
) {
|
|
223
|
+
// Checked before the children are read: this runs for the parent of every function node,
|
|
224
|
+
// and a high-arity parent (a list of callbacks) would otherwise cost O(children) each time.
|
|
225
|
+
return None;
|
|
226
|
+
}
|
|
227
|
+
// Comments are named children too (`(/* why */ () => 1)`), so they are skipped throughout.
|
|
228
|
+
let children = named_children(wrapper);
|
|
229
|
+
let mut values = children
|
|
230
|
+
.into_iter()
|
|
231
|
+
.filter(|child| !crate::ncss::COMMENT_NODE_TYPES.contains(&child.kind()));
|
|
232
|
+
match wrapper.kind() {
|
|
233
|
+
"type_assertion" => values.next_back(),
|
|
234
|
+
"parenthesized_statements" => {
|
|
235
|
+
let value = values.next()?;
|
|
236
|
+
values.next().is_none().then_some(value)
|
|
237
|
+
}
|
|
238
|
+
_ => values.next(),
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/// Climbs from a value through the transparent wrappers around it (`(() => 1)`, `(() => 2) as Fn`,
|
|
243
|
+
/// `<Fn>(() => 3)`, Rust `(|x| x) as fn(i32) -> i32`) to the outermost one, whose binding site
|
|
244
|
+
/// names the value.
|
|
245
|
+
fn unwrap_transparent_value_wrappers(node: Node<'_>) -> Node<'_> {
|
|
246
|
+
let mut bound = node;
|
|
247
|
+
while let Some(wrapper) = bound.parent().filter(|wrapper| {
|
|
248
|
+
wrapped_transparent_value(*wrapper).is_some_and(|value| value.id() == bound.id())
|
|
249
|
+
}) {
|
|
250
|
+
bound = wrapper;
|
|
251
|
+
}
|
|
252
|
+
bound
|
|
253
|
+
}
|
|
254
|
+
|
|
203
255
|
pub fn find_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
204
256
|
// JS truthiness: empty strings from MISSING nodes act like "no name" at every `if (name)`.
|
|
205
257
|
if let Some(wrapped_name) =
|
|
@@ -224,7 +276,8 @@ pub fn find_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
|
224
276
|
return Some(declarator_name);
|
|
225
277
|
}
|
|
226
278
|
|
|
227
|
-
let
|
|
279
|
+
let bound = unwrap_transparent_value_wrappers(node);
|
|
280
|
+
let parent = bound.parent()?;
|
|
228
281
|
|
|
229
282
|
// A Rust closure bound to a simple `let` identifier takes that identifier as its name.
|
|
230
283
|
if node.kind() == "closure_expression" && parent.kind() == "let_declaration" {
|
|
@@ -237,24 +290,49 @@ pub fn find_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
|
237
290
|
};
|
|
238
291
|
}
|
|
239
292
|
|
|
240
|
-
// A C++ lambda assigned to a variable (`auto f = [](int x) { ... };`) takes the variable name
|
|
241
|
-
|
|
242
|
-
|
|
293
|
+
// A C++ lambda assigned to a variable (`auto f = [](int x) { ... };`) takes the variable name,
|
|
294
|
+
// as does one that direct-initializes a deduced variable (`auto f{[] {}}`), whose type is the
|
|
295
|
+
// closure itself. With a written type (`std::thread worker([] {})`) the lambda is a constructor
|
|
296
|
+
// argument, and the constructor stores whatever it likes, so it names nothing.
|
|
297
|
+
if node.kind() == "lambda_expression" {
|
|
298
|
+
let declaration = match parent.kind() {
|
|
299
|
+
"init_declarator" => Some(parent),
|
|
300
|
+
"argument_list" | "initializer_list" if binding_children(parent).len() == 1 => parent
|
|
301
|
+
.parent()
|
|
302
|
+
.filter(|holder| holder.kind() == "init_declarator")
|
|
303
|
+
.filter(|holder| declares_deduced_type(*holder))
|
|
304
|
+
// `auto f = {[] {}}` deduces a list holding the closure, not the closure itself;
|
|
305
|
+
// only the direct form `auto f{[] {}}` makes the variable the closure.
|
|
306
|
+
.filter(|holder| {
|
|
307
|
+
parent.kind() == "argument_list"
|
|
308
|
+
|| !all_children(*holder)
|
|
309
|
+
.iter()
|
|
310
|
+
.any(|child| !child.is_named() && node_text(*child, code) == "=")
|
|
311
|
+
}),
|
|
312
|
+
_ => None,
|
|
313
|
+
};
|
|
314
|
+
if let Some(declaration) = declaration {
|
|
315
|
+
return unwrap_declarator_name(declaration.child_by_field_name("declarator"), code);
|
|
316
|
+
}
|
|
243
317
|
}
|
|
244
318
|
|
|
245
|
-
// A Go func literal bound via `add := func
|
|
246
|
-
// the same list position.
|
|
319
|
+
// A Go func literal bound via `add := func...`, `var add = func...`, or `add = func...` takes
|
|
320
|
+
// the identifier (or selector field) at the same list position.
|
|
247
321
|
if node.kind() == "func_literal" && parent.kind() == "expression_list" {
|
|
248
|
-
return find_go_func_literal_name(
|
|
322
|
+
return find_go_func_literal_name(bound, parent, code);
|
|
249
323
|
}
|
|
250
324
|
|
|
251
|
-
// Ruby lambdas assigned to a name take that name.
|
|
325
|
+
// Ruby and Python lambdas assigned to a name take that name.
|
|
252
326
|
if node.kind() == "lambda" && parent.kind() == "assignment" {
|
|
253
327
|
return find_ruby_assignment_name(parent, code);
|
|
254
328
|
}
|
|
329
|
+
if node.kind() == "lambda" && is_value_group(parent) {
|
|
330
|
+
return find_parallel_assignment_name(bound, code);
|
|
331
|
+
}
|
|
255
332
|
|
|
256
333
|
// A Kotlin lambda or anonymous function initializing a property (`val f = { ... }`, also through
|
|
257
|
-
// a label or annotation prefix) takes the property name
|
|
334
|
+
// a label or annotation prefix) takes the property name; one assigned to a variable or member
|
|
335
|
+
// (`run = { ... }`, `obj.run = { ... }`) takes the assigned name.
|
|
258
336
|
if node.kind() == "lambda_literal" || node.kind() == "anonymous_function" {
|
|
259
337
|
let mut holder = parent;
|
|
260
338
|
while holder.kind() == "prefix_expression" {
|
|
@@ -263,25 +341,550 @@ pub fn find_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
|
263
341
|
if holder.kind() == "property_declaration" {
|
|
264
342
|
return find_kotlin_property_name(holder, code);
|
|
265
343
|
}
|
|
344
|
+
if holder.kind() == "assignment" {
|
|
345
|
+
return find_kotlin_assignment_name(holder, code);
|
|
346
|
+
}
|
|
266
347
|
}
|
|
348
|
+
// A `lambda { }` / `proc { }` block is measured, but the call around it is what gets bound.
|
|
267
349
|
if (node.kind() == "block" || node.kind() == "do_block") && is_ruby_lambda_call(parent, code) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
350
|
+
let call = unwrap_transparent_value_wrappers(parent);
|
|
351
|
+
return match call.parent() {
|
|
352
|
+
Some(holder) if holder.kind() == "assignment" => {
|
|
353
|
+
find_ruby_assignment_name(holder, code)
|
|
271
354
|
}
|
|
355
|
+
Some(holder) if holder.kind() == "pair" => find_pair_key_name(holder, code),
|
|
356
|
+
Some(holder) if is_value_group(holder) => find_parallel_assignment_name(call, code),
|
|
272
357
|
_ => None,
|
|
273
358
|
};
|
|
274
359
|
}
|
|
275
360
|
|
|
361
|
+
// An object-literal or Ruby hash property (`{ run: () => {} }`, `{ run: -> {} }`) names its value
|
|
362
|
+
// after the key; an assignment
|
|
363
|
+
// (`obj.run = () => {}`, `run = () => {}`, Rust/C++ `self.cb = |x| x`, C++ `N::run = [] {}`,
|
|
364
|
+
// C# `this.Run = () => 1`) after its target.
|
|
365
|
+
if parent.kind() == "pair" {
|
|
366
|
+
return find_pair_key_name(parent, code);
|
|
367
|
+
}
|
|
368
|
+
// A Go keyed composite-literal element (`S{run: func() {}}`) names its value after the key.
|
|
369
|
+
if parent.kind() == "literal_element" {
|
|
370
|
+
return find_go_keyed_element_name(parent, code);
|
|
371
|
+
}
|
|
372
|
+
// A Rust struct-literal field (`S { cb: || 1 }`) names its closure after the field.
|
|
373
|
+
if parent.kind() == "field_initializer" {
|
|
374
|
+
return parent
|
|
375
|
+
.child_by_field_name("field")
|
|
376
|
+
.map(|field| node_text(field, code).to_string());
|
|
377
|
+
}
|
|
378
|
+
// A C++20 designated initializer (`S s{.run = []{}}`) likewise names its value after the field;
|
|
379
|
+
// an array designator (`{[0] = ...}`) names nothing, like a subscript assignment target.
|
|
380
|
+
if parent.kind() == "initializer_pair" {
|
|
381
|
+
return find_children_by_field_name(parent, "designator")
|
|
382
|
+
.last()
|
|
383
|
+
.filter(|designator| designator.kind() == "field_designator")
|
|
384
|
+
.and_then(|designator| designator.named_child(0))
|
|
385
|
+
.map(|field| node_text(field, code).to_string());
|
|
386
|
+
}
|
|
387
|
+
if parent.kind() == "assignment_expression" {
|
|
388
|
+
return find_assignment_target_name(parent, code);
|
|
389
|
+
}
|
|
390
|
+
|
|
276
391
|
// A JavaScript class field (`handle = () => {}`) names its property through the `property`
|
|
277
|
-
// field; TypeScript's `public_field_definition` exposes the same thing as `name`.
|
|
392
|
+
// field; TypeScript's `public_field_definition` exposes the same thing as `name`. A computed
|
|
393
|
+
// key is as unstable here as in an object literal, and a string key is read the same way.
|
|
278
394
|
let field_name = if parent.kind() == "field_definition" {
|
|
279
395
|
"property"
|
|
280
396
|
} else {
|
|
281
397
|
"name"
|
|
282
398
|
};
|
|
283
|
-
parent
|
|
284
|
-
|
|
399
|
+
let name = parent.child_by_field_name(field_name)?;
|
|
400
|
+
// Checked only once a name exists, so a high-arity parent without one still costs O(1): the
|
|
401
|
+
// parent names the function only when the function is its value. A receiver borrows nothing
|
|
402
|
+
// (`((Runnable) () -> 1).run()` is not named `run`).
|
|
403
|
+
if !is_value_of_parent(bound, parent) {
|
|
404
|
+
return None;
|
|
405
|
+
}
|
|
406
|
+
match name.kind() {
|
|
407
|
+
"computed_property_name" => None,
|
|
408
|
+
"string" => find_string_literal_content(name, code),
|
|
409
|
+
_ => Some(node_text(name, code).to_string()),
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/// The key of a `pair` when it is a plain, Ruby symbol, or string-literal property name; a
|
|
414
|
+
/// computed key (`[k]: ...`), an interpolated string or symbol, or an empty string names nothing.
|
|
415
|
+
fn find_pair_key_name(pair: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
416
|
+
let key = pair.child_by_field_name("key")?;
|
|
417
|
+
match key.kind() {
|
|
418
|
+
// A numeric key (`{ 1: () => {} }`) is as stable a property name as an identifier, signed
|
|
419
|
+
// (`{ -1: ... }`) or not; any other expression is computed and names nothing.
|
|
420
|
+
"property_identifier" | "hash_key_symbol" => Some(node_text(key, code).to_string()),
|
|
421
|
+
"number" | "integer" | "float" => Some(node_text(key, code).to_string()),
|
|
422
|
+
"unary_operator" | "unary" => signed_number_name(key, code),
|
|
423
|
+
"simple_symbol" => node_text(key, code)
|
|
424
|
+
.strip_prefix(':')
|
|
425
|
+
.map(|name| name.to_string()),
|
|
426
|
+
"string" | "delimited_symbol" => find_string_literal_content(key, code),
|
|
427
|
+
// Python's adjacent literals (`{"run" "ner": ...}`) are one compile-time key.
|
|
428
|
+
"concatenated_string" => {
|
|
429
|
+
let mut name = String::new();
|
|
430
|
+
for part in binding_children(key) {
|
|
431
|
+
if named_children(part)
|
|
432
|
+
.iter()
|
|
433
|
+
.any(|child| child.kind() == "interpolation")
|
|
434
|
+
{
|
|
435
|
+
return None;
|
|
436
|
+
}
|
|
437
|
+
name.push_str(&find_string_literal_content(part, code).unwrap_or_default());
|
|
438
|
+
}
|
|
439
|
+
(!name.is_empty()).then_some(name)
|
|
440
|
+
}
|
|
441
|
+
_ => None,
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/// The type a constraint stands for: an approximation (`~map[string]F`), an interface holding one
|
|
446
|
+
/// type (`interface{ ~map[string]F }`), or a wrapper around one names that type; a union of several
|
|
447
|
+
/// names none of them in particular.
|
|
448
|
+
fn core_constraint_type(constraint: Node<'_>) -> Node<'_> {
|
|
449
|
+
let mut current = constraint;
|
|
450
|
+
while matches!(
|
|
451
|
+
current.kind(),
|
|
452
|
+
"type_constraint" | "negated_type" | "type_elem" | "interface_type"
|
|
453
|
+
) {
|
|
454
|
+
match named_children(current).as_slice() {
|
|
455
|
+
[only] => current = *only,
|
|
456
|
+
_ => break,
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
current
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/// The name a signed number spells: the sign and the number it applies to, written without any
|
|
463
|
+
/// space between them and, for a rune, without its quotes. Any other unary expression is computed
|
|
464
|
+
/// and names nothing.
|
|
465
|
+
fn signed_number_name(key: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
466
|
+
if !is_signed_number(key, code) {
|
|
467
|
+
return None;
|
|
468
|
+
}
|
|
469
|
+
let operand = named_children(key).into_iter().next()?;
|
|
470
|
+
let sign = all_children(key)
|
|
471
|
+
.into_iter()
|
|
472
|
+
.find(|child| !child.is_named())
|
|
473
|
+
.map(|child| node_text(child, code))
|
|
474
|
+
.unwrap_or_default();
|
|
475
|
+
let text = match operand.kind() {
|
|
476
|
+
"rune_literal" => find_string_literal_content(operand, code)?,
|
|
477
|
+
_ => node_text(operand, code).to_string(),
|
|
478
|
+
};
|
|
479
|
+
Some(format!("{sign}{text}"))
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/// A number with a leading sign (`-1`), whose text is as stable a key as the number itself.
|
|
483
|
+
fn is_signed_number(key: Node<'_>, code: &Source<'_>) -> bool {
|
|
484
|
+
let signed = all_children(key)
|
|
485
|
+
.into_iter()
|
|
486
|
+
.find(|child| !child.is_named())
|
|
487
|
+
.is_some_and(|sign| matches!(node_text(sign, code), "-" | "+"));
|
|
488
|
+
signed
|
|
489
|
+
&& named_children(key).first().is_some_and(|operand| {
|
|
490
|
+
matches!(
|
|
491
|
+
operand.kind(),
|
|
492
|
+
"number"
|
|
493
|
+
| "integer"
|
|
494
|
+
| "float"
|
|
495
|
+
| "int_literal"
|
|
496
|
+
| "float_literal"
|
|
497
|
+
| "rune_literal"
|
|
498
|
+
| "imaginary_literal"
|
|
499
|
+
)
|
|
500
|
+
})
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/// A Go keyed composite-literal element: the key is the first `literal_element` of the pair, the
|
|
504
|
+
/// value the last. An unkeyed element sits under a `literal_value` instead and names nothing.
|
|
505
|
+
fn find_go_keyed_element_name(value_element: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
506
|
+
let keyed = value_element
|
|
507
|
+
.parent()
|
|
508
|
+
.filter(|parent| parent.kind() == "keyed_element")?;
|
|
509
|
+
let elements = named_children(keyed);
|
|
510
|
+
if elements.len() < 2 || elements.last()?.id() != value_element.id() {
|
|
511
|
+
return None;
|
|
512
|
+
}
|
|
513
|
+
let key = named_children(*elements.first()?).into_iter().next()?;
|
|
514
|
+
match key.kind() {
|
|
515
|
+
"identifier" | "field_identifier" if !has_value_keys(keyed, code) => {
|
|
516
|
+
Some(node_text(key, code).to_string())
|
|
517
|
+
}
|
|
518
|
+
"interpreted_string_literal" | "raw_string_literal" => {
|
|
519
|
+
find_string_literal_content(key, code)
|
|
520
|
+
}
|
|
521
|
+
// A literal key is as stable a name here as in any other language's mapping, signed or not;
|
|
522
|
+
// a rune carries quotes, which are read off like a string's.
|
|
523
|
+
"rune_literal" => find_string_literal_content(key, code),
|
|
524
|
+
"int_literal" | "float_literal" | "imaginary_literal" => {
|
|
525
|
+
Some(node_text(key, code).to_string())
|
|
526
|
+
}
|
|
527
|
+
"unary_expression" => signed_number_name(key, code),
|
|
528
|
+
_ => None,
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/// Whether the element belongs to a Go literal whose keys are evaluated values (a map, slice, or
|
|
533
|
+
/// array) rather than the field names of a struct: `map[string]F{key: ...}` stores under whatever
|
|
534
|
+
/// `key` holds, so it names nothing, exactly like a computed property key.
|
|
535
|
+
fn has_value_keys(keyed: Node<'_>, code: &Source<'_>) -> bool {
|
|
536
|
+
keyed
|
|
537
|
+
.parent()
|
|
538
|
+
.and_then(|body| key_type_of_literal_body(body, code))
|
|
539
|
+
.is_some_and(|declared| is_value_keyed_type(declared, code))
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/// Whether values written under this type are keyed by evaluated values rather than field names: a
|
|
543
|
+
/// map, slice or array, a name standing for one, or a constraint that admits only such types (a
|
|
544
|
+
/// union counts when every one of its terms does).
|
|
545
|
+
fn is_value_keyed_type(declared: Node<'_>, code: &Source<'_>) -> bool {
|
|
546
|
+
value_keyed_type(declared, code, 0)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/// Constraints nest, and one that embeds itself would nest forever, so the walk is depth-bounded;
|
|
550
|
+
/// real constraints are only a few levels deep.
|
|
551
|
+
const MAX_CONSTRAINT_DEPTH: usize = 16;
|
|
552
|
+
|
|
553
|
+
fn value_keyed_type(declared: Node<'_>, code: &Source<'_>, depth: usize) -> bool {
|
|
554
|
+
if depth >= MAX_CONSTRAINT_DEPTH {
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
let resolved = resolve_named_type(declared, code);
|
|
558
|
+
match resolved.kind() {
|
|
559
|
+
"map_type" | "slice_type" | "array_type" | "implicit_length_array_type" => true,
|
|
560
|
+
"type_constraint" | "interface_type" | "type_elem" | "negated_type" => {
|
|
561
|
+
// Method requirements restrict what a type does, not what it is, so only the type terms
|
|
562
|
+
// decide. One value-keyed term is enough: a union admitting a map has no stable field
|
|
563
|
+
// name, whichever type it is instantiated with.
|
|
564
|
+
binding_children(resolved)
|
|
565
|
+
.into_iter()
|
|
566
|
+
.filter(|term| !requires_methods_only(*term, code, depth + 1))
|
|
567
|
+
.any(|term| value_keyed_type(term, code, depth + 1))
|
|
568
|
+
}
|
|
569
|
+
_ => false,
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/// Whether a constraint element only requires methods, directly or through an embedded interface
|
|
574
|
+
/// that does; such an element leaves the underlying type free.
|
|
575
|
+
fn requires_methods_only(declared: Node<'_>, code: &Source<'_>, depth: usize) -> bool {
|
|
576
|
+
if matches!(declared.kind(), "method_elem" | "method_spec") {
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
if depth >= MAX_CONSTRAINT_DEPTH {
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
let resolved = resolve_named_type(declared, code);
|
|
583
|
+
match resolved.kind() {
|
|
584
|
+
"interface_type" | "type_constraint" | "type_elem" => {
|
|
585
|
+
let terms = binding_children(resolved);
|
|
586
|
+
!terms.is_empty()
|
|
587
|
+
&& terms
|
|
588
|
+
.iter()
|
|
589
|
+
.all(|term| requires_methods_only(*term, code, depth + 1))
|
|
590
|
+
}
|
|
591
|
+
_ => false,
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/// A literal's type may be a name declared in the same file (`type M map[string]F`), so the name is
|
|
596
|
+
/// resolved to the type it stands for. A name declared elsewhere stays unresolved and keeps the
|
|
597
|
+
/// struct reading, which is what a named literal type usually is.
|
|
598
|
+
fn resolve_named_type<'t>(declared: Node<'t>, code: &Source<'t>) -> Node<'t> {
|
|
599
|
+
let mut current = declared;
|
|
600
|
+
// A name may stand for another name (`type Alias M`) or be instantiated (`M[func()]`); the walk
|
|
601
|
+
// follows the chain as far as it goes and stops on a node it has already seen, which is what a
|
|
602
|
+
// declaration naming itself produces.
|
|
603
|
+
let mut visited = Vec::new();
|
|
604
|
+
while !visited.contains(¤t.id()) {
|
|
605
|
+
visited.push(current.id());
|
|
606
|
+
let next = match current.kind() {
|
|
607
|
+
"generic_type" => current.child_by_field_name("type"),
|
|
608
|
+
// Go allows parentheses around a type; they name the type they hold.
|
|
609
|
+
// Parentheses name the type they hold, and a nested literal of a pointer element type
|
|
610
|
+
// elides the `&`, so both stand for the type they wrap.
|
|
611
|
+
"parenthesized_type" | "pointer_type" => named_children(current).into_iter().next(),
|
|
612
|
+
_ => lookup_declared_type(current, code),
|
|
613
|
+
};
|
|
614
|
+
match next {
|
|
615
|
+
Some(next) => current = next,
|
|
616
|
+
None => break,
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
current
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/// The type a name stands for, resolved through the scopes enclosing it, innermost first.
|
|
623
|
+
fn lookup_declared_type<'t>(declared: Node<'t>, code: &Source<'t>) -> Option<Node<'t>> {
|
|
624
|
+
if declared.kind() != "type_identifier" {
|
|
625
|
+
return None;
|
|
626
|
+
}
|
|
627
|
+
let name = node_text(declared, code);
|
|
628
|
+
// Go allows a type declaration in any block, so each enclosing scope is searched from the
|
|
629
|
+
// innermost outwards, the way the language resolves the name; only its own declarations are
|
|
630
|
+
// read at each level, which keeps the lookup shallow.
|
|
631
|
+
let mut scope = Some(declared);
|
|
632
|
+
while let Some(current) = scope {
|
|
633
|
+
// A type parameter shadows any outer declaration of the same name, so its constraint is
|
|
634
|
+
// what the literal is written against.
|
|
635
|
+
if let Some(constraint) = named_children(current)
|
|
636
|
+
.into_iter()
|
|
637
|
+
.filter(|child| child.kind() == "type_parameter_list")
|
|
638
|
+
.flat_map(|list| declared_type_parameters(list))
|
|
639
|
+
.find(|(parameter_name, _)| node_text(*parameter_name, code) == name)
|
|
640
|
+
.and_then(|(_, constraint)| constraint)
|
|
641
|
+
{
|
|
642
|
+
return Some(core_constraint_type(constraint));
|
|
643
|
+
}
|
|
644
|
+
// A method's receiver carries the parameters of the type it is declared on
|
|
645
|
+
// (`func (r R[T]) ...`), so the name resolves through that type's declaration.
|
|
646
|
+
if current.kind() == "method_declaration" {
|
|
647
|
+
if let Some(constraint) = receiver_parameter_constraint(current, name, code) {
|
|
648
|
+
return Some(core_constraint_type(constraint));
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
if let Some(found) = find_type_spec(current, name, code) {
|
|
652
|
+
return found.child_by_field_name("type");
|
|
653
|
+
}
|
|
654
|
+
scope = current.parent();
|
|
655
|
+
}
|
|
656
|
+
None
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/// The declaration of a type named in this scope's own `type` declarations, without looking out.
|
|
660
|
+
fn find_type_spec<'t>(scope: Node<'t>, name: &str, code: &Source<'t>) -> Option<Node<'t>> {
|
|
661
|
+
named_children(scope)
|
|
662
|
+
.into_iter()
|
|
663
|
+
.filter(|child| child.kind() == "type_declaration")
|
|
664
|
+
.flat_map(named_children)
|
|
665
|
+
.filter(|spec| spec.kind() == "type_spec" || spec.kind() == "type_alias")
|
|
666
|
+
.find(|spec| {
|
|
667
|
+
spec.child_by_field_name("name")
|
|
668
|
+
.is_some_and(|declared_name| node_text(declared_name, code) == name)
|
|
669
|
+
})
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/// The constraint a receiver type argument stands for: the parameter at the same position of the
|
|
673
|
+
/// receiver's own type declaration (`type R[T ~map[string]F]` reached through `func (r R[T])`).
|
|
674
|
+
fn receiver_parameter_constraint<'t>(
|
|
675
|
+
method: Node<'t>,
|
|
676
|
+
name: &str,
|
|
677
|
+
code: &Source<'t>,
|
|
678
|
+
) -> Option<Node<'t>> {
|
|
679
|
+
let receiver = method.child_by_field_name("receiver")?;
|
|
680
|
+
let instantiation = named_children(receiver)
|
|
681
|
+
.into_iter()
|
|
682
|
+
.filter_map(|parameter| parameter.child_by_field_name("type"))
|
|
683
|
+
// A pointer or parenthesized receiver (`func (r *R[T])`, `func (r (R[T]))`) wraps the
|
|
684
|
+
// instantiation, in either order.
|
|
685
|
+
.map(unwrap_receiver_type)
|
|
686
|
+
.find(|declared| declared.kind() == "generic_type")?;
|
|
687
|
+
let arguments = binding_children(instantiation.child_by_field_name("type_arguments")?);
|
|
688
|
+
let position = arguments.iter().position(|argument| {
|
|
689
|
+
let argument = named_children(*argument)
|
|
690
|
+
.into_iter()
|
|
691
|
+
.next()
|
|
692
|
+
.unwrap_or(*argument);
|
|
693
|
+
node_text(argument, code) == name
|
|
694
|
+
})?;
|
|
695
|
+
let base = instantiation.child_by_field_name("type")?;
|
|
696
|
+
let declaration = find_declared_type_spec(method, node_text(base, code), code)?;
|
|
697
|
+
declared_type_parameters(declaration.child_by_field_name("type_parameters")?)
|
|
698
|
+
.get(position)?
|
|
699
|
+
.1
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/// The type a receiver names, past the pointer and parenthesis wrappers it may carry.
|
|
703
|
+
fn unwrap_receiver_type(declared: Node<'_>) -> Node<'_> {
|
|
704
|
+
let mut current = declared;
|
|
705
|
+
while matches!(current.kind(), "pointer_type" | "parenthesized_type") {
|
|
706
|
+
match named_children(current).into_iter().next() {
|
|
707
|
+
Some(inner) => current = inner,
|
|
708
|
+
None => break,
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
current
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/// The parameters a type-parameter list declares, in order; one declaration can name several that
|
|
715
|
+
/// share its constraint (`[A, B ~map[string]F]`), so each name is its own parameter.
|
|
716
|
+
fn declared_type_parameters<'t>(list: Node<'t>) -> Vec<(Node<'t>, Option<Node<'t>>)> {
|
|
717
|
+
binding_children(list)
|
|
718
|
+
.into_iter()
|
|
719
|
+
.filter(|declaration| declaration.kind() == "type_parameter_declaration")
|
|
720
|
+
.flat_map(|declaration| {
|
|
721
|
+
let constraint = declaration.child_by_field_name("type");
|
|
722
|
+
find_children_by_field_name(declaration, "name")
|
|
723
|
+
.into_iter()
|
|
724
|
+
.map(|name| (name, constraint))
|
|
725
|
+
.collect::<Vec<_>>()
|
|
726
|
+
})
|
|
727
|
+
.collect()
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/// The type governing a literal body's keys: the type its own literal declares, or, when a nested
|
|
731
|
+
/// literal elides it, the element type of the literal holding it (`map[string]map[string]F{"a":
|
|
732
|
+
/// {k: f}}` nests a map, `[]S{{run: f}}` a struct). A literal nested in a struct field keeps the
|
|
733
|
+
/// struct reading, since the field's type is not written at the literal.
|
|
734
|
+
fn key_type_of_literal_body<'t>(body: Node<'t>, code: &Source<'t>) -> Option<Node<'t>> {
|
|
735
|
+
let parent = body.parent()?;
|
|
736
|
+
if parent.kind() == "composite_literal" {
|
|
737
|
+
return parent.child_by_field_name("type");
|
|
738
|
+
}
|
|
739
|
+
if parent.kind() != "literal_element" {
|
|
740
|
+
return None;
|
|
741
|
+
}
|
|
742
|
+
let mut container = parent.parent()?;
|
|
743
|
+
if container.kind() == "keyed_element" {
|
|
744
|
+
container = container.parent()?;
|
|
745
|
+
}
|
|
746
|
+
if container.kind() != "literal_value" {
|
|
747
|
+
return None;
|
|
748
|
+
}
|
|
749
|
+
// The container's own type may be a name, which the element type is read through.
|
|
750
|
+
let declared = key_type_of_literal_body(container, code)?;
|
|
751
|
+
let holder = resolve_named_type(declared, code);
|
|
752
|
+
let element = match holder.kind() {
|
|
753
|
+
"map_type" => holder.child_by_field_name("value"),
|
|
754
|
+
"slice_type" | "array_type" | "implicit_length_array_type" => {
|
|
755
|
+
holder.child_by_field_name("element")
|
|
756
|
+
}
|
|
757
|
+
_ => None,
|
|
758
|
+
}?;
|
|
759
|
+
Some(instantiated_type_argument(element, declared, code).unwrap_or(element))
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/// A generic container's element type may be one of its own parameters, which the instantiation
|
|
763
|
+
/// binds (`G[map[string]F]` makes the element of `type G[T any] []T` that map).
|
|
764
|
+
fn instantiated_type_argument<'t>(
|
|
765
|
+
element: Node<'t>,
|
|
766
|
+
declared: Node<'t>,
|
|
767
|
+
code: &Source<'t>,
|
|
768
|
+
) -> Option<Node<'t>> {
|
|
769
|
+
if element.kind() != "type_identifier" || declared.kind() != "generic_type" {
|
|
770
|
+
return None;
|
|
771
|
+
}
|
|
772
|
+
let base = declared.child_by_field_name("type")?;
|
|
773
|
+
let declaration = find_declared_type_spec(base, node_text(base, code), code)?;
|
|
774
|
+
let position = declared_type_parameters(declaration.child_by_field_name("type_parameters")?)
|
|
775
|
+
.iter()
|
|
776
|
+
.position(|(name, _)| node_text(*name, code) == node_text(element, code))?;
|
|
777
|
+
let argument =
|
|
778
|
+
*binding_children(declared.child_by_field_name("type_arguments")?).get(position)?;
|
|
779
|
+
Some(
|
|
780
|
+
named_children(argument)
|
|
781
|
+
.into_iter()
|
|
782
|
+
.next()
|
|
783
|
+
.unwrap_or(argument),
|
|
784
|
+
)
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/// The declaration of a type name, searched from the innermost scope outwards.
|
|
788
|
+
fn find_declared_type_spec<'t>(from: Node<'t>, name: &str, code: &Source<'t>) -> Option<Node<'t>> {
|
|
789
|
+
let mut scope = Some(from);
|
|
790
|
+
while let Some(current) = scope {
|
|
791
|
+
if let Some(spec) = find_type_spec(current, name, code) {
|
|
792
|
+
return Some(spec);
|
|
793
|
+
}
|
|
794
|
+
scope = current.parent();
|
|
795
|
+
}
|
|
796
|
+
None
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/// The literal's content as written (escapes kept), read from the grammar's content children so
|
|
800
|
+
/// delimiters and prefixes (`"""k"""`, `r"k"`, `%q(k)`, `:"k"`) never leak into it. JavaScript
|
|
801
|
+
/// splits the content into `string_fragment` and `escape_sequence` siblings; Ruby and Python emit
|
|
802
|
+
/// `string_content` (Python nests escapes inside it). Interpolation makes the key unstable.
|
|
803
|
+
fn find_string_literal_content(literal: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
804
|
+
let children = named_children(literal);
|
|
805
|
+
if children.iter().any(|child| child.kind() == "interpolation") {
|
|
806
|
+
return None;
|
|
807
|
+
}
|
|
808
|
+
// Go exposes no content node at all: it names only the escapes, so the concatenation is
|
|
809
|
+
// trusted only when the literal really spells its content out.
|
|
810
|
+
let mut content = String::new();
|
|
811
|
+
let mut has_content_node = false;
|
|
812
|
+
for child in &children {
|
|
813
|
+
match child.kind() {
|
|
814
|
+
"string_content" | "string_fragment" => {
|
|
815
|
+
has_content_node = true;
|
|
816
|
+
content.push_str(node_text(*child, code));
|
|
817
|
+
}
|
|
818
|
+
"escape_sequence" => content.push_str(node_text(*child, code)),
|
|
819
|
+
_ => {}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
if has_content_node && !content.is_empty() {
|
|
823
|
+
return Some(content);
|
|
824
|
+
}
|
|
825
|
+
// A grammar that exposes no content child (Go's string literals) keeps its delimiters in the
|
|
826
|
+
// text; an empty literal is left without a name.
|
|
827
|
+
let text = node_text(literal, code);
|
|
828
|
+
let quote = text
|
|
829
|
+
.chars()
|
|
830
|
+
.next()
|
|
831
|
+
.filter(|first| matches!(first, '"' | '\'' | '`'))?;
|
|
832
|
+
let delimiter_length = text.chars().take_while(|char| *char == quote).count();
|
|
833
|
+
if text.len() < 2 * delimiter_length {
|
|
834
|
+
return None;
|
|
835
|
+
}
|
|
836
|
+
let (delimiter, rest) = text.split_at(delimiter_length);
|
|
837
|
+
let inner = rest.strip_suffix(delimiter)?;
|
|
838
|
+
(!inner.is_empty()).then(|| inner.to_string())
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/// A compound assignment (`x += f`) does not bind the function to its target, so only a plain `=`
|
|
842
|
+
/// names it. Grammars with an `operator` field (C/C++, C#, Java) expose it directly; Go and Kotlin
|
|
843
|
+
/// have none, so the operator is the assignment's own anonymous token child.
|
|
844
|
+
fn is_plain_assignment(assignment: Node<'_>, code: &Source<'_>) -> bool {
|
|
845
|
+
match assignment.child_by_field_name("operator") {
|
|
846
|
+
Some(operator) => node_text(operator, code) == "=",
|
|
847
|
+
None => all_children(assignment)
|
|
848
|
+
.iter()
|
|
849
|
+
.any(|child| !child.is_named() && node_text(*child, code) == "="),
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/// The assigned identifier, or the member name of a JS member, Rust/C++ field, C# member, or Java
|
|
854
|
+
/// field access (`a.b.run` names `run`) or a C++ qualified name (`N::run`); subscripts (`o["run"]`)
|
|
855
|
+
/// name nothing.
|
|
856
|
+
fn find_assignment_target_name(assignment: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
857
|
+
if !is_plain_assignment(assignment, code) {
|
|
858
|
+
return None;
|
|
859
|
+
}
|
|
860
|
+
let target = assignment.child_by_field_name("left")?;
|
|
861
|
+
let name = match target.kind() {
|
|
862
|
+
"identifier" => target,
|
|
863
|
+
"member_expression" => target.child_by_field_name("property")?,
|
|
864
|
+
"field_expression" => target.child_by_field_name("field")?,
|
|
865
|
+
"member_access_expression" => target.child_by_field_name("name")?,
|
|
866
|
+
"field_access" => target.child_by_field_name("field")?,
|
|
867
|
+
"qualified_identifier" => return unwrap_declarator_name(Some(target), code),
|
|
868
|
+
_ => return None,
|
|
869
|
+
};
|
|
870
|
+
Some(node_text(name, code).to_string())
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/// The Kotlin assignment target: the variable itself or the member of a trailing navigation suffix
|
|
874
|
+
/// (`obj.run` names `run`); a trailing indexing suffix (`arr[0] = { }`) names nothing, like
|
|
875
|
+
/// subscripts in the other languages.
|
|
876
|
+
fn find_kotlin_assignment_name(assignment: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
877
|
+
if !is_plain_assignment(assignment, code) {
|
|
878
|
+
return None;
|
|
879
|
+
}
|
|
880
|
+
let target = first_named_child_of_kind(assignment, "directly_assignable_expression")?;
|
|
881
|
+
let children = named_children(target);
|
|
882
|
+
let holder = match children.last()? {
|
|
883
|
+
last if last.kind() == "navigation_suffix" => *last,
|
|
884
|
+
last if last.kind() == "simple_identifier" && children.len() == 1 => target,
|
|
885
|
+
_ => return None,
|
|
886
|
+
};
|
|
887
|
+
first_named_child_of_kind(holder, "simple_identifier")
|
|
285
888
|
.map(|name| node_text(name, code).to_string())
|
|
286
889
|
}
|
|
287
890
|
|
|
@@ -390,13 +993,200 @@ fn first_named_child_of_kind<'t>(node: Node<'t>, kind: &str) -> Option<Node<'t>>
|
|
|
390
993
|
.find(|child| child.kind() == kind)
|
|
391
994
|
}
|
|
392
995
|
|
|
996
|
+
/// Whether the node occupies its parent's value position: the `value` field, or no field at all
|
|
997
|
+
/// (a C# `variable_declarator` holds its initializer without one).
|
|
998
|
+
fn is_value_of_parent(node: Node<'_>, parent: Node<'_>) -> bool {
|
|
999
|
+
for index in 0..parent.child_count() {
|
|
1000
|
+
if parent
|
|
1001
|
+
.child(index)
|
|
1002
|
+
.is_some_and(|child| child.id() == node.id())
|
|
1003
|
+
{
|
|
1004
|
+
return matches!(
|
|
1005
|
+
parent.field_name_for_child(index as u32),
|
|
1006
|
+
None | Some("value")
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
false
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
/// A value group of a Python or Ruby parallel assignment: the value list itself, or a tuple, list,
|
|
1014
|
+
/// or array literal that destructuring takes apart (`a, (b, c) = x, (f, y)`, `a, b = [f, g]`). A set
|
|
1015
|
+
/// or a mapping is unordered, so it groups nothing positionally.
|
|
1016
|
+
fn is_value_group(node: Node<'_>) -> bool {
|
|
1017
|
+
matches!(
|
|
1018
|
+
node.kind(),
|
|
1019
|
+
"expression_list" | "right_assignment_list" | "tuple" | "list" | "array"
|
|
1020
|
+
)
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/// The matching target groups, which a nested value group is aligned against level by level.
|
|
1024
|
+
fn is_target_group(node: Node<'_>) -> bool {
|
|
1025
|
+
matches!(
|
|
1026
|
+
node.kind(),
|
|
1027
|
+
"pattern_list"
|
|
1028
|
+
| "left_assignment_list"
|
|
1029
|
+
| "tuple_pattern"
|
|
1030
|
+
| "list_pattern"
|
|
1031
|
+
| "destructured_left_assignment"
|
|
1032
|
+
)
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
/// A parallel assignment binds each value to the target at the same position, at every level of
|
|
1036
|
+
/// destructuring. The position this value takes in each group it sits in is collected on the way up
|
|
1037
|
+
/// to the assignment, then replayed on the target side.
|
|
1038
|
+
fn find_parallel_assignment_name(value: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
1039
|
+
let mut positions = Vec::new();
|
|
1040
|
+
let mut current = value;
|
|
1041
|
+
let assignment = loop {
|
|
1042
|
+
let parent = current.parent()?;
|
|
1043
|
+
if is_value_group(parent) {
|
|
1044
|
+
positions.push((parent, current));
|
|
1045
|
+
current = parent;
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
1048
|
+
if parent.kind() == "assignment"
|
|
1049
|
+
&& parent.child_by_field_name("right")?.id() == current.id()
|
|
1050
|
+
{
|
|
1051
|
+
break parent;
|
|
1052
|
+
}
|
|
1053
|
+
return None;
|
|
1054
|
+
};
|
|
1055
|
+
let mut target = assignment.child_by_field_name("left")?;
|
|
1056
|
+
for (values, child) in positions.iter().rev() {
|
|
1057
|
+
// Ruby spells a fully parenthesized target list as a `left_assignment_list` holding one
|
|
1058
|
+
// destructured group (`(a, b) = f, g`), which aligns against that inner one. A Python
|
|
1059
|
+
// singleton tuple is a real destructuring level instead, so it is left alone.
|
|
1060
|
+
while target.kind() == "left_assignment_list" {
|
|
1061
|
+
match binding_children(target).as_slice() {
|
|
1062
|
+
[only] if only.kind() == "destructured_left_assignment" => target = *only,
|
|
1063
|
+
_ => break,
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
if !is_target_group(target) {
|
|
1067
|
+
return None;
|
|
1068
|
+
}
|
|
1069
|
+
target = aligned_target(*values, *child, target)?;
|
|
1070
|
+
}
|
|
1071
|
+
find_assignment_target_text(target, code)
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
/// The target a value takes within one group. Comments are named children of both sides but bind
|
|
1075
|
+
/// nothing, so they are skipped. Values before every splat align from the left, and values after
|
|
1076
|
+
/// every splat align from the right against the targets that follow the starred one; a value with a
|
|
1077
|
+
/// splat on both sides, or one the star swallows, binds nothing knowable here.
|
|
1078
|
+
fn aligned_target<'t>(values: Node<'_>, value: Node<'_>, targets: Node<'t>) -> Option<Node<'t>> {
|
|
1079
|
+
let value_list = binding_children(values);
|
|
1080
|
+
let index = value_list
|
|
1081
|
+
.iter()
|
|
1082
|
+
.position(|child| child.id() == value.id())?;
|
|
1083
|
+
let splat_before = value_list[..index].iter().any(is_splat);
|
|
1084
|
+
let splat_after = value_list[index + 1..].iter().any(is_splat);
|
|
1085
|
+
let trailing = value_list.len() - 1 - index;
|
|
1086
|
+
let unpacks = matches!(
|
|
1087
|
+
targets.kind(),
|
|
1088
|
+
"pattern_list" | "tuple_pattern" | "list_pattern"
|
|
1089
|
+
);
|
|
1090
|
+
let targets = binding_children(targets);
|
|
1091
|
+
let splats: Vec<usize> = targets
|
|
1092
|
+
.iter()
|
|
1093
|
+
.enumerate()
|
|
1094
|
+
.filter(|(_, target)| is_splat(target))
|
|
1095
|
+
.map(|(index, _)| index)
|
|
1096
|
+
.collect();
|
|
1097
|
+
// Python unpacking binds nothing unless the counts can fit, since it raises instead; Ruby fills
|
|
1098
|
+
// the extra targets with nil, so its names hold either way. A splat among the values hides how
|
|
1099
|
+
// many they are, but never fewer than the values written beside it.
|
|
1100
|
+
let value_splat = value_list.iter().any(is_splat);
|
|
1101
|
+
let written_values = value_list.iter().filter(|value| !is_splat(value)).count();
|
|
1102
|
+
let counts_fit = !unpacks
|
|
1103
|
+
|| match (splats.is_empty(), value_splat) {
|
|
1104
|
+
(true, false) => value_list.len() == targets.len(),
|
|
1105
|
+
(true, true) => written_values <= targets.len(),
|
|
1106
|
+
(false, false) => value_list.len() + 1 >= targets.len(),
|
|
1107
|
+
(false, true) => true,
|
|
1108
|
+
};
|
|
1109
|
+
if !counts_fit {
|
|
1110
|
+
return None;
|
|
1111
|
+
}
|
|
1112
|
+
match splats.as_slice() {
|
|
1113
|
+
[] if !splat_before => targets.get(index).copied(),
|
|
1114
|
+
// Python unpacking takes exactly as many values as it has targets, so a value with no splat
|
|
1115
|
+
// after it sits at a fixed distance from the end however the earlier splat expands.
|
|
1116
|
+
[] if unpacks && !splat_after => targets.get(targets.len() - 1 - trailing).copied(),
|
|
1117
|
+
&[splat] if !splat_before && index < splat => targets.get(index).copied(),
|
|
1118
|
+
&[splat] if !splat_after && trailing < targets.len() - splat - 1 => {
|
|
1119
|
+
// Aligning from the right needs the values to reach the trailing targets, which the
|
|
1120
|
+
// values written beside any splat can already guarantee. Otherwise only Python assures
|
|
1121
|
+
// it, by failing the assignment, while Ruby fills the trailing targets from the left
|
|
1122
|
+
// when it underflows.
|
|
1123
|
+
let reaches_trailing_targets = written_values + 1 >= targets.len()
|
|
1124
|
+
|| value_list.iter().any(|child| child.kind() == "list_splat");
|
|
1125
|
+
if reaches_trailing_targets {
|
|
1126
|
+
targets.get(targets.len() - 1 - trailing).copied()
|
|
1127
|
+
} else if !splat_before && targets[splat].kind() == "rest_assignment" {
|
|
1128
|
+
// Ruby empties the star and fills the trailing targets from the left when the
|
|
1129
|
+
// values run out, so each one binds the next target (`a, *r, c, d = x, f` binds
|
|
1130
|
+
// `f` to `c`); Python fails such an assignment instead.
|
|
1131
|
+
targets.get(index + 1).copied()
|
|
1132
|
+
} else {
|
|
1133
|
+
None
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
_ => None,
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
/// A splat on either side of a parallel assignment (`*xs`, `*rest`).
|
|
1141
|
+
fn is_splat(node: &Node<'_>) -> bool {
|
|
1142
|
+
matches!(
|
|
1143
|
+
node.kind(),
|
|
1144
|
+
"splat_argument" | "rest_assignment" | "list_splat" | "list_splat_pattern"
|
|
1145
|
+
)
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
fn binding_children<'t>(node: Node<'t>) -> Vec<Node<'t>> {
|
|
1149
|
+
named_children(node)
|
|
1150
|
+
.into_iter()
|
|
1151
|
+
.filter(|child| !crate::ncss::COMMENT_NODE_TYPES.contains(&child.kind()))
|
|
1152
|
+
.collect()
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/// The name a Ruby or Python assignment target binds: a local, constant, or Ruby instance, class or
|
|
1156
|
+
/// global variable; the method of a Ruby attribute writer (`self.run = ...`); or a Python attribute
|
|
1157
|
+
/// (`obj.run = ...`). A subscript or a splat has no stable name and binds none.
|
|
1158
|
+
fn find_assignment_target_text(target: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
1159
|
+
match target.kind() {
|
|
1160
|
+
"identifier" | "constant" | "instance_variable" | "class_variable" | "global_variable" => {
|
|
1161
|
+
Some(node_text(target, code).to_string())
|
|
1162
|
+
}
|
|
1163
|
+
"call" if target.child_by_field_name("receiver").is_some() => target
|
|
1164
|
+
.child_by_field_name("method")
|
|
1165
|
+
.map(|method| node_text(method, code).to_string()),
|
|
1166
|
+
"attribute" => target
|
|
1167
|
+
.child_by_field_name("attribute")
|
|
1168
|
+
.map(|attribute| node_text(attribute, code).to_string()),
|
|
1169
|
+
_ => None,
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/// The name a Ruby or Python single assignment binds. Ruby also allows a target list with one
|
|
1174
|
+
/// value: a value that is not an array goes to the first target that is not the star, descending
|
|
1175
|
+
/// into a nested group (`a, b = -> { 1 }` and `*a, b = -> { 1 }` both bind the lambda to a name).
|
|
393
1176
|
fn find_ruby_assignment_name(assignment: Node<'_>, code: &Source<'_>) -> Option<String> {
|
|
394
|
-
let
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
1177
|
+
let left = assignment.child_by_field_name("left")?;
|
|
1178
|
+
let mut target = left;
|
|
1179
|
+
if matches!(
|
|
1180
|
+
left.kind(),
|
|
1181
|
+
"left_assignment_list" | "destructured_left_assignment"
|
|
1182
|
+
) {
|
|
1183
|
+
while is_target_group(target) {
|
|
1184
|
+
target = binding_children(target)
|
|
1185
|
+
.into_iter()
|
|
1186
|
+
.find(|child| !is_splat(child))?;
|
|
1187
|
+
}
|
|
399
1188
|
}
|
|
1189
|
+
find_assignment_target_text(target, code)
|
|
400
1190
|
}
|
|
401
1191
|
|
|
402
1192
|
fn is_ruby_lambda_call(node: Node<'_>, code: &Source<'_>) -> bool {
|
|
@@ -422,7 +1212,10 @@ fn find_go_func_literal_name(
|
|
|
422
1212
|
.collect();
|
|
423
1213
|
let value_index = values.iter().position(|child| child.id() == node.id())?;
|
|
424
1214
|
|
|
425
|
-
if holder.kind() == "
|
|
1215
|
+
if holder.kind() == "assignment_statement" && !is_plain_assignment(holder, code) {
|
|
1216
|
+
return None;
|
|
1217
|
+
}
|
|
1218
|
+
if holder.kind() == "short_var_declaration" || holder.kind() == "assignment_statement" {
|
|
426
1219
|
let targets = holder.child_by_field_name("left").map(|left| {
|
|
427
1220
|
named_children(left)
|
|
428
1221
|
.into_iter()
|
|
@@ -448,12 +1241,16 @@ fn find_go_func_literal_name(
|
|
|
448
1241
|
None
|
|
449
1242
|
}
|
|
450
1243
|
|
|
451
|
-
/// Go's blank identifier `_` discards the value and creates no callable binding
|
|
1244
|
+
/// Go's blank identifier `_` discards the value and creates no callable binding; a selector target
|
|
1245
|
+
/// (`m.run = func...`) names its field.
|
|
452
1246
|
fn as_go_binding_name(target: Option<Node<'_>>, code: &Source<'_>) -> Option<String> {
|
|
453
1247
|
match target {
|
|
454
1248
|
Some(target) if target.kind() == "identifier" && node_text(target, code) != "_" => {
|
|
455
1249
|
Some(node_text(target, code).to_string())
|
|
456
1250
|
}
|
|
1251
|
+
Some(target) if target.kind() == "selector_expression" => target
|
|
1252
|
+
.child_by_field_name("field")
|
|
1253
|
+
.map(|field| node_text(field, code).to_string()),
|
|
457
1254
|
_ => None,
|
|
458
1255
|
}
|
|
459
1256
|
}
|
|
@@ -496,6 +1293,15 @@ fn is_react_component_wrapper_call(node: Node<'_>, code: &Source<'_>) -> bool {
|
|
|
496
1293
|
})
|
|
497
1294
|
}
|
|
498
1295
|
|
|
1296
|
+
/// Whether the declaration around this declarator deduces its type (`auto`), which makes the
|
|
1297
|
+
/// variable the closure itself rather than something constructed from it.
|
|
1298
|
+
fn declares_deduced_type(declarator: Node<'_>) -> bool {
|
|
1299
|
+
declarator
|
|
1300
|
+
.parent()
|
|
1301
|
+
.and_then(|declaration| declaration.child_by_field_name("type"))
|
|
1302
|
+
.is_some_and(|declared| declared.kind() == "placeholder_type_specifier")
|
|
1303
|
+
}
|
|
1304
|
+
|
|
499
1305
|
/// Unwraps a C/C++ declarator chain to the declared name; see unwrapDeclaratorName in metrics.ts.
|
|
500
1306
|
fn unwrap_declarator_name(declarator: Option<Node<'_>>, code: &Source<'_>) -> Option<String> {
|
|
501
1307
|
let mut current = declarator;
|