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