Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(transformer): Shorthand Properties #960

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions crates/oxc_ast/src/ast/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
3 changes: 3 additions & 0 deletions crates/oxc_transformer/src/es2015/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod shorthand_properties;

pub use shorthand_properties::ShorthandProperties;
64 changes: 64 additions & 0 deletions crates/oxc_transformer/src/es2015/shorthand_properties.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use oxc_ast::{ast::*, AstBuilder};
use oxc_span::GetSpan;

use std::rc::Rc;

/// ES2015: Shorthand Properties
///
/// References:
/// * <https://babel.dev/docs/babel-plugin-transform-shorthand-properties>
/// * <https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-shorthand-properties>
pub struct ShorthandProperties<'a> {
ast: Rc<AstBuilder<'a>>,
}

impl<'a> ShorthandProperties<'a> {
pub fn new(ast: Rc<AstBuilder<'a>>) -> 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;
Boshen marked this conversation as resolved.
Show resolved Hide resolved
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;
Boshen marked this conversation as resolved.
Show resolved Hide resolved

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);
}
}
17 changes: 17 additions & 0 deletions crates/oxc_transformer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//! * <https://babel.dev/docs/presets>
//! * <https://github.com/microsoft/TypeScript/blob/main/src/compiler/transformer.ts>

mod es2015;
mod es2016;
mod es2019;
mod es2021;
Expand All @@ -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;
Expand All @@ -39,6 +41,8 @@ pub struct Transformer<'a> {
es2019_optional_catch_binding: Option<OptionalCatchBinding<'a>>,
// es2016
es2016_exponentiation_operator: Option<ExponentiationOperator<'a>>,
// es2015
es2015_shorthand_properties: Option<ShorthandProperties<'a>>,
}

impl<'a> Transformer<'a> {
Expand Down Expand Up @@ -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
}

Expand All @@ -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);
}
}
}
1 change: 1 addition & 0 deletions crates/oxc_transformer/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub struct TransformOptions {
/// See <https://www.typescriptlang.org/tsconfig#target>
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
pub enum TransformTarget {
ES5,
ES2015,
ES2016,
ES2019,
Expand Down
11 changes: 10 additions & 1 deletion tasks/transform_conformance/babel.snap.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Passed: 86/1071
Passed: 91/1078

# babel-plugin-transform-class-properties
* Failed: assumption-constantSuper/complex-super-class/input.js
Expand Down Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions tasks/transform_conformance/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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");
Expand Down
Loading