mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(codegen): drop redundant space after else when minifying (#23025)
## What Found with the whitespace analyzer (remove each space in minified output, check it still canonicalizes to the same AST). `print_body` printed an unconditional hard space before a single-statement body, so an `else` body kept a space even before non-identifier tokens: | Input | Before | After | | --- | --- | --- | | ``if(x)a();else`t` `` | ``else `t` `` | ``else`t` `` | | `if(x)a();else++b` | `else ++b` | `else++b` | | `if(x)a();else[b]` | `else [b]` | `else[b]` | esbuild emits these tight too. ## Fix The body statement's first token already inserts a hard space via `print_space_before_identifier` whenever it would merge with a preceding identifier char — this is how `for`/`while`/`do` bodies (which pass `need_space=false`) already work; they're safe because they end in `)`. `else` ends in `e`, so the unconditional space was added as a blunt guard. Removing it and relying on the body's own guard keeps the required spaces (`else x`, `else return 5`, `else debugger`, `else var k`, `else 5`) while dropping the unnecessary ones. The `need_space` parameter is now unused and removed. Verified: every keyword-statement else-body (`return`/`throw`/`debugger`/`var`/…) self-guards; full codegen (116) + minifier (506) suites pass, and a 150-file real-world minify round-trip is idempotent.
This commit is contained in:
4 files changed
+25
-18
No files matched your search
@@ -253,7 +253,7 @@ fn print_if(if_stmt: &IfStatement<'_>, p: &mut Codegen, ctx: Context) {
|
||||
}
|
||||
}
|
||||
stmt => {
|
||||
p.print_body(stmt, false, ctx);
|
||||
p.print_body(stmt, ctx);
|
||||
if if_stmt.alternate.is_some() {
|
||||
p.print_indent();
|
||||
}
|
||||
@@ -273,7 +273,7 @@ fn print_if(if_stmt: &IfStatement<'_>, p: &mut Codegen, ctx: Context) {
|
||||
p.print_hard_space();
|
||||
print_if(if_stmt, p, ctx);
|
||||
}
|
||||
stmt => p.print_body(stmt, true, ctx),
|
||||
stmt => p.print_body(stmt, ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,7 +338,7 @@ impl Gen for ForStatement<'_> {
|
||||
}
|
||||
|
||||
p.print_ascii_byte(b')');
|
||||
p.print_body(&self.body, false, ctx);
|
||||
p.print_body(&self.body, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,7 +358,7 @@ impl Gen for ForInStatement<'_> {
|
||||
p.print_soft_space();
|
||||
p.print_expression(&self.right);
|
||||
p.print_ascii_byte(b')');
|
||||
p.print_body(&self.body, false, ctx);
|
||||
p.print_body(&self.body, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,7 +423,7 @@ impl Gen for ForOfStatement<'_> {
|
||||
p.print_soft_space();
|
||||
self.right.print_expr(p, Precedence::Comma, Context::empty());
|
||||
p.print_ascii_byte(b')');
|
||||
p.print_body(&self.body, false, ctx);
|
||||
p.print_body(&self.body, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,7 +458,7 @@ impl Gen for WhileStatement<'_> {
|
||||
p.print_ascii_byte(b'(');
|
||||
p.print_expression(&self.test);
|
||||
p.print_ascii_byte(b')');
|
||||
p.print_body(&self.body, false, ctx);
|
||||
p.print_body(&self.body, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,7 +576,7 @@ impl Gen for SwitchCase<'_> {
|
||||
let single_line = self.consequent.len() == 1
|
||||
&& !p.has_legal_orphans_before(self.consequent[0].span().start);
|
||||
if single_line {
|
||||
p.print_body(&self.consequent[0], false, ctx);
|
||||
p.print_body(&self.consequent[0], ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -612,7 +612,7 @@ impl Gen for LabeledStatement<'_> {
|
||||
p.add_source_mapping(self.span);
|
||||
self.label.print(p, ctx);
|
||||
p.print_colon();
|
||||
p.print_body(&self.body, false, ctx);
|
||||
p.print_body(&self.body, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -688,7 +688,7 @@ impl Gen for WithStatement<'_> {
|
||||
p.print_ascii_byte(b'(');
|
||||
p.print_expression(&self.object);
|
||||
p.print_ascii_byte(b')');
|
||||
p.print_body(&self.body, false, ctx);
|
||||
p.print_body(&self.body, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -630,7 +630,7 @@ impl<'a> Codegen<'a> {
|
||||
self.print_ascii_byte(b'}');
|
||||
}
|
||||
|
||||
fn print_body(&mut self, stmt: &Statement<'_>, need_space: bool, ctx: Context) {
|
||||
fn print_body(&mut self, stmt: &Statement<'_>, ctx: Context) {
|
||||
match stmt {
|
||||
Statement::BlockStatement(stmt) => {
|
||||
self.print_soft_space();
|
||||
@@ -642,9 +642,10 @@ impl<'a> Codegen<'a> {
|
||||
self.print_soft_newline();
|
||||
}
|
||||
stmt => {
|
||||
if need_space && self.options.minify {
|
||||
self.print_hard_space();
|
||||
}
|
||||
// The statement's first token inserts a hard space itself (via
|
||||
// `print_space_before_identifier`) when it would merge with a preceding
|
||||
// identifier char (e.g. `else`), so `else{...}`/`else++x`/`else[0]` stay
|
||||
// tight while `else x` keeps its space.
|
||||
self.print_next_indent_as_space = true;
|
||||
stmt.print(self, ctx);
|
||||
}
|
||||
|
||||
@@ -219,6 +219,12 @@ fn if_stmt() {
|
||||
"function f() { if (foo) return foo; else if (bar) return foo; }",
|
||||
"function f(){if(foo)return foo;else if(bar)return foo}",
|
||||
);
|
||||
// `else` only needs a space before an identifier-like body.
|
||||
test_minify("if(x)a();else b()", "if(x)a();else b();");
|
||||
test_minify("if(x)a();else++b", "if(x)a();else++b;");
|
||||
test_minify("if(x)a();else[b]", "if(x)a();else[b];");
|
||||
test_minify("if(x)a();else`b`", "if(x)a();else`b`;");
|
||||
test_minify("if(x)a();else debugger", "if(x)a();else debugger;");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -7,21 +7,21 @@ Original | minified | minified | gzip | gzip | Iterations | Fi
|
||||
|
||||
287.63 kB | 89.26 kB | 90.07 kB | 30.91 kB | 31.95 kB | 2 | jquery.js
|
||||
|
||||
342.15 kB | 116.98 kB | 118.14 kB | 43.18 kB | 44.37 kB | 2 | vue.js
|
||||
342.15 kB | 116.97 kB | 118.14 kB | 43.18 kB | 44.37 kB | 2 | vue.js
|
||||
|
||||
544.10 kB | 71.04 kB | 72.48 kB | 25.77 kB | 26.20 kB | 2 | lodash.js
|
||||
|
||||
555.77 kB | 267.40 kB | 270.13 kB | 88.00 kB | 90.80 kB | 2 | d3.js
|
||||
555.77 kB | 267.40 kB | 270.13 kB | 88.01 kB | 90.80 kB | 2 | d3.js
|
||||
|
||||
1.01 MB | 439.35 kB | 458.89 kB | 122.05 kB | 126.71 kB | 2 | bundle.min.js
|
||||
|
||||
1.25 MB | 642.65 kB | 646.76 kB | 159.41 kB | 163.73 kB | 2 | three.js
|
||||
|
||||
2.14 MB | 711.10 kB | 724.14 kB | 160.43 kB | 181.07 kB | 2 | victory.js
|
||||
2.14 MB | 711.10 kB | 724.14 kB | 160.44 kB | 181.07 kB | 2 | victory.js
|
||||
|
||||
3.20 MB | 1.00 MB | 1.01 MB | 322.61 kB | 331.56 kB | 2 | echarts.js
|
||||
|
||||
6.69 MB | 2.22 MB | 2.31 MB | 458.46 kB | 488.28 kB | 4 | antd.js
|
||||
6.69 MB | 2.22 MB | 2.31 MB | 458.45 kB | 488.28 kB | 4 | antd.js
|
||||
|
||||
10.95 MB | 3.33 MB | 3.49 MB | 853.35 kB | 915.50 kB | 4 | typescript.js
|
||||
10.95 MB | 3.33 MB | 3.49 MB | 853.33 kB | 915.50 kB | 4 | typescript.js
|
||||
|
||||
Reference in new issue
Block a user