mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-14 22:29:17 +00:00
fix(minifier): keep variable declaration initilized with class when keepNames is enabled (#25584)
Keep the variable declaration when that is initialized with class and keepNames is enabled:
```js
var MyStore = class { static { console.log(this.name) } };
```
fixes https://github.com/oxc-project/oxc/issues/25060
I used AI to generate the code and then reviewed the code and added the tests for `keepNames` disabled cases.
This commit is contained in:
3 files changed
+58
-5
No files matched your search
@@ -1,4 +1,4 @@
|
||||
use super::PeepholeOptimizations;
|
||||
use super::{PeepholeOptimizations, remove_unused_expression::ClassRemovability};
|
||||
use crate::{CompressOptionsUnused, TraverseCtx};
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ecmascript::constant_evaluation::{DetermineValueType, ValueType};
|
||||
@@ -64,6 +64,25 @@ impl<'a> PeepholeOptimizations {
|
||||
})
|
||||
}
|
||||
|
||||
/// Keep an unused declarator when dropping its class initializer would
|
||||
/// leave an executing anonymous class expression without its inferred
|
||||
/// name. The name can be observed by static blocks and decorators before
|
||||
/// any later use of the binding, so `keep_names` cannot be applied after
|
||||
/// the declarator has been reduced to a bare class expression.
|
||||
fn class_initializer_name_needs_to_be_kept(
|
||||
decl: &VariableDeclarator<'a>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> bool {
|
||||
let Some(init) = &decl.init else { return false };
|
||||
if !ctx.is_expression_whose_name_needs_to_be_kept(init) {
|
||||
return false;
|
||||
}
|
||||
let Expression::ClassExpression(class) = init.without_parentheses() else {
|
||||
return false;
|
||||
};
|
||||
matches!(Self::classify_class_removability(class, ctx), ClassRemovability::Keep)
|
||||
}
|
||||
|
||||
fn is_sync_iterator_expr(expr: &Expression<'a>, ctx: &TraverseCtx<'a>) -> bool {
|
||||
match expr {
|
||||
Expression::ArrayExpression(_)
|
||||
@@ -99,10 +118,9 @@ impl<'a> PeepholeOptimizations {
|
||||
match &decl.id {
|
||||
BindingPattern::BindingIdentifier(ident) => {
|
||||
if let Some(symbol_id) = ident.symbol_id.get() {
|
||||
return Self::symbol_is_unused_by_count(symbol_id, ctx)
|
||||
|| Self::self_recursive_function_declarator_is_unused(
|
||||
decl, symbol_id, ctx,
|
||||
);
|
||||
let is_unused = Self::symbol_is_unused_by_count(symbol_id, ctx)
|
||||
|| Self::self_recursive_function_declarator_is_unused(decl, symbol_id, ctx);
|
||||
return is_unused && !Self::class_initializer_name_needs_to_be_kept(decl, ctx);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
@@ -992,6 +992,20 @@ fn dce_remove_unused_class_identifier_heritage_under_assumptions() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dce_keeps_inferred_class_name_for_executing_class() {
|
||||
// The static block observes the name assigned by `var MyStore = class {}`.
|
||||
// DCE must not turn this into a bare anonymous class expression.
|
||||
test_same("var MyStore = class { static { console.log(this.name) } };");
|
||||
test_same("var MyStore = (class { static { console.log(this.name) } });");
|
||||
test(
|
||||
"var MyStore = (0, class { static { console.log(this.name) } });",
|
||||
// this can be compressed to `class { static { console.log(this.name) } }`
|
||||
"var MyStore = class { static { console.log(this.name) } };",
|
||||
);
|
||||
test_same("var MyStore = class { static foo = console.log(this.name) };");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "TODO: extend recursive reachability to mutual declarators and classes"]
|
||||
fn dce_recursive_unused_mutual_declarators_and_classes() {
|
||||
|
||||
@@ -142,6 +142,27 @@ fn remove_unused_variable_declaration() {
|
||||
test_options("for (var x; ; );", "for (; ;);", &options);
|
||||
test_options("for (var x = 1; ; );", "for (; ;);", &options);
|
||||
test_same_options("for (var x = foo; ; );", &options); // can be improved
|
||||
|
||||
test_options(
|
||||
"var MyStore = class { static { console.log(this.name) } };",
|
||||
"(class { static { console.log(this.name) } });",
|
||||
&options,
|
||||
);
|
||||
test_options(
|
||||
"var MyStore = (class { static { console.log(this.name) } });",
|
||||
"(class { static { console.log(this.name) } });",
|
||||
&options,
|
||||
);
|
||||
test_options(
|
||||
"var MyStore = (0, class { static { console.log(this.name) } });",
|
||||
"(class { static { console.log(this.name) } });",
|
||||
&options,
|
||||
);
|
||||
test_options(
|
||||
"var MyStore = class { static foo = console.log(this.name) };",
|
||||
"(class { static foo = console.log(this.name) });",
|
||||
&options,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user