diff --git a/crates/oxc_ast/src/ast_builder.rs b/crates/oxc_ast/src/ast_builder.rs index 664b940ce..6b911748e 100644 --- a/crates/oxc_ast/src/ast_builder.rs +++ b/crates/oxc_ast/src/ast_builder.rs @@ -55,6 +55,10 @@ impl<'a> AstBuilder<'a> { String::from_str_in(value, self.allocator).into_bump_str() } + pub fn copy(&self, src: &T) -> T { + unsafe { std::mem::transmute_copy(src) } + } + /// Moves the expression out by replacing it with a null expression. pub fn move_expression(&self, expr: &mut Expression<'a>) -> Expression<'a> { let null_literal = NullLiteral::new(expr.span()); @@ -340,6 +344,13 @@ impl<'a> AstBuilder<'a> { })) } + pub fn simple_assignment_target_identifier( + &self, + ident: IdentifierReference, + ) -> SimpleAssignmentTarget<'a> { + SimpleAssignmentTarget::AssignmentTargetIdentifier(self.alloc(ident)) + } + pub fn await_expression(&self, span: Span, argument: Expression<'a>) -> Expression<'a> { Expression::AwaitExpression(self.alloc(AwaitExpression { span, argument })) } @@ -417,6 +428,10 @@ impl<'a> AstBuilder<'a> { Expression::LogicalExpression(self.alloc(LogicalExpression { span, left, operator, right })) } + pub fn member_expression(&self, expr: MemberExpression<'a>) -> Expression<'a> { + Expression::MemberExpression(self.alloc(expr)) + } + pub fn computed_member_expression( &self, span: Span, @@ -424,14 +439,9 @@ impl<'a> AstBuilder<'a> { expression: Expression<'a>, optional: bool, // for optional chaining ) -> Expression<'a> { - Expression::MemberExpression(self.alloc({ - MemberExpression::ComputedMemberExpression(ComputedMemberExpression { - span, - object, - expression, - optional, - }) - })) + self.member_expression(MemberExpression::ComputedMemberExpression( + ComputedMemberExpression { span, object, expression, optional }, + )) } pub fn static_member_expression( @@ -441,13 +451,11 @@ impl<'a> AstBuilder<'a> { property: IdentifierName, optional: bool, // for optional chaining ) -> Expression<'a> { - Expression::MemberExpression(self.alloc({ - MemberExpression::StaticMemberExpression(StaticMemberExpression { - span, - object, - property, - optional, - }) + self.member_expression(MemberExpression::StaticMemberExpression(StaticMemberExpression { + span, + object, + property, + optional, })) } @@ -458,13 +466,11 @@ impl<'a> AstBuilder<'a> { field: PrivateIdentifier, optional: bool, ) -> Expression<'a> { - Expression::MemberExpression(self.alloc({ - MemberExpression::PrivateFieldExpression(PrivateFieldExpression { - span, - object, - field, - optional, - }) + self.member_expression(MemberExpression::PrivateFieldExpression(PrivateFieldExpression { + span, + object, + field, + optional, })) } diff --git a/crates/oxc_linter/src/rules/eslint/no_constant_binary_expression.rs b/crates/oxc_linter/src/rules/eslint/no_constant_binary_expression.rs index b98ad6a85..c5adee2ef 100644 --- a/crates/oxc_linter/src/rules/eslint/no_constant_binary_expression.rs +++ b/crates/oxc_linter/src/rules/eslint/no_constant_binary_expression.rs @@ -184,7 +184,7 @@ impl NoConstantBinaryExpression { AssignmentOperator::Assign => { Self::has_constant_nullishness(&assign_expr.right, non_nullish, ctx) } - op if op.is_logical_operator() => false, + op if op.is_logical() => false, _ => true, }, Expression::SequenceExpression(sequence_expr) => sequence_expr @@ -322,7 +322,7 @@ impl NoConstantBinaryExpression { AssignmentOperator::Assign => { Self::has_constant_strict_boolean_comparison(&assign_expr.right, ctx) } - op if op.is_logical_operator() => false, + op if op.is_logical() => false, _ => true, }, Expression::SequenceExpression(sequence_expr) => sequence_expr diff --git a/crates/oxc_syntax/src/operator.rs b/crates/oxc_syntax/src/operator.rs index e51464f5d..c334ee602 100644 --- a/crates/oxc_syntax/src/operator.rs +++ b/crates/oxc_syntax/src/operator.rs @@ -39,7 +39,7 @@ pub enum AssignmentOperator { } impl AssignmentOperator { - pub fn is_logical_operator(self) -> bool { + pub fn is_logical(self) -> bool { matches!(self, Self::LogicalAnd | Self::LogicalOr | Self::LogicalNullish) } diff --git a/crates/oxc_transformer/examples/transformer.rs b/crates/oxc_transformer/examples/transformer.rs index 448b7ea09..8701cbb8d 100644 --- a/crates/oxc_transformer/examples/transformer.rs +++ b/crates/oxc_transformer/examples/transformer.rs @@ -4,7 +4,7 @@ use oxc_allocator::Allocator; use oxc_formatter::{Formatter, FormatterOptions}; use oxc_parser::Parser; use oxc_span::SourceType; -use oxc_transformer::{TransformOptions, Transformer}; +use oxc_transformer::{TransformOptions, TransformTarget, Transformer}; // Instruction: // create a `test.js`, @@ -33,7 +33,7 @@ fn main() { println!("Original:\n"); println!("{printed}"); - let transform_options = TransformOptions::default(); + let transform_options = TransformOptions { target: TransformTarget::ES2015 }; Transformer::new(&allocator, &transform_options).build(program); let printed = Formatter::new(source_text.len(), formatter_options).build(program); println!("Transformed:\n"); diff --git a/crates/oxc_transformer/src/es2016/exponentiation_operator.rs b/crates/oxc_transformer/src/es2016/exponentiation_operator.rs index 9a3db1b2c..69e040f8e 100644 --- a/crates/oxc_transformer/src/es2016/exponentiation_operator.rs +++ b/crates/oxc_transformer/src/es2016/exponentiation_operator.rs @@ -9,7 +9,6 @@ use std::rc::Rc; /// References: /// * /// * -/// pub struct ExponentiationOperator<'a> { ast: Rc>, } diff --git a/crates/oxc_transformer/src/es2021/logical_assignment_operators.rs b/crates/oxc_transformer/src/es2021/logical_assignment_operators.rs new file mode 100644 index 000000000..51972f71a --- /dev/null +++ b/crates/oxc_transformer/src/es2021/logical_assignment_operators.rs @@ -0,0 +1,64 @@ +use oxc_ast::{ast::*, AstBuilder}; +use oxc_span::Span; +use oxc_syntax::operator::{AssignmentOperator, LogicalOperator}; + +use std::rc::Rc; + +/// ES2021: Logical Assignment Operators +/// +/// References: +/// * +/// * +pub struct LogicalAssignmentOperators<'a> { + ast: Rc>, +} + +impl<'a> LogicalAssignmentOperators<'a> { + pub fn new(ast: Rc>) -> Self { + Self { ast } + } + + pub fn transform_expression<'b>(&mut self, expr: &'b mut Expression<'a>) { + let Expression::AssignmentExpression(assignment_expr) = expr else { return }; + + let operator = match assignment_expr.operator { + AssignmentOperator::LogicalAnd => LogicalOperator::And, + AssignmentOperator::LogicalOr => LogicalOperator::Or, + AssignmentOperator::LogicalNullish => LogicalOperator::Coalesce, + _ => return, + }; + + // Create the left hand sife + // a || (a = b) + // ^ ^ + let left1: AssignmentTarget<'a> = self.ast.copy(&assignment_expr.left); + let left2; + match &assignment_expr.left { + AssignmentTarget::SimpleAssignmentTarget(target) => match target { + SimpleAssignmentTarget::AssignmentTargetIdentifier(ident) => { + left2 = self.ast.identifier_expression((*ident).clone()); + } + SimpleAssignmentTarget::MemberAssignmentTarget(member_expr) => { + let member_expr = self.ast.copy(&**member_expr); + left2 = self.ast.member_expression(member_expr); + } + // All other are TypeScript syntax. + _ => return, + }, + // It is a Syntax Error if AssignmentTargetType of LeftHandSideExpression is not simple. + // So safe to return here. + AssignmentTarget::AssignmentTargetPattern(_) => return, + }; + + // Create the right hand side + // a || (a = b) + // ^^^^^^^ + let assign_op = AssignmentOperator::Assign; + let right = self.ast.copy(&assignment_expr.right); + let right = self.ast.assignment_expression(Span::default(), assign_op, left1, right); + let right = self.ast.parenthesized_expression(Span::default(), right); + + let logical_expr = self.ast.logical_expression(Span::default(), left2, operator, right); + *expr = logical_expr; + } +} diff --git a/crates/oxc_transformer/src/es2021/mod.rs b/crates/oxc_transformer/src/es2021/mod.rs new file mode 100644 index 000000000..4818faba8 --- /dev/null +++ b/crates/oxc_transformer/src/es2021/mod.rs @@ -0,0 +1,3 @@ +mod logical_assignment_operators; + +pub use logical_assignment_operators::LogicalAssignmentOperators; diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index f73a6e859..ce9f27d5e 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -9,6 +9,7 @@ mod es2016; mod es2019; +mod es2021; use oxc_allocator::Allocator; use oxc_ast::{ast::*, AstBuilder, VisitMut}; @@ -16,6 +17,7 @@ use std::rc::Rc; use es2016::ExponentiationOperator; use es2019::OptionalCatchBinding; +use es2021::LogicalAssignmentOperators; #[derive(Debug, Default, Clone)] pub struct TransformOptions { @@ -28,16 +30,19 @@ pub enum TransformTarget { ES2015, ES2016, ES2019, + ES2021, #[default] ESNext, } #[derive(Default)] pub struct Transformer<'a> { - // es2016 - es2016_exponentiation_operator: Option>, + // es2021 + es2021_logical_assignment_operators: Option>, // es2019 es2019_optional_catch_binding: Option>, + // es2016 + es2016_exponentiation_operator: Option>, } impl<'a> Transformer<'a> { @@ -45,12 +50,16 @@ impl<'a> Transformer<'a> { let ast = Rc::new(AstBuilder::new(allocator)); let mut t = Self::default(); - if options.target < TransformTarget::ES2016 { - t.es2016_exponentiation_operator.replace(ExponentiationOperator::new(Rc::clone(&ast))); + if options.target < TransformTarget::ES2021 { + t.es2021_logical_assignment_operators + .replace(LogicalAssignmentOperators::new(Rc::clone(&ast))); } if options.target < TransformTarget::ES2019 { t.es2019_optional_catch_binding.replace(OptionalCatchBinding::new(Rc::clone(&ast))); } + if options.target < TransformTarget::ES2016 { + t.es2016_exponentiation_operator.replace(ExponentiationOperator::new(Rc::clone(&ast))); + } t } @@ -61,6 +70,7 @@ impl<'a> Transformer<'a> { impl<'a, 'b> VisitMut<'a, 'b> for Transformer<'a> { fn visit_expression(&mut self, expr: &'b mut Expression<'a>) { + self.es2021_logical_assignment_operators.as_mut().map(|t| t.transform_expression(expr)); self.es2016_exponentiation_operator.as_mut().map(|t| t.transform_expression(expr)); self.visit_expression_match(expr); diff --git a/tasks/transform_conformance/README.md b/tasks/transform_conformance/README.md index 378e41d7e..13ddcb305 100644 --- a/tasks/transform_conformance/README.md +++ b/tasks/transform_conformance/README.md @@ -37,8 +37,8 @@ Does not have any features. ### ES2019 -- [ ] optional-catch-binding -- [ ] json-strings +- [x] optional-catch-binding +- [ ] json-strings [Codegen] ### ES2018 diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index 1dd1fdd19..0e597ca80 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -9,6 +9,14 @@ * Passed: removal/bigint/input.js * Passed: used-with-transform-es2015-literals/input.js +# babel-plugin-transform-logical-assignment-operators +* Failed: logical-assignment/general-semantics/input.js +* Failed: logical-assignment/null-coalescing/input.js +* Failed: logical-assignment/null-coalescing-without-other/input.js +* Passed: logical-assignment/anonymous-functions-transform/input.js +* Passed: logical-assignment/arrow-functions-transform/input.js +* Passed: logical-assignment/named-functions-transform/input.js + # babel-plugin-transform-export-namespace-from * Failed: export-namespace/namespace-default/input.mjs * Failed: export-namespace/namespace-es6/input.mjs diff --git a/tasks/transform_conformance/src/lib.rs b/tasks/transform_conformance/src/lib.rs index 0c92f2b6d..d91057511 100644 --- a/tasks/transform_conformance/src/lib.rs +++ b/tasks/transform_conformance/src/lib.rs @@ -21,6 +21,7 @@ pub fn babel() { "babel-plugin-transform-unicode-sets-regex", // ES2021 "babel-plugin-transform-numeric-separator", + "babel-plugin-transform-logical-assignment-operators", // ES2020 "babel-plugin-transform-export-namespace-from", // ES2019