From d4b9c2b4e98e600f72e321ba22aa3d50cc2f803f Mon Sep 17 00:00:00 2001 From: Colby Mchenry Date: Wed, 14 Oct 2026 06:31:57 +0000 Subject: [PATCH] fix(cpp): a line starting with `*` is read as code, and block comments and string literals are blanked before declarations are read (#2552) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The line-at-a-time C++ declaration scans took any line starting with `*` for a comment continuation, skipping rocksdb's `const auto& el =` / `*reinterpret_cast*>(jhandle);`, and read code commented out in `/* … */` and the text of string literals (protobuf's `[[deprecated("Please use SpaceUsedLong() instead")]]`) as declarations. Block comments are now found in the per-file pass that finds raw strings, and every scan blanks them, with string contents, before reading a line. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + __tests__/cpp-block-comment-lines.test.ts | 209 ++++++++++++++++++++++ src/resolution/cpp-raw-strings.ts | 18 +- src/resolution/name-matcher.ts | 72 ++++---- 4 files changed, 254 insertions(+), 46 deletions(-) create mode 100644 __tests__/cpp-block-comment-lines.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index f694b4f07..0f5b7917c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -111,6 +111,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - In C++, a call whose receiver's type can't be read no longer links to a method of a class nested in the class the receiver's name suggests, or of a class whose name only contains that name. Before, such a guess took any method of the call's name whose qualified name contained the class's name: protocolbuffers/protobuf's `descriptor->file()` in a template reached `Descriptor::ExtensionRange::file`, and facebook/rocksdb's `CompositeEnv::IsInstanceOf(name)` reached `CompositeEnvWrapper::IsInstanceOf`. A call written through a class, like `PessimisticTransaction::PopSavePoint()` in an override, now links to the method that class inherits, here `TransactionBaseImpl::PopSavePoint`. Re-index C++ projects after upgrading. - In C++, an alias that names a class through a class derived from the one that declares it, like protocolbuffers/protobuf's `using ConformanceRequestSetting = BinaryAndJsonConformanceSuite::ConformanceRequestSetting;`, now leads to that class, `ConformanceTestSuite::ConformanceRequestSetting`. Before, an alias was followed only to a class declared under exactly the name it spells, so objects created through this one linked to the alias rather than the class. Re-index C++ projects after upgrading. - In C++, a name written in a googletest `TEST_F` or `TEST_P` body now finds the test fixture's nested types, those of its base classes included, before anything in the surrounding namespace, as C++ does for the member function such a body is: facebook/rocksdb's `new TestUserDefinedIndexFactory()` links to the class nested in the fixture's base. A class declared inside a test body, or inside any function, is now only found from that function, so the local classes many RocksDB tests each declare under one name, like `FakeWF` or `MyManager`, no longer stand in for one another, and a call like `fwf->SetIOError(true)` reaches the `FakeWF` its own test declares. Re-index C++ projects after upgrading. +- In C++, a declaration continued on a line that starts with `*`, like facebook/rocksdb's `const auto& el =` over `*reinterpret_cast*>(jhandle);`, is now read, so calls such as `el->OnFlushCompleted(…)` link to `EventListener`'s methods; such a line used to be skipped as part of a comment. Code commented out in a `/* … */` block, and the text of a string literal, like protocolbuffers/protobuf's `[[deprecated("Please use SpaceUsedLong() instead")]]`, are no longer read as declarations, which had sent a call to the wrong method or to none. Re-index C++ projects after upgrading. - Framework support now switches on when an app's `package.json` sits three or more folders deep, as with an ASP.NET solution's Angular or React app in `src/Web/ClientApp/`, or a React app in a workspace folder like `web/ui/mantine-ui/`. Before, only the project root and the first two folder levels were read, so frameworks such as Angular, React Router, Vue Router, SvelteKit and TanStack Router could go undetected there: an Angular app's routes, its `routerLink` and `router.navigate(…)` navigation and its template's event bindings were missing, so a handler used only from a `(click)` binding had no callers, and a React app's `navigate('/login')` calls didn't reach their route. Re-index projects laid out this way after upgrading. - In a repository that mixes languages, a web framework's naming conventions now link only code written in that framework's own languages. Before, Express's also linked Go and Python code: in etcd a Go method's `*zap.Logger` result type linked to the method itself, and in Harbor a Python `self.jaeger.validate()` call linked to another class's `validate`. Links that cross languages on purpose, like React Native and Expo native modules, Swift and Objective-C, Drupal's `routing.yml`, SvelteKit's `$lib/…` imports in `.ts` files and a Razor page's `@model`, are unchanged. - In C and C++, a call written from the global scope, like `::leveldb::RepairDB(…)` or `::hpb::CreateMessage(…)`, now links to the function of exactly that name, and one through a namespace alias, like protobuf's `::_pbi::…` calls, to the function in the aliased namespace. Before, these calls were dropped unless the repository also held Swift and Objective-C files, and even then a name spelled out in full, like `::google::protobuf::internal::TSanWrite(…)`, linked to nothing, while `::operator delete(…)` linked to one class's own `operator delete`. A global call never links to a function or method of the same name inside a namespace or class, such as a wrapper `open()` that calls the C library's `::open(…)`, and `::std::…` and `::testing::…` calls stay unlinked unless that library's source is part of your project. Re-index C and C++ projects after upgrading. diff --git a/__tests__/cpp-block-comment-lines.test.ts b/__tests__/cpp-block-comment-lines.test.ts new file mode 100644 index 000000000..fde985067 --- /dev/null +++ b/__tests__/cpp-block-comment-lines.test.ts @@ -0,0 +1,209 @@ +/** + * The C++ declaration scans read source a line at a time and took any line + * starting with `*` for the middle of a block comment, and any other line + * for code. So a declaration continued onto a line starting with `*` was + * missed — `Widget` above `*widget_;` — and code commented out inside + * `/* … *\/` without leading stars was read as a declaration. Block comments + * are now found by reading the file once, as raw strings are, and blanked, + * with the contents of string literals, in every scan that reads lines. + */ +import { describe, it, expect, afterEach } from 'vitest'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { CodeGraph } from '../src'; + +const roots: string[] = []; +afterEach(() => { + for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true }); +}); + +async function indexed(files: Record): Promise { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-cpp-block-comments-')); + roots.push(root); + for (const [rel, content] of Object.entries(files)) { + fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true }); + fs.writeFileSync(path.join(root, rel), content); + } + return CodeGraph.init(root, { index: true }); +} + +const lines = (...text: string[]): string => [...text, ''].join('\n'); + +/** What a function's or method's calls reach, as `qualifiedName @confidence`. */ +function callees(cg: CodeGraph, qualifiedName: string): string[] { + const from = [...cg.getNodesByKind('method'), ...cg.getNodesByKind('function')].find((n) => n.qualifiedName === qualifiedName)!; + return cg.getOutgoingEdgesFrom([from.id], ['calls']) + .map((e) => `${cg.getNode(e.target)?.qualifiedName} @${(e.metadata as { confidence?: number } | undefined)?.confidence}`) + .sort(); +} + +/** The same, with how each call was resolved. */ +function resolutions(cg: CodeGraph, qualifiedName: string): string[] { + const from = [...cg.getNodesByKind('method'), ...cg.getNodesByKind('function')].find((n) => n.qualifiedName === qualifiedName)!; + return cg.getOutgoingEdgesFrom([from.id], ['calls']) + .map((e) => { + const meta = e.metadata as { confidence?: number; resolvedBy?: string } | undefined; + return `${cg.getNode(e.target)?.qualifiedName} @${meta?.confidence} ${meta?.resolvedBy}`; + }) + .sort(); +} + +const SHAPES = lines( + 'class Widget {', + ' public:', + ' void Draw() {}', + '};', + 'class Canvas {', + ' public:', + ' void Draw() {}', + '};', +); + +describe('C++ declarations around block comments', () => { + it('reads a declaration continued on a line that starts with `*`', async () => { + const cg = await indexed({ + 'shapes.h': SHAPES, + 'painter.h': lines( + '#include "shapes.h"', + 'class Painter {', + ' public:', + ' void Paint() { widget_->Draw(); }', + ' private:', + ' Widget', + ' *widget_;', + '};', + ), + }); + try { + expect(callees(cg, 'Painter::Paint')).toEqual(['Widget::Draw @0.9']); + } finally { + cg.close(); + } + }); + + it('reads no declaration from code commented out in a block comment', async () => { + const cg = await indexed({ + 'shapes.h': SHAPES, + 'run.cc': lines( + '#include "shapes.h"', + 'void Run(Canvas* c) {', + ' /*', + ' Widget* c = nullptr;', + ' */', + ' c->Draw();', + '}', + 'void Doc(Canvas* w) {', + ' /*', + ' * Widget* w = nullptr;', + ' */', + ' w->Draw();', + '}', + ), + }); + try { + expect(callees(cg, 'Run')).toEqual(['Canvas::Draw @0.9']); + expect(callees(cg, 'Doc')).toEqual(['Canvas::Draw @0.9']); + } finally { + cg.close(); + } + }); + + it("reads no member, base, using-declaration or local from a class's or function's comments", async () => { + const cg = await indexed({ + 'others.h': lines( + 'class HolderBase {', + ' public:', + ' void GetMessage() {}', + '};', + 'class Old {', + ' public:', + ' void OnlyOld() {}', + '};', + 'class Base {};', + 'class Other {};', + 'template class TBase {', + ' public:', + ' void Insert(int x) {}', + '};', + ), + 'holders.h': lines( + '#include "others.h"', + 'class Holder : public HolderBase {', + ' public:', + ' /*', + ' LOCAL_VAR_ACCESSOR(Message*, Message);', + ' */', + ' void Run() { GetMessage(); }', + '};', + 'class Derived : public Base,', + ' /*', + ' public Old,', + ' */', + ' public Other {', + '};', + 'template class TDerived : public TBase {', + ' public:', + ' /*', + ' using TBase::Insert;', + ' */', + ' void Add() { Insert(1); }', + '};', + 'class Db {', + ' public:', + ' void Flush() {}', + ' void Run() {', + ' /*', + ' auto Flush = [] {};', + ' */', + ' Flush();', + ' }', + '};', + 'void UseDerived(Derived* d) { d->OnlyOld(); }', + ), + }); + try { + // No macro of `Holder`'s might declare `GetMessage`: C++ lookup goes + // on to the base's. + expect(resolutions(cg, 'Holder::Run')).toEqual(['HolderBase::GetMessage @0.9 instance-method']); + // `Derived`'s bases are `Base` and `Other`, the two the index links, + // neither with an `OnlyOld`: the call is not guessed onto `Old`'s. + expect(resolutions(cg, 'UseDerived')).toEqual([]); + // No using-declaration brings the dependent base's `Insert` in: only + // its name, which nothing else declares, links it. + expect(resolutions(cg, 'TDerived::Add')).toEqual(['TBase::Insert @0.9 exact-match']); + // `Flush` is no local: C++ lookup finds the member. + expect(resolutions(cg, 'Db::Run')).toEqual(['Db::Flush @0.9 instance-method']); + } finally { + cg.close(); + } + }); + + it('reads no local from a string literal', async () => { + // protobuf's `[[deprecated("Please use SpaceUsedLong() instead")]]` and + // rocksdb's `"Unknown code(%d): "` read as declarations of a local. + const cg = await indexed({ + 'status.h': lines( + 'class Status {', + ' public:', + ' int code() const { return 0; }', + ' [[deprecated("Please use code() instead")]] int Code() const { return code(); }', + ' void ToString() const;', + '};', + ), + 'status.cc': lines( + '#include "status.h"', + 'void Status::ToString() const {', + ' char tmp[30];', + ' snprintf(tmp, sizeof(tmp), "Unknown code(%d): ", code());', + '}', + ), + }); + try { + expect(resolutions(cg, 'Status::Code')).toEqual(['Status::code @0.9 instance-method']); + expect(resolutions(cg, 'Status::ToString')).toEqual(['Status::code @0.9 instance-method']); + } finally { + cg.close(); + } + }); +}); diff --git a/src/resolution/cpp-raw-strings.ts b/src/resolution/cpp-raw-strings.ts index 3eaf1b499..a951b6243 100644 --- a/src/resolution/cpp-raw-strings.ts +++ b/src/resolution/cpp-raw-strings.ts @@ -10,11 +10,13 @@ * found by reading the file once, as the compiler does: comments, ordinary * string and character literals, and raw strings with their delimiters. * An ordinary string can't span lines, so its contents are blanked on the - * line being read. + * line being read. Block comments are found in the same pass and blanked + * too: a line inside one is no code whatever it starts with, and a line + * outside one is code even when it starts with `*` (`*out = value;`). */ import type { ResolutionContext } from './types'; -/** Column ranges, `[start, end)`, a raw string literal takes on a line (its quotes and delimiters included). */ +/** Column ranges, `[start, end)`, a raw string literal or a block comment takes on a line (its quotes, delimiters and comment markers included). */ type Ranges = Array<[number, number]>; const RAW_STRING_LINES = new WeakMap>>(); @@ -27,17 +29,17 @@ export function clearCppRawStringMemos(context: ResolutionContext): void { const isWordChar = (c: string | undefined): boolean => c !== undefined && /[A-Za-z0-9_$]/.test(c); /** - * The 1-based lines of a C or C++ file that raw string literals cover, each - * with the columns they take. Empty for a file with none. + * The 1-based lines of a C or C++ file that raw string literals or block + * comments cover, each with the columns they take. Empty for a file with none. */ -export function cppRawStringLines(file: string, context: ResolutionContext): Map { +export function cppNonCodeLines(file: string, context: ResolutionContext): Map { let memo = RAW_STRING_LINES.get(context); if (!memo) RAW_STRING_LINES.set(context, (memo = new Map())); const hit = memo.get(file); if (hit) return hit; const lines = new Map(); const text = context.readFile(file); - if (text && text.includes('R"')) { + if (text && (text.includes('R"') || text.includes('/*'))) { let line = 1; let column = 0; const n = text.length; @@ -74,7 +76,9 @@ export function cppRawStringLines(file: string, context: ResolutionContext): Map advance(end < 0 ? n : end); } else if (c === '/' && next === '*') { const end = text.indexOf('*/', i + 2); - advance(end < 0 ? n : end + 2); + const to = end < 0 ? n : end + 2; + mark(i, line, column, to); + advance(to); } else if (c === '"') { // `R"`, `u8R"`, `uR"`, `UR"`, `LR"`, not part of a longer name. const prefix = /(?:u8|[uUL])?R$/.exec(text.slice(Math.max(0, i - 3), i)); diff --git a/src/resolution/name-matcher.ts b/src/resolution/name-matcher.ts index 28c43980d..fccea2444 100644 --- a/src/resolution/name-matcher.ts +++ b/src/resolution/name-matcher.ts @@ -16,7 +16,7 @@ import { isDartLocallyBound } from './dart-local-scope'; import { breakVbTie, isVbMemberInScope, isVbNestedTypeInScope, isVbTypeQualifiedBy, matchVbTypedCall, preferVbProject, sameVbProject } from './vbnet-receivers'; import { cppAliasedClass, cppAliasedTypeName, cppAliasedTypeWritten, cppDeclaresLocalAlias, cppParentScope, cppScopesWithin, cppTemplateArguments, cppTemplateParameters, cppTypeSegments, isCppPointerType, resolveCppAliasedType, stripCppTemplateArguments, type CppAliasedType } from './cpp-type-aliases'; import { clearCppNamespaceMemos, cppMacroNamespaceFrames, cppMacroNamespaceHeads, cppNamespaceAliases } from './cpp-namespaces'; -import { clearCppRawStringMemos, cppLiteralsBlanked, cppRawStringLines } from './cpp-raw-strings'; +import { clearCppRawStringMemos, cppLiteralsBlanked, cppNonCodeLines } from './cpp-raw-strings'; import { cppClassWritten, type CppNameSite } from './cpp-supertypes'; import { cppIncludedFile, cppIncluders } from './cpp-includers'; import { isTestPath } from '../search/query-utils'; @@ -9433,7 +9433,7 @@ function cppDataMemberIn(decl: Node, name: string, context: ResolutionContext): const declarator = buildDeclaratorRegex(escaped); const end = Math.min(decl.endLine ?? decl.startLine, lines.length); const nested = cppNestedSpans(decl, end, context); - const rawLines = cppRawStringLines(decl.filePath, context); + const rawLines = cppNonCodeLines(decl.filePath, context); const depths = cppClassParenDepths(decl, lines, end, rawLines, context); for (let l = decl.startLine; l <= end; l++) { if (!word.test(lines[l - 1]!) || nested.some(([start, stop]) => l >= start && l <= stop)) continue; @@ -9455,7 +9455,6 @@ function cppDataMemberIn(decl: Node, name: string, context: ResolutionContext): } if (!m || code!.slice(m.index + m[0].length).startsWith('(')) continue; let depth = depths[from - decl.startLine]!; - if (depth < 0) continue; for (const c of code!.slice(from === decl.startLine ? decl.startColumn : 0, m.index)) { if (c === '(') depth++; else if (c === ')') depth--; @@ -9473,16 +9472,15 @@ function cppDataMemberIn(decl: Node, name: string, context: ResolutionContext): /** * How many parentheses are open where each line of the C++ class - * declaration `decl`, which ends on line `end`, begins, or -1 for one that - * begins inside a block comment. Comments are followed across lines, not - * guessed as cppCodeOf does: it takes a code line that starts with a - * dereference, `*first_key, scratch);`, for one. + * declaration `decl`, which ends on line `end`, begins. Its block comments + * and literals are blanked first (cppNonCodeLines): a parenthesis in one + * opens nothing. */ function cppClassParenDepths( decl: Node, lines: readonly string[], end: number, - rawLines: ReturnType, + rawLines: ReturnType, context: ResolutionContext, ): number[] { let memo = CPP_CLASS_PAREN_DEPTHS.get(context); @@ -9491,27 +9489,14 @@ function cppClassParenDepths( if (!depths) { depths = []; let depth = 0; - let comment = false; for (let l = decl.startLine; l <= end; l++) { - depths.push(comment ? -1 : depth); + depths.push(depth); const text = cppLiteralsBlanked(lines[l - 1] ?? '', l, rawLines); for (let i = l === decl.startLine ? decl.startColumn : 0; i < text.length; i++) { const c = text[i]!; - if (comment) { - if (c === '*' && text[i + 1] === '/') { - comment = false; - i++; - } - } else if (c === '/' && text[i + 1] === '/') { - break; - } else if (c === '/' && text[i + 1] === '*') { - comment = true; - i++; - } else if (c === '(') { - depth++; - } else if (c === ')' && depth > 0) { - depth--; - } + if (c === '/' && text[i + 1] === '/') break; + else if (c === '(') depth++; + else if (c === ')' && depth > 0) depth--; } } memo.set(decl.id, depths); @@ -10794,9 +10779,11 @@ function cppMemberMacroArguments(decl: Node, context: ResolutionContext): string if (hit) return hit; const words: string[] = []; const lines = context.getFileLines?.(decl.filePath) ?? context.readFile(decl.filePath)?.split(/\r?\n/) ?? []; + const nonCode = cppNonCodeLines(decl.filePath, context); let depth = 0; for (let i = decl.startLine - 1; i < Math.min(lines.length, decl.endLine); i++) { - const code = cppCodeOf(i === decl.startLine - 1 ? lines[i]!.slice(decl.startColumn) : lines[i]!) ?? ''; + const blanked = cppLiteralsBlanked(lines[i]!, i + 1, nonCode); + const code = cppCodeOf(i === decl.startLine - 1 ? blanked.slice(decl.startColumn) : blanked) ?? ''; const call = depth === 1 ? /^\s*[A-Z][A-Z0-9_]*\s*\(([^;{}]*)\)\s*;?\s*$/.exec(code) : null; if (call) words.push(...call[1]!.split(/\W+/).filter((w) => w.length >= 3)); for (const c of code) { @@ -10822,7 +10809,8 @@ function cppBaseSpecifiers(decl: Node, context: ResolutionContext): string[] { if (hit) return hit; const specs: string[] = []; const lines = context.getFileLines?.(decl.filePath) ?? context.readFile(decl.filePath)?.split(/\r?\n/); - const head = lines?.slice(decl.startLine - 1, decl.startLine + 11) ?? []; + const nonCode = cppNonCodeLines(decl.filePath, context); + const head = (lines?.slice(decl.startLine - 1, decl.startLine + 11) ?? []).map((l, k) => cppLiteralsBlanked(l, decl.startLine + k, nonCode)); if (head.length > 0) { head[0] = head[0]!.slice(decl.startColumn); const text = head.map((l) => cppCodeOf(l) ?? '').join('\n'); @@ -10853,7 +10841,9 @@ function cppUsingDeclares(decl: Node, base: Node, name: string, context: Resolut if (!usings) { usings = []; const lines = context.getFileLines?.(decl.filePath) ?? context.readFile(decl.filePath)?.split(/\r?\n/) ?? []; - const body = lines.slice(decl.startLine - 1, decl.endLine ?? decl.startLine).map((l) => cppCodeOf(l) ?? '').join('\n'); + const nonCode = cppNonCodeLines(decl.filePath, context); + const body = lines.slice(decl.startLine - 1, decl.endLine ?? decl.startLine) + .map((l, k) => cppCodeOf(cppLiteralsBlanked(l, decl.startLine + k, nonCode)) ?? '').join('\n'); for (const m of body.matchAll(/\busing\s+(?!namespace\b)((?:::\s*)?[A-Za-z_][\w:]*(?:\s*<[^;{}]*>)?)\s*::\s*([A-Za-z_]\w*)\s*;/g)) { usings.push([cppLastSegment(stripCppTemplateArguments(m[1]!).trim()), m[2]!]); } @@ -10889,9 +10879,10 @@ function isCppLocalName(name: string, caller: Node, ref: UnresolvedRef, context: const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const word = new RegExp(`\\b${escaped}\\b`); const declarator = buildDeclaratorRegex(escaped); + const nonCode = cppNonCodeLines(caller.filePath, context); for (let i = Math.max(0, caller.startLine - 1); i < Math.min(lines.length, ref.line); i++) { if (!word.test(lines[i]!)) continue; - const code = cppCodeOf(lines[i]!); + const code = cppCodeOf(cppLiteralsBlanked(lines[i]!, i + 1, nonCode)); if (!code || !word.test(code)) continue; if (cppRebindsReceiver(code, escaped)) return true; // `std::function done;` reads as a declaration once its template arguments are gone. @@ -10905,13 +10896,16 @@ function isCppLocalName(name: string, caller: Node, ref: UnresolvedRef, context: } /** - * A C++ source line as code, for reading declarations: null for a line of a - * comment (`// …`, ` * …`), else the line with its comments blanked, columns - * kept. Receiver inference read leveldb's `// … non-null imm_` as a - * declaration of `imm_` with the type `null`. + * A C++ source line as code, for reading declarations: null for a `// …` + * line, else the line with its comments blanked, columns kept. Receiver + * inference read leveldb's `// … non-null imm_` as a declaration of `imm_` + * with the type `null`. A line inside a block comment is blank by the time + * it gets here (cppLiteralsBlanked, from cppNonCodeLines): a line starting + * with `*` is not one by that alone, as `*widget_;` continuing a declaration + * is not. */ function cppCodeOf(line: string): string | null { - if (/^\s*(?:\/\/|\*)/.test(line)) return null; + if (/^\s*\/\//.test(line)) return null; let code = line.replace(/\/\*.*?\*\//g, (c) => ' '.repeat(c.length)); // A block comment opening here runs past the line. const open = code.indexOf('/*'); @@ -10969,7 +10963,7 @@ function inferCppReceiverType( // A declaration in a string literal is text: protobuf's upb generator // writes `upb_MiniTableField field = $field_init$;` in the raw string it // emits, above calls on its own `upb::FieldDefPtr field` parameter. - const rawLines = cppRawStringLines(ref.filePath, context); + const rawLines = cppNonCodeLines(ref.filePath, context); // Past the calling function's first line, C++ looks the name up among the // members of the function's class before anything outside it. const caller = context.getNodeById?.(ref.fromNodeId); @@ -11072,7 +11066,7 @@ function inferCppReceiverType( ? context.getFileLines(headerPath) : (context.readFile(headerPath)?.split(/\r?\n/) ?? null); if (!headerLines) continue; - const headerRawLines = cppRawStringLines(headerPath, context); + const headerRawLines = cppNonCodeLines(headerPath, context); for (let i = 0; i < headerLines.length; i++) { const text = headerLines[i]!; @@ -11242,7 +11236,7 @@ function cppAutoInitializerStatement( lines: readonly string[], index: number, receiverName: string, - rawLines: ReturnType, + rawLines: ReturnType, ): string | null { const escaped = receiverName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const start = new RegExp(`\\b${escaped}\\b\\s*=(?!=)`).exec(line); @@ -11257,7 +11251,7 @@ function cppStatementFrom( text: string, lines: readonly string[], index: number, - rawLines: ReturnType, + rawLines: ReturnType, ): string | null { for (let next = index + 1; ; next++) { const end = cppStatementEnd(text); @@ -11289,7 +11283,7 @@ function cppStructuredBindingType( callLineIndex: number, ref: UnresolvedRef, context: ResolutionContext, - rawLines: ReturnType, + rawLines: ReturnType, ): CppWrittenType | null { if (!line.includes('[')) return null; for (const m of line.matchAll(CPP_STRUCTURED_BINDING)) {