Skip to content
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

feat(transformer): add arrow_functions plugin #1663

Merged
merged 1 commit into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions crates/oxc_transformer/src/es2015/arrow_functions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use std::rc::Rc;

use oxc_allocator::Vec;
use oxc_ast::{ast::*, AstBuilder, AstKind, VisitMut};
use oxc_span::{Atom, SPAN};
use serde::Deserialize;

use crate::context::TransformerCtx;
use crate::options::TransformOptions;
use crate::TransformTarget;

/// ES2015 Arrow Functions
///
/// References:
/// * <https://babeljs.io/docs/babel-plugin-transform-arrow-functions>
/// * <https://github.com/babel/babel/tree/main/packages/babel-plugin-transform-arrow-functions>
pub struct ArrowFunctions<'a> {
ast: Rc<AstBuilder<'a>>,
nodes: Vec<'a, AstKind<'a>>,
uid: usize,
has_this: bool,
/// Insert a variable declaration at the top of the BlockStatement
insert: bool,
}

#[derive(Debug, Default, Clone, Deserialize)]
pub struct ArrowFunctionsOptions {
/// This option enables the following:
/// * Wrap the generated function in .bind(this) and keeps uses of this inside the function as-is, instead of using a renamed this.
/// * Add a runtime check to ensure the functions are not instantiated.
/// * Add names to arrow functions.
pub spec: bool,
}

impl<'a> VisitMut<'a> for ArrowFunctions<'a> {
fn enter_node(&mut self, kind: AstKind<'a>) {
self.nodes.push(kind);
}

fn leave_node(&mut self, _kind: AstKind<'a>) {
self.nodes.pop();
}

fn visit_jsx_identifier(&mut self, ident: &mut JSXIdentifier) {
let parent_kind = self.nodes.last().unwrap();
let parent_parent_kind = self.nodes[self.nodes.len() - 2];
if ident.name == "this"
&& (matches!(parent_kind, AstKind::JSXElementName(_))
|| matches!(parent_parent_kind, AstKind::JSXMemberExpression(_)))
{
if !self.has_this {
self.has_this = true;
self.uid += 1;
}
*ident = self.ast.jsx_identifier(SPAN, self.get_this_name());
}
}
}

impl<'a> ArrowFunctions<'a> {
pub fn new(
ast: Rc<AstBuilder<'a>>,
_: TransformerCtx<'a>,
options: &TransformOptions,
) -> Option<Self> {
(options.target < TransformTarget::ES2015 || options.arrow_functions.is_some()).then(|| {
let nodes = ast.new_vec();
Self { ast, uid: 0, nodes, has_this: false, insert: false }
})
}

fn get_this_name(&self) -> Atom {
let uid = if self.uid == 1 { String::new() } else { self.uid.to_string() };
format!("_this{uid}",).into()
}

pub fn transform_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>) {
if self.insert {
let binding_pattern = self.ast.binding_pattern(
self.ast
.binding_pattern_identifier(BindingIdentifier::new(SPAN, self.get_this_name())),
None,
false,
);

let variable_declarator = self.ast.variable_declarator(
SPAN,
VariableDeclarationKind::Var,
binding_pattern,
Some(self.ast.this_expression(SPAN)),
false,
);

let stmt = self.ast.variable_declaration(
SPAN,
VariableDeclarationKind::Var,
self.ast.new_vec_single(variable_declarator),
Modifiers::empty(),
);
stmts.insert(0, Statement::Declaration(Declaration::VariableDeclaration(stmt)));
self.insert = false;
}

// Insert to parent block
if self.has_this {
self.insert = true;
self.has_this = false;
}
}

pub fn transform_expression(&mut self, expr: &mut Expression<'a>) {
if let Expression::ArrowExpression(arrow_expr) = expr {
let mut body = self.ast.copy(&arrow_expr.body);

if arrow_expr.expression {
let first_stmt = body.statements.remove(0);
if let Statement::ExpressionStatement(stmt) = first_stmt {
let return_statement =
self.ast.return_statement(SPAN, Some(self.ast.copy(&stmt.expression)));
body.statements.push(return_statement);
}
}

self.visit_function_body(&mut body);

let new_function = self.ast.function(
FunctionType::FunctionExpression,
SPAN,
None,
false,
arrow_expr.generator,
arrow_expr.r#async,
self.ast.copy(&arrow_expr.params),
Some(body),
self.ast.copy(&arrow_expr.type_parameters),
self.ast.copy(&arrow_expr.return_type),
Modifiers::empty(),
);

*expr = Expression::FunctionExpression(new_function);
}
}
}
2 changes: 2 additions & 0 deletions crates/oxc_transformer/src/es2015/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
mod arrow_functions;
mod duplicate_keys;
mod function_name;
mod shorthand_properties;
mod template_literals;

pub use arrow_functions::{ArrowFunctions, ArrowFunctionsOptions};
pub use duplicate_keys::DuplicateKeys;
pub use function_name::FunctionName;
pub use shorthand_properties::ShorthandProperties;
Expand Down
5 changes: 5 additions & 0 deletions crates/oxc_transformer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ use crate::{
};

pub use crate::{
es2015::ArrowFunctionsOptions,
es2020::NullishCoalescingOperatorOptions,
options::{TransformOptions, TransformTarget},
react_jsx::{ReactJsxOptions, ReactJsxRuntime, ReactJsxRuntimeOption},
Expand All @@ -63,6 +64,7 @@ pub struct Transformer<'a> {
es2016_exponentiation_operator: Option<ExponentiationOperator<'a>>,
// es2015
es2015_function_name: Option<FunctionName<'a>>,
es2015_arrow_functions: Option<ArrowFunctions<'a>>,
es2015_shorthand_properties: Option<ShorthandProperties<'a>>,
es2015_template_literals: Option<TemplateLiterals<'a>>,
es2015_duplicate_keys: Option<DuplicateKeys<'a>>,
Expand Down Expand Up @@ -100,6 +102,7 @@ impl<'a> Transformer<'a> {
es2016_exponentiation_operator: ExponentiationOperator::new(Rc::clone(&ast), ctx.clone(), &options),
// es2015
es2015_function_name: FunctionName::new(Rc::clone(&ast), ctx.clone(), &options),
es2015_arrow_functions: ArrowFunctions::new(Rc::clone(&ast), ctx.clone(), &options),
es2015_shorthand_properties: ShorthandProperties::new(Rc::clone(&ast), &options),
es2015_template_literals: TemplateLiterals::new(Rc::clone(&ast), &options),
es2015_duplicate_keys: DuplicateKeys::new(Rc::clone(&ast), &options),
Expand Down Expand Up @@ -155,6 +158,7 @@ impl<'a> VisitMut<'a> for Transformer<'a> {
self.es2021_logical_assignment_operators.as_mut().map(|t| t.add_vars_to_statements(stmts));
self.es2020_nullish_coalescing_operators.as_mut().map(|t| t.add_vars_to_statements(stmts));
self.es2016_exponentiation_operator.as_mut().map(|t| t.add_vars_to_statements(stmts));
self.es2015_arrow_functions.as_mut().map(|t| t.transform_statements(stmts));
}

fn visit_statement(&mut self, stmt: &mut Statement<'a>) {
Expand All @@ -174,6 +178,7 @@ impl<'a> VisitMut<'a> for Transformer<'a> {

self.es2021_logical_assignment_operators.as_mut().map(|t| t.transform_expression(expr));
self.es2020_nullish_coalescing_operators.as_mut().map(|t| t.transform_expression(expr));
self.es2015_arrow_functions.as_mut().map(|t| t.transform_expression(expr));
self.es2016_exponentiation_operator.as_mut().map(|t| t.transform_expression(expr));
self.es2015_template_literals.as_mut().map(|t| t.transform_expression(expr));

Expand Down
6 changes: 5 additions & 1 deletion crates/oxc_transformer/src/options.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use oxc_syntax::assumptions::CompilerAssumptions;

use crate::{es2020::NullishCoalescingOperatorOptions, react_jsx::ReactJsxOptions};
use crate::{
es2015::ArrowFunctionsOptions, es2020::NullishCoalescingOperatorOptions,
react_jsx::ReactJsxOptions,
};

#[derive(Debug, Default, Clone)]
pub struct TransformOptions {
Expand All @@ -22,6 +25,7 @@ pub struct TransformOptions {
// es2015
pub duplicate_keys: bool,
pub function_name: bool,
pub arrow_functions: Option<ArrowFunctionsOptions>,
pub shorthand_properties: bool,
pub sticky_regex: bool,
pub template_literals: bool,
Expand Down
25 changes: 21 additions & 4 deletions tasks/transform_conformance/babel.snap.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Passed: 277/1111
Passed: 288/1137

# All Passed:
* babel-plugin-transform-numeric-separator
Expand Down Expand Up @@ -746,6 +746,25 @@ Passed: 277/1111
# babel-plugin-transform-exponentiation-operator (3/4)
* regression/4349/input.js

# babel-plugin-transform-arrow-functions (9/26)
* arrow-functions/arguments/input.js
* arrow-functions/arguments-global-undeclared/input.js
* arrow-functions/arguments-global-var/input.js
* arrow-functions/default-parameters/input.js
* arrow-functions/destructuring-parameters/input.js
* arrow-functions/implicit-var-arguments/input.js
* arrow-functions/nested/input.js
* arrow-functions/self-referential/input.js
* arrow-functions/spec/input.js
* arrow-functions/super-call/input.js
* arrow-functions/super-prop/input.js
* arrow-functions/this/input.js
* assumption-newableArrowFunctions-false/basic/input.js
* assumption-newableArrowFunctions-false/naming/input.js
* assumption-newableArrowFunctions-false/self-referential/input.js
* spec/newableArrowFunction-default/input.js
* spec/newableArrowFunction-vs-spec-false/input.js

# babel-plugin-transform-unicode-regex (1/4)
* unicode-regex/basic/input.js
* unicode-regex/negated-set/input.js
Expand Down Expand Up @@ -869,12 +888,10 @@ Passed: 277/1111
* regression/15768/input.ts
* variable-declaration/non-null-in-optional-chain/input.ts

# babel-plugin-transform-react-jsx (144/156)
# babel-plugin-transform-react-jsx (146/156)
* autoImport/after-polyfills-compiled-to-cjs/input.mjs
* autoImport/complicated-scope-module/input.js
* react/arrow-functions/input.js
* react/optimisation.react.constant-elements/input.js
* react-automatic/arrow-functions/input.js
* react-automatic/optimisation.react.constant-elements/input.js
* react-automatic/should-handle-attributed-elements/input.js
* react-automatic/should-throw-when-filter-is-specified/input.js
Expand Down
5 changes: 4 additions & 1 deletion tasks/transform_conformance/babel_exec.snap.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Passed: 375/441
Passed: 377/444

# All Passed:
* babel-plugin-transform-class-static-block
Expand Down Expand Up @@ -94,3 +94,6 @@ Passed: 375/441
* object-spread-loose-builtins/no-object-assign-exec/exec.js
* object-spread-loose-builtins/side-effect/exec.js

# babel-plugin-transform-arrow-functions (2/3)
* arrow-functions/implicit-var-arguments/exec.js

1 change: 1 addition & 0 deletions tasks/transform_conformance/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const CASES: &[&str] = &[
// ES2016
"babel-plugin-transform-exponentiation-operator",
// ES2015
"babel-plugin-transform-arrow-functions",
"babel-plugin-transform-shorthand-properties",
"babel-plugin-transform-sticky-regex",
"babel-plugin-transform-unicode-regex",
Expand Down
7 changes: 5 additions & 2 deletions tasks/transform_conformance/src/test_case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use oxc_semantic::SemanticBuilder;
use oxc_span::{SourceType, VALID_EXTENSIONS};
use oxc_tasks_common::{normalize_path, print_diff_in_terminal, BabelOptions};
use oxc_transformer::{
NullishCoalescingOperatorOptions, ReactJsxOptions, TransformOptions, TransformTarget,
Transformer,
ArrowFunctionsOptions, NullishCoalescingOperatorOptions, ReactJsxOptions, TransformOptions,
TransformTarget, Transformer,
};
use serde::de::DeserializeOwned;
use serde_json::Value;
Expand Down Expand Up @@ -95,6 +95,9 @@ pub trait TestCase {
assumptions: options.assumptions,
class_static_block: options.get_plugin("transform-class-static-block").is_some(),
function_name: options.get_plugin("transform-function-name").is_some(),
arrow_functions: options
.get_plugin("transform-arrow-functions")
.map(get_options::<ArrowFunctionsOptions>),
logical_assignment_operators: options
.get_plugin("transform-logical-assignment-operators")
.is_some(),
Expand Down
Loading