mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(transformer): logical assignment operators (#923)
This commit is contained in:
parent
1bb649243a
commit
5863f8ffdb
11 changed files with 125 additions and 34 deletions
|
|
@ -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,
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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>>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
3
crates/oxc_transformer/src/es2021/mod.rs
Normal file
3
crates/oxc_transformer/src/es2021/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
mod logical_assignment_operators;
|
||||
|
||||
pub use logical_assignment_operators::LogicalAssignmentOperators;
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ Does not have any features.
|
|||
|
||||
### ES2019
|
||||
|
||||
- [ ] optional-catch-binding
|
||||
- [ ] json-strings
|
||||
- [x] optional-catch-binding
|
||||
- [ ] json-strings [Codegen]
|
||||
|
||||
### ES2018
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue