feat(transformer): logical assignment operators (#923)

This commit is contained in:
Boshen 2023-09-16 23:54:53 +08:00 committed by GitHub
parent 1bb649243a
commit 5863f8ffdb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 125 additions and 34 deletions

View file

@ -55,6 +55,10 @@ impl<'a> AstBuilder<'a> {
String::from_str_in(value, self.allocator).into_bump_str()
}
pub fn copy<T>(&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,
}))
}

View file

@ -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

View file

@ -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)
}

View file

@ -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");

View file

@ -9,7 +9,6 @@ use std::rc::Rc;
/// References:
/// * <https://babel.dev/docs/babel-plugin-transform-exponentiation-operator>
/// * <https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-exponentiation-operator>
///
pub struct ExponentiationOperator<'a> {
ast: Rc<AstBuilder<'a>>,
}

View file

@ -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:
/// * <https://babel.dev/docs/babel-plugin-transform-logical-assignment-operators>
/// * <https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-logical-assignment-operator>
pub struct LogicalAssignmentOperators<'a> {
ast: Rc<AstBuilder<'a>>,
}
impl<'a> LogicalAssignmentOperators<'a> {
pub fn new(ast: Rc<AstBuilder<'a>>) -> 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;
}
}

View file

@ -0,0 +1,3 @@
mod logical_assignment_operators;
pub use logical_assignment_operators::LogicalAssignmentOperators;

View file

@ -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<ExponentiationOperator<'a>>,
// es2021
es2021_logical_assignment_operators: Option<LogicalAssignmentOperators<'a>>,
// es2019
es2019_optional_catch_binding: Option<OptionalCatchBinding<'a>>,
// es2016
es2016_exponentiation_operator: Option<ExponentiationOperator<'a>>,
}
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);

View file

@ -37,8 +37,8 @@ Does not have any features.
### ES2019
- [ ] optional-catch-binding
- [ ] json-strings
- [x] optional-catch-binding
- [ ] json-strings [Codegen]
### ES2018

View file

@ -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

View file

@ -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