diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 394ed08cac265..14bbe1adf5ded 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -420,6 +420,13 @@ impl<'a> PropertyKey<'a> { _ => false, } } + + pub fn is_specific_string_literal(&self, string: &str) -> bool { + match self { + PropertyKey::Expression(expr) => expr.is_specific_string_literal(string), + _ => false, + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] diff --git a/crates/oxc_transformer/src/es2015/mod.rs b/crates/oxc_transformer/src/es2015/mod.rs new file mode 100644 index 0000000000000..7ff2ccadb763f --- /dev/null +++ b/crates/oxc_transformer/src/es2015/mod.rs @@ -0,0 +1,3 @@ +mod shorthand_properties; + +pub use shorthand_properties::ShorthandProperties; diff --git a/crates/oxc_transformer/src/es2015/shorthand_properties.rs b/crates/oxc_transformer/src/es2015/shorthand_properties.rs new file mode 100644 index 0000000000000..9442412890c55 --- /dev/null +++ b/crates/oxc_transformer/src/es2015/shorthand_properties.rs @@ -0,0 +1,64 @@ +use oxc_ast::{ast::*, AstBuilder}; +use oxc_span::GetSpan; + +use std::rc::Rc; + +/// ES2015: Shorthand Properties +/// +/// References: +/// * +/// * +pub struct ShorthandProperties<'a> { + ast: Rc>, +} + +impl<'a> ShorthandProperties<'a> { + pub fn new(ast: Rc>) -> Self { + Self { ast } + } + + pub fn transform_object_property<'b>(&mut self, obj_prop: &'b mut ObjectProperty<'a>) { + if !obj_prop.shorthand && !obj_prop.method { + return; + } + + obj_prop.shorthand = false; + obj_prop.method = false; + + if obj_prop.computed { + // all computed key can never be transformed to `__proto__` setter unexpectedly + return; + } + + // We should handle the edge case of `__proto__` property. + // All shorthand properties with key `__proto__` can never be `__proto__` setter. + // But the transformed result can be `__proto__` setter unexpectedly. + // It's easy to fix it by using computed property. + + let is_proto_string = obj_prop.key.is_specific_string_literal("__proto__"); + + if !is_proto_string && !obj_prop.key.is_specific_id("__proto__") { + return; + } + // We reach here, it means that the key is `__proto__` or `"__proto__"`. + + // Transform `__proto__`/`"__proto__"` to computed property. + obj_prop.computed = true; + + if is_proto_string { + // After the transformation, the string literal `"__proto__"` is already expected result. + // + // input: + // "__proto__"() {} + // output: + // ["__proto__"]: function() {} + return; + } + + // Convert `[__proto__]` to `["__proto__"]` + + let proto = StringLiteral { span: obj_prop.key.span(), value: "__proto__".into() }; + let expr = self.ast.literal_string_expression(proto); + obj_prop.key = PropertyKey::Expression(expr); + } +} diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index b771e9f9e94d4..ca941a6d03be9 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -7,6 +7,7 @@ //! * //! * +mod es2015; mod es2016; mod es2019; mod es2021; @@ -19,6 +20,7 @@ use oxc_ast::{ast::*, AstBuilder, VisitMut}; use oxc_span::SourceType; use std::rc::Rc; +use es2015::ShorthandProperties; use es2016::ExponentiationOperator; use es2019::OptionalCatchBinding; use es2021::LogicalAssignmentOperators; @@ -39,6 +41,8 @@ pub struct Transformer<'a> { es2019_optional_catch_binding: Option>, // es2016 es2016_exponentiation_operator: Option>, + // es2015 + es2015_shorthand_properties: Option>, } impl<'a> Transformer<'a> { @@ -66,6 +70,9 @@ impl<'a> Transformer<'a> { if options.target < TransformTarget::ES2016 { t.es2016_exponentiation_operator.replace(ExponentiationOperator::new(Rc::clone(&ast))); } + if options.target < TransformTarget::ES2015 { + t.es2015_shorthand_properties.replace(ShorthandProperties::new(Rc::clone(&ast))); + } t } @@ -92,4 +99,14 @@ impl<'a, 'b> VisitMut<'a, 'b> for Transformer<'a> { } self.visit_statements(&mut clause.body.body); } + + fn visit_object_property(&mut self, prop: &'b mut ObjectProperty<'a>) { + self.es2015_shorthand_properties.as_mut().map(|t| t.transform_object_property(prop)); + + self.visit_property_key(&mut prop.key); + self.visit_expression(&mut prop.value); + if let Some(init) = &mut prop.init { + self.visit_expression(init); + } + } } diff --git a/crates/oxc_transformer/src/options.rs b/crates/oxc_transformer/src/options.rs index 0059aaffb0ffb..c633716f6b165 100644 --- a/crates/oxc_transformer/src/options.rs +++ b/crates/oxc_transformer/src/options.rs @@ -7,6 +7,7 @@ pub struct TransformOptions { /// See #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] pub enum TransformTarget { + ES5, ES2015, ES2016, ES2019, diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index 7a1c45439971d..1a879b2590a6f 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -1,4 +1,4 @@ -Passed: 86/1071 +Passed: 91/1078 # babel-plugin-transform-class-properties * Failed: assumption-constantSuper/complex-super-class/input.js @@ -750,6 +750,15 @@ Passed: 86/1071 * Failed: regression/4403/input.js * Passed: exponentiation-operator/binary/input.js +# babel-plugin-transform-shorthand-properties +* Failed: shorthand-properties/method-type-annotations/input.js +* Failed: shorthand-properties/shorthand-comments/input.js +* Passed: shorthand-properties/method-plain/input.js +* Passed: shorthand-properties/proto/input.js +* Passed: shorthand-properties/shorthand-mixed/input.js +* Passed: shorthand-properties/shorthand-multiple/input.js +* Passed: shorthand-properties/shorthand-single/input.js + # babel-plugin-transform-typescript * Failed: class/abstract-class-decorated/input.ts * Failed: class/abstract-class-decorated-method/input.ts diff --git a/tasks/transform_conformance/src/lib.rs b/tasks/transform_conformance/src/lib.rs index 0e02a5eb023e1..764d9a3d8b644 100644 --- a/tasks/transform_conformance/src/lib.rs +++ b/tasks/transform_conformance/src/lib.rs @@ -54,6 +54,8 @@ pub fn babel(options: &BabelOptions) { "babel-plugin-transform-async-to-generator", // ES2016 "babel-plugin-transform-exponentiation-operator", + // ES2015 + "babel-plugin-transform-shorthand-properties", // TypeScript "babel-plugin-transform-typescript", // React @@ -133,7 +135,7 @@ fn babel_test(input_path: &Path, options: &BabelOptions) -> bool { let expected = output_path.and_then(|path| fs::read_to_string(path).ok()); if let Some(expected) = &expected { let transform_options = TransformOptions { - target: TransformTarget::ES2015, + target: TransformTarget::ES5, react: Some(TransformReactOptions::default()), }; let program = allocator.alloc(ret.program); @@ -146,7 +148,7 @@ fn babel_test(input_path: &Path, options: &BabelOptions) -> bool { let passed = trim_transformed == trim_expected; if filtered { println!("Expected:\n"); - println!("{expected:?}\n"); + println!("{expected}\n"); println!("Transformed:\n"); println!("{transformed}\n"); println!("Diff:\n");