mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +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 env;
|
||||||
mod es2015;
|
mod es2015;
|
||||||
mod es2016;
|
mod es2016;
|
||||||
|
mod es2020;
|
||||||
mod react;
|
mod react;
|
||||||
mod typescript;
|
mod typescript;
|
||||||
|
|
||||||
|
|
@ -27,6 +28,7 @@ mod helpers {
|
||||||
use std::{path::Path, rc::Rc};
|
use std::{path::Path, rc::Rc};
|
||||||
|
|
||||||
use es2016::ES2016;
|
use es2016::ES2016;
|
||||||
|
use es2020::ES2020;
|
||||||
use oxc_allocator::{Allocator, Vec};
|
use oxc_allocator::{Allocator, Vec};
|
||||||
use oxc_ast::{ast::*, AstBuilder, Trivias};
|
use oxc_ast::{ast::*, AstBuilder, Trivias};
|
||||||
use oxc_diagnostics::OxcDiagnostic;
|
use oxc_diagnostics::OxcDiagnostic;
|
||||||
|
|
@ -60,6 +62,7 @@ pub struct Transformer<'a> {
|
||||||
// NOTE: all callbacks must run in order.
|
// NOTE: all callbacks must run in order.
|
||||||
x0_typescript: TypeScript<'a>,
|
x0_typescript: TypeScript<'a>,
|
||||||
x1_react: React<'a>,
|
x1_react: React<'a>,
|
||||||
|
x2_es2020: ES2020<'a>,
|
||||||
x2_es2016: ES2016<'a>,
|
x2_es2016: ES2016<'a>,
|
||||||
x3_es2015: ES2015<'a>,
|
x3_es2015: ES2015<'a>,
|
||||||
}
|
}
|
||||||
|
|
@ -86,6 +89,7 @@ impl<'a> Transformer<'a> {
|
||||||
x0_typescript: TypeScript::new(options.typescript, Rc::clone(&ctx)),
|
x0_typescript: TypeScript::new(options.typescript, Rc::clone(&ctx)),
|
||||||
x1_react: React::new(options.react, Rc::clone(&ctx)),
|
x1_react: React::new(options.react, Rc::clone(&ctx)),
|
||||||
x2_es2016: ES2016::new(options.es2016, 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),
|
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>) {
|
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
self.x0_typescript.transform_expression(expr);
|
self.x0_typescript.transform_expression(expr);
|
||||||
self.x1_react.transform_expression(expr, ctx);
|
self.x1_react.transform_expression(expr, ctx);
|
||||||
|
self.x2_es2020.transform_expression(expr, ctx);
|
||||||
self.x2_es2016.transform_expression(expr, ctx);
|
self.x2_es2016.transform_expression(expr, ctx);
|
||||||
self.x3_es2015.transform_expression(expr);
|
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>) {
|
fn enter_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
|
||||||
self.x0_typescript.transform_statements(stmts);
|
self.x0_typescript.transform_statements(stmts);
|
||||||
|
self.x2_es2020.transform_statements(stmts, ctx);
|
||||||
self.x2_es2016.transform_statements(stmts, ctx);
|
self.x2_es2016.transform_statements(stmts, ctx);
|
||||||
self.x3_es2015.enter_statements(stmts);
|
self.x3_es2015.enter_statements(stmts);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
|
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.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.x2_es2016.transform_statements_on_exit(stmts, ctx);
|
||||||
self.x3_es2015.exit_statements(stmts);
|
self.x3_es2015.exit_statements(stmts);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use crate::{
|
||||||
env::{can_enable_plugin, EnvOptions, Versions},
|
env::{can_enable_plugin, EnvOptions, Versions},
|
||||||
es2015::{ArrowFunctionsOptions, ES2015Options},
|
es2015::{ArrowFunctionsOptions, ES2015Options},
|
||||||
es2016::ES2016Options,
|
es2016::ES2016Options,
|
||||||
|
es2020::ES2020Options,
|
||||||
options::babel::BabelOptions,
|
options::babel::BabelOptions,
|
||||||
react::ReactOptions,
|
react::ReactOptions,
|
||||||
typescript::TypeScriptOptions,
|
typescript::TypeScriptOptions,
|
||||||
|
|
@ -37,6 +38,8 @@ pub struct TransformOptions {
|
||||||
pub es2015: ES2015Options,
|
pub es2015: ES2015Options,
|
||||||
|
|
||||||
pub es2016: ES2016Options,
|
pub es2016: ES2016Options,
|
||||||
|
|
||||||
|
pub es2020: ES2020Options,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransformOptions {
|
impl TransformOptions {
|
||||||
|
|
@ -112,6 +115,11 @@ impl TransformOptions {
|
||||||
enable_plugin(plugin_name, options, &env_options, &targets).is_some()
|
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 typescript = {
|
||||||
let plugin_name = "transform-typescript";
|
let plugin_name = "transform-typescript";
|
||||||
from_value::<TypeScriptOptions>(get_plugin_options(plugin_name, options))
|
from_value::<TypeScriptOptions>(get_plugin_options(plugin_name, options))
|
||||||
|
|
@ -144,6 +152,7 @@ impl TransformOptions {
|
||||||
react,
|
react,
|
||||||
es2015,
|
es2015,
|
||||||
es2016,
|
es2016,
|
||||||
|
es2020,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
commit: 12619ffe
|
commit: 12619ffe
|
||||||
|
|
||||||
Passed: 456/931
|
Passed: 458/943
|
||||||
|
|
||||||
# All Passed:
|
# All Passed:
|
||||||
* babel-preset-react
|
* babel-preset-react
|
||||||
|
|
@ -433,6 +433,18 @@ Passed: 456/931
|
||||||
* shipped-proposals/new-class-features-chrome-94/input.js
|
* shipped-proposals/new-class-features-chrome-94/input.js
|
||||||
* shipped-proposals/new-class-features-firefox-70/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)
|
# babel-plugin-transform-exponentiation-operator (3/4)
|
||||||
* regression/4349/input.js
|
* regression/4349/input.js
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
commit: 12619ffe
|
commit: 12619ffe
|
||||||
|
|
||||||
Passed: 10/16
|
Passed: 12/18
|
||||||
|
|
||||||
# All Passed:
|
# All Passed:
|
||||||
|
* babel-plugin-transform-nullish-coalescing-operator
|
||||||
* babel-plugin-transform-exponentiation-operator
|
* babel-plugin-transform-exponentiation-operator
|
||||||
* babel-plugin-transform-arrow-functions
|
* babel-plugin-transform-arrow-functions
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ pub(crate) const PLUGINS: &[&str] = &[
|
||||||
// // ES2020
|
// // ES2020
|
||||||
// "babel-plugin-transform-export-namespace-from",
|
// "babel-plugin-transform-export-namespace-from",
|
||||||
// "babel-plugin-transform-dynamic-import",
|
// "babel-plugin-transform-dynamic-import",
|
||||||
// "babel-plugin-transform-nullish-coalescing-operator",
|
"babel-plugin-transform-nullish-coalescing-operator",
|
||||||
// "babel-plugin-transform-optional-chaining",
|
// "babel-plugin-transform-optional-chaining",
|
||||||
// // [Syntax] "babel-plugin-transform-syntax-bigint",
|
// // [Syntax] "babel-plugin-transform-syntax-bigint",
|
||||||
// // [Syntax] "babel-plugin-transform-syntax-dynamic-import",
|
// // [Syntax] "babel-plugin-transform-syntax-dynamic-import",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue