* Check if a reference name has any possible match in the codebase. * Uses the pre-built knownNames set to skip expensive resolution * for names that definitely don't exist as symbols.
(name: string)
| 764 | * for names that definitely don't exist as symbols. |
| 765 | */ |
| 766 | private hasAnyPossibleMatch(name: string): boolean { |
| 767 | if (!this.knownNames) return true; // no pre-filter available |
| 768 | |
| 769 | // Direct name match |
| 770 | if (this.knownNames.has(name)) return true; |
| 771 | |
| 772 | // For qualified names like "obj.method" or "Class::method", check the parts |
| 773 | const dotIdx = name.indexOf('.'); |
| 774 | if (dotIdx > 0) { |
| 775 | const receiver = name.substring(0, dotIdx); |
| 776 | const member = name.substring(dotIdx + 1); |
| 777 | if (this.knownNames.has(receiver) || this.knownNames.has(member)) return true; |
| 778 | // Also check capitalized receiver (instance-method resolution) |
| 779 | const capitalized = receiver.charAt(0).toUpperCase() + receiver.slice(1); |
| 780 | if (this.knownNames.has(capitalized)) return true; |
| 781 | // JVM FQN: `com.example.foo.Bar` — the only useful segment is the |
| 782 | // last one (`Bar`); the earlier check finds `example.foo.Bar` which |
| 783 | // never matches a node name. |
| 784 | const lastDot = name.lastIndexOf('.'); |
| 785 | if (lastDot > dotIdx) { |
| 786 | const tail = name.substring(lastDot + 1); |
| 787 | if (tail && this.knownNames.has(tail)) return true; |
| 788 | } |
| 789 | } |
| 790 | const colonIdx = name.indexOf('::'); |
| 791 | if (colonIdx > 0) { |
| 792 | const receiver = name.substring(0, colonIdx); |
| 793 | const member = name.substring(colonIdx + 2); |
| 794 | if (this.knownNames.has(receiver) || this.knownNames.has(member)) return true; |
| 795 | // Multi-segment path `a::b::c` (a Rust/C++ module call like |
| 796 | // `database::profiles::find`) — the only segment that names a symbol is |
| 797 | // the last (`c`); `member` above is `b::c`, which never matches a node |
| 798 | // name, so without this the pre-filter drops the ref before the Rust path |
| 799 | // resolver ever sees it. Mirror the dotted-name leaf check above. |
| 800 | const lastColon = name.lastIndexOf('::'); |
| 801 | if (lastColon > colonIdx) { |
| 802 | const tail = name.substring(lastColon + 2); |
| 803 | if (tail && this.knownNames.has(tail)) return true; |
| 804 | } |
| 805 | } |
| 806 | |
| 807 | // Lua/Luau method calls use a single `:` (`lg:log`); R uses `$` (`lg$log`). |
| 808 | // Check the member (and receiver) around these separators too, so the ref |
| 809 | // isn't dropped here before the method-call resolver ever sees it. The `:` |
| 810 | // case is skipped when the name actually contains `::` (handled above). |
| 811 | for (const sep of [':', '$']) { |
| 812 | if (sep === ':' && name.includes('::')) continue; |
| 813 | const sepIdx = name.indexOf(sep); |
| 814 | if (sepIdx > 0) { |
| 815 | const receiver = name.substring(0, sepIdx); |
| 816 | const member = name.substring(sepIdx + 1); |
| 817 | if (this.knownNames.has(member) || this.knownNames.has(receiver)) return true; |
| 818 | const capitalized = receiver.charAt(0).toUpperCase() + receiver.slice(1); |
| 819 | if (this.knownNames.has(capitalized)) return true; |
| 820 | } |
| 821 | } |
| 822 | |
| 823 | // For path-like references (e.g., "snippets/drawer-menu.liquid"), check the filename |
no test coverage detected