mirror of
https://github.com/danbulant/oxc
synced 2026-05-20 20:58:48 +00:00
feat(minifier): remove redundant curly braces from block statements (#390)
This commit is contained in:
parent
db51772c8c
commit
8ea9e38ee5
11 changed files with 143 additions and 21 deletions
|
|
@ -1018,6 +1018,14 @@ pub enum ForStatementInit<'a> {
|
|||
Expression(Expression<'a>),
|
||||
}
|
||||
|
||||
impl<'a> ForStatementInit<'a> {
|
||||
/// LexicalDeclaration[In, Yield, Await] :
|
||||
/// LetOrConst BindingList[?In, ?Yield, ?Await] ;
|
||||
pub fn is_lexical_declaration(&self) -> bool {
|
||||
matches!(self, Self::VariableDeclaration(decl) if decl.kind.is_lexical())
|
||||
}
|
||||
}
|
||||
|
||||
/// For-In Statement
|
||||
#[derive(Debug, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))]
|
||||
|
|
@ -1048,6 +1056,14 @@ pub enum ForStatementLeft<'a> {
|
|||
AssignmentTarget(AssignmentTarget<'a>),
|
||||
}
|
||||
|
||||
impl<'a> ForStatementLeft<'a> {
|
||||
/// LexicalDeclaration[In, Yield, Await] :
|
||||
/// LetOrConst BindingList[?In, ?Yield, ?Await] ;
|
||||
pub fn is_lexical_declaration(&self) -> bool {
|
||||
matches!(self, Self::VariableDeclaration(decl) if decl.kind.is_lexical())
|
||||
}
|
||||
}
|
||||
|
||||
/// Continue Statement
|
||||
#[derive(Debug, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))]
|
||||
|
|
|
|||
|
|
@ -133,10 +133,14 @@ impl<'a> AstLower<'a> {
|
|||
}
|
||||
|
||||
fn lower_for_statement(&mut self, stmt: &ast::ForStatement<'a>) -> hir::Statement<'a> {
|
||||
let is_lexical_declaration =
|
||||
stmt.init.as_ref().is_some_and(ast::ForStatementInit::is_lexical_declaration);
|
||||
self.semantic.enter_for_statement(is_lexical_declaration);
|
||||
let init = stmt.init.as_ref().map(|init| self.lower_for_statement_init(init));
|
||||
let test = stmt.test.as_ref().map(|expr| self.lower_expression(expr));
|
||||
let update = stmt.update.as_ref().map(|expr| self.lower_expression(expr));
|
||||
let body = self.lower_statement(&stmt.body);
|
||||
self.semantic.leave_for_statement(is_lexical_declaration);
|
||||
self.hir.for_statement(stmt.span, init, test, update, body)
|
||||
}
|
||||
|
||||
|
|
@ -155,16 +159,22 @@ impl<'a> AstLower<'a> {
|
|||
}
|
||||
|
||||
fn lower_for_in_statement(&mut self, stmt: &ast::ForInStatement<'a>) -> hir::Statement<'a> {
|
||||
let is_lexical_declaration = stmt.left.is_lexical_declaration();
|
||||
self.semantic.enter_for_in_of_statement(is_lexical_declaration);
|
||||
let left = self.lower_for_statement_left(&stmt.left);
|
||||
let right = self.lower_expression(&stmt.right);
|
||||
let body = self.lower_statement(&stmt.body);
|
||||
self.semantic.leave_for_in_of_statement(is_lexical_declaration);
|
||||
self.hir.for_in_statement(stmt.span, left, right, body)
|
||||
}
|
||||
|
||||
fn lower_for_of_statement(&mut self, stmt: &ast::ForOfStatement<'a>) -> hir::Statement<'a> {
|
||||
let is_lexical_declaration = stmt.left.is_lexical_declaration();
|
||||
self.semantic.enter_for_in_of_statement(is_lexical_declaration);
|
||||
let left = self.lower_for_statement_left(&stmt.left);
|
||||
let right = self.lower_expression(&stmt.right);
|
||||
let body = self.lower_statement(&stmt.body);
|
||||
self.semantic.leave_for_in_of_statement(is_lexical_declaration);
|
||||
self.hir.for_of_statement(stmt.span, stmt.r#await, left, right, body)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ fn main() {
|
|||
|
||||
let name = args.subcommand().ok().flatten().unwrap_or_else(|| String::from("test.js"));
|
||||
let mangle = args.contains("--mangle");
|
||||
let twice = args.contains("--twice");
|
||||
|
||||
let path = Path::new(&name);
|
||||
let source_text = std::fs::read_to_string(path).unwrap_or_else(|_| panic!("{name} not found"));
|
||||
|
|
@ -30,4 +31,10 @@ fn main() {
|
|||
let options = MinifierOptions { mangle, ..MinifierOptions::default() };
|
||||
let printed = Minifier::new(&source_text, source_type, options).build();
|
||||
println!("{printed}");
|
||||
|
||||
if twice {
|
||||
let options = MinifierOptions { mangle, ..MinifierOptions::default() };
|
||||
let printed = Minifier::new(&printed, source_type, options).build();
|
||||
println!("{printed}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,6 +79,20 @@ impl<'a> Compressor<'a> {
|
|||
|
||||
/* Statements */
|
||||
|
||||
/// Remove block from single line blocks
|
||||
/// `{ block } -> block`
|
||||
#[allow(clippy::only_used_in_recursion)] // `&self` is only used in recursion
|
||||
fn compress_block<'b>(&self, stmt: &'b mut Statement<'a>) {
|
||||
if let Statement::BlockStatement(block) = stmt {
|
||||
// Avoid compressing `if (x) { var x = 1 }` to `if (x) var x = 1` due to different
|
||||
// semantics according to AnnexB, which lead to different semantics.
|
||||
if block.body.len() == 1 && !matches!(&block.body[0], Statement::Declaration(_)) {
|
||||
*stmt = block.body.remove(0);
|
||||
self.compress_block(stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Drop `drop_debugger` statement.
|
||||
/// Enabled by `compress.drop_debugger`
|
||||
fn drop_debugger<'b>(&mut self, stmt: &'b Statement<'a>) -> bool {
|
||||
|
|
@ -248,6 +262,7 @@ impl<'a, 'b> VisitMut<'a, 'b> for Compressor<'a> {
|
|||
}
|
||||
|
||||
fn visit_statement(&mut self, stmt: &'b mut Statement<'a>) {
|
||||
self.compress_block(stmt);
|
||||
self.compress_while(stmt);
|
||||
self.visit_statement_match(stmt);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,7 +102,25 @@ fn print_if(if_stmt: &IfStatement<'_>, p: &mut Printer) {
|
|||
p.print(b'(');
|
||||
if_stmt.test.gen(p);
|
||||
p.print(b')');
|
||||
if_stmt.consequent.gen(p);
|
||||
|
||||
match &if_stmt.consequent {
|
||||
Some(Statement::BlockStatement(block)) => {
|
||||
p.print_block1(block);
|
||||
}
|
||||
Some(stmt) if wrap_to_avoid_ambiguous_else(stmt) => {
|
||||
p.print(b'{');
|
||||
stmt.gen(p);
|
||||
p.print(b'}');
|
||||
p.needs_semicolon = false;
|
||||
}
|
||||
Some(stmt) => {
|
||||
stmt.gen(p);
|
||||
}
|
||||
None => {
|
||||
p.print(b';');
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(alternate) = if_stmt.alternate.as_ref() {
|
||||
p.print_semicolon_if_needed();
|
||||
p.print(b' ');
|
||||
|
|
@ -122,6 +140,36 @@ fn print_if(if_stmt: &IfStatement<'_>, p: &mut Printer) {
|
|||
}
|
||||
}
|
||||
|
||||
// <https://github.com/evanw/esbuild/blob/e6a8169c3a574f4c67d4cdd5f31a938b53eb7421/internal/js_printer/js_printer.go#L3444>
|
||||
fn wrap_to_avoid_ambiguous_else(stmt: &Statement) -> bool {
|
||||
let mut current = stmt;
|
||||
loop {
|
||||
current = match current {
|
||||
Statement::IfStatement(Box(IfStatement { alternate, .. })) => {
|
||||
if let Some(stmt) = &alternate {
|
||||
stmt
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Statement::ForStatement(Box(ForStatement { body, .. }))
|
||||
| Statement::ForOfStatement(Box(ForOfStatement { body, .. }))
|
||||
| Statement::ForInStatement(Box(ForInStatement { body, .. }))
|
||||
| Statement::WhileStatement(Box(WhileStatement { body, .. }))
|
||||
| Statement::WithStatement(Box(WithStatement { body, .. }))
|
||||
| Statement::LabeledStatement(Box(LabeledStatement { body, .. })) => {
|
||||
if let Some(stmt) = &body {
|
||||
stmt
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
impl<'a> Gen for BlockStatement<'a> {
|
||||
fn gen(&self, p: &mut Printer) {
|
||||
p.print_block1(self);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,10 @@ pub struct Printer {
|
|||
|
||||
// states
|
||||
prev_op_end: usize,
|
||||
|
||||
/// For avoiding `;` if the previous statement ends with `}`.
|
||||
needs_semicolon: bool,
|
||||
|
||||
prev_op: Option<Operator>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ fn fold_return_result() {
|
|||
expect("function f(){return void 0;}", "function f(){return}");
|
||||
expect("function f(){return void foo();}", "function f(){return void foo()}");
|
||||
expect("function f(){return undefined;}", "function f(){return}");
|
||||
expect("function f(){if(a()){return undefined;}}", "function f(){if(a()){return}}");
|
||||
expect("function f(){if(a()){return undefined;}}", "function f(){if(a())return}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use oxc_allocator::Allocator;
|
|||
use oxc_ast::ast::*;
|
||||
use oxc_minifier::{CompressOptions, Minifier, MinifierOptions, PrinterOptions};
|
||||
use oxc_parser::Parser;
|
||||
use oxc_span::SourceType;
|
||||
use oxc_span::{SourceType, Span};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
#[test]
|
||||
|
|
@ -103,7 +103,8 @@ impl TestCase {
|
|||
// Parse input / expect
|
||||
if let Statement::LabeledStatement(labeled_stmt) = stmt
|
||||
&& let Statement::BlockStatement(block_stmt) = &labeled_stmt.body {
|
||||
let code = block_stmt.span.source_text(source_text).to_string().into_boxed_str();
|
||||
let span = block_stmt.span;
|
||||
let code = Span::new(span.start + 1, span.end - 1).source_text(source_text).to_string().into_boxed_str();
|
||||
match labeled_stmt.label.name.as_str() {
|
||||
"input" => {
|
||||
input = code;
|
||||
|
|
|
|||
|
|
@ -205,4 +205,31 @@ impl SemanticBuilder {
|
|||
pub fn leave_catch_clause(&mut self) {
|
||||
self.leave_scope();
|
||||
}
|
||||
|
||||
// ForStatement : for ( LexicalDeclaration Expressionopt ; Expressionopt ) Statement
|
||||
// 1. Let oldEnv be the running execution context's LexicalEnvironment.
|
||||
// 2. Let loopEnv be NewDeclarativeEnvironment(oldEnv).
|
||||
pub fn enter_for_statement(&mut self, is_lexical_declaration: bool) {
|
||||
if is_lexical_declaration {
|
||||
self.enter_scope(ScopeFlags::empty());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn leave_for_statement(&mut self, is_lexical_declaration: bool) {
|
||||
if is_lexical_declaration {
|
||||
self.leave_scope();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enter_for_in_of_statement(&mut self, is_lexical_declaration: bool) {
|
||||
if is_lexical_declaration {
|
||||
self.enter_scope(ScopeFlags::empty());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn leave_for_in_of_statement(&mut self, is_lexical_declaration: bool) {
|
||||
if is_lexical_declaration {
|
||||
self.leave_scope();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,11 +71,6 @@ impl Mangler {
|
|||
// Walk the scope tree and compute the slot number for each scope
|
||||
for scope_id in scope_tree.descendants() {
|
||||
let bindings = scope_tree.get_bindings(scope_id);
|
||||
// Skip if the scope is empty
|
||||
if bindings.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The current slot number is continued by the maximum slot from the parent scope
|
||||
let parent_max_slot = scope_tree
|
||||
.get_parent_id(scope_id)
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
Original -> Minified -> Gzip Brotli
|
||||
72.14 kB -> 24.58 kB -> 8.70 kB 7.66 kB react.development.js
|
||||
72.14 kB -> 24.36 kB -> 8.67 kB 7.63 kB react.development.js
|
||||
|
||||
173.90 kB -> 62.53 kB -> 19.65 kB 17.82 kB moment.js
|
||||
173.90 kB -> 62.04 kB -> 19.54 kB 17.74 kB moment.js
|
||||
|
||||
287.63 kB -> 94.34 kB -> 32.65 kB 29.59 kB jquery.js
|
||||
287.63 kB -> 93.32 kB -> 32.35 kB 29.39 kB jquery.js
|
||||
|
||||
342.15 kB -> 125.39 kB -> 45.62 kB 40.34 kB vue.js
|
||||
342.15 kB -> 123.97 kB -> 45.28 kB 40.17 kB vue.js
|
||||
|
||||
544.10 kB -> 75.44 kB -> 26.34 kB 23.51 kB lodash.js
|
||||
544.10 kB -> 74.91 kB -> 26.25 kB 23.54 kB lodash.js
|
||||
|
||||
555.77 kB -> 276.19 kB -> 91.67 kB 77.58 kB d3.js
|
||||
555.77 kB -> 275.56 kB -> 91.51 kB 77.42 kB d3.js
|
||||
|
||||
977.19 kB -> 458.34 kB -> 124.55 kB 107.80 kB bundle.min.js
|
||||
977.19 kB -> 456.98 kB -> 124.12 kB 107.58 kB bundle.min.js
|
||||
|
||||
1.25 MB -> 679.93 kB -> 167.23 kB 135.53 kB three.js
|
||||
1.25 MB -> 678.14 kB -> 166.78 kB 135.11 kB three.js
|
||||
|
||||
2.14 MB -> 749.70 kB -> 182.45 kB 139.44 kB victory.js
|
||||
2.14 MB -> 748.49 kB -> 182.38 kB 139.17 kB victory.js
|
||||
|
||||
3.20 MB -> 1.04 MB -> 335.34 kB 271.81 kB echarts.js
|
||||
3.20 MB -> 1.04 MB -> 333.55 kB 270.51 kB echarts.js
|
||||
|
||||
6.69 MB -> 2.42 MB -> 500.04 kB 390.96 kB antd.js
|
||||
6.69 MB -> 2.41 MB -> 499.10 kB 390.65 kB antd.js
|
||||
|
||||
10.82 MB -> 3.54 MB -> 915.71 kB 704.86 kB typescript.js
|
||||
10.82 MB -> 3.53 MB -> 912.43 kB 703.18 kB typescript.js
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue