mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 20:32:10 +00:00
feat(transformer): finish 2016 exponentiation operator (#996)
This commit is contained in:
parent
6c18b3e8ec
commit
0f72066f2e
17 changed files with 378 additions and 84 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1807,6 +1807,7 @@ dependencies = [
|
|||
"oxc_allocator",
|
||||
"oxc_codegen",
|
||||
"oxc_parser",
|
||||
"oxc_semantic",
|
||||
"oxc_span",
|
||||
"oxc_tasks_common",
|
||||
"oxc_transformer",
|
||||
|
|
|
|||
|
|
@ -92,29 +92,18 @@ pub enum Expression<'a> {
|
|||
impl<'a> Expression<'a> {
|
||||
/// `PrimaryExpression`
|
||||
/// [tc39/ecma262#prod-PrimaryExpression](https://tc39.es/ecma262/#prod-PrimaryExpression)
|
||||
#[rustfmt::skip]
|
||||
pub fn is_primary_expression(&self) -> bool {
|
||||
self.is_literal_expression()
|
||||
|| matches!(
|
||||
self,
|
||||
Self::Identifier(_)
|
||||
| Self::ThisExpression(_)
|
||||
| Self::FunctionExpression(_)
|
||||
| Self::ClassExpression(_)
|
||||
| Self::ParenthesizedExpression(_)
|
||||
| Self::ArrayExpression(_)
|
||||
| Self::ObjectExpression(_)
|
||||
)
|
||||
self.is_literal() || matches!(self, Self::Identifier(_) | Self::ThisExpression(_) | Self::FunctionExpression(_)
|
||||
| Self::ClassExpression(_) | Self::ParenthesizedExpression(_)
|
||||
| Self::ArrayExpression(_) | Self::ObjectExpression(_))
|
||||
}
|
||||
|
||||
pub fn is_literal_expression(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::BooleanLiteral(_)
|
||||
| Self::NullLiteral(_)
|
||||
| Self::NumberLiteral(_)
|
||||
| Self::BigintLiteral(_)
|
||||
| Self::RegExpLiteral(_)
|
||||
| Self::StringLiteral(_) // TemplateLiteral is not `Literal` type per oxc_ast
|
||||
#[rustfmt::skip]
|
||||
pub fn is_literal(&self) -> bool {
|
||||
// Note: TemplateLiteral is not `Literal`
|
||||
matches!(self, Self::BooleanLiteral(_) | Self::NullLiteral(_) | Self::NumberLiteral(_)
|
||||
| Self::BigintLiteral(_) | Self::RegExpLiteral(_) | Self::StringLiteral(_)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -540,6 +529,10 @@ pub enum MemberExpression<'a> {
|
|||
}
|
||||
|
||||
impl<'a> MemberExpression<'a> {
|
||||
pub fn is_computed(&self) -> bool {
|
||||
matches!(self, MemberExpression::ComputedMemberExpression(_))
|
||||
}
|
||||
|
||||
pub fn optional(&self) -> bool {
|
||||
match self {
|
||||
MemberExpression::ComputedMemberExpression(expr) => expr.optional,
|
||||
|
|
@ -814,6 +807,13 @@ impl<'a> AssignmentTarget<'a> {
|
|||
pub fn is_destructuring_pattern(&self) -> bool {
|
||||
matches!(self, Self::AssignmentTargetPattern(_))
|
||||
}
|
||||
|
||||
pub fn is_identifier(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::SimpleAssignmentTarget(SimpleAssignmentTarget::AssignmentTargetIdentifier(_))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash)]
|
||||
|
|
|
|||
|
|
@ -211,6 +211,10 @@ pub struct StringLiteral {
|
|||
}
|
||||
|
||||
impl StringLiteral {
|
||||
pub fn new(span: Span, value: Atom) -> Self {
|
||||
Self { span, value }
|
||||
}
|
||||
|
||||
/// Static Semantics: `IsStringWellFormedUnicode`
|
||||
/// test for \uD800-\uDFFF
|
||||
pub fn is_string_well_formed_unicode(&self) -> bool {
|
||||
|
|
|
|||
|
|
@ -77,6 +77,16 @@ impl<'a> AstBuilder<'a> {
|
|||
mem::replace(stmts, self.new_vec())
|
||||
}
|
||||
|
||||
pub fn move_assignment_target(
|
||||
&self,
|
||||
target: &mut AssignmentTarget<'a>,
|
||||
) -> AssignmentTarget<'a> {
|
||||
let ident = IdentifierReference::new(Span::default(), "".into());
|
||||
let dummy = self.simple_assignment_target_identifier(ident);
|
||||
let dummy = AssignmentTarget::SimpleAssignmentTarget(dummy);
|
||||
mem::replace(target, dummy)
|
||||
}
|
||||
|
||||
pub fn program(
|
||||
&self,
|
||||
span: Span,
|
||||
|
|
@ -90,10 +100,6 @@ impl<'a> AstBuilder<'a> {
|
|||
|
||||
/* ---------- Literals ---------- */
|
||||
|
||||
pub fn string_literal(&self, span: Span, value: Atom) -> StringLiteral {
|
||||
StringLiteral { span, value }
|
||||
}
|
||||
|
||||
pub fn number_literal(
|
||||
&self,
|
||||
span: Span,
|
||||
|
|
@ -415,6 +421,13 @@ impl<'a> AstBuilder<'a> {
|
|||
SimpleAssignmentTarget::AssignmentTargetIdentifier(self.alloc(ident))
|
||||
}
|
||||
|
||||
pub fn simple_assignment_target_member_expression(
|
||||
&self,
|
||||
expr: MemberExpression<'a>,
|
||||
) -> SimpleAssignmentTarget<'a> {
|
||||
SimpleAssignmentTarget::MemberAssignmentTarget(self.alloc(expr))
|
||||
}
|
||||
|
||||
pub fn await_expression(&self, span: Span, argument: Expression<'a>) -> Expression<'a> {
|
||||
Expression::AwaitExpression(self.alloc(AwaitExpression { span, argument }))
|
||||
}
|
||||
|
|
@ -496,6 +509,21 @@ impl<'a> AstBuilder<'a> {
|
|||
Expression::MemberExpression(self.alloc(expr))
|
||||
}
|
||||
|
||||
pub fn computed_member(
|
||||
&self,
|
||||
span: Span,
|
||||
object: Expression<'a>,
|
||||
expression: Expression<'a>,
|
||||
optional: bool, // for optional chaining
|
||||
) -> MemberExpression<'a> {
|
||||
MemberExpression::ComputedMemberExpression(ComputedMemberExpression {
|
||||
span,
|
||||
object,
|
||||
expression,
|
||||
optional,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn computed_member_expression(
|
||||
&self,
|
||||
span: Span,
|
||||
|
|
@ -503,9 +531,22 @@ impl<'a> AstBuilder<'a> {
|
|||
expression: Expression<'a>,
|
||||
optional: bool, // for optional chaining
|
||||
) -> Expression<'a> {
|
||||
self.member_expression(MemberExpression::ComputedMemberExpression(
|
||||
ComputedMemberExpression { span, object, expression, optional },
|
||||
))
|
||||
self.member_expression(self.computed_member(span, object, expression, optional))
|
||||
}
|
||||
|
||||
pub fn static_member(
|
||||
&self,
|
||||
span: Span,
|
||||
object: Expression<'a>,
|
||||
property: IdentifierName,
|
||||
optional: bool, // for optional chaining
|
||||
) -> MemberExpression<'a> {
|
||||
MemberExpression::StaticMemberExpression(StaticMemberExpression {
|
||||
span,
|
||||
object,
|
||||
property,
|
||||
optional,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn static_member_expression(
|
||||
|
|
@ -515,12 +556,7 @@ impl<'a> AstBuilder<'a> {
|
|||
property: IdentifierName,
|
||||
optional: bool, // for optional chaining
|
||||
) -> Expression<'a> {
|
||||
self.member_expression(MemberExpression::StaticMemberExpression(StaticMemberExpression {
|
||||
span,
|
||||
object,
|
||||
property,
|
||||
optional,
|
||||
}))
|
||||
self.member_expression(self.static_member(span, object, property, optional))
|
||||
}
|
||||
|
||||
pub fn private_field_expression(
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ pub fn is_static_boolean<'a>(expr: &Expression<'a>, ctx: &LintContext<'a>) -> bo
|
|||
/// Checks if a branch node of `LogicalExpression` short circuits the whole condition
|
||||
fn is_logical_identity(op: LogicalOperator, expr: &Expression) -> bool {
|
||||
match expr {
|
||||
expr if expr.is_literal_expression() => {
|
||||
expr if expr.is_literal() => {
|
||||
let boolean_value = expr.get_boolean_value();
|
||||
(op == LogicalOperator::Or && boolean_value == Some(true))
|
||||
|| (op == LogicalOperator::And && boolean_value == Some(false))
|
||||
|
|
@ -142,7 +142,7 @@ impl<'a, 'b> IsConstant<'a, 'b> for Expression<'a> {
|
|||
Self::Identifier(ident) => {
|
||||
ident.name == "undefined" && ctx.semantic().is_reference_to_global_variable(ident)
|
||||
}
|
||||
_ if self.is_literal_expression() => true,
|
||||
_ if self.is_literal() => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ impl NoConstantBinaryExpression {
|
|||
| Expression::UpdateExpression(_)
|
||||
| Expression::BinaryExpression(_)
|
||||
| Expression::UnaryExpression(_) => true,
|
||||
expr if expr.is_literal_expression() => true,
|
||||
expr if expr.is_literal() => true,
|
||||
Expression::CallExpression(call_expr) => {
|
||||
if let Expression::Identifier(ident) = &call_expr.callee {
|
||||
return ["Boolean", "String", "Number"].contains(&ident.name.as_str())
|
||||
|
|
@ -267,7 +267,7 @@ impl NoConstantBinaryExpression {
|
|||
Expression::ParenthesizedExpression(paren_expr) => {
|
||||
Self::has_constant_loose_boolean_comparison(&paren_expr.expression, ctx)
|
||||
}
|
||||
expr if expr.is_literal_expression() => true,
|
||||
expr if expr.is_literal() => true,
|
||||
expr if expr.evaluate_to_undefined() => true,
|
||||
_ => false,
|
||||
}
|
||||
|
|
@ -288,7 +288,7 @@ impl NoConstantBinaryExpression {
|
|||
| Expression::NewExpression(_)
|
||||
| Expression::TemplateLiteral(_)
|
||||
| Expression::UpdateExpression(_) => true,
|
||||
expr if expr.is_literal_expression() => true,
|
||||
expr if expr.is_literal() => true,
|
||||
Expression::BinaryExpression(binary_expr) => {
|
||||
binary_expr.operator.is_numeric_or_string_binary_operator()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ impl<'a> Compressor<'a> {
|
|||
let right_string = get_string_value(right)?;
|
||||
// let value = left_string.to_owned().
|
||||
let value = left_string + right_string;
|
||||
let string_literal = self.ast.string_literal(span, Atom::from(value));
|
||||
let string_literal = StringLiteral::new(span, Atom::from(value));
|
||||
Some(self.ast.literal_string_expression(string_literal))
|
||||
},
|
||||
|
||||
|
|
@ -587,7 +587,7 @@ impl<'a> Compressor<'a> {
|
|||
};
|
||||
|
||||
if let Some(type_name) = type_name {
|
||||
let string_literal = self.ast.string_literal(span, Atom::from(type_name));
|
||||
let string_literal = StringLiteral::new(span, Atom::from(type_name));
|
||||
return Some(self.ast.literal_string_expression(string_literal));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,6 +92,10 @@ impl SymbolTable {
|
|||
&self.references[reference_id]
|
||||
}
|
||||
|
||||
pub fn has_binding(&self, reference_id: ReferenceId) -> bool {
|
||||
self.references[reference_id].symbol_id().is_some()
|
||||
}
|
||||
|
||||
pub fn is_global_reference(&self, reference_id: ReferenceId) -> bool {
|
||||
self.references[reference_id].symbol_id().is_none()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{env, path::Path};
|
||||
use std::{cell::RefCell, env, path::Path, rc::Rc};
|
||||
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_codegen::{Codegen, CodegenOptions};
|
||||
|
|
@ -34,12 +34,16 @@ fn main() {
|
|||
println!("{printed}");
|
||||
|
||||
let program = allocator.alloc(ret.program);
|
||||
let _ = SemanticBuilder::new(&source_text, source_type).build(program);
|
||||
|
||||
let semantic = SemanticBuilder::new(&source_text, source_type).build(program).semantic;
|
||||
let (symbols, _scope_tree) = semantic.into_symbol_table_and_scope_tree();
|
||||
let symbols = Rc::new(RefCell::new(symbols));
|
||||
|
||||
let transform_options = TransformOptions {
|
||||
target: TransformTarget::ES2015,
|
||||
react: Some(TransformReactOptions::default()),
|
||||
};
|
||||
Transformer::new(&allocator, source_type, transform_options).build(program);
|
||||
Transformer::new(&allocator, source_type, &symbols, transform_options).build(program);
|
||||
let printed = Codegen::<false>::new(source_text.len(), codegen_options).build(program);
|
||||
println!("Transformed:\n");
|
||||
println!("{printed}");
|
||||
|
|
|
|||
|
|
@ -1,41 +1,257 @@
|
|||
use oxc_allocator::Vec;
|
||||
use oxc_ast::{ast::*, AstBuilder};
|
||||
use oxc_span::Span;
|
||||
use oxc_syntax::operator::BinaryOperator;
|
||||
use oxc_semantic::SymbolTable;
|
||||
use oxc_span::{Atom, Span};
|
||||
use oxc_syntax::operator::{AssignmentOperator, BinaryOperator};
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::{cell::RefCell, mem, rc::Rc};
|
||||
|
||||
/// 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> {
|
||||
ast: Rc<AstBuilder<'a>>,
|
||||
symbols: Rc<RefCell<SymbolTable>>,
|
||||
vars: Vec<'a, VariableDeclarator<'a>>,
|
||||
}
|
||||
|
||||
struct Exploded<'a> {
|
||||
reference: AssignmentTarget<'a>,
|
||||
uid: Expression<'a>,
|
||||
}
|
||||
|
||||
impl<'a> ExponentiationOperator<'a> {
|
||||
pub fn new(ast: Rc<AstBuilder<'a>>) -> Self {
|
||||
Self { ast }
|
||||
pub fn new(ast: Rc<AstBuilder<'a>>, symbols: Rc<RefCell<SymbolTable>>) -> Self {
|
||||
let vars = ast.new_vec();
|
||||
Self { ast, symbols, vars }
|
||||
}
|
||||
|
||||
pub fn transform_expression<'b>(&mut self, expr: &'b mut Expression<'a>) {
|
||||
let Expression::BinaryExpression(binary_expression) = expr else { return };
|
||||
if binary_expression.operator != BinaryOperator::Exponential {
|
||||
pub fn leave_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>) {
|
||||
if self.vars.is_empty() {
|
||||
return;
|
||||
}
|
||||
// left ** right
|
||||
let left = self.ast.move_expression(&mut binary_expression.left);
|
||||
let right = self.ast.move_expression(&mut binary_expression.right);
|
||||
// Math.pow
|
||||
let ident_math = IdentifierReference::new(Span::default(), "Math".into());
|
||||
let decls = mem::replace(&mut self.vars, self.ast.new_vec());
|
||||
let kind = VariableDeclarationKind::Var;
|
||||
let decl = self.ast.variable_declaration(Span::default(), kind, decls, Modifiers::empty());
|
||||
let stmt = Statement::Declaration(Declaration::VariableDeclaration(decl));
|
||||
stmts.insert(0, stmt);
|
||||
}
|
||||
|
||||
pub fn transform_expression(&mut self, expr: &mut Expression<'a>) {
|
||||
// left ** right
|
||||
if let Expression::BinaryExpression(binary_expr) = expr {
|
||||
if binary_expr.operator == BinaryOperator::Exponential {
|
||||
let left = self.ast.move_expression(&mut binary_expr.left);
|
||||
let right = self.ast.move_expression(&mut binary_expr.right);
|
||||
*expr = self.math_pow(left, right);
|
||||
}
|
||||
}
|
||||
|
||||
// left **= right
|
||||
if let Expression::AssignmentExpression(assign_expr) = expr {
|
||||
if assign_expr.operator == AssignmentOperator::Exponential {
|
||||
let mut nodes = self.ast.new_vec();
|
||||
let left = self.ast.move_assignment_target(&mut assign_expr.left);
|
||||
let Some(Exploded { reference, uid }) = self.explode(left, &mut nodes) else {
|
||||
return;
|
||||
};
|
||||
let right = self.ast.move_expression(&mut assign_expr.right);
|
||||
let right = self.math_pow(uid, right);
|
||||
let assign_expr = self.ast.assignment_expression(
|
||||
Span::default(),
|
||||
AssignmentOperator::Assign,
|
||||
reference,
|
||||
right,
|
||||
);
|
||||
nodes.push(assign_expr);
|
||||
*expr = self.ast.sequence_expression(Span::default(), nodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `left ** right` -> `Math.pow(left, right)`
|
||||
fn math_pow(&mut self, left: Expression<'a>, right: Expression<'a>) -> Expression<'a> {
|
||||
let ident_math = IdentifierReference::new(Span::default(), Atom::from("Math"));
|
||||
let object = self.ast.identifier_reference_expression(ident_math);
|
||||
let property = IdentifierName::new(Span::default(), "pow".into());
|
||||
let property = IdentifierName::new(Span::default(), Atom::from("pow"));
|
||||
let callee = self.ast.static_member_expression(Span::default(), object, property, false);
|
||||
// Math.pow(left, right)
|
||||
let mut arguments = self.ast.new_vec_with_capacity(2);
|
||||
arguments.push(Argument::Expression(left));
|
||||
arguments.push(Argument::Expression(right));
|
||||
let call_expr = self.ast.call_expression(Span::default(), callee, arguments, false, None);
|
||||
*expr = call_expr;
|
||||
self.ast.call_expression(Span::default(), callee, arguments, false, None)
|
||||
}
|
||||
|
||||
/// 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: AssignmentTarget<'a>,
|
||||
nodes: &mut Vec<'a, Expression<'a>>,
|
||||
) -> Option<Exploded<'a>> {
|
||||
let node = match node {
|
||||
AssignmentTarget::SimpleAssignmentTarget(target) => target,
|
||||
AssignmentTarget::AssignmentTargetPattern(_) => {
|
||||
// Invalid Syntax
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let obj = self.get_obj_ref(self.ast.copy(&node), nodes)?;
|
||||
let (reference, uid) = match node {
|
||||
SimpleAssignmentTarget::AssignmentTargetIdentifier(ident) => {
|
||||
let reference = AssignmentTarget::SimpleAssignmentTarget(
|
||||
SimpleAssignmentTarget::AssignmentTargetIdentifier(ident),
|
||||
);
|
||||
(reference, obj)
|
||||
}
|
||||
SimpleAssignmentTarget::MemberAssignmentTarget(member_expr) => {
|
||||
let computed = member_expr.is_computed();
|
||||
let prop = self.get_prop_ref(member_expr.unbox(), nodes)?;
|
||||
let optional = false;
|
||||
let obj_clone = self.ast.copy(&obj);
|
||||
let span = Span::default();
|
||||
let (reference, uid) = match &prop {
|
||||
Expression::Identifier(ident) if !computed => {
|
||||
let ident = IdentifierName::new(span, ident.name.clone());
|
||||
(
|
||||
self.ast.static_member(span, obj_clone, ident.clone(), optional),
|
||||
self.ast.static_member_expression(span, obj, ident, optional),
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
let prop_clone = self.ast.copy(&prop);
|
||||
(
|
||||
self.ast.computed_member(span, obj_clone, prop_clone, optional),
|
||||
self.ast.computed_member_expression(span, obj, prop, optional),
|
||||
)
|
||||
}
|
||||
};
|
||||
(
|
||||
AssignmentTarget::SimpleAssignmentTarget(
|
||||
self.ast.simple_assignment_target_member_expression(reference),
|
||||
),
|
||||
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: SimpleAssignmentTarget<'a>,
|
||||
nodes: &mut Vec<'a, Expression<'a>>,
|
||||
) -> Option<Expression<'a>> {
|
||||
let reference = match node {
|
||||
SimpleAssignmentTarget::AssignmentTargetIdentifier(ident) => {
|
||||
if ident
|
||||
.reference_id
|
||||
.get()
|
||||
.is_some_and(|reference_id| self.symbols.borrow().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(self.ast.identifier_reference_expression(ident.unbox()));
|
||||
}
|
||||
// could possibly trigger a getter so we need to only evaluate it once
|
||||
self.ast.identifier_reference_expression(ident.unbox())
|
||||
}
|
||||
SimpleAssignmentTarget::MemberAssignmentTarget(member_expr) => {
|
||||
let expr = match member_expr.unbox() {
|
||||
MemberExpression::ComputedMemberExpression(e) => e.object,
|
||||
MemberExpression::StaticMemberExpression(e) => e.object,
|
||||
MemberExpression::PrivateFieldExpression(e) => e.object,
|
||||
};
|
||||
// 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| self.symbols.borrow().has_binding(reference_id)))
|
||||
{
|
||||
return Some(expr);
|
||||
}
|
||||
expr
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
Some(self.add_new_reference(reference, nodes))
|
||||
}
|
||||
|
||||
/// Make sure side-effects of evaluating `ref` of `obj.ref` and `obj[ref]` only happen once.
|
||||
fn get_prop_ref(
|
||||
&mut self,
|
||||
node: MemberExpression<'a>,
|
||||
nodes: &mut Vec<'a, Expression<'a>>,
|
||||
) -> Option<Expression<'a>> {
|
||||
let prop = match node {
|
||||
MemberExpression::ComputedMemberExpression(expr) => {
|
||||
let expr = expr.expression;
|
||||
if expr.is_literal() {
|
||||
return Some(expr);
|
||||
}
|
||||
expr
|
||||
}
|
||||
MemberExpression::StaticMemberExpression(expr) => {
|
||||
let ident = expr.property;
|
||||
let string_literal = StringLiteral::new(Span::default(), ident.name);
|
||||
return Some(self.ast.literal_string_expression(string_literal));
|
||||
}
|
||||
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(prop, nodes))
|
||||
}
|
||||
|
||||
fn add_new_reference(
|
||||
&mut self,
|
||||
expr: Expression<'a>,
|
||||
nodes: &mut Vec<'a, Expression<'a>>,
|
||||
) -> Expression<'a> {
|
||||
let name = generate_uid_identifier_based_on_node(&expr);
|
||||
// TODO: scope.push({ id: temp });
|
||||
|
||||
// Add `var name` to scope
|
||||
let binding_identifier = BindingIdentifier::new(Span::default(), name.clone());
|
||||
let binding_pattern_kind = self.ast.binding_pattern_identifier(binding_identifier);
|
||||
let binding = self.ast.binding_pattern(binding_pattern_kind, None, false);
|
||||
let kind = VariableDeclarationKind::Var;
|
||||
let decl = self.ast.variable_declarator(Span::default(), kind, binding, None, false);
|
||||
self.vars.push(decl);
|
||||
|
||||
// Add new reference `_name = name` to nodes
|
||||
let ident = IdentifierReference::new(Span::default(), name);
|
||||
let target = self.ast.simple_assignment_target_identifier(ident.clone());
|
||||
let target = AssignmentTarget::SimpleAssignmentTarget(target);
|
||||
let op = AssignmentOperator::Assign;
|
||||
nodes.push(self.ast.assignment_expression(Span::default(), op, target, expr));
|
||||
self.ast.identifier_reference_expression(ident)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// <https://github.com/babel/babel/blob/419644f27c5c59deb19e71aaabd417a3bc5483ca/packages/babel-traverse/src/scope/index.ts#L543>
|
||||
fn generate_uid_identifier_based_on_node(expr: &Expression) -> Atom {
|
||||
let mut parts = std::vec::Vec::with_capacity(1);
|
||||
gather_node_parts(expr, &mut parts);
|
||||
let name = parts.join("$");
|
||||
Atom::from(format!("_{name}"))
|
||||
}
|
||||
|
||||
// TODO: use a trait and add this to oxc_ast (syntax directed operations)
|
||||
fn gather_node_parts(expr: &Expression, parts: &mut std::vec::Vec<Atom>) {
|
||||
match expr {
|
||||
Expression::Identifier(ident) => parts.push(ident.name.clone()),
|
||||
_ => parts.push(Atom::from("ref")),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,18 +17,18 @@ mod react_jsx;
|
|||
mod regexp;
|
||||
mod typescript;
|
||||
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_ast::{ast::*, AstBuilder, VisitMut};
|
||||
use oxc_span::SourceType;
|
||||
use regexp::RegexpFlags;
|
||||
use std::rc::Rc;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use es2015::ShorthandProperties;
|
||||
use es2016::ExponentiationOperator;
|
||||
use es2019::OptionalCatchBinding;
|
||||
use es2021::LogicalAssignmentOperators;
|
||||
use react_jsx::ReactJsx;
|
||||
use typescript::TypeScript;
|
||||
use oxc_allocator::{Allocator, Vec};
|
||||
use oxc_ast::{ast::*, AstBuilder, VisitMut};
|
||||
use oxc_semantic::SymbolTable;
|
||||
use oxc_span::SourceType;
|
||||
|
||||
use crate::{
|
||||
es2015::ShorthandProperties, es2016::ExponentiationOperator, es2019::OptionalCatchBinding,
|
||||
es2021::LogicalAssignmentOperators, react_jsx::ReactJsx, regexp::RegexpFlags,
|
||||
typescript::TypeScript,
|
||||
};
|
||||
|
||||
pub use crate::options::{
|
||||
TransformOptions, TransformReactOptions, TransformReactRuntime, TransformTarget,
|
||||
|
|
@ -55,6 +55,7 @@ impl<'a> Transformer<'a> {
|
|||
pub fn new(
|
||||
allocator: &'a Allocator,
|
||||
source_type: SourceType,
|
||||
symbols: &Rc<RefCell<SymbolTable>>,
|
||||
options: TransformOptions,
|
||||
) -> Self {
|
||||
let ast = Rc::new(AstBuilder::new(allocator));
|
||||
|
|
@ -80,7 +81,8 @@ impl<'a> Transformer<'a> {
|
|||
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.es2016_exponentiation_operator
|
||||
.replace(ExponentiationOperator::new(Rc::clone(&ast), Rc::clone(symbols)));
|
||||
}
|
||||
if options.target < TransformTarget::ES2015 {
|
||||
t.es2015_shorthand_properties.replace(ShorthandProperties::new(Rc::clone(&ast)));
|
||||
|
|
@ -94,6 +96,13 @@ impl<'a> Transformer<'a> {
|
|||
}
|
||||
|
||||
impl<'a> VisitMut<'a> for Transformer<'a> {
|
||||
fn visit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>) {
|
||||
for stmt in stmts.iter_mut() {
|
||||
self.visit_statement(stmt);
|
||||
}
|
||||
self.es2016_exponentiation_operator.as_mut().map(|t| t.leave_statements(stmts));
|
||||
}
|
||||
|
||||
fn visit_expression(&mut self, expr: &mut Expression<'a>) {
|
||||
// self.typescript.as_mut().map(|t| t.transform_expression(expr));
|
||||
// self.react_jsx.as_mut().map(|t| t.transform_expression(expr));
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ impl<'a> RegexpFlags<'a> {
|
|||
}
|
||||
let ident = IdentifierReference::new(Span::default(), Atom::from("RegExp"));
|
||||
let callee = self.ast.identifier_reference_expression(ident);
|
||||
let pattern = self.ast.string_literal(Span::default(), Atom::from(regex.pattern.as_str()));
|
||||
let flags = self.ast.string_literal(Span::default(), Atom::from(regex.flags.to_string()));
|
||||
let pattern = StringLiteral::new(Span::default(), Atom::from(regex.pattern.as_str()));
|
||||
let flags = StringLiteral::new(Span::default(), Atom::from(regex.flags.to_string()));
|
||||
let pattern_literal = self.ast.literal_string_expression(pattern);
|
||||
let flags_literal = self.ast.literal_string_expression(flags);
|
||||
let mut arguments = self.ast.new_vec_with_capacity(2);
|
||||
|
|
|
|||
|
|
@ -210,8 +210,13 @@ impl Oxc {
|
|||
}
|
||||
|
||||
if run_options.transform() {
|
||||
// FIXME: this should not be duplicated with the linter semantic,
|
||||
// we need to fix the API so symbols and scopes can be shared.
|
||||
let semantic = SemanticBuilder::new(source_text, source_type).build(program).semantic;
|
||||
let (symbols, _scope_tree) = semantic.into_symbol_table_and_scope_tree();
|
||||
let symbols = Rc::new(RefCell::new(symbols));
|
||||
let options = TransformOptions { target: TransformTarget::ES2015, react: None };
|
||||
Transformer::new(&allocator, source_type, options).build(program);
|
||||
Transformer::new(&allocator, source_type, &symbols, options).build(program);
|
||||
}
|
||||
|
||||
let program = allocator.alloc(program);
|
||||
|
|
|
|||
|
|
@ -6,11 +6,12 @@ static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
|||
#[global_allocator]
|
||||
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
|
||||
use std::{fs, hint::black_box};
|
||||
use std::{cell::RefCell, fs, hint::black_box, rc::Rc};
|
||||
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_benchmark::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use oxc_parser::Parser;
|
||||
use oxc_semantic::SemanticBuilder;
|
||||
use oxc_span::SourceType;
|
||||
use oxc_tasks_common::project_root;
|
||||
use oxc_transformer::{TransformOptions, Transformer};
|
||||
|
|
@ -28,11 +29,14 @@ fn bench_transformer(criterion: &mut Criterion) {
|
|||
let id = BenchmarkId::from_parameter(file);
|
||||
group.bench_with_input(id, &source_text, |b, source_text| {
|
||||
let allocator = Allocator::default();
|
||||
let ret = Parser::new(&allocator, source_text, source_type).parse();
|
||||
let program = allocator.alloc(ret.program);
|
||||
let program = Parser::new(&allocator, source_text, source_type).parse().program;
|
||||
let semantic = SemanticBuilder::new(source_text, source_type).build(&program).semantic;
|
||||
let (symbols, _scope_tree) = semantic.into_symbol_table_and_scope_tree();
|
||||
let symbols = Rc::new(RefCell::new(symbols));
|
||||
let program = allocator.alloc(program);
|
||||
b.iter(|| {
|
||||
let transform_options = TransformOptions::default();
|
||||
Transformer::new(&allocator, source_type, transform_options)
|
||||
Transformer::new(&allocator, source_type, &symbols, transform_options)
|
||||
.build(black_box(program));
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ doctest = false
|
|||
oxc_span = { workspace = true }
|
||||
oxc_allocator = { workspace = true }
|
||||
oxc_parser = { workspace = true }
|
||||
oxc_semantic = { workspace = true }
|
||||
oxc_codegen = { workspace = true }
|
||||
oxc_transformer = { workspace = true }
|
||||
oxc_tasks_common = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
Passed: 92/1091
|
||||
Passed: 94/1091
|
||||
|
||||
# babel-plugin-transform-unicode-sets-regex (0/4)
|
||||
* Failed: basic/basic/input.js
|
||||
|
|
@ -736,10 +736,8 @@ Passed: 92/1091
|
|||
* Failed: regression/gh-6923/input.js
|
||||
* Failed: regression/in-uncompiled-class-fields/input.js
|
||||
|
||||
# babel-plugin-transform-exponentiation-operator (1/4)
|
||||
* Failed: exponentiation-operator/assignment/input.js
|
||||
# babel-plugin-transform-exponentiation-operator (3/4)
|
||||
* Failed: regression/4349/input.js
|
||||
* Failed: regression/4403/input.js
|
||||
|
||||
# babel-plugin-transform-shorthand-properties (All passed)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
use std::{
|
||||
cell::RefCell,
|
||||
fs::{self, File},
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_codegen::{Codegen, CodegenOptions};
|
||||
use oxc_parser::Parser;
|
||||
use oxc_semantic::SemanticBuilder;
|
||||
use oxc_span::{SourceType, VALID_EXTENSIONS};
|
||||
use oxc_tasks_common::{normalize_path, project_root};
|
||||
use oxc_transformer::{TransformOptions, TransformReactOptions, TransformTarget, Transformer};
|
||||
|
|
@ -140,13 +143,22 @@ fn babel_test(input_path: &Path, options: &BabelOptions) -> bool {
|
|||
Codegen::<false>::new(source_text.len(), CodegenOptions).build(&expected_program);
|
||||
|
||||
// Get transformed text.
|
||||
|
||||
let transformed_program = Parser::new(&allocator, &source_text, source_type).parse().program;
|
||||
let transform_options = TransformOptions {
|
||||
target: TransformTarget::ES5,
|
||||
react: Some(TransformReactOptions::default()),
|
||||
};
|
||||
|
||||
let semantic =
|
||||
SemanticBuilder::new(&source_text, source_type).build(&transformed_program).semantic;
|
||||
let (symbols, _scope_tree) = semantic.into_symbol_table_and_scope_tree();
|
||||
let symbols = Rc::new(RefCell::new(symbols));
|
||||
|
||||
let transformed_program = allocator.alloc(transformed_program);
|
||||
Transformer::new(&allocator, source_type, transform_options).build(transformed_program);
|
||||
|
||||
Transformer::new(&allocator, source_type, &symbols, transform_options)
|
||||
.build(transformed_program);
|
||||
let transformed_code =
|
||||
Codegen::<false>::new(source_text.len(), CodegenOptions).build(transformed_program);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue