{"record":{"id":"94f85271f8d316d8","repo":"oven-sh/bun","slug":"unknown-preprocessor-macro-name","errorCode":null,"errorMessage":"Unknown preprocessor macro ${name}","messagePattern":"Unknown preprocessor macro (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/codegen/replacements.ts","lineNumber":302,"sourceCode":"          \"[\" +\n          inner.result\n            .slice(1, -1)\n            .replaceAll(\"'\", '\"')\n            .replace(/,[\\s\\n]*$/s, \"\") +\n          \"]\";\n        args = JSON.parse(str);\n      } catch {\n        throw new Error(`Call is not known at bundle-time: '$${name}${inner.result}'`);\n      }\n      if (args.length != 2 || typeof args[0] !== \"string\" || typeof args[1] !== \"string\") {\n        throw new Error(`$${name} takes two string arguments, but got '$${name}${inner.result}'`);\n      }\n\n      const id = registerNativeCall(\"bind\", args[0], args[1], null);\n\n      return [slice.slice(0, match.index) + \"__intrinsic__lazy(\" + id + \")\", inner.rest, true];\n    } else {\n      throw new Error(\"Unknown preprocessor macro \" + name);\n    }\n  }\n  return [slice, rest, false];\n}\n\n/** Applies source code replacements as defined in `globalReplacements` */\nexport function applyGlobalReplacements(src: string) {\n  let result = src;\n  for (const replacement of globalReplacements) {\n    result = result.replace(replacement.from, replacement.toRaw ?? replacement.to!.replaceAll(\"$\", \"__intrinsic__\"));\n  }\n  return result;\n}\n","sourceCodeStart":284,"sourceCodeEnd":316,"githubUrl":"https://github.com/oven-sh/bun/blob/8c5296ac459e8252d3cd702f3fbcbb0c249d95d5/src/codegen/replacements.ts#L284-L316","documentation":"The preprocessor rewrites every $-prefixed identifier in builtin JS to __intrinsic__<name> and then tries to interpret <name>( calls as known macros ($debug, $assert, $rust, $cpp, $newRustFunction, $newCppFunction, $isPromise*, $bindgenFn). Any other $-prefixed identifier followed by ( is treated as an unknown macro and fails the build.","triggerScenarios":"Defining or calling a helper whose name starts with $ inside src/js (e.g. `function $fmt(...)` then `$fmt(x)`), or adding a new intended macro without registering it in `function_replacements` in replacements.ts.","commonSituations":"Using jQuery-style $ naming conventions in builtin code; adding a new bundle-time macro but forgetting the registry list.","solutions":["Rename the helper so it does not start with $ — the $ prefix is reserved for preprocessor macros in src/js.","If it is a genuine new macro, add its name to `function_replacements` in src/codegen/replacements.ts and implement its expansion branch in applyReplacements.","Search src/js for `\\$${name}(` to find the offending call site the message names."],"exampleFix":"// before\nfunction $fmt(s) { return s.trim(); }\n$fmt(x);\n\n// after\nfunction fmt(s) { return s.trim(); }\nfmt(x);","handlingStrategy":"validation","validationCode":"const knownMacros = new Set([\"debug\", \"assert\", \"rust\", \"cpp\", \"newRustFunction\", \"newCppFunction\", \"isPromiseFulfilled\", \"isPromiseRejected\", \"isPromisePending\", \"bindgenFn\"]);\nfor (const m of src.matchAll(/(?:^|[^a-zA-Z0-9_])\\$([a-zA-Z0-9_]+)\\s*\\(/g)) {\n  if (!knownMacros.has(m[1])) throw new Error(`$${m[1]} is not a preprocessor macro; rename it to not start with $`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Reserve the $ prefix in src/js for preprocessor macros; name helpers without it.","When adding a real macro, update function_replacements and the applyReplacements dispatch in the same commit.","Lint for unknown $-identifiers so offenders surface before the bundler throws."],"tags":["codegen","macro-preprocessor","naming","builtin-bundler"],"backgroundTag":null,"analyzedSha":"8c5296ac459e8252d3cd702f3fbcbb0c249d95d5","analyzedAt":"2026-08-16T08:01:58.794Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}