All files / transpiler/logic detectAssemblySyntax.ts

100% Statements 17/17
100% Branches 12/12
100% Functions 3/3
100% Lines 13/13

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81                                                              15x                             15x               126x 126x                 131x 72x 72x       45x 185x 185x 131x 126x   38x        
/**
 * Detect whether header content is GNU-assembler source rather than C.
 *
 * Some framework headers are pure assembler despite the `.h` extension — e.g.
 * xtensa `coreasm.h`, pulled in transitively by FreeRTOS port headers. They
 * `#define _ASMLANGUAGE` and contain `.macro` definitions whose bodies use
 * assembler instruction mnemonics such as `loop`. None of this is valid C, so
 * when the C parser error-recovers over it those mnemonics get mis-collected as
 * C symbols and then false-conflict with C-Next symbols of the same name (e.g.
 * Arduino `loop()`) — reported as "defined in multiple languages".
 *
 * Headers that merely GUARD assembler with `#ifdef __ASSEMBLER__` are unaffected:
 * preprocessing as C strips those blocks, so no assembler reaches this check.
 * Crucially, the guard token (`#ifndef _ASMLANGUAGE`, `#ifndef __ASSEMBLY__`, …)
 * survives on the raw-fallback path (preprocessing failed), so only the
 * `#define <marker>` form is matched — a header that fences its C prototypes
 * behind such a guard is a normal C header, not assembler.
 *
 * This is a heuristic, not a general assembler classifier. The directive check
 * is robust (a line whose first token is a GAS directive is never valid C at
 * file scope); the marker check recognizes the common whole-file "this unit is
 * assembly" self-declarations across toolchains. It is applied line-by-line
 * rather than with a single multi-quantifier regex to keep matching linear.
 *
 * @param content Header content (preprocessed, or raw on a preprocess fallback)
 * @returns true if the content is assembler and must be skipped for C symbol collection
 */
 
// GNU-assembler directives that are never valid C at file scope. A line whose
// first token is one of these marks assembler; a `.macro` body in particular
// holds instruction mnemonics like `loop` the C parser would mis-collect.
const ASSEMBLER_DIRECTIVES = new Set([
  ".macro",
  ".endm",
  ".section",
  ".global",
  ".globl",
  ".rept",
  ".endr",
  ".p2align",
]);
 
// Whole-file "this translation unit is assembly" self-declaration macros,
// matched ONLY in their `#define` form (never the `#ifndef`/`#ifdef` guard form,
// which fences C prototypes in real C headers — see file docstring). A C header
// never defines these, since doing so would disable its own C content.
const ASSEMBLY_MARKER_MACROS = new Set([
  "_ASMLANGUAGE", // xtensa (e.g. coreasm.h)
  "__ASSEMBLY__", // Linux kernel, U-Boot, many RTOS ports
  "__ASSEMBLER__", // GCC/Clang assembling convention
]);
 
/** The run of non-whitespace at the start of an already-trimmed line. */
function firstToken(trimmedLine: string): string {
  const end = trimmedLine.search(/\s/);
  return end === -1 ? trimmedLine : trimmedLine.slice(0, end);
}
 
/**
 * True when the line is `#define <marker>` for a known assembly self-declaration
 * macro (optionally `# define`, optionally with a value). A guard such as
 * `#ifndef __ASSEMBLY__` is deliberately NOT matched — see file docstring.
 */
function isAssemblyMarkerDefine(trimmedLine: string): boolean {
  if (!trimmedLine.startsWith("#")) return false;
  const tokens = trimmedLine.slice(1).trim().split(/\s+/);
  return tokens[0] === "define" && ASSEMBLY_MARKER_MACROS.has(tokens[1]);
}
 
function detectAssemblySyntax(content: string): boolean {
  for (const rawLine of content.split("\n")) {
    const line = rawLine.trim();
    if (line === "") continue;
    if (isAssemblyMarkerDefine(line)) return true;
    if (ASSEMBLER_DIRECTIVES.has(firstToken(line))) return true;
  }
  return false;
}
 
export default detectAssemblySyntax;