( receiverName: string, ref: UnresolvedRef, context: ResolutionContext, depth = 0, )
| 685 | } |
| 686 | |
| 687 | function inferCppReceiverType( |
| 688 | receiverName: string, |
| 689 | ref: UnresolvedRef, |
| 690 | context: ResolutionContext, |
| 691 | depth = 0, |
| 692 | ): string | null { |
| 693 | // Per-file lines cache when available — this runs per `receiver->method()` |
| 694 | // ref and re-splitting the file each time is the same quadratic as the |
| 695 | // shared inferrer's (#1122). |
| 696 | const lines = context.getFileLines |
| 697 | ? context.getFileLines(ref.filePath) |
| 698 | : (context.readFile(ref.filePath)?.split(/\r?\n/) ?? null); |
| 699 | if (!lines || lines.length === 0) return null; |
| 700 | |
| 701 | const callLineIndex = Math.max(0, Math.min(lines.length - 1, ref.line - 1)); |
| 702 | const escapedReceiver = receiverName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); |
| 703 | const receiverPattern = new RegExp(`\\b${escapedReceiver}\\b`); |
| 704 | const declaratorRegex = buildDeclaratorRegex(escapedReceiver); |
| 705 | |
| 706 | for (let i = callLineIndex; i >= 0; i--) { |
| 707 | const line = lines[i]; |
| 708 | if (!line || !receiverPattern.test(line)) continue; |
| 709 | |
| 710 | const declaratorMatch = line.match(declaratorRegex); |
| 711 | if (declaratorMatch) { |
| 712 | const normalized = normalizeCppTypeName(declaratorMatch[1] ?? ''); |
| 713 | if (normalized === 'auto') { |
| 714 | // `auto x = Foo::instance();` — the declared type is deduced; recover it |
| 715 | // from the initializer (call return type / construction) (#645). |
| 716 | const initType = inferCppAutoInitializerType(line, receiverName, ref, context, depth); |
| 717 | if (initType) return initType; |
| 718 | // No usable initializer on this line — keep scanning earlier ones. |
| 719 | } else if (normalized) { |
| 720 | return normalized; |
| 721 | } |
| 722 | } |
| 723 | } |
| 724 | |
| 725 | const headerCandidates = [ |
| 726 | ref.filePath.replace(/\.(?:c|cc|cpp|cxx)$/i, '.h'), |
| 727 | ref.filePath.replace(/\.(?:c|cc|cpp|cxx)$/i, '.hpp'), |
| 728 | ref.filePath.replace(/\.(?:c|cc|cpp|cxx)$/i, '.hxx'), |
| 729 | ].filter((candidate, index, arr) => arr.indexOf(candidate) === index && candidate !== ref.filePath); |
| 730 | |
| 731 | for (const headerPath of headerCandidates) { |
| 732 | if (!context.fileExists(headerPath)) continue; |
| 733 | const headerLines = context.getFileLines |
| 734 | ? context.getFileLines(headerPath) |
| 735 | : (context.readFile(headerPath)?.split(/\r?\n/) ?? null); |
| 736 | if (!headerLines) continue; |
| 737 | |
| 738 | for (const line of headerLines) { |
| 739 | if (!receiverPattern.test(line)) continue; |
| 740 | const declaratorMatch = line.match(declaratorRegex); |
| 741 | if (!declaratorMatch) continue; |
| 742 | const normalized = normalizeCppTypeName(declaratorMatch[1] ?? ''); |
| 743 | if (normalized && normalized !== 'auto') return normalized; |
| 744 | } |
no test coverage detected