fix(extraction): blank C designated-initializer macro args before parsing (#1755)

tree-sitter-c has no rule for `.field = value` as a call argument. A
statement-level `MACRO(a, b, .x = …, .y = { … },);` recovers by extending
the enclosing function_definition to EOF — later functions vanish or nest
as outer::inner (#1729). blankCDesignatedMacroArgs empties such argument
lists to spaces (newlines kept) at the head of preParseCSource, before the
kernel route point, so both wasm and kernel C arms see the same bytes.

Tests cover the issue fixture (trailing-comma designated args) and a
120-field scale guard. Refs #1729.

Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
This commit is contained in:
Colby Mchenry
2026-09-08 01:38:46 -05:00
committed by GitHub
co-authored by Colby McHenry
parent 9a32487491
commit 8df9ecac9d
3 changed files with 146 additions and 1 deletions
+49 -1
View File
@@ -1515,8 +1515,56 @@ export function blankCNamedVariadicDefineDots(source: string): string {
* C-detected headers in CUDA projects (llm.c keeps `__device__` helpers and
* kernel prototypes in plain `.h`) — the same content-gated CUDA blank as
* C++. Offset-preserving. */
/**
* Blank the argument list of a statement-level `MACRO( … );` call whose
* arguments are designated initializers — betaflight's
*
* RESET_CONFIG(pidProfile_t, pidProfile,
* .pid = { [PID_ROLL] = PID_ROLL_DEFAULT, … },
* .pidSumLimit = PIDSUM_LIMIT,
* …
* );
*
* tree-sitter-c has no rule for `.field = value` as a call argument. Even a
* small statement-level `M(a, b, .x = 1, .y = { 1, 2 },);` with a trailing
* comma recovers by extending the enclosing `function_definition` to EOF —
* the next function vanishes and later ones nest under the first (#1729 —
* 310 functions in 73 files on a betaflight tree, which name matching then
* treated as unreachable closures). Emptying the argument list to spaces,
* newlines kept, leaves `RESET_CONFIG(\n\n…\n);` — a call the grammar parses
* cleanly — at the cost of the references inside the initializer, which the
* broken parse was not yielding either. Statement-level only (`);` follows),
* macro-cased name only, offsets preserved. Runs before the kernel route
* point, so both the wasm and kernel C arms see the same bytes.
*/
export function blankCDesignatedMacroArgs(source: string): string {
if (source.indexOf('=') === -1) return source;
const out = source.split('');
const re = /^[ \t]*([A-Z_][A-Z0-9_]*)\s*\(/gm;
let m: RegExpExecArray | null;
while ((m = re.exec(source))) {
const open = m.index + m[0].length - 1;
let depth = 1;
let i = open + 1;
for (; i < source.length && depth > 0; i++) {
const c = source[i];
if (c === '(') depth++;
else if (c === ')') depth--;
}
if (depth !== 0) continue;
const close = i - 1;
const args = source.slice(open + 1, close);
// A designator at argument depth: `.name =` or `[index] =`.
if (!/(^|[,{(\s])(\.[A-Za-z_]\w*|\[[^\]]+\])\s*=[^=]/.test(args)) continue;
if (!/^\s*;/.test(source.slice(close + 1))) continue;
for (let k = open + 1; k < close; k++) if (out[k] !== '\n') out[k] = ' ';
re.lastIndex = close;
}
return out.join('');
}
function preParseCSource(source: string): string {
const inner = blankCKernelAnnotations(blankCCplusplusGuardBodies(source));
const inner = blankCDesignatedMacroArgs(blankCKernelAnnotations(blankCCplusplusGuardBodies(source)));
let blanked = blankCLeadingAttrMacros(
blankLoneMacroLines(
blankCStatementMacroCalls(