Skip to content

Commit 0b100f9

Browse files
committed
fix(minifier)!: receive supported engines instead of ecmascript versions
1 parent 8ddb4f6 commit 0b100f9

13 files changed

+198
-199
lines changed

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/oxc_minifier/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ oxc_allocator = { workspace = true }
2525
oxc_ast = { workspace = true }
2626
oxc_ast_visit = { workspace = true }
2727
oxc_codegen = { workspace = true }
28+
oxc_compat = { workspace = true }
2829
oxc_ecmascript = { workspace = true }
2930
oxc_mangler = { workspace = true }
3031
oxc_parser = { workspace = true }

crates/oxc_minifier/src/ctx.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use oxc_syntax::{
1616
use oxc_traverse::Ancestor;
1717

1818
use crate::{options::CompressOptions, state::MinifierState, symbol_value::SymbolValue};
19+
use oxc_compat::ESFeature;
1920

2021
pub type TraverseCtx<'a> = oxc_traverse::TraverseCtx<'a, MinifierState<'a>>;
2122

@@ -106,6 +107,13 @@ impl<'a> Ctx<'a, '_> {
106107
&self.0.state.options
107108
}
108109

110+
/// Check if the target engines supports a feature.
111+
///
112+
/// Returns `true` if the feature is supported.
113+
pub fn supports_feature(&self, feature: ESFeature) -> bool {
114+
!self.options().target.has_feature(feature)
115+
}
116+
109117
pub fn source_type(&self) -> SourceType {
110118
self.0.state.source_type
111119
}

crates/oxc_minifier/src/options.rs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
1-
use oxc_syntax::es_target::ESTarget;
1+
use oxc_compat::EngineTargets;
22

33
pub use oxc_ecmascript::side_effects::PropertyReadSideEffects;
44

55
#[derive(Debug, Clone)]
66
pub struct CompressOptions {
7-
/// Set desired EcmaScript standard version for output.
7+
/// Engine targets for feature detection.
88
///
9-
/// e.g.
9+
/// Used to determine which ES features are supported by the target engines
10+
/// and whether transformations can be applied.
1011
///
11-
/// * catch optional binding when >= es2019
12-
/// * `??` operator >= es2020
13-
///
14-
/// Default `ESTarget::ESNext`
15-
pub target: ESTarget,
12+
/// Default: empty (supports all features)
13+
pub target: EngineTargets,
1614

1715
/// Remove `debugger;` statements.
1816
///
@@ -59,7 +57,7 @@ impl Default for CompressOptions {
5957
impl CompressOptions {
6058
pub fn smallest() -> Self {
6159
Self {
62-
target: ESTarget::ESNext,
60+
target: EngineTargets::default(),
6361
keep_names: CompressOptionsKeepNames::all_false(),
6462
drop_debugger: true,
6563
drop_console: false,
@@ -73,7 +71,7 @@ impl CompressOptions {
7371

7472
pub fn safest() -> Self {
7573
Self {
76-
target: ESTarget::ESNext,
74+
target: EngineTargets::default(),
7775
keep_names: CompressOptionsKeepNames::all_true(),
7876
drop_debugger: false,
7977
drop_console: false,
@@ -87,7 +85,7 @@ impl CompressOptions {
8785

8886
pub fn dce() -> Self {
8987
Self {
90-
target: ESTarget::ESNext,
88+
target: EngineTargets::default(),
9189
keep_names: CompressOptionsKeepNames::all_true(),
9290
drop_debugger: false,
9391
drop_console: false,

crates/oxc_minifier/src/peephole/minimize_conditional_expression.rs

Lines changed: 72 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1+
use crate::ctx::Ctx;
12
use oxc_allocator::TakeIn;
23
use oxc_ast::{NONE, ast::*};
4+
use oxc_compat::ESFeature;
35
use oxc_ecmascript::{
46
constant_evaluation::{ConstantEvaluation, ConstantValue},
57
side_effects::MayHaveSideEffects,
68
};
79
use oxc_span::{ContentEq, GetSpan};
8-
use oxc_syntax::es_target::ESTarget;
9-
10-
use crate::ctx::Ctx;
1110

1211
use super::PeepholeOptimizations;
1312

@@ -299,70 +298,73 @@ impl<'a> PeepholeOptimizations {
299298
}
300299

301300
// Try using the "??" or "?." operators
302-
if ctx.options().target >= ESTarget::ES2020 {
303-
if let Expression::BinaryExpression(test_binary) = &mut expr.test {
304-
if let Some(is_negate) = match test_binary.operator {
305-
BinaryOperator::Inequality => Some(true),
306-
BinaryOperator::Equality => Some(false),
307-
_ => None,
308-
} {
309-
// a == null / a != null / (a = foo) == null / (a = foo) != null
310-
let value_expr_with_id_name = if test_binary.left.is_null() {
311-
if let Some(id) = Self::extract_id_or_assign_to_id(&test_binary.right)
312-
.filter(|id| !ctx.is_global_reference(id))
313-
{
314-
Some((id.name, &mut test_binary.right))
315-
} else {
316-
None
317-
}
318-
} else if test_binary.right.is_null() {
319-
if let Some(id) = Self::extract_id_or_assign_to_id(&test_binary.left)
320-
.filter(|id| !ctx.is_global_reference(id))
321-
{
322-
Some((id.name, &mut test_binary.left))
323-
} else {
324-
None
325-
}
326-
} else {
327-
None
328-
};
329-
if let Some((target_id_name, value_expr)) = value_expr_with_id_name {
330-
// `a == null ? b : a` -> `a ?? b`
331-
// `a != null ? a : b` -> `a ?? b`
332-
// `(a = foo) == null ? b : a` -> `(a = foo) ?? b`
333-
// `(a = foo) != null ? a : b` -> `(a = foo) ?? b`
334-
let maybe_same_id_expr =
301+
if (ctx.supports_feature(ESFeature::ES2020NullishCoalescingOperator)
302+
|| ctx.supports_feature(ESFeature::ES2020OptionalChaining))
303+
&& let Expression::BinaryExpression(test_binary) = &mut expr.test
304+
&& let Some(is_negate) = match test_binary.operator {
305+
BinaryOperator::Inequality => Some(true),
306+
BinaryOperator::Equality => Some(false),
307+
_ => None,
308+
}
309+
{
310+
// a == null / a != null / (a = foo) == null / (a = foo) != null
311+
let value_expr_with_id_name = if test_binary.left.is_null() {
312+
if let Some(id) = Self::extract_id_or_assign_to_id(&test_binary.right)
313+
.filter(|id| !ctx.is_global_reference(id))
314+
{
315+
Some((id.name, &mut test_binary.right))
316+
} else {
317+
None
318+
}
319+
} else if test_binary.right.is_null() {
320+
if let Some(id) = Self::extract_id_or_assign_to_id(&test_binary.left)
321+
.filter(|id| !ctx.is_global_reference(id))
322+
{
323+
Some((id.name, &mut test_binary.left))
324+
} else {
325+
None
326+
}
327+
} else {
328+
None
329+
};
330+
if let Some((target_id_name, value_expr)) = value_expr_with_id_name {
331+
if ctx.supports_feature(ESFeature::ES2020NullishCoalescingOperator) {
332+
// `a == null ? b : a` -> `a ?? b`
333+
// `a != null ? a : b` -> `a ?? b`
334+
// `(a = foo) == null ? b : a` -> `(a = foo) ?? b`
335+
// `(a = foo) != null ? a : b` -> `(a = foo) ?? b`
336+
let maybe_same_id_expr =
337+
if is_negate { &mut expr.consequent } else { &mut expr.alternate };
338+
if maybe_same_id_expr.is_specific_id(&target_id_name) {
339+
return Some(ctx.ast.expression_logical(
340+
expr.span,
341+
value_expr.take_in(ctx.ast),
342+
LogicalOperator::Coalesce,
343+
if is_negate {
344+
expr.alternate.take_in(ctx.ast)
345+
} else {
346+
expr.consequent.take_in(ctx.ast)
347+
},
348+
));
349+
}
350+
}
351+
if ctx.supports_feature(ESFeature::ES2020OptionalChaining) {
352+
// "a == null ? undefined : a.b.c[d](e)" => "a?.b.c[d](e)"
353+
// "a != null ? a.b.c[d](e) : undefined" => "a?.b.c[d](e)"
354+
// "(a = foo) == null ? undefined : a.b.c[d](e)" => "(a = foo)?.b.c[d](e)"
355+
// "(a = foo) != null ? a.b.c[d](e) : undefined" => "(a = foo)?.b.c[d](e)"
356+
let maybe_undefined_expr =
357+
if is_negate { &expr.alternate } else { &expr.consequent };
358+
if ctx.is_expression_undefined(maybe_undefined_expr) {
359+
let expr_to_inject_optional_chaining =
335360
if is_negate { &mut expr.consequent } else { &mut expr.alternate };
336-
if maybe_same_id_expr.is_specific_id(&target_id_name) {
337-
return Some(ctx.ast.expression_logical(
338-
expr.span,
339-
value_expr.take_in(ctx.ast),
340-
LogicalOperator::Coalesce,
341-
if is_negate {
342-
expr.alternate.take_in(ctx.ast)
343-
} else {
344-
expr.consequent.take_in(ctx.ast)
345-
},
346-
));
347-
}
348-
349-
// "a == null ? undefined : a.b.c[d](e)" => "a?.b.c[d](e)"
350-
// "a != null ? a.b.c[d](e) : undefined" => "a?.b.c[d](e)"
351-
// "(a = foo) == null ? undefined : a.b.c[d](e)" => "(a = foo)?.b.c[d](e)"
352-
// "(a = foo) != null ? a.b.c[d](e) : undefined" => "(a = foo)?.b.c[d](e)"
353-
let maybe_undefined_expr =
354-
if is_negate { &expr.alternate } else { &expr.consequent };
355-
if ctx.is_expression_undefined(maybe_undefined_expr) {
356-
let expr_to_inject_optional_chaining =
357-
if is_negate { &mut expr.consequent } else { &mut expr.alternate };
358-
if Self::inject_optional_chaining_if_matched(
359-
&target_id_name,
360-
value_expr,
361-
expr_to_inject_optional_chaining,
362-
ctx,
363-
) {
364-
return Some(expr_to_inject_optional_chaining.take_in(ctx.ast));
365-
}
361+
if Self::inject_optional_chaining_if_matched(
362+
&target_id_name,
363+
value_expr,
364+
expr_to_inject_optional_chaining,
365+
ctx,
366+
) {
367+
return Some(expr_to_inject_optional_chaining.take_in(ctx.ast));
366368
}
367369
}
368370
}
@@ -590,18 +592,7 @@ impl<'a> PeepholeOptimizations {
590592

591593
#[cfg(test)]
592594
mod test {
593-
use oxc_syntax::es_target::ESTarget;
594-
595-
use crate::{
596-
CompressOptions,
597-
tester::{test, test_options, test_same},
598-
};
599-
600-
fn test_es2019(source_text: &str, expected: &str) {
601-
let target = ESTarget::ES2019;
602-
let options = CompressOptions { target, ..CompressOptions::default() };
603-
test_options(source_text, expected, &options);
604-
}
595+
use crate::tester::{test, test_same, test_target};
605596

606597
#[test]
607598
fn test_minimize_expr_condition() {
@@ -663,7 +654,7 @@ mod test {
663654
test("var a; a != null ? a : b", "var a; a ?? b");
664655
test("var a; (a = _a) != null ? a : b", "var a; (a = _a) ?? b");
665656
test("v = a != null ? a : b", "v = a == null ? b : a"); // accessing global `a` may have a getter with side effects
666-
test_es2019("var a; v = a != null ? a : b", "var a; v = a == null ? b : a");
657+
test_target("var a; v = a != null ? a : b", "var a; v = a == null ? b : a", "chrome79");
667658
test("var a; v = a != null ? a.b.c[d](e) : undefined", "var a; v = a?.b.c[d](e)");
668659
test(
669660
"var a; v = (a = _a) != null ? a.b.c[d](e) : undefined",
@@ -674,9 +665,10 @@ mod test {
674665
"var a, undefined = 1; v = a != null ? a.b.c[d](e) : undefined",
675666
"var a; v = a == null ? 1 : a.b.c[d](e)",
676667
);
677-
test_es2019(
668+
test_target(
678669
"var a; v = a != null ? a.b.c[d](e) : undefined",
679670
"var a; v = a == null ? void 0 : a.b.c[d](e)",
671+
"chrome79",
680672
);
681673
test("v = cmp !== 0 ? cmp : (bar, cmp);", "v = (cmp === 0 && bar, cmp);");
682674
test("v = cmp === 0 ? cmp : (bar, cmp);", "v = (cmp === 0 || bar, cmp);");

crates/oxc_minifier/src/peephole/minimize_conditions.rs

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use oxc_allocator::TakeIn;
22
use oxc_ast::ast::*;
3+
use oxc_compat::ESFeature;
34
use oxc_ecmascript::{
45
constant_evaluation::{ConstantEvaluation, ConstantValue, DetermineValueType},
56
side_effects::MayHaveSideEffects,
67
};
78
use oxc_semantic::ReferenceFlags;
89
use oxc_span::GetSpan;
9-
use oxc_syntax::es_target::ESTarget;
1010

1111
use crate::ctx::Ctx;
1212

@@ -175,10 +175,9 @@ impl<'a> PeepholeOptimizations {
175175
expr: &mut AssignmentExpression<'a>,
176176
ctx: &mut Ctx<'a, '_>,
177177
) {
178-
if ctx.options().target < ESTarget::ES2020 {
179-
return;
180-
}
181-
if !matches!(expr.operator, AssignmentOperator::Assign) {
178+
if !ctx.supports_feature(ESFeature::ES2021LogicalAssignmentOperators)
179+
|| !matches!(expr.operator, AssignmentOperator::Assign)
180+
{
182181
return;
183182
}
184183

@@ -263,11 +262,7 @@ impl<'a> PeepholeOptimizations {
263262
/// <https://github.com/google/closure-compiler/blob/v20240609/test/com/google/javascript/jscomp/PeepholeMinimizeConditionsTest.java>
264263
#[cfg(test)]
265264
mod test {
266-
use crate::{
267-
CompressOptions,
268-
tester::{test, test_options, test_same, test_same_options},
269-
};
270-
use oxc_syntax::es_target::ESTarget;
265+
use crate::tester::{test, test_same, test_target, test_target_same};
271266

272267
/** Check that removing blocks with 1 child works */
273268
#[test]
@@ -1406,9 +1401,7 @@ mod test {
14061401
// foo() might have a side effect
14071402
test_same("foo().a || (foo().a = 3)");
14081403

1409-
let target = ESTarget::ES2019;
1410-
let options = CompressOptions { target, ..CompressOptions::default() };
1411-
test_same_options("x || (x = 3)", &options);
1404+
test_target_same("x || (x = 3)", "chrome84");
14121405

14131406
test("x || (a, x = 3)", "x ||= (a, 3)");
14141407
test("x && (a, x = 3)", "x &&= (a, 3)");
@@ -1445,9 +1438,7 @@ mod test {
14451438
// Example case: `let f = false; f = f || (() => {}); console.log(f.name)`
14461439
test("var x; x = x || (() => 'a')", "var x; x ||= (() => 'a')");
14471440

1448-
let target = ESTarget::ES2019;
1449-
let options = CompressOptions { target, ..CompressOptions::default() };
1450-
test_options("var x; x = x || 1", "var x = x || 1", &options);
1441+
test_target("var x; x = x || 1", "var x = x || 1", "chrome84");
14511442
}
14521443

14531444
#[test]

crates/oxc_minifier/src/peephole/minimize_logical_expression.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use oxc_allocator::TakeIn;
22
use oxc_ast::ast::*;
3+
use oxc_compat::ESFeature;
34
use oxc_semantic::ReferenceFlags;
45
use oxc_span::{ContentEq, GetSpan};
5-
use oxc_syntax::es_target::ESTarget;
66

77
use crate::ctx::Ctx;
88

@@ -209,7 +209,7 @@ impl<'a> PeepholeOptimizations {
209209
expr: &mut Expression<'a>,
210210
ctx: &mut Ctx<'a, '_>,
211211
) {
212-
if ctx.options().target < ESTarget::ES2020 {
212+
if !ctx.supports_feature(ESFeature::ES2021LogicalAssignmentOperators) {
213213
return;
214214
}
215215
let Expression::LogicalExpression(e) = expr else { return };

0 commit comments

Comments
 (0)