feat(transformer): finish 2016 exponentiation operator (#996)

This commit is contained in:
Boshen 2023-10-16 09:30:04 +08:00 committed by GitHub
parent 6c18b3e8ec
commit 0f72066f2e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 378 additions and 84 deletions

1
Cargo.lock generated
View file

@ -1807,6 +1807,7 @@ dependencies = [
"oxc_allocator", "oxc_allocator",
"oxc_codegen", "oxc_codegen",
"oxc_parser", "oxc_parser",
"oxc_semantic",
"oxc_span", "oxc_span",
"oxc_tasks_common", "oxc_tasks_common",
"oxc_transformer", "oxc_transformer",

View file

@ -92,29 +92,18 @@ pub enum Expression<'a> {
impl<'a> Expression<'a> { impl<'a> Expression<'a> {
/// `PrimaryExpression` /// `PrimaryExpression`
/// [tc39/ecma262#prod-PrimaryExpression](https://tc39.es/ecma262/#prod-PrimaryExpression) /// [tc39/ecma262#prod-PrimaryExpression](https://tc39.es/ecma262/#prod-PrimaryExpression)
#[rustfmt::skip]
pub fn is_primary_expression(&self) -> bool { pub fn is_primary_expression(&self) -> bool {
self.is_literal_expression() self.is_literal() || matches!(self, Self::Identifier(_) | Self::ThisExpression(_) | Self::FunctionExpression(_)
|| matches!( | Self::ClassExpression(_) | Self::ParenthesizedExpression(_)
self, | Self::ArrayExpression(_) | Self::ObjectExpression(_))
Self::Identifier(_)
| Self::ThisExpression(_)
| Self::FunctionExpression(_)
| Self::ClassExpression(_)
| Self::ParenthesizedExpression(_)
| Self::ArrayExpression(_)
| Self::ObjectExpression(_)
)
} }
pub fn is_literal_expression(&self) -> bool { #[rustfmt::skip]
matches!( pub fn is_literal(&self) -> bool {
self, // Note: TemplateLiteral is not `Literal`
Self::BooleanLiteral(_) matches!(self, Self::BooleanLiteral(_) | Self::NullLiteral(_) | Self::NumberLiteral(_)
| Self::NullLiteral(_) | Self::BigintLiteral(_) | Self::RegExpLiteral(_) | Self::StringLiteral(_)
| Self::NumberLiteral(_)
| Self::BigintLiteral(_)
| Self::RegExpLiteral(_)
| Self::StringLiteral(_) // TemplateLiteral is not `Literal` type per oxc_ast
) )
} }
@ -540,6 +529,10 @@ pub enum MemberExpression<'a> {
} }
impl<'a> MemberExpression<'a> { impl<'a> MemberExpression<'a> {
pub fn is_computed(&self) -> bool {
matches!(self, MemberExpression::ComputedMemberExpression(_))
}
pub fn optional(&self) -> bool { pub fn optional(&self) -> bool {
match self { match self {
MemberExpression::ComputedMemberExpression(expr) => expr.optional, MemberExpression::ComputedMemberExpression(expr) => expr.optional,
@ -814,6 +807,13 @@ impl<'a> AssignmentTarget<'a> {
pub fn is_destructuring_pattern(&self) -> bool { pub fn is_destructuring_pattern(&self) -> bool {
matches!(self, Self::AssignmentTargetPattern(_)) matches!(self, Self::AssignmentTargetPattern(_))
} }
pub fn is_identifier(&self) -> bool {
matches!(
self,
Self::SimpleAssignmentTarget(SimpleAssignmentTarget::AssignmentTargetIdentifier(_))
)
}
} }
#[derive(Debug, Hash)] #[derive(Debug, Hash)]

View file

@ -211,6 +211,10 @@ pub struct StringLiteral {
} }
impl StringLiteral { impl StringLiteral {
pub fn new(span: Span, value: Atom) -> Self {
Self { span, value }
}
/// Static Semantics: `IsStringWellFormedUnicode` /// Static Semantics: `IsStringWellFormedUnicode`
/// test for \uD800-\uDFFF /// test for \uD800-\uDFFF
pub fn is_string_well_formed_unicode(&self) -> bool { pub fn is_string_well_formed_unicode(&self) -> bool {

View file

@ -77,6 +77,16 @@ impl<'a> AstBuilder<'a> {
mem::replace(stmts, self.new_vec()) 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( pub fn program(
&self, &self,
span: Span, span: Span,
@ -90,10 +100,6 @@ impl<'a> AstBuilder<'a> {
/* ---------- Literals ---------- */ /* ---------- Literals ---------- */
pub fn string_literal(&self, span: Span, value: Atom) -> StringLiteral {
StringLiteral { span, value }
}
pub fn number_literal( pub fn number_literal(
&self, &self,
span: Span, span: Span,
@ -415,6 +421,13 @@ impl<'a> AstBuilder<'a> {
SimpleAssignmentTarget::AssignmentTargetIdentifier(self.alloc(ident)) 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> { pub fn await_expression(&self, span: Span, argument: Expression<'a>) -> Expression<'a> {
Expression::AwaitExpression(self.alloc(AwaitExpression { span, argument })) Expression::AwaitExpression(self.alloc(AwaitExpression { span, argument }))
} }
@ -496,6 +509,21 @@ impl<'a> AstBuilder<'a> {
Expression::MemberExpression(self.alloc(expr)) 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( pub fn computed_member_expression(
&self, &self,
span: Span, span: Span,
@ -503,9 +531,22 @@ impl<'a> AstBuilder<'a> {
expression: Expression<'a>, expression: Expression<'a>,
optional: bool, // for optional chaining optional: bool, // for optional chaining
) -> Expression<'a> { ) -> Expression<'a> {
self.member_expression(MemberExpression::ComputedMemberExpression( self.member_expression(self.computed_member(span, object, expression, optional))
ComputedMemberExpression { 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( pub fn static_member_expression(
@ -515,12 +556,7 @@ impl<'a> AstBuilder<'a> {
property: IdentifierName, property: IdentifierName,
optional: bool, // for optional chaining optional: bool, // for optional chaining
) -> Expression<'a> { ) -> Expression<'a> {
self.member_expression(MemberExpression::StaticMemberExpression(StaticMemberExpression { self.member_expression(self.static_member(span, object, property, optional))
span,
object,
property,
optional,
}))
} }
pub fn private_field_expression( pub fn private_field_expression(

View file

@ -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 /// Checks if a branch node of `LogicalExpression` short circuits the whole condition
fn is_logical_identity(op: LogicalOperator, expr: &Expression) -> bool { fn is_logical_identity(op: LogicalOperator, expr: &Expression) -> bool {
match expr { match expr {
expr if expr.is_literal_expression() => { expr if expr.is_literal() => {
let boolean_value = expr.get_boolean_value(); let boolean_value = expr.get_boolean_value();
(op == LogicalOperator::Or && boolean_value == Some(true)) (op == LogicalOperator::Or && boolean_value == Some(true))
|| (op == LogicalOperator::And && boolean_value == Some(false)) || (op == LogicalOperator::And && boolean_value == Some(false))
@ -142,7 +142,7 @@ impl<'a, 'b> IsConstant<'a, 'b> for Expression<'a> {
Self::Identifier(ident) => { Self::Identifier(ident) => {
ident.name == "undefined" && ctx.semantic().is_reference_to_global_variable(ident) ident.name == "undefined" && ctx.semantic().is_reference_to_global_variable(ident)
} }
_ if self.is_literal_expression() => true, _ if self.is_literal() => true,
_ => false, _ => false,
} }
} }

View file

@ -167,7 +167,7 @@ impl NoConstantBinaryExpression {
| Expression::UpdateExpression(_) | Expression::UpdateExpression(_)
| Expression::BinaryExpression(_) | Expression::BinaryExpression(_)
| Expression::UnaryExpression(_) => true, | Expression::UnaryExpression(_) => true,
expr if expr.is_literal_expression() => true, expr if expr.is_literal() => true,
Expression::CallExpression(call_expr) => { Expression::CallExpression(call_expr) => {
if let Expression::Identifier(ident) = &call_expr.callee { if let Expression::Identifier(ident) = &call_expr.callee {
return ["Boolean", "String", "Number"].contains(&ident.name.as_str()) return ["Boolean", "String", "Number"].contains(&ident.name.as_str())
@ -267,7 +267,7 @@ impl NoConstantBinaryExpression {
Expression::ParenthesizedExpression(paren_expr) => { Expression::ParenthesizedExpression(paren_expr) => {
Self::has_constant_loose_boolean_comparison(&paren_expr.expression, ctx) 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, expr if expr.evaluate_to_undefined() => true,
_ => false, _ => false,
} }
@ -288,7 +288,7 @@ impl NoConstantBinaryExpression {
| Expression::NewExpression(_) | Expression::NewExpression(_)
| Expression::TemplateLiteral(_) | Expression::TemplateLiteral(_)
| Expression::UpdateExpression(_) => true, | Expression::UpdateExpression(_) => true,
expr if expr.is_literal_expression() => true, expr if expr.is_literal() => true,
Expression::BinaryExpression(binary_expr) => { Expression::BinaryExpression(binary_expr) => {
binary_expr.operator.is_numeric_or_string_binary_operator() binary_expr.operator.is_numeric_or_string_binary_operator()
} }

View file

@ -264,7 +264,7 @@ impl<'a> Compressor<'a> {
let right_string = get_string_value(right)?; let right_string = get_string_value(right)?;
// let value = left_string.to_owned(). // let value = left_string.to_owned().
let value = left_string + right_string; 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)) Some(self.ast.literal_string_expression(string_literal))
}, },
@ -587,7 +587,7 @@ impl<'a> Compressor<'a> {
}; };
if let Some(type_name) = type_name { 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)); return Some(self.ast.literal_string_expression(string_literal));
} }
} }

View file

@ -92,6 +92,10 @@ impl SymbolTable {
&self.references[reference_id] &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 { pub fn is_global_reference(&self, reference_id: ReferenceId) -> bool {
self.references[reference_id].symbol_id().is_none() self.references[reference_id].symbol_id().is_none()
} }

View file

@ -1,4 +1,4 @@
use std::{env, path::Path}; use std::{cell::RefCell, env, path::Path, rc::Rc};
use oxc_allocator::Allocator; use oxc_allocator::Allocator;
use oxc_codegen::{Codegen, CodegenOptions}; use oxc_codegen::{Codegen, CodegenOptions};
@ -34,12 +34,16 @@ fn main() {
println!("{printed}"); println!("{printed}");
let program = allocator.alloc(ret.program); 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 { let transform_options = TransformOptions {
target: TransformTarget::ES2015, target: TransformTarget::ES2015,
react: Some(TransformReactOptions::default()), 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); let printed = Codegen::<false>::new(source_text.len(), codegen_options).build(program);
println!("Transformed:\n"); println!("Transformed:\n");
println!("{printed}"); println!("{printed}");

View file

@ -1,41 +1,257 @@
use oxc_allocator::Vec;
use oxc_ast::{ast::*, AstBuilder}; use oxc_ast::{ast::*, AstBuilder};
use oxc_span::Span; use oxc_semantic::SymbolTable;
use oxc_syntax::operator::BinaryOperator; 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 /// ES2016: Exponentiation Operator
/// ///
/// References: /// References:
/// * <https://babel.dev/docs/babel-plugin-transform-exponentiation-operator> /// * <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-plugin-transform-exponentiation-operator>
/// * <https://github.com/babel/babel/blob/main/packages/babel-helper-builder-binary-assignment-operator-visitor>
pub struct ExponentiationOperator<'a> { pub struct ExponentiationOperator<'a> {
ast: Rc<AstBuilder<'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> { impl<'a> ExponentiationOperator<'a> {
pub fn new(ast: Rc<AstBuilder<'a>>) -> Self { pub fn new(ast: Rc<AstBuilder<'a>>, symbols: Rc<RefCell<SymbolTable>>) -> Self {
Self { ast } let vars = ast.new_vec();
Self { ast, symbols, vars }
} }
pub fn transform_expression<'b>(&mut self, expr: &'b mut Expression<'a>) { pub fn leave_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>) {
let Expression::BinaryExpression(binary_expression) = expr else { return }; if self.vars.is_empty() {
if binary_expression.operator != BinaryOperator::Exponential {
return; return;
} }
// left ** right let decls = mem::replace(&mut self.vars, self.ast.new_vec());
let left = self.ast.move_expression(&mut binary_expression.left); let kind = VariableDeclarationKind::Var;
let right = self.ast.move_expression(&mut binary_expression.right); let decl = self.ast.variable_declaration(Span::default(), kind, decls, Modifiers::empty());
// Math.pow let stmt = Statement::Declaration(Declaration::VariableDeclaration(decl));
let ident_math = IdentifierReference::new(Span::default(), "Math".into()); 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 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); 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); let mut arguments = self.ast.new_vec_with_capacity(2);
arguments.push(Argument::Expression(left)); arguments.push(Argument::Expression(left));
arguments.push(Argument::Expression(right)); arguments.push(Argument::Expression(right));
let call_expr = self.ast.call_expression(Span::default(), callee, arguments, false, None); self.ast.call_expression(Span::default(), callee, arguments, false, None)
*expr = call_expr; }
/// 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")),
} }
} }

View file

@ -17,18 +17,18 @@ mod react_jsx;
mod regexp; mod regexp;
mod typescript; mod typescript;
use oxc_allocator::Allocator; use std::{cell::RefCell, rc::Rc};
use oxc_ast::{ast::*, AstBuilder, VisitMut};
use oxc_span::SourceType;
use regexp::RegexpFlags;
use std::rc::Rc;
use es2015::ShorthandProperties; use oxc_allocator::{Allocator, Vec};
use es2016::ExponentiationOperator; use oxc_ast::{ast::*, AstBuilder, VisitMut};
use es2019::OptionalCatchBinding; use oxc_semantic::SymbolTable;
use es2021::LogicalAssignmentOperators; use oxc_span::SourceType;
use react_jsx::ReactJsx;
use typescript::TypeScript; use crate::{
es2015::ShorthandProperties, es2016::ExponentiationOperator, es2019::OptionalCatchBinding,
es2021::LogicalAssignmentOperators, react_jsx::ReactJsx, regexp::RegexpFlags,
typescript::TypeScript,
};
pub use crate::options::{ pub use crate::options::{
TransformOptions, TransformReactOptions, TransformReactRuntime, TransformTarget, TransformOptions, TransformReactOptions, TransformReactRuntime, TransformTarget,
@ -55,6 +55,7 @@ impl<'a> Transformer<'a> {
pub fn new( pub fn new(
allocator: &'a Allocator, allocator: &'a Allocator,
source_type: SourceType, source_type: SourceType,
symbols: &Rc<RefCell<SymbolTable>>,
options: TransformOptions, options: TransformOptions,
) -> Self { ) -> Self {
let ast = Rc::new(AstBuilder::new(allocator)); 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))); t.es2019_optional_catch_binding.replace(OptionalCatchBinding::new(Rc::clone(&ast)));
} }
if options.target < TransformTarget::ES2016 { 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 { if options.target < TransformTarget::ES2015 {
t.es2015_shorthand_properties.replace(ShorthandProperties::new(Rc::clone(&ast))); 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> { 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>) { fn visit_expression(&mut self, expr: &mut Expression<'a>) {
// self.typescript.as_mut().map(|t| t.transform_expression(expr)); // self.typescript.as_mut().map(|t| t.transform_expression(expr));
// self.react_jsx.as_mut().map(|t| t.transform_expression(expr)); // self.react_jsx.as_mut().map(|t| t.transform_expression(expr));

View file

@ -37,8 +37,8 @@ impl<'a> RegexpFlags<'a> {
} }
let ident = IdentifierReference::new(Span::default(), Atom::from("RegExp")); let ident = IdentifierReference::new(Span::default(), Atom::from("RegExp"));
let callee = self.ast.identifier_reference_expression(ident); let callee = self.ast.identifier_reference_expression(ident);
let pattern = self.ast.string_literal(Span::default(), Atom::from(regex.pattern.as_str())); let pattern = StringLiteral::new(Span::default(), Atom::from(regex.pattern.as_str()));
let flags = self.ast.string_literal(Span::default(), Atom::from(regex.flags.to_string())); let flags = StringLiteral::new(Span::default(), Atom::from(regex.flags.to_string()));
let pattern_literal = self.ast.literal_string_expression(pattern); let pattern_literal = self.ast.literal_string_expression(pattern);
let flags_literal = self.ast.literal_string_expression(flags); let flags_literal = self.ast.literal_string_expression(flags);
let mut arguments = self.ast.new_vec_with_capacity(2); let mut arguments = self.ast.new_vec_with_capacity(2);

View file

@ -210,8 +210,13 @@ impl Oxc {
} }
if run_options.transform() { 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 }; 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); let program = allocator.alloc(program);

View file

@ -6,11 +6,12 @@ static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;
#[global_allocator] #[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; 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_allocator::Allocator;
use oxc_benchmark::{criterion_group, criterion_main, BenchmarkId, Criterion}; use oxc_benchmark::{criterion_group, criterion_main, BenchmarkId, Criterion};
use oxc_parser::Parser; use oxc_parser::Parser;
use oxc_semantic::SemanticBuilder;
use oxc_span::SourceType; use oxc_span::SourceType;
use oxc_tasks_common::project_root; use oxc_tasks_common::project_root;
use oxc_transformer::{TransformOptions, Transformer}; use oxc_transformer::{TransformOptions, Transformer};
@ -28,11 +29,14 @@ fn bench_transformer(criterion: &mut Criterion) {
let id = BenchmarkId::from_parameter(file); let id = BenchmarkId::from_parameter(file);
group.bench_with_input(id, &source_text, |b, source_text| { group.bench_with_input(id, &source_text, |b, source_text| {
let allocator = Allocator::default(); let allocator = Allocator::default();
let ret = Parser::new(&allocator, source_text, source_type).parse(); let program = Parser::new(&allocator, source_text, source_type).parse().program;
let program = allocator.alloc(ret.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(|| { b.iter(|| {
let transform_options = TransformOptions::default(); let transform_options = TransformOptions::default();
Transformer::new(&allocator, source_type, transform_options) Transformer::new(&allocator, source_type, &symbols, transform_options)
.build(black_box(program)); .build(black_box(program));
}); });
}); });

View file

@ -17,6 +17,7 @@ doctest = false
oxc_span = { workspace = true } oxc_span = { workspace = true }
oxc_allocator = { workspace = true } oxc_allocator = { workspace = true }
oxc_parser = { workspace = true } oxc_parser = { workspace = true }
oxc_semantic = { workspace = true }
oxc_codegen = { workspace = true } oxc_codegen = { workspace = true }
oxc_transformer = { workspace = true } oxc_transformer = { workspace = true }
oxc_tasks_common = { workspace = true } oxc_tasks_common = { workspace = true }

View file

@ -1,4 +1,4 @@
Passed: 92/1091 Passed: 94/1091
# babel-plugin-transform-unicode-sets-regex (0/4) # babel-plugin-transform-unicode-sets-regex (0/4)
* Failed: basic/basic/input.js * Failed: basic/basic/input.js
@ -736,10 +736,8 @@ Passed: 92/1091
* Failed: regression/gh-6923/input.js * Failed: regression/gh-6923/input.js
* Failed: regression/in-uncompiled-class-fields/input.js * Failed: regression/in-uncompiled-class-fields/input.js
# babel-plugin-transform-exponentiation-operator (1/4) # babel-plugin-transform-exponentiation-operator (3/4)
* Failed: exponentiation-operator/assignment/input.js
* Failed: regression/4349/input.js * Failed: regression/4349/input.js
* Failed: regression/4403/input.js
# babel-plugin-transform-shorthand-properties (All passed) # babel-plugin-transform-shorthand-properties (All passed)

View file

@ -1,13 +1,16 @@
use std::{ use std::{
cell::RefCell,
fs::{self, File}, fs::{self, File},
io::Write, io::Write,
path::{Path, PathBuf}, path::{Path, PathBuf},
rc::Rc,
}; };
use walkdir::WalkDir; use walkdir::WalkDir;
use oxc_allocator::Allocator; use oxc_allocator::Allocator;
use oxc_codegen::{Codegen, CodegenOptions}; use oxc_codegen::{Codegen, CodegenOptions};
use oxc_parser::Parser; use oxc_parser::Parser;
use oxc_semantic::SemanticBuilder;
use oxc_span::{SourceType, VALID_EXTENSIONS}; use oxc_span::{SourceType, VALID_EXTENSIONS};
use oxc_tasks_common::{normalize_path, project_root}; use oxc_tasks_common::{normalize_path, project_root};
use oxc_transformer::{TransformOptions, TransformReactOptions, TransformTarget, Transformer}; 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); Codegen::<false>::new(source_text.len(), CodegenOptions).build(&expected_program);
// Get transformed text. // Get transformed text.
let transformed_program = Parser::new(&allocator, &source_text, source_type).parse().program; let transformed_program = Parser::new(&allocator, &source_text, source_type).parse().program;
let transform_options = TransformOptions { let transform_options = TransformOptions {
target: TransformTarget::ES5, target: TransformTarget::ES5,
react: Some(TransformReactOptions::default()), 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); 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 = let transformed_code =
Codegen::<false>::new(source_text.len(), CodegenOptions).build(transformed_program); Codegen::<false>::new(source_text.len(), CodegenOptions).build(transformed_program);