diff --git a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs index 764a565c396ce..34b9b0f20e7bd 100644 --- a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs @@ -78,6 +78,12 @@ impl<'a> PeepholeOptimizations { prop: &mut MethodDefinition<'a>, ctx: Ctx<'a, '_>, ) { + let property_key_parent: ClassPropertyKeyParent = prop.into(); + if let PropertyKey::StringLiteral(str) = &prop.key { + if property_key_parent.should_keep_as_computed_property(&str.value) { + return; + } + } self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx); } @@ -86,6 +92,12 @@ impl<'a> PeepholeOptimizations { prop: &mut PropertyDefinition<'a>, ctx: Ctx<'a, '_>, ) { + let property_key_parent: ClassPropertyKeyParent = prop.into(); + if let PropertyKey::StringLiteral(str) = &prop.key { + if property_key_parent.should_keep_as_computed_property(&str.value) { + return; + } + } self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx); } @@ -94,6 +106,12 @@ impl<'a> PeepholeOptimizations { prop: &mut AccessorProperty<'a>, ctx: Ctx<'a, '_>, ) { + let property_key_parent: ClassPropertyKeyParent = prop.into(); + if let PropertyKey::StringLiteral(str) = &prop.key { + if property_key_parent.should_keep_as_computed_property(&str.value) { + return; + } + } self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx); } @@ -847,9 +865,7 @@ impl<'a> PeepholeOptimizations { }; let PropertyKey::StringLiteral(s) = key else { return }; let value = s.value.as_str(); - // Uncaught SyntaxError: Classes may not have a field named 'constructor' - // Uncaught SyntaxError: Class constructor may not be a private method - if matches!(value, "__proto__" | "prototype" | "constructor" | "#constructor") { + if value == "__proto__" { return; } if is_identifier_name(value) { @@ -1107,6 +1123,68 @@ impl<'a> LatePeepholeOptimizations { } } +struct ClassPropertyKeyParent { + pub ty: ClassPropertyKeyParentType, + /// Whether the property is static. + pub r#static: bool, +} + +impl ClassPropertyKeyParent { + /// Whether the key should be kept as a computed property to avoid early errors. + /// + /// + /// + /// + fn should_keep_as_computed_property(&self, key: &str) -> bool { + match key { + "prototype" => self.r#static, + "constructor" => match self.ty { + // Uncaught SyntaxError: Class constructor may not be an accessor + ClassPropertyKeyParentType::MethodDefinition => !self.r#static, + // Uncaught SyntaxError: Classes may not have a field named 'constructor' + // Uncaught SyntaxError: Class constructor may not be a private method + ClassPropertyKeyParentType::AccessorProperty + | ClassPropertyKeyParentType::PropertyDefinition => true, + }, + "#constructor" => true, + _ => false, + } + } +} + +enum ClassPropertyKeyParentType { + PropertyDefinition, + AccessorProperty, + MethodDefinition, +} + +impl From<&PropertyDefinition<'_>> for ClassPropertyKeyParent { + fn from(prop: &PropertyDefinition<'_>) -> Self { + Self { ty: ClassPropertyKeyParentType::PropertyDefinition, r#static: prop.r#static } + } +} + +impl From<&AccessorProperty<'_>> for ClassPropertyKeyParent { + fn from(accessor: &AccessorProperty<'_>) -> Self { + Self { ty: ClassPropertyKeyParentType::AccessorProperty, r#static: accessor.r#static } + } +} + +impl From<&MethodDefinition<'_>> for ClassPropertyKeyParent { + fn from(method: &MethodDefinition<'_>) -> Self { + Self { ty: ClassPropertyKeyParentType::MethodDefinition, r#static: method.r#static } + } +} + +impl From<&mut T> for ClassPropertyKeyParent +where + ClassPropertyKeyParent: for<'a> std::convert::From<&'a T>, +{ + fn from(prop: &mut T) -> Self { + (&*prop).into() + } +} + /// Port from #[cfg(test)] mod test { @@ -1607,10 +1685,34 @@ mod test { ); test("class C { ['-1']() {} }", "class C { '-1'() {} }"); - test_same("class C { ['prototype']() {} }"); test_same("class C { ['__proto__']() {} }"); - test_same("class C { ['constructor']() {} }"); - test_same("class C { ['#constructor']() {} }"); + + // + // + // + test_same("class C { static ['prototype']() {} }"); // class C { static prototype() {} } is an early error + test_same("class C { static ['prototype'] = 0 }"); // class C { prototype = 0 } is an early error + test_same("class C { static accessor ['prototype'] = 0 }"); // class C { accessor prototype = 0 } is an early error + test("class C { ['prototype']() {} }", "class C { prototype() {} }"); + test("class C { ['prototype'] = 0 }", "class C { prototype = 0 }"); + test("class C { accessor ['prototype'] = 0 }", "class C { accessor prototype = 0 }"); + test_same("class C { ['constructor'] = 0 }"); // class C { constructor = 0 } is an early error + test_same("class C { accessor ['constructor'] = 0 }"); // class C { accessor constructor = 0 } is an early error + test_same("class C { static ['constructor'] = 0 }"); // class C { static constructor = 0 } is an early error + test_same("class C { static accessor ['constructor'] = 0 }"); // class C { static accessor constructor = 0 } is an early error + test_same("class C { ['constructor']() {} }"); // computed `constructor` is not treated as a constructor + test_same("class C { *['constructor']() {} }"); // class C { *constructor() {} } is an early error + test_same("class C { async ['constructor']() {} }"); // class C { async constructor() {} } is an early error + test_same("class C { async *['constructor']() {} }"); // class C { async *constructor() {} } is an early error + test_same("class C { get ['constructor']() {} }"); // class C { get constructor() {} } is an early error + test_same("class C { set ['constructor'](v) {} }"); // class C { set constructor(v) {} } is an early error + test("class C { static ['constructor']() {} }", "class C { static constructor() {} }"); + test_same("class C { ['#constructor'] = 0 }"); // class C { #constructor = 0 } is an early error + test_same("class C { accessor ['#constructor'] = 0 }"); // class C { accessor #constructor = 0 } is an early error + test_same("class C { ['#constructor']() {} }"); // class C { #constructor() {} } is an early error + test_same("class C { static ['#constructor'] = 0 }"); // class C { static #constructor = 0 } is an early error + test_same("class C { static accessor ['#constructor'] = 0 }"); // class C { static accessor #constructor = 0 } is an early error + test_same("class C { static ['#constructor']() {} }"); // class C { static #constructor() {} } is an early error } #[test] diff --git a/crates/oxc_minifier/tests/peephole/esbuild.rs b/crates/oxc_minifier/tests/peephole/esbuild.rs index eb9f49ad6eec4..47059c62c680f 100644 --- a/crates/oxc_minifier/tests/peephole/esbuild.rs +++ b/crates/oxc_minifier/tests/peephole/esbuild.rs @@ -70,56 +70,56 @@ fn js_parser_test() { "function f() { x() } var f; function f() { y() }", "function f() { x();}var f;function f() { y();}", ); - // test("class Foo { ['constructor'] = 0 }", "class Foo { ['constructor'] = 0;}"); - // test("class Foo { ['constructor']() {} }", "class Foo { ['constructor']() { }}"); - // test("class Foo { *['constructor']() {} }", "class Foo { *['constructor']() { }}"); - // test("class Foo { get ['constructor']() {} }", "class Foo { get ['constructor']() { }}"); - // test("class Foo { set ['constructor'](x) {} }", "class Foo { set ['constructor'](x) { }}"); - // test("class Foo { async ['constructor']() {} }", "class Foo { async ['constructor']() { }}"); - // test("class Foo { static ['constructor'] = 0 }", "class Foo { static ['constructor'] = 0;}"); - // test("class Foo { static ['constructor']() {} }", "class Foo { static constructor() { }}"); - // test("class Foo { static *['constructor']() {} }", "class Foo { static *constructor() { }}"); - // test( - // "class Foo { static get ['constructor']() {} }", - // "class Foo { static get constructor() { }}", - // ); - // test( - // "class Foo { static set ['constructor'](x) {} }", - // "class Foo { static set constructor(x) { }}", - // ); - // test( - // "class Foo { static async ['constructor']() {} }", - // "class Foo { static async constructor() { }}", - // ); - // test("class Foo { ['prototype'] = 0 }", "class Foo { prototype = 0;}"); - // test("class Foo { ['prototype']() {} }", "class Foo { prototype() { }}"); - // test("class Foo { *['prototype']() {} }", "class Foo { *prototype() { }}"); - // test("class Foo { get ['prototype']() {} }", "class Foo { get prototype() { }}"); - // test("class Foo { set ['prototype'](x) {} }", "class Foo { set prototype(x) { }}"); - // test("class Foo { async ['prototype']() {} }", "class Foo { async prototype() { }}"); - // test("class Foo { static ['prototype'] = 0 }", "class Foo { static ['prototype'] = 0;}"); - // test("class Foo { static ['prototype']() {} }", "class Foo { static ['prototype']() { }}"); - // test("class Foo { static *['prototype']() {} }", "class Foo { static *['prototype']() { }}"); - // test( - // "class Foo { static get ['prototype']() {} }", - // "class Foo { static get ['prototype']() { }}", - // ); - // test( - // "class Foo { static set ['prototype'](x) {} }", - // "class Foo { static set ['prototype'](x) { }}", - // ); - // test( - // "class Foo { static async ['prototype']() {} }", - // "class Foo { static async ['prototype']() { }}", - // ); - // test( - // "class Foo { constructor() {} ['constructor']() {} }", - // "class Foo { constructor() { } ['constructor']() { }}", - // ); - // test( - // "class Foo { static constructor() {} static ['constructor']() {} }", - // "class Foo { static constructor() { } static constructor() { }}", - // ); + test("class Foo { ['constructor'] = 0 }", "class Foo { ['constructor'] = 0;}"); + test("class Foo { ['constructor']() {} }", "class Foo { ['constructor']() { }}"); + test("class Foo { *['constructor']() {} }", "class Foo { *['constructor']() { }}"); + test("class Foo { get ['constructor']() {} }", "class Foo { get ['constructor']() { }}"); + test("class Foo { set ['constructor'](x) {} }", "class Foo { set ['constructor'](x) { }}"); + test("class Foo { async ['constructor']() {} }", "class Foo { async ['constructor']() { }}"); + test("class Foo { static ['constructor'] = 0 }", "class Foo { static ['constructor'] = 0;}"); + test("class Foo { static ['constructor']() {} }", "class Foo { static constructor() { }}"); + test("class Foo { static *['constructor']() {} }", "class Foo { static *constructor() { }}"); + test( + "class Foo { static get ['constructor']() {} }", + "class Foo { static get constructor() { }}", + ); + test( + "class Foo { static set ['constructor'](x) {} }", + "class Foo { static set constructor(x) { }}", + ); + test( + "class Foo { static async ['constructor']() {} }", + "class Foo { static async constructor() { }}", + ); + test("class Foo { ['prototype'] = 0 }", "class Foo { prototype = 0;}"); + test("class Foo { ['prototype']() {} }", "class Foo { prototype() { }}"); + test("class Foo { *['prototype']() {} }", "class Foo { *prototype() { }}"); + test("class Foo { get ['prototype']() {} }", "class Foo { get prototype() { }}"); + test("class Foo { set ['prototype'](x) {} }", "class Foo { set prototype(x) { }}"); + test("class Foo { async ['prototype']() {} }", "class Foo { async prototype() { }}"); + test("class Foo { static ['prototype'] = 0 }", "class Foo { static ['prototype'] = 0;}"); + test("class Foo { static ['prototype']() {} }", "class Foo { static ['prototype']() { }}"); + test("class Foo { static *['prototype']() {} }", "class Foo { static *['prototype']() { }}"); + test( + "class Foo { static get ['prototype']() {} }", + "class Foo { static get ['prototype']() { }}", + ); + test( + "class Foo { static set ['prototype'](x) {} }", + "class Foo { static set ['prototype'](x) { }}", + ); + test( + "class Foo { static async ['prototype']() {} }", + "class Foo { static async ['prototype']() { }}", + ); + test( + "class Foo { constructor() {} ['constructor']() {} }", + "class Foo { constructor() { } ['constructor']() { }}", + ); + test( + "class Foo { static constructor() {} static ['constructor']() {} }", + "class Foo { static constructor() { } static constructor() { }}", + ); test("class x { '0' = y }", "class x { 0 = y;}"); test("class x { '123' = y }", "class x { 123 = y;}"); test("class x { ['-123'] = y }", "class x { '-123' = y;}");