Skip to content

Commit c050149

Browse files
committed
refactor(minifier): inline statement methods
1 parent 53c51f9 commit c050149

File tree

5 files changed

+134
-122
lines changed

5 files changed

+134
-122
lines changed

crates/oxc_minifier/examples/minifier.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ fn main() -> std::io::Result<()> {
5151
let mut allocator = Allocator::default();
5252
let ret = minify(&allocator, &source_text, source_type, source_map_path, mangle, nospace);
5353
let printed = ret.code;
54-
// println!("{printed}");
54+
println!("{printed}");
5555

5656
if let Some(source_map) = ret.map {
5757
let result = source_map.to_json_string();

crates/oxc_minifier/src/peephole/minimize_expression_in_boolean_context.rs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,6 @@ use crate::ctx::Ctx;
88
use super::PeepholeOptimizations;
99

1010
impl<'a> PeepholeOptimizations {
11-
pub fn try_fold_stmt_in_boolean_context(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
12-
let expr = match stmt {
13-
Statement::IfStatement(s) => Some(&mut s.test),
14-
Statement::WhileStatement(s) => Some(&mut s.test),
15-
Statement::ForStatement(s) => s.test.as_mut(),
16-
Statement::DoWhileStatement(s) => Some(&mut s.test),
17-
_ => None,
18-
};
19-
20-
if let Some(expr) = expr {
21-
Self::try_fold_expr_in_boolean_context(expr, ctx);
22-
}
23-
}
24-
2511
/// Simplify syntax when we know it's used inside a boolean context, e.g. `if (boolean_context) {}`.
2612
///
2713
/// `SimplifyBooleanExpr`: <https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L2059>

crates/oxc_minifier/src/peephole/mod.rs

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,39 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
129129
}
130130

131131
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
132-
let mut ctx = Ctx::new(ctx);
133-
Self::try_fold_stmt_in_boolean_context(stmt, &mut ctx);
134-
Self::remove_dead_code_exit_statement(stmt, &mut ctx);
135-
if let Statement::IfStatement(if_stmt) = stmt {
136-
if let Some(folded_stmt) = Self::try_minimize_if(if_stmt, &mut ctx) {
137-
*stmt = folded_stmt;
138-
ctx.state.changed = true;
132+
let ctx = &mut Ctx::new(ctx);
133+
match stmt {
134+
Statement::BlockStatement(_) => Self::try_optimize_block(stmt, ctx),
135+
Statement::IfStatement(s) => {
136+
Self::try_fold_expr_in_boolean_context(&mut s.test, ctx);
137+
Self::try_fold_if(stmt, ctx);
138+
if let Statement::IfStatement(if_stmt) = stmt {
139+
if let Some(folded_stmt) = Self::try_minimize_if(if_stmt, ctx) {
140+
*stmt = folded_stmt;
141+
ctx.state.changed = true;
142+
}
143+
}
144+
}
145+
Statement::WhileStatement(s) => {
146+
Self::try_fold_expr_in_boolean_context(&mut s.test, ctx);
147+
}
148+
Statement::ForStatement(s) => {
149+
if let Some(test) = &mut s.test {
150+
Self::try_fold_expr_in_boolean_context(test, ctx);
151+
}
152+
Self::try_fold_for(stmt, ctx);
153+
}
154+
Statement::DoWhileStatement(s) => {
155+
Self::try_fold_expr_in_boolean_context(&mut s.test, ctx);
156+
}
157+
Statement::TryStatement(_) => Self::try_fold_try(stmt, ctx),
158+
Statement::LabeledStatement(_) => Self::try_fold_labeled(stmt, ctx),
159+
Statement::FunctionDeclaration(_) => {
160+
Self::remove_unused_function_declaration(stmt, ctx);
139161
}
162+
Statement::ClassDeclaration(_) => Self::remove_unused_class_declaration(stmt, ctx),
163+
Statement::ExpressionStatement(_) => Self::try_fold_expression_stmt(stmt, ctx),
164+
_ => {}
140165
}
141166
}
142167

@@ -412,8 +437,24 @@ impl<'a> Traverse<'a, MinifierState<'a>> for DeadCodeElimination {
412437
}
413438

414439
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
415-
let mut ctx = Ctx::new(ctx);
416-
PeepholeOptimizations::remove_dead_code_exit_statement(stmt, &mut ctx);
440+
let ctx = &mut Ctx::new(ctx);
441+
match stmt {
442+
Statement::BlockStatement(_) => PeepholeOptimizations::try_optimize_block(stmt, ctx),
443+
Statement::IfStatement(_) => PeepholeOptimizations::try_fold_if(stmt, ctx),
444+
Statement::ForStatement(_) => PeepholeOptimizations::try_fold_for(stmt, ctx),
445+
Statement::TryStatement(_) => PeepholeOptimizations::try_fold_try(stmt, ctx),
446+
Statement::LabeledStatement(_) => PeepholeOptimizations::try_fold_labeled(stmt, ctx),
447+
Statement::FunctionDeclaration(_) => {
448+
PeepholeOptimizations::remove_unused_function_declaration(stmt, ctx);
449+
}
450+
Statement::ClassDeclaration(_) => {
451+
PeepholeOptimizations::remove_unused_class_declaration(stmt, ctx);
452+
}
453+
Statement::ExpressionStatement(_) => {
454+
PeepholeOptimizations::try_fold_expression_stmt(stmt, ctx);
455+
}
456+
_ => {}
457+
}
417458
}
418459

419460
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {

crates/oxc_minifier/src/peephole/remove_dead_code.rs

Lines changed: 62 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,11 @@ use super::PeepholeOptimizations;
1616
/// See `KeepVar` at the end of this file for `var` hoisting logic.
1717
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeRemoveDeadCode.java>
1818
impl<'a> PeepholeOptimizations {
19-
pub fn remove_dead_code_exit_statement(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
20-
if let Some(new_stmt) = match stmt {
21-
Statement::BlockStatement(s) => Self::try_optimize_block(s, ctx),
22-
Statement::IfStatement(s) => Self::try_fold_if(s, ctx),
23-
Statement::ForStatement(s) => Self::try_fold_for(s, ctx),
24-
Statement::TryStatement(s) => Self::try_fold_try(s, ctx),
25-
Statement::LabeledStatement(s) => Self::try_fold_labeled(s, ctx),
26-
Statement::FunctionDeclaration(f) => Self::remove_unused_function_declaration(f, ctx),
27-
Statement::ClassDeclaration(c) => Self::remove_unused_class_declaration(c, ctx),
28-
_ => None,
29-
} {
30-
*stmt = new_stmt;
31-
ctx.state.changed = true;
32-
}
33-
34-
Self::try_fold_expression_stmt(stmt, ctx);
35-
}
36-
3719
/// Remove block from single line blocks
3820
/// `{ block } -> block`
39-
fn try_optimize_block(
40-
stmt: &mut BlockStatement<'a>,
41-
ctx: &Ctx<'a, '_>,
42-
) -> Option<Statement<'a>> {
43-
match stmt.body.len() {
21+
pub fn try_optimize_block(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
22+
let Statement::BlockStatement(s) = stmt else { return };
23+
match s.body.len() {
4424
0 => {
4525
let parent = ctx.parent();
4626
if parent.is_while_statement()
@@ -52,34 +32,33 @@ impl<'a> PeepholeOptimizations {
5232
|| parent.is_program()
5333
{
5434
// Remove the block if it is empty and the parent is a block statement.
55-
return Some(ctx.ast.statement_empty(stmt.span));
35+
*stmt = ctx.ast.statement_empty(s.span);
36+
ctx.state.changed = true;
5637
}
57-
None
5838
}
5939
1 => {
60-
let s = &stmt.body[0];
61-
if matches!(s, Statement::VariableDeclaration(decl) if !decl.kind.is_var())
62-
|| matches!(s, Statement::ClassDeclaration(_))
63-
|| matches!(s, Statement::FunctionDeclaration(_))
40+
let first = &s.body[0];
41+
if matches!(first, Statement::VariableDeclaration(decl) if !decl.kind.is_var())
42+
|| matches!(first, Statement::ClassDeclaration(_))
43+
|| matches!(first, Statement::FunctionDeclaration(_))
6444
{
65-
return None;
45+
return;
6646
}
67-
Some(stmt.body.remove(0))
47+
*stmt = s.body.remove(0);
48+
ctx.state.changed = true;
6849
}
69-
_ => None,
50+
_ => {}
7051
}
7152
}
7253

73-
fn try_fold_if(if_stmt: &mut IfStatement<'a>, ctx: &mut Ctx<'a, '_>) -> Option<Statement<'a>> {
54+
pub fn try_fold_if(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
55+
let Statement::IfStatement(if_stmt) = stmt else { return };
7456
// Descend and remove `else` blocks first.
7557
match &mut if_stmt.alternate {
76-
Some(Statement::IfStatement(alternate)) => {
77-
if let Some(new_stmt) = Self::try_fold_if(alternate, ctx) {
78-
if matches!(new_stmt, Statement::EmptyStatement(_)) {
79-
if_stmt.alternate = None;
80-
} else {
81-
if_stmt.alternate = Some(new_stmt);
82-
}
58+
Some(Statement::IfStatement(_)) => {
59+
Self::try_fold_if(if_stmt.alternate.as_mut().unwrap(), ctx);
60+
if matches!(if_stmt.alternate, Some(Statement::EmptyStatement(_))) {
61+
if_stmt.alternate = None;
8362
}
8463
}
8564
Some(Statement::BlockStatement(s)) if s.body.is_empty() => {
@@ -119,7 +98,7 @@ impl<'a> PeepholeOptimizations {
11998
} else {
12099
if_stmt.consequent = var_stmt;
121100
}
122-
return None;
101+
return;
123102
}
124103
if test_has_side_effects {
125104
if !has_var_stmt {
@@ -129,25 +108,21 @@ impl<'a> PeepholeOptimizations {
129108
if_stmt.consequent = ctx.ast.statement_empty(if_stmt.consequent.span());
130109
}
131110
}
132-
return None;
111+
return;
133112
}
134-
return Some(if boolean {
113+
*stmt = if boolean {
135114
if_stmt.consequent.take_in(ctx.ast)
115+
} else if let Some(alternate) = if_stmt.alternate.take() {
116+
alternate
136117
} else {
137-
if_stmt.alternate.as_mut().map_or_else(
138-
|| ctx.ast.statement_empty(if_stmt.span),
139-
|alternate| alternate.take_in(ctx.ast),
140-
)
141-
});
118+
ctx.ast.statement_empty(if_stmt.span)
119+
};
120+
ctx.state.changed = true;
142121
}
143-
None
144122
}
145123

146-
fn try_fold_for(
147-
for_stmt: &mut ForStatement<'a>,
148-
149-
ctx: &mut Ctx<'a, '_>,
150-
) -> Option<Statement<'a>> {
124+
pub fn try_fold_for(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
125+
let Statement::ForStatement(for_stmt) = stmt else { return };
151126
if let Some(init) = &mut for_stmt.init {
152127
if let Some(init) = init.as_expression_mut() {
153128
if Self::remove_unused_expression(init, ctx) {
@@ -166,14 +141,18 @@ impl<'a> PeepholeOptimizations {
166141
let test_boolean =
167142
for_stmt.test.as_ref().and_then(|test| test.evaluate_value_to_boolean(ctx));
168143
if for_stmt.test.as_ref().is_some_and(|test| test.may_have_side_effects(ctx)) {
169-
return None;
144+
return;
170145
}
171146
match test_boolean {
172-
Some(false) => match &mut for_stmt.init {
173-
Some(ForStatementInit::VariableDeclaration(var_init)) => {
147+
Some(false) => match &for_stmt.init {
148+
Some(ForStatementInit::VariableDeclaration(_)) => {
174149
let mut keep_var = KeepVar::new(ctx.ast);
175150
keep_var.visit_statement(&for_stmt.body);
176151
let mut var_decl = keep_var.get_variable_declaration();
152+
let Some(ForStatementInit::VariableDeclaration(var_init)) = &mut for_stmt.init
153+
else {
154+
return;
155+
};
177156
if var_init.kind.is_var() {
178157
if let Some(var_decl) = &mut var_decl {
179158
var_decl
@@ -183,28 +162,29 @@ impl<'a> PeepholeOptimizations {
183162
var_decl = Some(var_init.take_in_box(ctx.ast));
184163
}
185164
}
186-
Some(var_decl.map_or_else(
165+
*stmt = var_decl.map_or_else(
187166
|| ctx.ast.statement_empty(for_stmt.span),
188167
Statement::VariableDeclaration,
189-
))
168+
);
169+
ctx.state.changed = true;
190170
}
191171
None => {
192172
let mut keep_var = KeepVar::new(ctx.ast);
193173
keep_var.visit_statement(&for_stmt.body);
194-
Some(keep_var.get_variable_declaration().map_or_else(
174+
*stmt = keep_var.get_variable_declaration().map_or_else(
195175
|| ctx.ast.statement_empty(for_stmt.span),
196176
Statement::VariableDeclaration,
197-
))
177+
);
178+
ctx.state.changed = true;
198179
}
199-
_ => None,
180+
_ => {}
200181
},
201182
Some(true) => {
202183
// Remove the test expression.
203184
for_stmt.test = None;
204185
ctx.state.changed = true;
205-
None
206186
}
207-
None => None,
187+
None => {}
208188
}
209189
}
210190

@@ -213,7 +193,8 @@ impl<'a> PeepholeOptimizations {
213193
/// ```js
214194
/// a: break a;
215195
/// ```
216-
fn try_fold_labeled(s: &mut LabeledStatement<'a>, ctx: &Ctx<'a, '_>) -> Option<Statement<'a>> {
196+
pub fn try_fold_labeled(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
197+
let Statement::LabeledStatement(s) = stmt else { return };
217198
let id = s.label.name.as_str();
218199
// Check the first statement in the block, or just the `break [id] ` statement.
219200
// Check if we need to remove the whole block.
@@ -222,17 +203,20 @@ impl<'a> PeepholeOptimizations {
222203
if break_stmt.label.as_ref().is_some_and(|l| l.name.as_str() == id) => {}
223204
Statement::BlockStatement(block) if block.body.first().is_some_and(|first| matches!(first, Statement::BreakStatement(break_stmt) if break_stmt.label.as_ref().is_some_and(|l| l.name.as_str() == id))) => {}
224205
Statement::EmptyStatement(_) => {
225-
return Some(ctx.ast.statement_empty(s.span))
206+
*stmt = ctx.ast.statement_empty(s.span);
207+
ctx.state.changed=true;
208+
return
226209
}
227-
_ => return None,
210+
_ => return
228211
}
229212
let mut var = KeepVar::new(ctx.ast);
230213
var.visit_statement(&s.body);
231214
let var_decl = var.get_variable_declaration_statement();
232-
var_decl.unwrap_or_else(|| ctx.ast.statement_empty(s.span)).into()
215+
*stmt = var_decl.unwrap_or_else(|| ctx.ast.statement_empty(s.span));
216+
ctx.state.changed = true;
233217
}
234218

235-
fn try_fold_expression_stmt(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
219+
pub fn try_fold_expression_stmt(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
236220
let Statement::ExpressionStatement(expr_stmt) = stmt else { return };
237221
// We need to check if it is in arrow function with `expression: true`.
238222
// This is the only scenario where we can't remove it even if `ExpressionStatement`.
@@ -248,11 +232,13 @@ impl<'a> PeepholeOptimizations {
248232
}
249233
}
250234

251-
fn try_fold_try(s: &mut TryStatement<'a>, ctx: &Ctx<'a, '_>) -> Option<Statement<'a>> {
252-
if let Some(handler) = &mut s.handler {
235+
pub fn try_fold_try(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
236+
let Statement::TryStatement(s) = stmt else { return };
237+
if let Some(handler) = &s.handler {
253238
if s.block.body.is_empty() {
254239
let mut var = KeepVar::new(ctx.ast);
255240
var.visit_block_statement(&handler.body);
241+
let Some(handler) = &mut s.handler else { return };
256242
handler.body.body.clear();
257243
if let Some(var_decl) = var.get_variable_declaration_statement() {
258244
handler.body.body.push(var_decl);
@@ -269,15 +255,14 @@ impl<'a> PeepholeOptimizations {
269255
if s.block.body.is_empty()
270256
&& s.handler.as_ref().is_none_or(|handler| handler.body.body.is_empty())
271257
{
272-
if let Some(finalizer) = &mut s.finalizer {
258+
*stmt = if let Some(finalizer) = &mut s.finalizer {
273259
let mut block = ctx.ast.block_statement(finalizer.span, ctx.ast.vec());
274260
std::mem::swap(&mut **finalizer, &mut block);
275-
Some(Statement::BlockStatement(ctx.ast.alloc(block)))
261+
Statement::BlockStatement(ctx.ast.alloc(block))
276262
} else {
277-
Some(ctx.ast.statement_empty(s.span))
278-
}
279-
} else {
280-
None
263+
ctx.ast.statement_empty(s.span)
264+
};
265+
ctx.state.changed = true;
281266
}
282267
}
283268

0 commit comments

Comments
 (0)