Skip to content

Commit 3f33e8c

Browse files
committed
feat(minifier): remove unused assignment expression (#12314)
``` var x = 1; x = 2; // remove ``` Remove the entier thing if the binding has no read references.
1 parent cd98426 commit 3f33e8c

File tree

9 files changed

+66
-29
lines changed

9 files changed

+66
-29
lines changed

crates/oxc_minifier/src/keep_var.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ use oxc_ast::{AstBuilder, NONE, ast::*};
33
use oxc_ast_visit::Visit;
44
use oxc_ecmascript::BoundNames;
55
use oxc_span::{Atom, SPAN, Span};
6+
use oxc_syntax::symbol::SymbolId;
67

78
pub struct KeepVar<'a> {
89
ast: AstBuilder<'a>,
9-
vars: std::vec::Vec<(Atom<'a>, Span)>,
10+
vars: std::vec::Vec<(Atom<'a>, Span, Option<SymbolId>)>,
1011
all_hoisted: bool,
1112
}
1213

@@ -44,7 +45,7 @@ impl<'a> Visit<'a> for KeepVar<'a> {
4445
fn visit_variable_declaration(&mut self, it: &VariableDeclaration<'a>) {
4546
if it.kind.is_var() {
4647
it.bound_names(&mut |ident| {
47-
self.vars.push((ident.name, ident.span));
48+
self.vars.push((ident.name, ident.span, ident.symbol_id.get()));
4849
});
4950
if it.has_init() {
5051
self.all_hoisted = false;
@@ -68,8 +69,15 @@ impl<'a> KeepVar<'a> {
6869
}
6970

7071
let kind = VariableDeclarationKind::Var;
71-
let decls = self.ast.vec_from_iter(self.vars.into_iter().map(|(name, span)| {
72-
let binding_kind = self.ast.binding_pattern_kind_binding_identifier(span, name);
72+
let decls = self.ast.vec_from_iter(self.vars.into_iter().map(|(name, span, symbol_id)| {
73+
let binding_kind = symbol_id.map_or_else(
74+
|| self.ast.binding_pattern_kind_binding_identifier(span, name),
75+
|symbol_id| {
76+
self.ast.binding_pattern_kind_binding_identifier_with_symbol_id(
77+
span, name, symbol_id,
78+
)
79+
},
80+
);
7381
let id = self.ast.binding_pattern(binding_kind, NONE, false);
7482
self.ast.variable_declarator(span, kind, id, None, false)
7583
}));

crates/oxc_minifier/src/options.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ impl CompressOptions {
4848
target: ESTarget::ESNext,
4949
keep_names: CompressOptionsKeepNames::all_false(),
5050
drop_debugger: true,
51-
drop_console: true,
51+
drop_console: false,
5252
unused: CompressOptionsUnused::Remove,
5353
treeshake: TreeShakeOptions::default(),
5454
}

crates/oxc_minifier/src/peephole/minimize_statements.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,10 @@ impl<'a> PeepholeOptimizations {
416416
}
417417
if let BindingPatternKind::BindingIdentifier(ident) = &decl.id.kind {
418418
if let Some(symbol_id) = ident.symbol_id.get() {
419-
return ctx.scoping().symbol_is_unused(symbol_id);
419+
return ctx
420+
.scoping()
421+
.get_resolved_references(symbol_id)
422+
.all(|r| !r.flags().is_read());
420423
}
421424
}
422425
false

crates/oxc_minifier/src/peephole/mod.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -154,17 +154,13 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
154154
}
155155

156156
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
157-
let refs_before =
158-
ctx.scoping().resolved_references().flatten().copied().collect::<FxHashSet<_>>();
159-
160157
self.exit_program_or_function();
161158

159+
let refs_before =
160+
ctx.scoping().resolved_references().flatten().copied().collect::<FxHashSet<_>>();
162161
let mut counter = ReferencesCounter::default();
163162
counter.visit_program(program);
164-
let refs_after = counter.refs;
165-
166-
let removed_refs = refs_before.difference(&refs_after);
167-
for reference_id_to_remove in removed_refs {
163+
for reference_id_to_remove in refs_before.difference(&counter.refs) {
168164
ctx.scoping_mut().delete_reference(*reference_id_to_remove);
169165
}
170166
}

crates/oxc_minifier/src/peephole/normalize.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,10 @@ impl<'a> Normalize {
391391

392392
#[cfg(test)]
393393
mod test {
394-
use crate::tester::{test, test_same};
394+
use crate::{
395+
CompressOptions,
396+
tester::{default_options, test, test_options, test_same},
397+
};
395398

396399
#[test]
397400
fn test_while() {
@@ -430,11 +433,13 @@ mod test {
430433

431434
#[test]
432435
fn drop_console() {
433-
test("console.log()", "");
434-
test("(() => console.log())()", "");
435-
test(
436+
let options = CompressOptions { drop_console: true, ..default_options() };
437+
test_options("console.log()", "", &options);
438+
test_options("(() => console.log())()", "", &options);
439+
test_options(
436440
"(() => { try { return console.log() } catch {} })()",
437441
"(() => { try { return } catch {} })()",
442+
&options,
438443
);
439444
}
440445

crates/oxc_minifier/src/peephole/remove_dead_code.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use oxc_ecmascript::{constant_evaluation::ConstantEvaluation, side_effects::MayH
55
use oxc_span::GetSpan;
66
use oxc_traverse::Ancestor;
77

8-
use crate::{ctx::Ctx, keep_var::KeepVar};
8+
use crate::{CompressOptionsUnused, ctx::Ctx, keep_var::KeepVar};
99

1010
use super::{LatePeepholeOptimizations, PeepholeOptimizations, State};
1111

@@ -47,16 +47,43 @@ impl<'a> PeepholeOptimizations {
4747
Expression::ConditionalExpression(e) => {
4848
self.try_fold_conditional_expression(e, state, ctx)
4949
}
50-
Expression::SequenceExpression(sequence_expression) => {
51-
self.try_fold_sequence_expression(sequence_expression, state, ctx)
52-
}
50+
Expression::SequenceExpression(e) => self.try_fold_sequence_expression(e, state, ctx),
51+
Expression::AssignmentExpression(e) => self.remove_dead_assignment_expression(e, ctx),
5352
_ => None,
5453
} {
5554
*expr = folded_expr;
5655
state.changed = true;
5756
}
5857
}
5958

59+
fn remove_dead_assignment_expression(
60+
&self,
61+
e: &mut AssignmentExpression<'a>,
62+
ctx: &mut Ctx<'a, '_>,
63+
) -> Option<Expression<'a>> {
64+
if matches!(
65+
ctx.state.options.unused,
66+
CompressOptionsUnused::Keep | CompressOptionsUnused::KeepAssign
67+
) {
68+
return None;
69+
}
70+
let SimpleAssignmentTarget::AssignmentTargetIdentifier(ident) =
71+
e.left.as_simple_assignment_target()?
72+
else {
73+
return None;
74+
};
75+
let reference_id = ident.reference_id.get()?;
76+
let symbol_id = ctx.scoping().get_reference(reference_id).symbol_id()?;
77+
// Keep error for assigning to `const foo = 1; foo = 2`.
78+
if ctx.scoping().symbol_flags(symbol_id).is_const_variable() {
79+
return None;
80+
}
81+
if !ctx.scoping().get_resolved_references(symbol_id).all(|r| !r.flags().is_read()) {
82+
return None;
83+
}
84+
Some(e.right.take_in(ctx.ast))
85+
}
86+
6087
/// Removes dead code thats comes after `return`, `throw`, `continue` and `break` statements.
6188
pub fn remove_dead_code_exit_statements(
6289
&self,

crates/oxc_minifier/tests/peephole/dead_code_elimination.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,6 @@ fn dce_from_terser() {
210210
}",
211211
);
212212

213-
// NOTE: `if (x)` is changed to `if (true)` because const inlining is not implemented yet.
214213
test(
215214
r#"function f() {
216215
g();
@@ -230,7 +229,6 @@ fn dce_from_terser() {
230229
"#,
231230
r#"function f() {
232231
g();
233-
x = 10;
234232
throw new Error("foo");
235233
var x;
236234
}

napi/minify/test/terser.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1685,7 +1685,7 @@ test('issue_3146_4', () => {
16851685
run(code, expected);
16861686
});
16871687

1688-
test('issue_3192', () => {
1688+
test.skip('issue_3192', () => {
16891689
const code =
16901690
'(function(a){console.log(a="foo",arguments[0])})("bar");(function(a){"use strict";console.log(a="foo",arguments[0])})("bar");';
16911691
const expected = ['foo foo', 'foo bar'];

tasks/minsize/minsize.snap

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
| Oxc | ESBuild | Oxc | ESBuild |
22
Original | minified | minified | gzip | gzip | Fixture
33
-------------------------------------------------------------------------------------
4-
72.14 kB | 23.49 kB | 23.70 kB | 8.47 kB | 8.54 kB | react.development.js
4+
72.14 kB | 23.45 kB | 23.70 kB | 8.46 kB | 8.54 kB | react.development.js
55

66
173.90 kB | 59.51 kB | 59.82 kB | 19.18 kB | 19.33 kB | moment.js
77

@@ -11,17 +11,17 @@ Original | minified | minified | gzip | gzip | Fixture
1111

1212
544.10 kB | 71.38 kB | 72.48 kB | 25.85 kB | 26.20 kB | lodash.js
1313

14-
555.77 kB | 270.83 kB | 270.13 kB | 88.25 kB | 90.80 kB | d3.js
14+
555.77 kB | 270.80 kB | 270.13 kB | 88.24 kB | 90.80 kB | d3.js
1515

1616
1.01 MB | 440.17 kB | 458.89 kB | 122.37 kB | 126.71 kB | bundle.min.js
1717

1818
1.25 MB | 647 kB | 646.76 kB | 160.28 kB | 163.73 kB | three.js
1919

20-
2.14 MB | 716.12 kB | 724.14 kB | 161.80 kB | 181.07 kB | victory.js
20+
2.14 MB | 716.10 kB | 724.14 kB | 161.76 kB | 181.07 kB | victory.js
2121

2222
3.20 MB | 1.01 MB | 1.01 MB | 324.08 kB | 331.56 kB | echarts.js
2323

24-
6.69 MB | 2.25 MB | 2.31 MB | 463.80 kB | 488.28 kB | antd.js
24+
6.69 MB | 2.25 MB | 2.31 MB | 463.18 kB | 488.28 kB | antd.js
2525

26-
10.95 MB | 3.35 MB | 3.49 MB | 860.95 kB | 915.50 kB | typescript.js
26+
10.95 MB | 3.34 MB | 3.49 MB | 856.90 kB | 915.50 kB | typescript.js
2727

0 commit comments

Comments
 (0)