fix(codegen): fix printing negative BigIntLiteral after - (#27378)

Same fix as #27377, but for Rust `oxc_codegen`.

Unlike the JS codegen, incorrect output could be generated without manual AST manipulation - the minifier also produces such an AST in some cases.

```javascript
// Input
x = -~0n;
x = y - ~1n;
```

```javascript
// Minified output before this PR
x=--1n,x=y--2n;
```

[Playground](https://playground.oxc.rs/?options=%7B%22run%22%3A%7B%22lint%22%3Afalse%2C%22formatter%22%3Afalse%2C%22transform%22%3Afalse%2C%22isolatedDeclarations%22%3Afalse%2C%22whitespace%22%3Atrue%2C%22mangle%22%3Afalse%2C%22mangleProps%22%3Afalse%2C%22compress%22%3Atrue%2C%22scope%22%3Atrue%2C%22symbol%22%3Atrue%2C%22cfg%22%3Afalse%7D%2C%22parser%22%3A%7B%22extension%22%3A%22js%22%2C%22allowReturnOutsideFunction%22%3Atrue%2C%22preserveParens%22%3Atrue%2C%22allowV8Intrinsics%22%3Atrue%2C%22semanticErrors%22%3Atrue%7D%2C%22linter%22%3A%7B%7D%2C%22formatter%22%3A%7B%22useTabs%22%3Afalse%2C%22tabWidth%22%3A2%2C%22endOfLine%22%3A%22lf%22%2C%22printWidth%22%3A80%2C%22singleQuote%22%3Afalse%2C%22jsxSingleQuote%22%3Afalse%2C%22quoteProps%22%3A%22as-needed%22%2C%22trailingComma%22%3A%22all%22%2C%22semi%22%3Atrue%2C%22arrowParens%22%3A%22always%22%2C%22bracketSpacing%22%3Atrue%2C%22bracketSameLine%22%3Afalse%2C%22objectWrap%22%3A%22preserve%22%2C%22singleAttributePerLine%22%3Afalse%7D%2C%22transformer%22%3A%7B%22target%22%3A%22es2015%22%2C%22useDefineForClassFields%22%3Atrue%2C%22experimentalDecorators%22%3Atrue%2C%22emitDecoratorMetadata%22%3Atrue%2C%22optimizeEnums%22%3Atrue%2C%22optimizeConstEnums%22%3Atrue%7D%2C%22isolatedDeclarations%22%3A%7B%22stripInternal%22%3Afalse%7D%2C%22codegen%22%3A%7B%22normal%22%3Atrue%2C%22jsdoc%22%3Atrue%2C%22annotation%22%3Atrue%2C%22legal%22%3Atrue%7D%2C%22compress%22%3A%7B%7D%2C%22mangle%22%3A%7B%22topLevel%22%3Atrue%2C%22keepNames%22%3Afalse%7D%2C%22mangleProps%22%3A%7B%22include%22%3A%22_%24%22%2C%22reserved%22%3A%5B%5D%2C%22quoted%22%3Afalse%2C%22debug%22%3Afalse%7D%2C%22controlFlow%22%3A%7B%22verbose%22%3Afalse%7D%2C%22inject%22%3A%7B%22inject%22%3A%7B%7D%7D%2C%22define%22%3A%7B%22define%22%3A%7B%7D%7D%7D&code=x+%3D+-%7E0n%3B%0Ax+%3D+y+-+%7E1n%3B%0A)

After this PR:

```javascript
x=- -1n,x=y- -2n;
```

Additionally, avoid printing a redundant space in minified output when a negative `BigIntLiteral` is wrapped in parentheses:

```javascript
// Before
return (-1n).x;
// After
return(-1n).x;
```
This commit is contained in:
overlookmotel committed 2026-10-06 19:06:07 +00:00
1 parent 5461c1ade4
commit 82bfc5fefa
2 files changed
+102 -8

No files matched your search

+15 -8
View File
@@ -1482,17 +1482,24 @@ impl GenExpr for NumericLiteral<'_> {
impl GenExpr for BigIntLiteral<'_> {
fn gen_expr(&self, p: &mut Codegen, precedence: Precedence, _ctx: Context) {
p.print_space_before_identifier();
p.add_source_mapping(self.span);
let value = self.value.as_str();
if value.starts_with('-') && precedence >= Precedence::Prefix {
p.print_ascii_byte(b'(');
p.print_str(value);
p.print_str("n)");
if value.starts_with('-') {
if precedence >= Precedence::Prefix {
p.add_source_mapping(self.span);
p.print_ascii_byte(b'(');
p.print_str(value);
p.print_str("n)");
return;
}
// Printed as-is, starting with `-`, so it must not run into a `-` or `--` printed before it,
// the same as a negative `NumericLiteral`. `- -1n`, not `--1n`.
p.print_space_before_operator(Operator::Unary(UnaryOperator::UnaryNegation));
} else {
p.print_str(value);
p.print_ascii_byte(b'n');
p.print_space_before_identifier();
}
p.add_source_mapping(self.span);
p.print_str(value);
p.print_ascii_byte(b'n');
}
}
@@ -780,6 +780,93 @@ fn big_int() {
test_minify("function a() { return 1n }", "function a(){return 1n}");
}
/// A negative `BigIntLiteral` never comes from the parser (`-1n` parses as a unary minus around `1n`),
/// but the minifier creates one when it folds e.g. `~0n` to `-1n`.
/// Its `-` must not run into a `-` printed before it, which would make `--`.
/// After a keyword it needs no space, whether it starts with `-` or is wrapped in `(...)`,
/// as neither can continue the keyword.
#[test]
fn negative_big_int_literal() {
let allocator = Allocator::default();
let ast = AstBuilder::new(&allocator);
let negative_one =
|| Expression::new_big_int_literal(SPAN, "-1", None, BigintBase::Decimal, &ast);
let expr_stmt = |expr| Statement::new_expression_statement(SPAN, expr, &ast);
let cases = [
// `-(-1n)`
(
expr_stmt(Expression::new_unary_expression(
SPAN,
UnaryOperator::UnaryNegation,
negative_one(),
&ast,
)),
"- -1n;\n",
"- -1n;",
),
// `+(-1n)` needs no space
(
expr_stmt(Expression::new_unary_expression(
SPAN,
UnaryOperator::UnaryPlus,
negative_one(),
&ast,
)),
"+-1n;\n",
"+-1n;",
),
// `y - (-1n)`
(
expr_stmt(Expression::new_binary_expression(
SPAN,
Expression::new_identifier(SPAN, "y", &ast),
BinaryOperator::Subtraction,
negative_one(),
&ast,
)),
"y - -1n;\n",
"y- -1n;",
),
// `return -1n` needs no space when minified
(
Statement::new_return_statement(SPAN, Some(negative_one()), &ast),
"return -1n;\n",
"return-1n;",
),
// `return (-1n).x` needs no space when minified either
(
Statement::new_return_statement(
SPAN,
Some(Expression::new_static_member_expression(
SPAN,
negative_one(),
IdentifierName::new(SPAN, "x", &ast),
false,
&ast,
)),
&ast,
),
"return (-1n).x;\n",
"return(-1n).x;",
),
];
for (stmt, expected, expected_minified) in cases {
let program =
Program::new(SPAN, oxc_span::SourceType::mjs(), "", [], None, [], [stmt], &ast);
let result = Codegen::new().build(&program).code;
assert_eq!(result, expected);
let result = Codegen::new()
.with_options(CodegenOptions { minify: true, ..CodegenOptions::default() })
.build(&program)
.code;
assert_eq!(result, expected_minified);
}
}
#[test]
#[ignore = "Minify bigint is not implemented."]
fn big_int_minify() {