Skip to content

Commit

Permalink
feat(transformer): support nullish-coalescing-operator plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
Dunqing committed Aug 14, 2024
1 parent 68bdf45 commit c0d4397
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 3 deletions.
56 changes: 56 additions & 0 deletions crates/oxc_transformer/src/es2020/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
mod nullish_coalescing_operator;
mod options;

pub use nullish_coalescing_operator::NullishCoalescingOperator;
pub use options::ES2020Options;
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 ES2020<'a> {
ctx: Ctx<'a>,
options: ES2020Options,

// Plugins
nullish_coalescing_operator: NullishCoalescingOperator<'a>,
}

impl<'a> ES2020<'a> {
pub fn new(options: ES2020Options, ctx: Ctx<'a>) -> Self {
Self {
nullish_coalescing_operator: NullishCoalescingOperator::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.nullish_coalescing_operator {
self.nullish_coalescing_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.nullish_coalescing_operator {
self.nullish_coalescing_operator.transform_statements_on_exit(statements, ctx);
}
}

pub fn transform_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.nullish_coalescing_operator {
self.nullish_coalescing_operator.transform_expression(expr, ctx);
}
}
}
125 changes: 125 additions & 0 deletions crates/oxc_transformer/src/es2020/nullish_coalescing_operator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use std::cell::Cell;

use oxc_semantic::{ReferenceFlag, SymbolFlags};
use oxc_traverse::TraverseCtx;

use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_span::SPAN;
use oxc_syntax::operator::{AssignmentOperator, BinaryOperator, LogicalOperator};

use crate::context::Ctx;

/// ES2020: Nullish Coalescing Operator
///
/// References:
/// * <https://babeljs.io/docs/babel-plugin-transform-nullish-coalescing-operator>
/// * <https://github.com/babel/babel/tree/main/packages/babel-plugin-transform-nullish-coalescing-operator>
pub struct NullishCoalescingOperator<'a> {
_ctx: Ctx<'a>,
var_declarations: std::vec::Vec<Vec<'a, VariableDeclarator<'a>>>,
}

impl<'a> NullishCoalescingOperator<'a> {
pub fn new(ctx: Ctx<'a>) -> Self {
Self { _ctx: ctx, var_declarations: vec![] }
}

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));
}
}

fn create_new_var_with_expression(
&mut self,
expr: &Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> IdentifierReference<'a> {
// Add `var name` to scope
let name = match expr {
Expression::Identifier(ref ident) => ident.name.as_str(),
_ => "nullish_coalescing_operator",
};
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));
};

ctx.create_reference_id(SPAN, symbol_name, Some(symbol_id), ReferenceFlag::Read)
}

pub fn transform_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
// left ?? right
let Expression::LogicalExpression(logical_expr) = expr else { return };
if logical_expr.operator != LogicalOperator::Coalesce {
return;
}

let reference;
let assignment;

// skip creating extra reference when `left` is static
if ctx.symbols().is_static(&logical_expr.left) {
reference = ctx.ast.copy(&logical_expr.left);
assignment = ctx.ast.copy(&logical_expr.left);
} else {
let ident = self.create_new_var_with_expression(&logical_expr.left, ctx);
reference = ctx.ast.expression_from_identifier_reference(ident.clone());
let left = AssignmentTarget::from(
ctx.ast.simple_assignment_target_from_identifier_reference(ident),
);
let right = ctx.ast.copy(&logical_expr.left);
assignment =
ctx.ast.expression_assignment(SPAN, AssignmentOperator::Assign, left, right);
};

let op = BinaryOperator::StrictInequality;
let null = ctx.ast.expression_null_literal(SPAN);
let left = ctx.ast.expression_binary(SPAN, ctx.ast.copy(&assignment), op, null);

let right = ctx.ast.expression_binary(SPAN, ctx.ast.copy(&reference), op, ctx.ast.void_0());

let test = ctx.ast.expression_logical(SPAN, left, LogicalOperator::And, right);

let right = ctx.ast.move_expression(&mut logical_expr.right);

*expr = ctx.ast.expression_conditional(SPAN, test, reference, right);
}
}
16 changes: 16 additions & 0 deletions crates/oxc_transformer/src/es2020/options.rs
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 ES2020Options {
#[serde(skip)]
pub nullish_coalescing_operator: bool,
}

impl ES2020Options {
#[must_use]
pub fn with_nullish_coalescing_operator(mut self, enable: bool) -> Self {
self.nullish_coalescing_operator = enable;
self
}
}
7 changes: 7 additions & 0 deletions crates/oxc_transformer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod options;
mod env;
mod es2015;
mod es2016;
mod es2020;
mod react;
mod typescript;

Expand All @@ -27,6 +28,7 @@ mod helpers {
use std::{path::Path, rc::Rc};

use es2016::ES2016;
use es2020::ES2020;
use oxc_allocator::{Allocator, Vec};
use oxc_ast::{ast::*, AstBuilder, Trivias};
use oxc_diagnostics::OxcDiagnostic;
Expand Down Expand Up @@ -60,6 +62,7 @@ pub struct Transformer<'a> {
// NOTE: all callbacks must run in order.
x0_typescript: TypeScript<'a>,
x1_react: React<'a>,
x2_es2020: ES2020<'a>,
x2_es2016: ES2016<'a>,
x3_es2015: ES2015<'a>,
}
Expand All @@ -86,6 +89,7 @@ impl<'a> Transformer<'a> {
x0_typescript: TypeScript::new(options.typescript, Rc::clone(&ctx)),
x1_react: React::new(options.react, Rc::clone(&ctx)),
x2_es2016: ES2016::new(options.es2016, Rc::clone(&ctx)),
x2_es2020: ES2020::new(options.es2020, Rc::clone(&ctx)),
x3_es2015: ES2015::new(options.es2015, ctx),
}
}
Expand Down Expand Up @@ -164,6 +168,7 @@ impl<'a> Traverse<'a> for Transformer<'a> {
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
self.x0_typescript.transform_expression(expr);
self.x1_react.transform_expression(expr, ctx);
self.x2_es2020.transform_expression(expr, ctx);
self.x2_es2016.transform_expression(expr, ctx);
self.x3_es2015.transform_expression(expr);
}
Expand Down Expand Up @@ -251,12 +256,14 @@ impl<'a> Traverse<'a> for Transformer<'a> {

fn enter_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
self.x0_typescript.transform_statements(stmts);
self.x2_es2020.transform_statements(stmts, ctx);
self.x2_es2016.transform_statements(stmts, ctx);
self.x3_es2015.enter_statements(stmts);
}

fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
self.x0_typescript.transform_statements_on_exit(stmts, ctx);
self.x2_es2020.transform_statements_on_exit(stmts, ctx);
self.x2_es2016.transform_statements_on_exit(stmts, ctx);
self.x3_es2015.exit_statements(stmts);
}
Expand Down
9 changes: 9 additions & 0 deletions crates/oxc_transformer/src/options/transformer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::{
env::{can_enable_plugin, EnvOptions, Versions},
es2015::{ArrowFunctionsOptions, ES2015Options},
es2016::ES2016Options,
es2020::ES2020Options,
options::babel::BabelOptions,
react::ReactOptions,
typescript::TypeScriptOptions,
Expand Down Expand Up @@ -37,6 +38,8 @@ pub struct TransformOptions {
pub es2015: ES2015Options,

pub es2016: ES2016Options,

pub es2020: ES2020Options,
}

impl TransformOptions {
Expand Down Expand Up @@ -112,6 +115,11 @@ impl TransformOptions {
enable_plugin(plugin_name, options, &env_options, &targets).is_some()
});

let es2020 = ES2020Options::default().with_nullish_coalescing_operator({
let plugin_name = "transform-nullish-coalescing-operator";
enable_plugin(plugin_name, options, &env_options, &targets).is_some()
});

let typescript = {
let plugin_name = "transform-typescript";
from_value::<TypeScriptOptions>(get_plugin_options(plugin_name, options))
Expand Down Expand Up @@ -144,6 +152,7 @@ impl TransformOptions {
react,
es2015,
es2016,
es2020,
})
}
}
Expand Down
14 changes: 13 additions & 1 deletion tasks/transform_conformance/babel.snap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
commit: 12619ffe

Passed: 456/931
Passed: 458/943

# All Passed:
* babel-preset-react
Expand Down Expand Up @@ -433,6 +433,18 @@ Passed: 456/931
* shipped-proposals/new-class-features-chrome-94/input.js
* shipped-proposals/new-class-features-firefox-70/input.js

# babel-plugin-transform-nullish-coalescing-operator (2/12)
* assumption-noDocumentAll/transform/input.js
* assumption-noDocumentAll/transform-in-default-destructuring/input.js
* assumption-noDocumentAll/transform-in-default-param/input.js
* assumption-noDocumentAll/transform-in-function/input.js
* assumption-noDocumentAll/transform-static-refs-in-default/input.js
* assumption-noDocumentAll/transform-static-refs-in-function/input.js
* nullish-coalescing/transform-in-default-destructuring/input.js
* nullish-coalescing/transform-in-default-param/input.js
* nullish-coalescing/transform-in-function/input.js
* nullish-coalescing/transform-loose/input.js

# babel-plugin-transform-exponentiation-operator (3/4)
* regression/4349/input.js

Expand Down
3 changes: 2 additions & 1 deletion tasks/transform_conformance/babel_exec.snap.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
commit: 12619ffe

Passed: 10/16
Passed: 12/18

# All Passed:
* babel-plugin-transform-nullish-coalescing-operator
* babel-plugin-transform-exponentiation-operator
* babel-plugin-transform-arrow-functions

Expand Down
2 changes: 1 addition & 1 deletion tasks/transform_conformance/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub(crate) const PLUGINS: &[&str] = &[
// // ES2020
// "babel-plugin-transform-export-namespace-from",
// "babel-plugin-transform-dynamic-import",
// "babel-plugin-transform-nullish-coalescing-operator",
"babel-plugin-transform-nullish-coalescing-operator",
// "babel-plugin-transform-optional-chaining",
// // [Syntax] "babel-plugin-transform-syntax-bigint",
// // [Syntax] "babel-plugin-transform-syntax-dynamic-import",
Expand Down

0 comments on commit c0d4397

Please sign in to comment.