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.
@@ -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 parent = node.parent()?;
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
- if node.kind() == "lambda_expression" && parent.kind() == "init_declarator" {
242
- return unwrap_declarator_name(parent.child_by_field_name("declarator"), code);
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...` or `var add = func...` takes the identifier at
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(node, parent, code);
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
- return match parent.parent() {
269
- Some(grandparent) if grandparent.kind() == "assignment" => {
270
- find_ruby_assignment_name(grandparent, code)
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
- .child_by_field_name(field_name)
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(&current.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 left_node = assignment.child_by_field_name("left")?;
395
- if left_node.kind() == "identifier" || left_node.kind() == "constant" {
396
- Some(node_text(left_node, code).to_string())
397
- } else {
398
- None
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() == "short_var_declaration" {
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;