mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(transformer): support nullish-coalescing-operator plugin (#4884)
The implementation copy from the original implementation which removed in https://github.com/oxc-project/oxc/pull/2865.
This commit is contained in:
parent
3a66e5843d
commit
69da9fda3a
8 changed files with 250 additions and 3 deletions
56
crates/oxc_transformer/src/es2020/mod.rs
Normal file
56
crates/oxc_transformer/src/es2020/mod.rs
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
146
crates/oxc_transformer/src/es2020/nullish_coalescing_operator.rs
Normal file
146
crates/oxc_transformer/src/es2020/nullish_coalescing_operator.rs
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
use std::cell::Cell;
|
||||
|
||||
use oxc_semantic::{ReferenceFlag, SymbolFlags};
|
||||
use oxc_traverse::TraverseCtx;
|
||||
|
||||
use oxc_allocator::{CloneIn, 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![] }
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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(ident) => ident.name.as_str(),
|
||||
// TODO: needs to port generateUidIdentifierBasedOnNode
|
||||
// https://github.com/babel/babel/blob/419644f27c5c59deb19e71aaabd417a3bc5483ca/packages/babel-traverse/src/scope/index.ts#L543-L545
|
||||
_ => "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
|
||||
if !matches!(expr, Expression::LogicalExpression(logical_expr) if logical_expr.operator == LogicalOperator::Coalesce)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Take ownership of the `LogicalExpression`
|
||||
let logical_expr = match ctx.ast.move_expression(expr) {
|
||||
Expression::LogicalExpression(logical_expr) => logical_expr.unbox(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
// skip creating extra reference when `left` is static
|
||||
let (reference, assignment) = if ctx.symbols().is_static(&logical_expr.left) {
|
||||
(Self::clone_expression(&logical_expr.left, ctx), logical_expr.left)
|
||||
} else {
|
||||
let ident = self.create_new_var_with_expression(&logical_expr.left, ctx);
|
||||
let left =
|
||||
AssignmentTarget::from(ctx.ast.simple_assignment_target_from_identifier_reference(
|
||||
Self::clone_identifier_reference(&ident, ctx),
|
||||
));
|
||||
let right = logical_expr.left;
|
||||
(
|
||||
ctx.ast.expression_from_identifier_reference(ident),
|
||||
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);
|
||||
|
||||
*expr = ctx.ast.expression_conditional(SPAN, test, reference, logical_expr.right);
|
||||
}
|
||||
}
|
||||
16
crates/oxc_transformer/src/es2020/options.rs
Normal file
16
crates/oxc_transformer/src/es2020/options.rs
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ mod options;
|
|||
mod env;
|
||||
mod es2015;
|
||||
mod es2016;
|
||||
mod es2020;
|
||||
mod react;
|
||||
mod typescript;
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
@ -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>,
|
||||
}
|
||||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -37,6 +38,8 @@ pub struct TransformOptions {
|
|||
pub es2015: ES2015Options,
|
||||
|
||||
pub es2016: ES2016Options,
|
||||
|
||||
pub es2020: ES2020Options,
|
||||
}
|
||||
|
||||
impl TransformOptions {
|
||||
|
|
@ -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))
|
||||
|
|
@ -144,6 +152,7 @@ impl TransformOptions {
|
|||
react,
|
||||
es2015,
|
||||
es2016,
|
||||
es2020,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
commit: 12619ffe
|
||||
|
||||
Passed: 456/931
|
||||
Passed: 458/943
|
||||
|
||||
# All Passed:
|
||||
* babel-preset-react
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue