fix(packages/codegen): support printing V8IntrinsicExpressions (#27597)

`oxc_codegen` (Rust) supports printing `V8IntrinsicExpression`s but `oxc-codegen` (JS) didn't. Add support for it.
This commit is contained in:
overlookmotel committed 2026-10-12 17:10:51 +00:00
1 parent 0c98dc54ac
commit 0353d37faf
2 files changed
+58 -1

No files matched your search

+27 -1
View File
@@ -248,6 +248,9 @@ export function printExpression(
printTSTypeAssertion(node, state, precedence, ctx);
break;
/* END_IF */
case "V8IntrinsicExpression":
printV8IntrinsicExpression(node, state, precedence, ctx);
break;
default:
throw new Error(`Unknown expression type: ${node.type}`);
}
@@ -365,7 +368,7 @@ function printCallExpression(
* Arguments print at `PREC_COMMA`, so a sequence expression parenthesizes itself instead of reading as more arguments.
*/
function printArguments(
node: ESTree.CallExpression | ESTree.NewExpression,
node: ESTree.CallExpression | ESTree.NewExpression | ESTree.V8IntrinsicExpression,
args: ESTree.Argument[],
state: State,
): void {
@@ -1029,3 +1032,26 @@ function printChainExpression(
printExpression(node.expression, state, precedence, ctx);
}
}
/**
* A V8 runtime call, e.g. `%DebugPrint(x)`, which the parser only produces with its `allowV8Intrinsics` option.
*
* Parenthesized wherever a postfix operator follows it - a member access, a call or a tagged template -
* and under `new`, as `oxc_codegen` does: `(%DebugPrint(x)).y`.
*/
function printV8IntrinsicExpression(
node: ESTree.V8IntrinsicExpression,
state: State,
precedence: number,
ctx: number,
): void {
const wrap = precedence >= PREC_POSTFIX || (ctx & CTX_FORBID_CALL) !== 0;
if (wrap) write(state, "(", CAT_OTHER);
writeWithMap(state, "%", CAT_OTHER, node.start, node.end, node);
const { name } = node;
writeWithMapNamed(state, name.name, name.start, name.end, name);
printArguments(node, node.arguments, state);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
+31
View File
@@ -78,6 +78,14 @@ const call = (callee: ESTree.Expression, args: ESTree.Argument[] = []) =>
arguments: args,
optional: false,
});
const newExpr = (callee: ESTree.Expression, args: ESTree.Argument[] = []) =>
node<ESTree.NewExpression>({ type: "NewExpression", callee, arguments: args });
const v8 = (name: string, args: ESTree.Argument[] = []) =>
node<ESTree.V8IntrinsicExpression>({
type: "V8IntrinsicExpression",
name: node<ESTree.IdentifierName>({ type: "Identifier", name }),
arguments: args,
});
const unary = (operator: ESTree.UnaryOperator, argument: ESTree.Expression) =>
node<ESTree.UnaryExpression>({ type: "UnaryExpression", operator, argument, prefix: true });
const update = (
@@ -479,6 +487,29 @@ describe("for-of heads starting with `async` or `let`", () => {
]);
});
// V8 runtime calls, e.g. `%DebugPrint(x)`. The parser only produces them with its `allowV8Intrinsics` option,
// which neither `oxc-parser`'s Node.js API nor the conformance binding exposes, so only a hand-built AST reaches
// them. Each expected output is what `oxc_codegen` prints for the same source, parsed with `allow_v8_intrinsics`.
describe("V8 intrinsics", () => {
const debugPrint = () => v8("DebugPrint", [id("x")]);
checkCases([
["statement", e(debugPrint()), "%DebugPrint(x);\n"],
["no-args", e(v8("DebugPrint")), "%DebugPrint();\n"],
["string-arg", e(v8("DebugPrint", [str("hi")])), '%DebugPrint("hi");\n'],
["nested", e(v8("DebugPrint", [v8("GetFoo"), id("x")])), "%DebugPrint(%GetFoo(), x);\n"],
// Parenthesized where a postfix operator follows, and under `new`
["member", e(member(debugPrint(), id("y"))), "(%DebugPrint(x)).y;\n"],
["computed-member", e(computedMember(debugPrint(), num(0))), "(%DebugPrint(x))[0];\n"],
["callee", e(call(debugPrint())), "(%DebugPrint(x))();\n"],
["new-callee", e(newExpr(debugPrint())), "new (%DebugPrint(x))();\n"],
// Not parenthesized as an operand or an argument
["unary-operand", e(unary("!", debugPrint())), "!%DebugPrint(x);\n"],
["binary-operand", e(bin("+", debugPrint(), num(1))), "%DebugPrint(x) + 1;\n"],
["argument", e(call(id("f"), [debugPrint()])), "f(%DebugPrint(x));\n"],
]);
});
// The three `export default` declaration forms - interface, class and function - each end with a
// newline, so whatever follows starts on its own line. `export default interface` printed without
// one until `oxc_codegen` was fixed, gluing the next statement onto the closing brace.