mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-26 04:19:33 +08:00
fix(cpp): a line starting with * is read as code, and block comments and string literals are blanked before declarations are read (#2552)
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<std::shared_ptr<EventListener>*>(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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d94a92ea89
commit
d4b9c2b4e9
@@ -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<ResolutionContext, Map<string, Map<number, Ranges>>>();
|
||||
@@ -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<number, Ranges> {
|
||||
export function cppNonCodeLines(file: string, context: ResolutionContext): Map<number, Ranges> {
|
||||
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<number, Ranges>();
|
||||
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));
|
||||
|
||||
@@ -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<typeof cppRawStringLines>,
|
||||
rawLines: ReturnType<typeof cppNonCodeLines>,
|
||||
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<void()> 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<typeof cppRawStringLines>,
|
||||
rawLines: ReturnType<typeof cppNonCodeLines>,
|
||||
): 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<typeof cppRawStringLines>,
|
||||
rawLines: ReturnType<typeof cppNonCodeLines>,
|
||||
): 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<typeof cppRawStringLines>,
|
||||
rawLines: ReturnType<typeof cppNonCodeLines>,
|
||||
): CppWrittenType | null {
|
||||
if (!line.includes('[')) return null;
|
||||
for (const m of line.matchAll(CPP_STRUCTURED_BINDING)) {
|
||||
|
||||
Reference in New Issue
Block a user