From 0f72066f2e1a3c240507e2e12d775ceb2eccf556 Mon Sep 17 00:00:00 2001 From: Boshen Date: Mon, 16 Oct 2023 09:30:04 +0800 Subject: [PATCH] feat(transformer): finish 2016 exponentiation operator (#996) --- Cargo.lock | 1 + crates/oxc_ast/src/ast/js.rs | 40 +-- crates/oxc_ast/src/ast/literal.rs | 4 + crates/oxc_ast/src/ast_builder.rs | 62 ++++- crates/oxc_linter/src/ast_util.rs | 4 +- .../eslint/no_constant_binary_expression.rs | 6 +- crates/oxc_minifier/src/compressor/fold.rs | 4 +- crates/oxc_semantic/src/symbol.rs | 4 + .../oxc_transformer/examples/transformer.rs | 10 +- .../src/es2016/exponentiation_operator.rs | 250 ++++++++++++++++-- crates/oxc_transformer/src/lib.rs | 33 ++- .../src/regexp/regexp_flags.rs | 4 +- crates/oxc_wasm/src/lib.rs | 7 +- tasks/benchmark/benches/transformer.rs | 12 +- tasks/transform_conformance/Cargo.toml | 1 + tasks/transform_conformance/babel.snap.md | 6 +- tasks/transform_conformance/src/lib.rs | 14 +- 17 files changed, 378 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aaafe3ca0..946837706 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1807,6 +1807,7 @@ dependencies = [ "oxc_allocator", "oxc_codegen", "oxc_parser", + "oxc_semantic", "oxc_span", "oxc_tasks_common", "oxc_transformer", diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index be9ef846f..b0dde5c90 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -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)] diff --git a/crates/oxc_ast/src/ast/literal.rs b/crates/oxc_ast/src/ast/literal.rs index 955616e2a..8cc202f72 100644 --- a/crates/oxc_ast/src/ast/literal.rs +++ b/crates/oxc_ast/src/ast/literal.rs @@ -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 { diff --git a/crates/oxc_ast/src/ast_builder.rs b/crates/oxc_ast/src/ast_builder.rs index 55cbc5017..bd32ac7e1 100644 --- a/crates/oxc_ast/src/ast_builder.rs +++ b/crates/oxc_ast/src/ast_builder.rs @@ -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( diff --git a/crates/oxc_linter/src/ast_util.rs b/crates/oxc_linter/src/ast_util.rs index e775e2fcf..b4728ac53 100644 --- a/crates/oxc_linter/src/ast_util.rs +++ b/crates/oxc_linter/src/ast_util.rs @@ -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, } } diff --git a/crates/oxc_linter/src/rules/eslint/no_constant_binary_expression.rs b/crates/oxc_linter/src/rules/eslint/no_constant_binary_expression.rs index c5adee2ef..05bb021c7 100644 --- a/crates/oxc_linter/src/rules/eslint/no_constant_binary_expression.rs +++ b/crates/oxc_linter/src/rules/eslint/no_constant_binary_expression.rs @@ -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() } diff --git a/crates/oxc_minifier/src/compressor/fold.rs b/crates/oxc_minifier/src/compressor/fold.rs index af6e71932..fcc557d2e 100644 --- a/crates/oxc_minifier/src/compressor/fold.rs +++ b/crates/oxc_minifier/src/compressor/fold.rs @@ -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)); } } diff --git a/crates/oxc_semantic/src/symbol.rs b/crates/oxc_semantic/src/symbol.rs index 7f3c4fd5e..8ae0c7040 100644 --- a/crates/oxc_semantic/src/symbol.rs +++ b/crates/oxc_semantic/src/symbol.rs @@ -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() } diff --git a/crates/oxc_transformer/examples/transformer.rs b/crates/oxc_transformer/examples/transformer.rs index 2c8e4af87..eb63ca05a 100644 --- a/crates/oxc_transformer/examples/transformer.rs +++ b/crates/oxc_transformer/examples/transformer.rs @@ -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::::new(source_text.len(), codegen_options).build(program); println!("Transformed:\n"); println!("{printed}"); diff --git a/crates/oxc_transformer/src/es2016/exponentiation_operator.rs b/crates/oxc_transformer/src/es2016/exponentiation_operator.rs index 5e52275c3..7e65c1d75 100644 --- a/crates/oxc_transformer/src/es2016/exponentiation_operator.rs +++ b/crates/oxc_transformer/src/es2016/exponentiation_operator.rs @@ -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: /// * /// * +/// * pub struct ExponentiationOperator<'a> { ast: Rc>, + symbols: Rc>, + vars: Vec<'a, VariableDeclarator<'a>>, +} + +struct Exploded<'a> { + reference: AssignmentTarget<'a>, + uid: Expression<'a>, } impl<'a> ExponentiationOperator<'a> { - pub fn new(ast: Rc>) -> Self { - Self { ast } + pub fn new(ast: Rc>, symbols: Rc>) -> 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> { + 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> { + 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> { + 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: +// +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) { + match expr { + Expression::Identifier(ident) => parts.push(ident.name.clone()), + _ => parts.push(Atom::from("ref")), } } diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index b7b1b3f4c..e814379db 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -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>, 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)); diff --git a/crates/oxc_transformer/src/regexp/regexp_flags.rs b/crates/oxc_transformer/src/regexp/regexp_flags.rs index 70ade676a..b45ffe1a7 100644 --- a/crates/oxc_transformer/src/regexp/regexp_flags.rs +++ b/crates/oxc_transformer/src/regexp/regexp_flags.rs @@ -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); diff --git a/crates/oxc_wasm/src/lib.rs b/crates/oxc_wasm/src/lib.rs index 593caa50d..43555aa01 100644 --- a/crates/oxc_wasm/src/lib.rs +++ b/crates/oxc_wasm/src/lib.rs @@ -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); diff --git a/tasks/benchmark/benches/transformer.rs b/tasks/benchmark/benches/transformer.rs index b5f92b6b8..dcc7d9990 100644 --- a/tasks/benchmark/benches/transformer.rs +++ b/tasks/benchmark/benches/transformer.rs @@ -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)); }); }); diff --git a/tasks/transform_conformance/Cargo.toml b/tasks/transform_conformance/Cargo.toml index b0ffc5202..34239df10 100644 --- a/tasks/transform_conformance/Cargo.toml +++ b/tasks/transform_conformance/Cargo.toml @@ -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 } diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index 4aa7e03fc..808a5081d 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -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) diff --git a/tasks/transform_conformance/src/lib.rs b/tasks/transform_conformance/src/lib.rs index b9be82a03..ec19a1444 100644 --- a/tasks/transform_conformance/src/lib.rs +++ b/tasks/transform_conformance/src/lib.rs @@ -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::::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::::new(source_text.len(), CodegenOptions).build(transformed_program);