-
-
Notifications
You must be signed in to change notification settings - Fork 856
feat(transformer): support exponentiation operator plugin #4876
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
graphite-app
merged 1 commit into
main
from
08-13-feat_transformer_support_exponentiation_operator_plugin
Aug 15, 2024
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
322 changes: 322 additions & 0 deletions
322
crates/oxc_transformer/src/es2016/exponentiation_operator.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,322 @@ | ||
| use std::cell::Cell; | ||
|
|
||
| use oxc_allocator::{CloneIn, Vec}; | ||
| use oxc_ast::ast::*; | ||
| use oxc_semantic::{ReferenceFlag, SymbolFlags}; | ||
| use oxc_span::SPAN; | ||
| use oxc_syntax::operator::{AssignmentOperator, BinaryOperator}; | ||
| use oxc_traverse::TraverseCtx; | ||
|
|
||
| use crate::context::Ctx; | ||
|
|
||
| /// ES2016: Exponentiation Operator | ||
| /// | ||
| /// References: | ||
| /// * <https://babel.dev/docs/babel-plugin-transform-exponentiation-operator> | ||
| /// * <https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-exponentiation-operator> | ||
| /// * <https://github.com/babel/babel/blob/main/packages/babel-helper-builder-binary-assignment-operator-visitor> | ||
| pub struct ExponentiationOperator<'a> { | ||
| _ctx: Ctx<'a>, | ||
| var_declarations: std::vec::Vec<Vec<'a, VariableDeclarator<'a>>>, | ||
| } | ||
|
|
||
| #[derive(Debug)] | ||
| struct Exploded<'a> { | ||
| reference: AssignmentTarget<'a>, | ||
| uid: Expression<'a>, | ||
| } | ||
|
|
||
| impl<'a> ExponentiationOperator<'a> { | ||
| pub fn new(ctx: Ctx<'a>) -> Self { | ||
| Self { _ctx: ctx, var_declarations: vec![] } | ||
| } | ||
|
|
||
| fn clone_identifier_reference( | ||
| ident: &IdentifierReference<'a>, | ||
| ctx: &mut TraverseCtx<'a>, | ||
| ) -> IdentifierReference<'a> { | ||
| let reference = ctx.symbols().get_reference(ident.reference_id.get().unwrap()); | ||
| let symbol_id = reference.symbol_id(); | ||
| let flag = reference.flag(); | ||
| ctx.create_reference_id(ident.span, ident.name.clone(), symbol_id, *flag) | ||
| } | ||
|
|
||
| fn clone_expression(expr: &Expression<'a>, ctx: &mut TraverseCtx<'a>) -> Expression<'a> { | ||
| match expr { | ||
| Expression::Identifier(ident) => ctx | ||
| .ast | ||
| .expression_from_identifier_reference(Self::clone_identifier_reference(ident, ctx)), | ||
| _ => expr.clone_in(ctx.ast.allocator), | ||
| } | ||
| } | ||
|
|
||
| pub fn transform_statements( | ||
| &mut self, | ||
| _statements: &mut Vec<'a, Statement<'a>>, | ||
| ctx: &mut TraverseCtx<'a>, | ||
| ) { | ||
| self.var_declarations.push(ctx.ast.vec()); | ||
| } | ||
|
|
||
| pub fn transform_statements_on_exit( | ||
| &mut self, | ||
| statements: &mut Vec<'a, Statement<'a>>, | ||
| ctx: &mut TraverseCtx<'a>, | ||
| ) { | ||
| if let Some(declarations) = self.var_declarations.pop() { | ||
| if declarations.is_empty() { | ||
| return; | ||
| } | ||
| let variable = ctx.ast.alloc_variable_declaration( | ||
| SPAN, | ||
| VariableDeclarationKind::Var, | ||
| declarations, | ||
| false, | ||
| ); | ||
| statements.insert(0, Statement::VariableDeclaration(variable)); | ||
| } | ||
| } | ||
|
|
||
| pub fn transform_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { | ||
| // left ** right | ||
| if let Expression::BinaryExpression(binary_expr) = expr { | ||
| if binary_expr.operator == BinaryOperator::Exponential { | ||
| let left = ctx.ast.move_expression(&mut binary_expr.left); | ||
| let right = ctx.ast.move_expression(&mut binary_expr.right); | ||
Dunqing marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| *expr = Self::math_pow(left, right, ctx); | ||
| } | ||
| } | ||
|
|
||
| // left **= right | ||
| if let Expression::AssignmentExpression(assign_expr) = expr { | ||
| if assign_expr.operator == AssignmentOperator::Exponential { | ||
| let mut nodes = ctx.ast.vec(); | ||
| let Some(Exploded { reference, uid }) = | ||
| self.explode(&mut assign_expr.left, &mut nodes, ctx) | ||
| else { | ||
| return; | ||
| }; | ||
Dunqing marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| let right = ctx.ast.move_expression(&mut assign_expr.right); | ||
| let right = Self::math_pow(uid, right, ctx); | ||
| let assign_expr = ctx.ast.expression_assignment( | ||
| SPAN, | ||
| AssignmentOperator::Assign, | ||
| reference, | ||
| right, | ||
| ); | ||
| nodes.push(assign_expr); | ||
| *expr = ctx.ast.expression_sequence(SPAN, nodes); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// `left ** right` -> `Math.pow(left, right)` | ||
| fn math_pow( | ||
| left: Expression<'a>, | ||
| right: Expression<'a>, | ||
| ctx: &mut TraverseCtx<'a>, | ||
| ) -> Expression<'a> { | ||
| let ident_math = | ||
| ctx.create_reference_id(SPAN, ctx.ast.atom("Math"), None, ReferenceFlag::Read); | ||
| let object = ctx.ast.expression_from_identifier_reference(ident_math); | ||
| let property = ctx.ast.identifier_name(SPAN, "pow"); | ||
| let callee = | ||
| Expression::from(ctx.ast.member_expression_static(SPAN, object, property, false)); | ||
| let mut arguments = ctx.ast.vec_with_capacity(2); | ||
| arguments.push(Argument::from(left)); | ||
| arguments.push(Argument::from(right)); | ||
| ctx.ast.expression_call( | ||
| SPAN, | ||
| arguments, | ||
| callee, | ||
| None::<TSTypeParameterInstantiation<'_>>, | ||
| false, | ||
| ) | ||
| } | ||
|
|
||
| /// Change `lhs **= 2` to `var temp; temp = lhs, lhs = Math.pow(temp, 2);`. | ||
| /// If the lhs is a member expression `obj.ref` or `obj[ref]`, assign them to a temporary variable so side-effects are not computed twice. | ||
| /// For `obj.ref`, change it to `var _obj; _obj = obj, _obj["ref"] = Math.pow(_obj["ref"], 2)`. | ||
| /// For `obj[ref]`, change it to `var _obj, _ref; _obj = obj, _ref = ref, _obj[_ref] = Math.pow(_obj[_ref], 2);`. | ||
| fn explode( | ||
| &mut self, | ||
| node: &mut AssignmentTarget<'a>, | ||
| nodes: &mut Vec<'a, Expression<'a>>, | ||
| ctx: &mut TraverseCtx<'a>, | ||
| ) -> Option<Exploded<'a>> { | ||
| let node = node.as_simple_assignment_target_mut()?; | ||
| let obj = self.get_obj_ref(node, nodes, ctx)?; | ||
| let (reference, uid) = match node { | ||
| SimpleAssignmentTarget::AssignmentTargetIdentifier(ident) => { | ||
| let reference = AssignmentTarget::AssignmentTargetIdentifier( | ||
| ctx.ast.alloc(Self::clone_identifier_reference(ident.as_ref(), ctx)), | ||
| ); | ||
| (reference, obj) | ||
| } | ||
| match_member_expression!(SimpleAssignmentTarget) => { | ||
| let member_expr = node.to_member_expression_mut(); | ||
| let computed = member_expr.is_computed(); | ||
| let prop = self.get_prop_ref(member_expr, nodes, ctx)?; | ||
| let optional = false; | ||
| let obj_clone = Self::clone_expression(&obj, ctx); | ||
| let (reference, uid) = match &prop { | ||
| Expression::Identifier(ident) if !computed => { | ||
| let ident = IdentifierName::new(SPAN, ident.name.clone()); | ||
| ( | ||
| // TODO: | ||
| // Both of these are the same, but it's in order to avoid after cloning without reference_id. | ||
| // Related: https://github.com/oxc-project/oxc/issues/4804 | ||
| ctx.ast.member_expression_static( | ||
| SPAN, | ||
| obj_clone, | ||
| ident.clone(), | ||
| optional, | ||
| ), | ||
| ctx.ast.member_expression_static(SPAN, obj, ident, optional), | ||
| ) | ||
| } | ||
| _ => { | ||
| let prop_clone = Self::clone_expression(&prop, ctx); | ||
| ( | ||
| ctx.ast | ||
| .member_expression_computed(SPAN, obj_clone, prop_clone, optional), | ||
| ctx.ast.member_expression_computed(SPAN, obj, prop, optional), | ||
| ) | ||
| } | ||
| }; | ||
| ( | ||
| AssignmentTarget::from( | ||
| ctx.ast.simple_assignment_target_member_expression(reference), | ||
| ), | ||
| Expression::from(uid), | ||
| ) | ||
| } | ||
| _ => return None, | ||
| }; | ||
| Some(Exploded { reference, uid }) | ||
| } | ||
|
|
||
| /// Make sure side-effects of evaluating `obj` of `obj.ref` and `obj[ref]` only happen once. | ||
| fn get_obj_ref( | ||
| &mut self, | ||
| node: &mut SimpleAssignmentTarget<'a>, | ||
| nodes: &mut Vec<'a, Expression<'a>>, | ||
| ctx: &mut TraverseCtx<'a>, | ||
| ) -> Option<Expression<'a>> { | ||
| let reference = match node { | ||
| SimpleAssignmentTarget::AssignmentTargetIdentifier(ident) => { | ||
| if ident | ||
| .reference_id | ||
| .get() | ||
| .is_some_and(|reference_id| ctx.symbols().has_binding(reference_id)) | ||
| { | ||
| // this variable is declared in scope so we can be 100% sure | ||
| // that evaluating it multiple times won't trigger a getter | ||
| // or something else | ||
| return Some(ctx.ast.expression_from_identifier_reference( | ||
| Self::clone_identifier_reference(ident, ctx), | ||
| )); | ||
| } | ||
| // could possibly trigger a getter so we need to only evaluate it once | ||
| ctx.ast.expression_from_identifier_reference(Self::clone_identifier_reference( | ||
| ident, ctx, | ||
| )) | ||
| } | ||
| match_member_expression!(SimpleAssignmentTarget) => { | ||
| let expr = match node { | ||
| SimpleAssignmentTarget::ComputedMemberExpression(e) => &mut e.object, | ||
| SimpleAssignmentTarget::StaticMemberExpression(e) => &mut e.object, | ||
| SimpleAssignmentTarget::PrivateFieldExpression(e) => &mut e.object, | ||
| _ => unreachable!(), | ||
| }; | ||
| let expr = ctx.ast.move_expression(expr); | ||
| // the object reference that we need to save is locally declared | ||
| // so as per the previous comment we can be 100% sure evaluating | ||
| // it multiple times will be safe | ||
| // Super cannot be directly assigned so lets return it also | ||
| if matches!(expr, Expression::Super(_)) | ||
| || matches!(&expr, Expression::Identifier(ident) if ident | ||
| .reference_id | ||
| .get() | ||
| .is_some_and(|reference_id| ctx.symbols().has_binding(reference_id))) | ||
| { | ||
| return Some(expr); | ||
| } | ||
|
|
||
| expr | ||
| } | ||
| _ => return None, | ||
| }; | ||
| Some(self.add_new_reference(reference, nodes, ctx)) | ||
| } | ||
|
|
||
| /// Make sure side-effects of evaluating `ref` of `obj.ref` and `obj[ref]` only happen once. | ||
| fn get_prop_ref( | ||
| &mut self, | ||
| node: &mut MemberExpression<'a>, | ||
| nodes: &mut Vec<'a, Expression<'a>>, | ||
| ctx: &mut TraverseCtx<'a>, | ||
| ) -> Option<Expression<'a>> { | ||
| let expr = match node { | ||
| MemberExpression::ComputedMemberExpression(expr) => { | ||
| let expr = ctx.ast.move_expression(&mut expr.expression); | ||
| if expr.is_literal() { | ||
| return Some(expr); | ||
| } | ||
| expr | ||
| } | ||
| MemberExpression::StaticMemberExpression(expr) => { | ||
| return Some(ctx.ast.expression_string_literal(SPAN, expr.property.name.clone())); | ||
| } | ||
| MemberExpression::PrivateFieldExpression(_) => { | ||
| // From babel: "We can't generate property ref for private name, please install `@babel/plugin-transform-class-properties`" | ||
| return None; | ||
| } | ||
| }; | ||
| Some(self.add_new_reference(expr, nodes, ctx)) | ||
| } | ||
|
|
||
| fn add_new_reference( | ||
| &mut self, | ||
| expr: Expression<'a>, | ||
| nodes: &mut Vec<'a, Expression<'a>>, | ||
| ctx: &mut TraverseCtx<'a>, | ||
| ) -> Expression<'a> { | ||
| let name = match expr { | ||
| Expression::Identifier(ref ident) => ident.name.clone().as_str(), | ||
| _ => "ref", | ||
| }; | ||
|
|
||
| let symbol_id = | ||
| ctx.generate_uid_in_current_scope(name, SymbolFlags::FunctionScopedVariable); | ||
| let symbol_name = ctx.ast.atom(ctx.symbols().get_name(symbol_id)); | ||
|
|
||
| { | ||
| // var _name; | ||
| let binding_identifier = BindingIdentifier { | ||
| span: SPAN, | ||
| name: symbol_name.clone(), | ||
| symbol_id: Cell::new(Some(symbol_id)), | ||
| }; | ||
| let kind = VariableDeclarationKind::Var; | ||
| let id = ctx.ast.binding_pattern_kind_from_binding_identifier(binding_identifier); | ||
| let id = ctx.ast.binding_pattern(id, None::<TSTypeAnnotation<'_>>, false); | ||
| self.var_declarations | ||
| .last_mut() | ||
| .unwrap() | ||
| .push(ctx.ast.variable_declarator(SPAN, kind, id, None, false)); | ||
| } | ||
|
|
||
| let ident = | ||
| ctx.create_reference_id(SPAN, symbol_name, Some(symbol_id), ReferenceFlag::Read); | ||
|
|
||
| // let ident = self.create_new_var_with_expression(&expr); | ||
| // Add new reference `_name = name` to nodes | ||
| let left = ctx.ast.simple_assignment_target_from_identifier_reference( | ||
| Self::clone_identifier_reference(&ident, ctx), | ||
| ); | ||
| let op = AssignmentOperator::Assign; | ||
| nodes.push(ctx.ast.expression_assignment(SPAN, op, AssignmentTarget::from(left), expr)); | ||
| ctx.ast.expression_from_identifier_reference(ident) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| mod exponentiation_operator; | ||
| mod options; | ||
|
|
||
| pub use exponentiation_operator::ExponentiationOperator; | ||
| pub use options::ES2016Options; | ||
| use oxc_allocator::Vec; | ||
| use oxc_ast::ast::*; | ||
| use oxc_traverse::TraverseCtx; | ||
| use std::rc::Rc; | ||
|
|
||
| use crate::context::Ctx; | ||
|
|
||
| #[allow(dead_code)] | ||
| pub struct ES2016<'a> { | ||
| ctx: Ctx<'a>, | ||
| options: ES2016Options, | ||
|
|
||
| // Plugins | ||
| exponentiation_operator: ExponentiationOperator<'a>, | ||
| } | ||
|
|
||
| impl<'a> ES2016<'a> { | ||
| pub fn new(options: ES2016Options, ctx: Ctx<'a>) -> Self { | ||
| Self { exponentiation_operator: ExponentiationOperator::new(Rc::clone(&ctx)), ctx, options } | ||
| } | ||
|
|
||
| pub fn transform_statements( | ||
| &mut self, | ||
| statements: &mut Vec<'a, Statement<'a>>, | ||
| ctx: &mut TraverseCtx<'a>, | ||
| ) { | ||
| if self.options.exponentiation_operator { | ||
| self.exponentiation_operator.transform_statements(statements, ctx); | ||
| } | ||
| } | ||
|
|
||
| pub fn transform_statements_on_exit( | ||
| &mut self, | ||
| statements: &mut Vec<'a, Statement<'a>>, | ||
| ctx: &mut TraverseCtx<'a>, | ||
| ) { | ||
| if self.options.exponentiation_operator { | ||
| self.exponentiation_operator.transform_statements_on_exit(statements, ctx); | ||
| } | ||
| } | ||
|
|
||
| pub fn transform_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { | ||
| if self.options.exponentiation_operator { | ||
| self.exponentiation_operator.transform_expression(expr, ctx); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| use serde::Deserialize; | ||
|
|
||
| #[derive(Debug, Default, Clone, Deserialize)] | ||
| #[serde(default, rename_all = "camelCase", deny_unknown_fields)] | ||
| pub struct ES2016Options { | ||
| #[serde(skip)] | ||
| pub exponentiation_operator: bool, | ||
| } | ||
|
|
||
| impl ES2016Options { | ||
| #[must_use] | ||
| pub fn with_exponentiation_operator(mut self, enable: bool) -> Self { | ||
| self.exponentiation_operator = enable; | ||
| self | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.