feat(transformer): support exponentiation operator plugin (#4876)

The implementation copy from the original implementation which removed in https://github.com/oxc-project/oxc/pull/2865.
This commit is contained in:
Dunqing 2024-08-15 10:10:32 +00:00
parent b3e189764f
commit 3a66e5843d
8 changed files with 415 additions and 5 deletions

View file

@ -0,0 +1,322 @@
use std::cell::Cell;
use oxc_allocator::{CloneIn, Vec};
use oxc_ast::ast::*;
use oxc_semantic::{ReferenceFlag, SymbolFlags};
use oxc_span::SPAN;
use oxc_syntax::operator::{AssignmentOperator, BinaryOperator};
use oxc_traverse::TraverseCtx;
use crate::context::Ctx;
/// ES2016: Exponentiation Operator
///
/// References:
/// * <https://babel.dev/docs/babel-plugin-transform-exponentiation-operator>
/// * <https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-exponentiation-operator>
/// * <https://github.com/babel/babel/blob/main/packages/babel-helper-builder-binary-assignment-operator-visitor>
pub struct ExponentiationOperator<'a> {
_ctx: Ctx<'a>,
var_declarations: std::vec::Vec<Vec<'a, VariableDeclarator<'a>>>,
}
#[derive(Debug)]
struct Exploded<'a> {
reference: AssignmentTarget<'a>,
uid: Expression<'a>,
}
impl<'a> ExponentiationOperator<'a> {
pub fn new(ctx: Ctx<'a>) -> Self {
Self { _ctx: ctx, var_declarations: vec![] }
}
fn clone_identifier_reference(
ident: &IdentifierReference<'a>,
ctx: &mut TraverseCtx<'a>,
) -> IdentifierReference<'a> {
let reference = ctx.symbols().get_reference(ident.reference_id.get().unwrap());
let symbol_id = reference.symbol_id();
let flag = reference.flag();
ctx.create_reference_id(ident.span, ident.name.clone(), symbol_id, *flag)
}
fn clone_expression(expr: &Expression<'a>, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
match expr {
Expression::Identifier(ident) => ctx
.ast
.expression_from_identifier_reference(Self::clone_identifier_reference(ident, ctx)),
_ => expr.clone_in(ctx.ast.allocator),
}
}
pub fn transform_statements(
&mut self,
_statements: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
self.var_declarations.push(ctx.ast.vec());
}
pub fn transform_statements_on_exit(
&mut self,
statements: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
if let Some(declarations) = self.var_declarations.pop() {
if declarations.is_empty() {
return;
}
let variable = ctx.ast.alloc_variable_declaration(
SPAN,
VariableDeclarationKind::Var,
declarations,
false,
);
statements.insert(0, Statement::VariableDeclaration(variable));
}
}
pub fn transform_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
// left ** right
if let Expression::BinaryExpression(binary_expr) = expr {
if binary_expr.operator == BinaryOperator::Exponential {
let left = ctx.ast.move_expression(&mut binary_expr.left);
let right = ctx.ast.move_expression(&mut binary_expr.right);
*expr = Self::math_pow(left, right, ctx);
}
}
// left **= right
if let Expression::AssignmentExpression(assign_expr) = expr {
if assign_expr.operator == AssignmentOperator::Exponential {
let mut nodes = ctx.ast.vec();
let Some(Exploded { reference, uid }) =
self.explode(&mut assign_expr.left, &mut nodes, ctx)
else {
return;
};
let right = ctx.ast.move_expression(&mut assign_expr.right);
let right = Self::math_pow(uid, right, ctx);
let assign_expr = ctx.ast.expression_assignment(
SPAN,
AssignmentOperator::Assign,
reference,
right,
);
nodes.push(assign_expr);
*expr = ctx.ast.expression_sequence(SPAN, nodes);
}
}
}
/// `left ** right` -> `Math.pow(left, right)`
fn math_pow(
left: Expression<'a>,
right: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
let ident_math =
ctx.create_reference_id(SPAN, ctx.ast.atom("Math"), None, ReferenceFlag::Read);
let object = ctx.ast.expression_from_identifier_reference(ident_math);
let property = ctx.ast.identifier_name(SPAN, "pow");
let callee =
Expression::from(ctx.ast.member_expression_static(SPAN, object, property, false));
let mut arguments = ctx.ast.vec_with_capacity(2);
arguments.push(Argument::from(left));
arguments.push(Argument::from(right));
ctx.ast.expression_call(
SPAN,
arguments,
callee,
None::<TSTypeParameterInstantiation<'_>>,
false,
)
}
/// Change `lhs **= 2` to `var temp; temp = lhs, lhs = Math.pow(temp, 2);`.
/// If the lhs is a member expression `obj.ref` or `obj[ref]`, assign them to a temporary variable so side-effects are not computed twice.
/// For `obj.ref`, change it to `var _obj; _obj = obj, _obj["ref"] = Math.pow(_obj["ref"], 2)`.
/// For `obj[ref]`, change it to `var _obj, _ref; _obj = obj, _ref = ref, _obj[_ref] = Math.pow(_obj[_ref], 2);`.
fn explode(
&mut self,
node: &mut AssignmentTarget<'a>,
nodes: &mut Vec<'a, Expression<'a>>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Exploded<'a>> {
let node = node.as_simple_assignment_target_mut()?;
let obj = self.get_obj_ref(node, nodes, ctx)?;
let (reference, uid) = match node {
SimpleAssignmentTarget::AssignmentTargetIdentifier(ident) => {
let reference = AssignmentTarget::AssignmentTargetIdentifier(
ctx.ast.alloc(Self::clone_identifier_reference(ident.as_ref(), ctx)),
);
(reference, obj)
}
match_member_expression!(SimpleAssignmentTarget) => {
let member_expr = node.to_member_expression_mut();
let computed = member_expr.is_computed();
let prop = self.get_prop_ref(member_expr, nodes, ctx)?;
let optional = false;
let obj_clone = Self::clone_expression(&obj, ctx);
let (reference, uid) = match &prop {
Expression::Identifier(ident) if !computed => {
let ident = IdentifierName::new(SPAN, ident.name.clone());
(
// TODO:
// Both of these are the same, but it's in order to avoid after cloning without reference_id.
// Related: https://github.com/oxc-project/oxc/issues/4804
ctx.ast.member_expression_static(
SPAN,
obj_clone,
ident.clone(),
optional,
),
ctx.ast.member_expression_static(SPAN, obj, ident, optional),
)
}
_ => {
let prop_clone = Self::clone_expression(&prop, ctx);
(
ctx.ast
.member_expression_computed(SPAN, obj_clone, prop_clone, optional),
ctx.ast.member_expression_computed(SPAN, obj, prop, optional),
)
}
};
(
AssignmentTarget::from(
ctx.ast.simple_assignment_target_member_expression(reference),
),
Expression::from(uid),
)
}
_ => return None,
};
Some(Exploded { reference, uid })
}
/// Make sure side-effects of evaluating `obj` of `obj.ref` and `obj[ref]` only happen once.
fn get_obj_ref(
&mut self,
node: &mut SimpleAssignmentTarget<'a>,
nodes: &mut Vec<'a, Expression<'a>>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
let reference = match node {
SimpleAssignmentTarget::AssignmentTargetIdentifier(ident) => {
if ident
.reference_id
.get()
.is_some_and(|reference_id| ctx.symbols().has_binding(reference_id))
{
// this variable is declared in scope so we can be 100% sure
// that evaluating it multiple times won't trigger a getter
// or something else
return Some(ctx.ast.expression_from_identifier_reference(
Self::clone_identifier_reference(ident, ctx),
));
}
// could possibly trigger a getter so we need to only evaluate it once
ctx.ast.expression_from_identifier_reference(Self::clone_identifier_reference(
ident, ctx,
))
}
match_member_expression!(SimpleAssignmentTarget) => {
let expr = match node {
SimpleAssignmentTarget::ComputedMemberExpression(e) => &mut e.object,
SimpleAssignmentTarget::StaticMemberExpression(e) => &mut e.object,
SimpleAssignmentTarget::PrivateFieldExpression(e) => &mut e.object,
_ => unreachable!(),
};
let expr = ctx.ast.move_expression(expr);
// the object reference that we need to save is locally declared
// so as per the previous comment we can be 100% sure evaluating
// it multiple times will be safe
// Super cannot be directly assigned so lets return it also
if matches!(expr, Expression::Super(_))
|| matches!(&expr, Expression::Identifier(ident) if ident
.reference_id
.get()
.is_some_and(|reference_id| ctx.symbols().has_binding(reference_id)))
{
return Some(expr);
}
expr
}
_ => return None,
};
Some(self.add_new_reference(reference, nodes, ctx))
}
/// Make sure side-effects of evaluating `ref` of `obj.ref` and `obj[ref]` only happen once.
fn get_prop_ref(
&mut self,
node: &mut MemberExpression<'a>,
nodes: &mut Vec<'a, Expression<'a>>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
let expr = match node {
MemberExpression::ComputedMemberExpression(expr) => {
let expr = ctx.ast.move_expression(&mut expr.expression);
if expr.is_literal() {
return Some(expr);
}
expr
}
MemberExpression::StaticMemberExpression(expr) => {
return Some(ctx.ast.expression_string_literal(SPAN, expr.property.name.clone()));
}
MemberExpression::PrivateFieldExpression(_) => {
// From babel: "We can't generate property ref for private name, please install `@babel/plugin-transform-class-properties`"
return None;
}
};
Some(self.add_new_reference(expr, nodes, ctx))
}
fn add_new_reference(
&mut self,
expr: Expression<'a>,
nodes: &mut Vec<'a, Expression<'a>>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
let name = match expr {
Expression::Identifier(ref ident) => ident.name.clone().as_str(),
_ => "ref",
};
let symbol_id =
ctx.generate_uid_in_current_scope(name, SymbolFlags::FunctionScopedVariable);
let symbol_name = ctx.ast.atom(ctx.symbols().get_name(symbol_id));
{
// var _name;
let binding_identifier = BindingIdentifier {
span: SPAN,
name: symbol_name.clone(),
symbol_id: Cell::new(Some(symbol_id)),
};
let kind = VariableDeclarationKind::Var;
let id = ctx.ast.binding_pattern_kind_from_binding_identifier(binding_identifier);
let id = ctx.ast.binding_pattern(id, None::<TSTypeAnnotation<'_>>, false);
self.var_declarations
.last_mut()
.unwrap()
.push(ctx.ast.variable_declarator(SPAN, kind, id, None, false));
}
let ident =
ctx.create_reference_id(SPAN, symbol_name, Some(symbol_id), ReferenceFlag::Read);
// let ident = self.create_new_var_with_expression(&expr);
// Add new reference `_name = name` to nodes
let left = ctx.ast.simple_assignment_target_from_identifier_reference(
Self::clone_identifier_reference(&ident, ctx),
);
let op = AssignmentOperator::Assign;
nodes.push(ctx.ast.expression_assignment(SPAN, op, AssignmentTarget::from(left), expr));
ctx.ast.expression_from_identifier_reference(ident)
}
}

View file

@ -0,0 +1,52 @@
mod exponentiation_operator;
mod options;
pub use exponentiation_operator::ExponentiationOperator;
pub use options::ES2016Options;
use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_traverse::TraverseCtx;
use std::rc::Rc;
use crate::context::Ctx;
#[allow(dead_code)]
pub struct ES2016<'a> {
ctx: Ctx<'a>,
options: ES2016Options,
// Plugins
exponentiation_operator: ExponentiationOperator<'a>,
}
impl<'a> ES2016<'a> {
pub fn new(options: ES2016Options, ctx: Ctx<'a>) -> Self {
Self { exponentiation_operator: ExponentiationOperator::new(Rc::clone(&ctx)), ctx, options }
}
pub fn transform_statements(
&mut self,
statements: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
if self.options.exponentiation_operator {
self.exponentiation_operator.transform_statements(statements, ctx);
}
}
pub fn transform_statements_on_exit(
&mut self,
statements: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
if self.options.exponentiation_operator {
self.exponentiation_operator.transform_statements_on_exit(statements, ctx);
}
}
pub fn transform_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.exponentiation_operator {
self.exponentiation_operator.transform_expression(expr, ctx);
}
}
}

View file

@ -0,0 +1,16 @@
use serde::Deserialize;
#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
pub struct ES2016Options {
#[serde(skip)]
pub exponentiation_operator: bool,
}
impl ES2016Options {
#[must_use]
pub fn with_exponentiation_operator(mut self, enable: bool) -> Self {
self.exponentiation_operator = enable;
self
}
}

View file

@ -15,6 +15,7 @@ mod options;
// Presets: <https://babel.dev/docs/presets>
mod env;
mod es2015;
mod es2016;
mod react;
mod typescript;
@ -25,6 +26,7 @@ mod helpers {
use std::{path::Path, rc::Rc};
use es2016::ES2016;
use oxc_allocator::{Allocator, Vec};
use oxc_ast::{ast::*, AstBuilder, Trivias};
use oxc_diagnostics::OxcDiagnostic;
@ -58,6 +60,7 @@ pub struct Transformer<'a> {
// NOTE: all callbacks must run in order.
x0_typescript: TypeScript<'a>,
x1_react: React<'a>,
x2_es2016: ES2016<'a>,
x3_es2015: ES2015<'a>,
}
@ -82,6 +85,7 @@ impl<'a> Transformer<'a> {
ctx: Rc::clone(&ctx),
x0_typescript: TypeScript::new(options.typescript, Rc::clone(&ctx)),
x1_react: React::new(options.react, Rc::clone(&ctx)),
x2_es2016: ES2016::new(options.es2016, Rc::clone(&ctx)),
x3_es2015: ES2015::new(options.es2015, ctx),
}
}
@ -160,6 +164,7 @@ impl<'a> Traverse<'a> for Transformer<'a> {
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
self.x0_typescript.transform_expression(expr);
self.x1_react.transform_expression(expr, ctx);
self.x2_es2016.transform_expression(expr, ctx);
self.x3_es2015.transform_expression(expr);
}
@ -244,13 +249,15 @@ impl<'a> Traverse<'a> for Transformer<'a> {
self.x0_typescript.transform_property_definition(def);
}
fn enter_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, _ctx: &mut TraverseCtx<'a>) {
fn enter_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
self.x0_typescript.transform_statements(stmts);
self.x2_es2016.transform_statements(stmts, ctx);
self.x3_es2015.enter_statements(stmts);
}
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
self.x0_typescript.transform_statements_on_exit(stmts, ctx);
self.x2_es2016.transform_statements_on_exit(stmts, ctx);
self.x3_es2015.exit_statements(stmts);
}

View file

@ -7,6 +7,7 @@ use crate::{
compiler_assumptions::CompilerAssumptions,
env::{can_enable_plugin, EnvOptions, Versions},
es2015::{ArrowFunctionsOptions, ES2015Options},
es2016::ES2016Options,
options::babel::BabelOptions,
react::ReactOptions,
typescript::TypeScriptOptions,
@ -34,6 +35,8 @@ pub struct TransformOptions {
pub react: ReactOptions,
pub es2015: ES2015Options,
pub es2016: ES2016Options,
}
impl TransformOptions {
@ -104,6 +107,11 @@ impl TransformOptions {
})
});
let es2016 = ES2016Options::default().with_exponentiation_operator({
let plugin_name = "transform-exponentiation-operator";
enable_plugin(plugin_name, options, &env_options, &targets).is_some()
});
let typescript = {
let plugin_name = "transform-typescript";
from_value::<TypeScriptOptions>(get_plugin_options(plugin_name, options))
@ -135,6 +143,7 @@ impl TransformOptions {
typescript,
react,
es2015,
es2016,
})
}
}

View file

@ -1,6 +1,6 @@
commit: 12619ffe
Passed: 453/927
Passed: 456/931
# All Passed:
* babel-preset-react
@ -433,6 +433,9 @@ Passed: 453/927
* shipped-proposals/new-class-features-chrome-94/input.js
* shipped-proposals/new-class-features-firefox-70/input.js
# babel-plugin-transform-exponentiation-operator (3/4)
* regression/4349/input.js
# babel-plugin-transform-arrow-functions (1/6)
* assumption-newableArrowFunctions-false/basic/input.js
* assumption-newableArrowFunctions-false/naming/input.js

View file

@ -1,8 +1,9 @@
commit: 12619ffe
Passed: 8/14
Passed: 10/16
# All Passed:
* babel-plugin-transform-exponentiation-operator
* babel-plugin-transform-arrow-functions

View file

@ -30,8 +30,8 @@ pub(crate) const PLUGINS: &[&str] = &[
// // [Regex] "babel-plugin-transform-named-capturing-groups-regex",
// // ES2017
// "babel-plugin-transform-async-to-generator",
// // ES2016
// "babel-plugin-transform-exponentiation-operator",
// ES2016
"babel-plugin-transform-exponentiation-operator",
// // ES2015
"babel-plugin-transform-arrow-functions",
// "babel-plugin-transform-function-name",