mirror of
https://github.com/oxc-project/oxc.git
synced 2026-09-14 19:36:11 +08:00
fix(codegen): preserve in restriction through yield arguments (#26413)
## Problem
A `yield` expression in a classic `for` loop initializer can lose the parentheses required around an `in` operator in its argument. This turns valid JavaScript into output that cannot be parsed, in both default and minified code generation.
For example, this input is valid:
```js
function *g(o) {
for (yield (1 in o); false;);
}
```
Previously, codegen emitted:
```js
// Default output
function* g(o) {
for (yield 1 in o; false;);
}
// Minified output
function*g(o){for(yield 1 in o;false;);}
```
The unparenthesized `in` is not allowed at this position in a classic `for` initializer. The same restriction applies to the argument of `yield*`, and can also be lost through nested yields or an assignment inside the yield argument.
## Change
`ForStatement` already prints its initializer with `Context::FORBID_IN`. However, `YieldExpression::gen_expr` ignored its incoming context and always printed its argument with `Context::empty()`.
Preserve `FORBID_IN` when printing the argument of an unwrapped yield expression. This lets the existing expression printers retain the required grouping:
```js
// Default output
function* g(o) {
for (yield (1 in o); false;);
}
// Minified output
function*g(o){for(yield(1 in o);false;);}
```
Only the relevant `FORBID_IN` flag is forwarded; the argument's existing precedence remains `Precedence::Yield`. Both `yield` and `yield*` use this path.
## Parentheses around yield
When precedence requires parentheses around the entire yield expression, those parentheses already allow `in` in the argument. Clear the restriction in that case to avoid redundant inner grouping:
```js
// Input
function *g(o) {
for ((yield (1 in o)) + 1; false;);
}
// Default output
function* g(o) {
for ((yield 1 in o) + 1; false;);
}
// Minified output
function*g(o){for((yield 1 in o)+1;false;);}
```
Outside a restricted initializer, ordinary yield expressions continue to omit unnecessary parentheses: `yield (1 in o)` prints as `yield 1 in o`.
This commit is contained in:
@@ -1910,8 +1910,10 @@ impl GenExpr for ArrowFunctionExpression<'_> {
|
||||
}
|
||||
|
||||
impl GenExpr for YieldExpression<'_> {
|
||||
fn gen_expr(&self, p: &mut Codegen, precedence: Precedence, _ctx: Context) {
|
||||
p.wrap(precedence >= Precedence::Assign, |p| {
|
||||
fn gen_expr(&self, p: &mut Codegen, precedence: Precedence, ctx: Context) {
|
||||
let wrap = precedence >= Precedence::Assign;
|
||||
let argument_ctx = if wrap { Context::empty() } else { ctx & Context::FORBID_IN };
|
||||
p.wrap(wrap, |p| {
|
||||
p.print_space_before_identifier();
|
||||
p.add_source_mapping(self.span);
|
||||
p.print_str("yield");
|
||||
@@ -1920,7 +1922,7 @@ impl GenExpr for YieldExpression<'_> {
|
||||
}
|
||||
if let Some(argument) = self.argument.as_ref() {
|
||||
p.print_soft_space();
|
||||
argument.print_expr(p, Precedence::Yield, Context::empty());
|
||||
argument.print_expr(p, Precedence::Yield, argument_ctx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -686,6 +686,53 @@ fn in_expr_in_sequence_in_for_loop_init() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn in_expr_in_yield_expression() {
|
||||
for (keyword, prefix) in [("yield", "yield "), ("yield*", "yield*")] {
|
||||
for (init, expected, minified) in [
|
||||
(
|
||||
format!("{keyword} (1 in o)"),
|
||||
format!("{keyword} (1 in o)"),
|
||||
format!("{keyword}(1 in o)"),
|
||||
),
|
||||
(
|
||||
format!("x = {keyword} (1 in o)"),
|
||||
format!("x = {keyword} (1 in o)"),
|
||||
format!("x={keyword}(1 in o)"),
|
||||
),
|
||||
(
|
||||
format!("{keyword} yield (1 in o)"),
|
||||
format!("{keyword} yield (1 in o)"),
|
||||
format!("{prefix}yield(1 in o)"),
|
||||
),
|
||||
(
|
||||
format!("{keyword} (x = (1 in o))"),
|
||||
format!("{keyword} x = (1 in o)"),
|
||||
format!("{prefix}x=(1 in o)"),
|
||||
),
|
||||
// Parentheses around the yield expression allow `in` in its argument.
|
||||
(
|
||||
format!("({keyword} (1 in o)) + 1"),
|
||||
format!("({keyword} 1 in o) + 1"),
|
||||
format!("({prefix}1 in o)+1"),
|
||||
),
|
||||
] {
|
||||
let source = format!("function *g(o) {{ for ({init}; false;); }}");
|
||||
test(&source, &format!("function* g(o) {{\n\tfor ({expected}; false;);\n}}\n"));
|
||||
test_minify(&source, &format!("function*g(o){{for({minified};false;);}}"));
|
||||
crate::test_idempotency(&source);
|
||||
crate::test_idempotency_options(
|
||||
&source,
|
||||
&CodegenOptions { minify: true, ..CodegenOptions::default() },
|
||||
);
|
||||
}
|
||||
|
||||
let source = format!("function *g(o) {{ {keyword} (1 in o); }}");
|
||||
test(&source, &format!("function* g(o) {{\n\t{keyword} 1 in o;\n}}\n"));
|
||||
test_minify(&source, &format!("function*g(o){{{prefix}1 in o}}"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn in_expr_in_arrow_function_expression() {
|
||||
test("() => ('foo' in bar)", "() => \"foo\" in bar;\n");
|
||||
|
||||
Reference in New Issue
Block a user