( node: SyntaxNode, type: string, source: string )
| 610 | } |
| 611 | |
| 612 | function normalizeSpecial( |
| 613 | node: SyntaxNode, |
| 614 | type: string, |
| 615 | source: string |
| 616 | ): NormalizedRef[] { |
| 617 | switch (type) { |
| 618 | // Java method references. Receiver decides the resolution route (#808): |
| 619 | // `this::run0` / `super::close` → `this.<m>` (class-scoped resolver; |
| 620 | // super rides the inherited-member supertype pass) |
| 621 | // `Type::method` (capitalized) → qualified `Type::method` (suffix- |
| 622 | // matched against that type's members, cross-file capable) |
| 623 | // `variable::method` → nothing (receiver type unknown statically — |
| 624 | // the deferred obj.method class) |
| 625 | case 'method_reference': { |
| 626 | let last: SyntaxNode | null = null; |
| 627 | for (let i = 0; i < node.namedChildCount; i++) { |
| 628 | const child = node.namedChild(i); |
| 629 | if (child && child.type === 'identifier') last = child; |
| 630 | } |
| 631 | if (!last) return []; |
| 632 | const m = getNodeText(last, source); |
| 633 | const text = getNodeText(node, source); |
| 634 | if (text.startsWith('this::') || text.startsWith('super::')) { |
| 635 | return [{ name: `this.${m}`, node: last }]; |
| 636 | } |
| 637 | const recv = text.match(/^([A-Z][A-Za-z0-9_]*)\s*::/); |
| 638 | if (recv) { |
| 639 | // `Type::method` — but `Type::new` (constructor ref) has no method |
| 640 | // node to land on; let the stoplist drop it via the bare name. |
| 641 | return m === 'new' ? [] : [{ name: `${recv[1]}::${m}`, node: last }]; |
| 642 | } |
| 643 | return []; |
| 644 | } |
| 645 | |
| 646 | // Kotlin `::targetCb` (one part) / `OtherClass::handle` (two parts — |
| 647 | // receiver is a type_identifier; lowercase receivers are variables, the |
| 648 | // deferred obj.method class). |
| 649 | case 'callable_reference': { |
| 650 | let receiver: SyntaxNode | null = null; |
| 651 | let member: SyntaxNode | null = null; |
| 652 | for (let i = 0; i < node.namedChildCount; i++) { |
| 653 | const child = node.namedChild(i); |
| 654 | if (!child) continue; |
| 655 | if (child.type === 'type_identifier') receiver = child; |
| 656 | if (child.type === 'simple_identifier') member = child; |
| 657 | } |
| 658 | if (!member) return []; |
| 659 | const m = getNodeText(member, source); |
| 660 | if (!receiver) return [{ name: m, node: member }]; // ::topLevelFn |
| 661 | const recvText = getNodeText(receiver, source); |
| 662 | return /^[A-Z]/.test(recvText) |
| 663 | ? [{ name: `${recvText}::${m}`, node: member }] |
| 664 | : []; // variable::method — unknown receiver type |
| 665 | } |
| 666 | |
| 667 | // Kotlin `this::fire` parses as navigation_expression with a `::fire` |
| 668 | // navigation_suffix — route through the class-scoped `this.` resolver. |
| 669 | // Ordinary `a.b` navigation (and any non-`this` receiver) MUST yield |
no test coverage detected