Skip to content

Commit

Permalink
feat(transformer): support helper loader
Browse files Browse the repository at this point in the history
  • Loading branch information
Dunqing committed Sep 29, 2024
1 parent ea908f7 commit bfa24af
Show file tree
Hide file tree
Showing 11 changed files with 307 additions and 55 deletions.
18 changes: 15 additions & 3 deletions crates/oxc_transformer/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@ use std::{
cell::RefCell,
mem,
path::{Path, PathBuf},
rc::Rc,
};

use oxc_allocator::Allocator;
use oxc_ast::{AstBuilder, Trivias};
use oxc_diagnostics::OxcDiagnostic;
use oxc_span::SourceType;

use crate::{helpers::module_imports::ModuleImports, TransformOptions};
use crate::{
helpers::{loader::HelperLoader, module_imports::ModuleImports},
TransformOptions,
};

pub struct TransformCtx<'a> {
errors: RefCell<Vec<OxcDiagnostic>>,
Expand All @@ -30,7 +34,8 @@ pub struct TransformCtx<'a> {

// Helpers
/// Manage import statement globally
pub module_imports: ModuleImports<'a>,
pub module_imports: Rc<RefCell<ModuleImports<'a>>>,
pub helpers: Rc<RefCell<HelperLoader<'a>>>,
}

impl<'a> TransformCtx<'a> {
Expand All @@ -50,6 +55,12 @@ impl<'a> TransformCtx<'a> {
.strip_prefix(&options.cwd)
.map_or_else(|_| source_path.to_path_buf(), |p| Path::new("<CWD>").join(p));

let module_imports = Rc::new(RefCell::new(ModuleImports::new(allocator)));
let helpers = Rc::new(RefCell::new(HelperLoader::new(
&options.helper_loader,
allocator,
&module_imports,
)));
Self {
errors: RefCell::new(vec![]),
ast: AstBuilder::new(allocator),
Expand All @@ -58,7 +69,8 @@ impl<'a> TransformCtx<'a> {
source_type,
source_text,
trivias,
module_imports: ModuleImports::new(allocator),
helpers,
module_imports,
}
}

Expand Down
13 changes: 8 additions & 5 deletions crates/oxc_transformer/src/es2018/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,28 @@ pub use options::ES2018Options;
use oxc_ast::ast::*;
use oxc_traverse::{Traverse, TraverseCtx};

pub struct ES2018 {
use crate::context::TransformCtx;

pub struct ES2018<'a, 'ctx> {
options: ES2018Options,

// Plugins
object_rest_spread: ObjectRestSpread,
object_rest_spread: ObjectRestSpread<'a, 'ctx>,
}

impl ES2018 {
pub fn new(options: ES2018Options) -> Self {
impl<'a, 'ctx> ES2018<'a, 'ctx> {
pub fn new(options: ES2018Options, ctx: &'ctx TransformCtx<'a>) -> Self {
Self {
object_rest_spread: ObjectRestSpread::new(
options.object_rest_spread.unwrap_or_default(),
ctx,
),
options,
}
}
}

impl<'a> Traverse<'a> for ES2018 {
impl<'a, 'ctx> Traverse<'a> for ES2018<'a, 'ctx> {
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.object_rest_spread.is_some() {
self.object_rest_spread.enter_expression(expr, ctx);
Expand Down
14 changes: 8 additions & 6 deletions crates/oxc_transformer/src/es2018/object_rest_spread/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ use oxc_ast::ast::*;
use oxc_traverse::{Traverse, TraverseCtx};
use serde::Deserialize;

use crate::context::TransformCtx;

mod object_rest;
mod object_spread;

Expand All @@ -43,27 +45,27 @@ pub struct ObjectRestSpreadOptions {
pub(crate) use_built_ins: bool,
}

pub struct ObjectRestSpread {
pub struct ObjectRestSpread<'a, 'ctx> {
#[allow(dead_code)]
options: ObjectRestSpreadOptions,

// Plugins
object_spread: ObjectSpread,
object_spread: ObjectSpread<'a, 'ctx>,
#[allow(dead_code)]
object_rest: ObjectRest,
}

impl ObjectRestSpread {
pub fn new(options: ObjectRestSpreadOptions) -> Self {
impl<'a, 'ctx> ObjectRestSpread<'a, 'ctx> {
pub fn new(options: ObjectRestSpreadOptions, ctx: &'ctx TransformCtx<'a>) -> Self {
Self {
object_spread: ObjectSpread::new(options),
object_spread: ObjectSpread::new(options, ctx),
object_rest: ObjectRest::new(options),
options,
}
}
}

impl<'a> Traverse<'a> for ObjectRestSpread {
impl<'a, 'ctx> Traverse<'a> for ObjectRestSpread<'a, 'ctx> {
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
self.object_spread.enter_expression(expr, ctx);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,21 @@ use oxc_semantic::{ReferenceFlags, SymbolId};
use oxc_span::SPAN;
use oxc_traverse::{Traverse, TraverseCtx};

use crate::context::TransformCtx;

use super::ObjectRestSpreadOptions;

pub struct ObjectSpread {
pub struct ObjectSpread<'a, 'ctx> {
options: ObjectRestSpreadOptions,
ctx: &'ctx TransformCtx<'a>,
}

impl ObjectSpread {
pub fn new(options: ObjectRestSpreadOptions) -> Self {
Self { options }
impl<'a, 'ctx> ObjectSpread<'a, 'ctx> {
pub fn new(options: ObjectRestSpreadOptions, ctx: &'ctx TransformCtx<'a>) -> Self {
Self { options, ctx }
}
}
impl<'a> Traverse<'a> for ObjectSpread {
impl<'a, 'ctx> Traverse<'a> for ObjectSpread<'a, 'ctx> {
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
let Expression::ObjectExpression(obj_expr) = expr else {
return;
Expand Down Expand Up @@ -77,9 +80,8 @@ impl<'a> Traverse<'a> for ObjectSpread {
arguments.push(Argument::from(ctx.ast.move_expression(&mut spread_prop.argument)));

let object_id = ctx.scopes().find_binding(ctx.current_scope_id(), "Object");
let babel_helpers_id = ctx.scopes().find_binding(ctx.current_scope_id(), "babelHelpers");

let callee = self.get_extend_object_callee(object_id, babel_helpers_id, ctx);
let callee = self.get_extend_object_callee(object_id, ctx);

// ({ ...x }) => _objectSpread({}, x)
*expr = ctx.ast.expression_call(SPAN, callee, NONE, arguments, false);
Expand All @@ -91,14 +93,14 @@ impl<'a> Traverse<'a> for ObjectSpread {
arguments.push(Argument::from(ctx.ast.move_expression(expr)));
arguments.push(Argument::from(ctx.ast.expression_object(SPAN, obj_prop_list, None)));

let callee = self.get_extend_object_callee(object_id, babel_helpers_id, ctx);
let callee = self.get_extend_object_callee(object_id, ctx);

*expr = ctx.ast.expression_call(SPAN, callee, NONE, arguments, false);
}
}
}

impl<'a> ObjectSpread {
impl<'a, 'ctx> ObjectSpread<'a, 'ctx> {
fn object_assign(symbol_id: Option<SymbolId>, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
let ident =
ctx.create_reference_id(SPAN, Atom::from("Object"), symbol_id, ReferenceFlags::Read);
Expand All @@ -108,32 +110,19 @@ impl<'a> ObjectSpread {
Expression::from(ctx.ast.member_expression_static(SPAN, object, property, false))
}

fn babel_external_helper(
symbol_id: Option<SymbolId>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
let ident = ctx.create_reference_id(
SPAN,
Atom::from("babelHelpers"),
symbol_id,
ReferenceFlags::Read,
);
let object = ctx.ast.expression_from_identifier_reference(ident);
let property = ctx.ast.identifier_name(SPAN, Atom::from("objectSpread2"));

Expression::from(ctx.ast.member_expression_static(SPAN, object, property, false))
fn babel_external_helper(&mut self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
self.ctx.helpers.borrow_mut().get_callee(Atom::from("objectSpread2"), ctx)
}

fn get_extend_object_callee(
&mut self,
object_id: Option<SymbolId>,
babel_helpers_id: Option<SymbolId>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
if self.options.set_spread_properties {
Self::object_assign(object_id, ctx)
} else {
Self::babel_external_helper(babel_helpers_id, ctx)
self.babel_external_helper(ctx)
}
}
}
136 changes: 136 additions & 0 deletions crates/oxc_transformer/src/helpers/loader/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
pub mod options;

use std::{cell::RefCell, rc::Rc};

use options::{HelperLoaderMode, HelperLoaderOptions};
use oxc_allocator::{Allocator, Vec};
use oxc_ast::{
ast::{Argument, CallExpression, Expression, TSTypeParameterInstantiation},
AstBuilder,
};
use oxc_semantic::{ReferenceFlags, SymbolFlags, SymbolId};
use oxc_span::{Atom, SPAN};
use oxc_traverse::TraverseCtx;
use rustc_hash::FxHashMap;

use super::module_imports::{ImportSpecifier, ModuleImports};

pub struct HelperLoader<'a> {
ast: AstBuilder<'a>,
mode: HelperLoaderMode,
module_name: Atom<'a>,
/// (helper_name, (import_name, symbol_id))
loaded_helper: FxHashMap<Atom<'a>, (Atom<'a>, Option<SymbolId>)>,
module_imports: Rc<RefCell<ModuleImports<'a>>>,
}

impl<'a> HelperLoader<'a> {
pub fn new(
options: &HelperLoaderOptions,
allocator: &'a Allocator,
module_imports: &Rc<RefCell<ModuleImports<'a>>>,
) -> Self {
let ast = AstBuilder::new(allocator);
let module_name = ast.atom(&options.module_name);
Self {
ast,
mode: options.mode,
module_name,
loaded_helper: FxHashMap::default(),
module_imports: Rc::clone(module_imports),
}
}

fn add_default_import(&self, helper_name: &str, ctx: &mut TraverseCtx<'a>) -> SymbolId {
let source = ctx.ast.atom(&format!("{}/helpers/{helper_name}", self.module_name));
let symbol_id = ctx.generate_uid_in_root_scope(helper_name, SymbolFlags::Import);
let name = self.ast.atom(&ctx.symbols().names[symbol_id]);
self.module_imports
.borrow_mut()
.add_default(source, ImportSpecifier::new(name, None, symbol_id));
symbol_id
}

fn transform_for_runtime_helper(
&mut self,
helper_name: &str,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
if !self.loaded_helper.contains_key(helper_name) {
let symbol_id = self.add_default_import(helper_name, ctx);
let name = self.ast.atom(&ctx.symbols().names[symbol_id]);
self.loaded_helper.insert(ctx.ast.atom(helper_name), (name, Some(symbol_id)));
}

let (name, symbol_id) = self.loaded_helper[helper_name].clone();
let ident = ctx.create_reference_id(SPAN, name, symbol_id, ReferenceFlags::Read);
ctx.ast.expression_from_identifier_reference(ident)
}

fn transform_for_external_helper(
&mut self,
helper_name: Atom<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
// TODO: Wether to use find_binding instead of get_root_binding

Check warning on line 75 in crates/oxc_transformer/src/helpers/loader/mod.rs

View workflow job for this annotation

GitHub Actions / Spell Check

"Wether" should be "Weather" or "Whether".
if !self.loaded_helper.contains_key(&helper_name) {
let symbol_id = ctx.scopes().get_root_binding("babelHelpers");
let name = self.ast.atom("babelHelpers");
self.loaded_helper.insert(helper_name.clone(), (name, symbol_id));
}

let (name, symbol_id) = self.loaded_helper[&helper_name].clone();
let ident = ctx.create_reference_id(SPAN, name, symbol_id, ReferenceFlags::Read);
let object = ctx.ast.expression_from_identifier_reference(ident);
let property = ctx.ast.identifier_name(SPAN, helper_name);
Expression::from(ctx.ast.member_expression_static(SPAN, object, property, false))
}

#[allow(dead_code)]
pub fn call(
&mut self,
helper_name: Atom<'a>,
arguments: Vec<'a, Argument<'a>>,
ctx: &mut TraverseCtx<'a>,
) -> CallExpression<'a> {
let callee = self.get_callee(helper_name, ctx);
ctx.ast.call_expression(
SPAN,
callee,
None::<TSTypeParameterInstantiation<'a>>,
arguments,
false,
)
}

#[allow(dead_code)]
pub fn call_expr(
&mut self,
helper_name: Atom<'a>,
arguments: Vec<'a, Argument<'a>>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
let callee = self.get_callee(helper_name, ctx);
ctx.ast.expression_call(
SPAN,
callee,
None::<TSTypeParameterInstantiation<'a>>,
arguments,
false,
)
}

pub fn get_callee(
&mut self,
helper_name: Atom<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
match self.mode {
HelperLoaderMode::Runtime => self.transform_for_runtime_helper(&helper_name, ctx),
HelperLoaderMode::External => self.transform_for_external_helper(helper_name, ctx),
HelperLoaderMode::Inline => {
panic!("Inline helpers are not supported yet");
}
}
}
}
31 changes: 31 additions & 0 deletions crates/oxc_transformer/src/helpers/loader/options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use std::borrow::Cow;

use serde::Deserialize;

#[derive(Default, Clone, Copy, Debug, Deserialize)]
pub enum HelperLoaderMode {
/// Babel default mode
Inline,
/// Babel test default mode
External,
/// Like @babel/plugin-transform-runtime does
#[default]
Runtime,
}

#[derive(Clone, Debug, Deserialize)]
pub struct HelperLoaderOptions {
#[serde(default = "default_as_module_name")]
pub module_name: Cow<'static, str>,
pub mode: HelperLoaderMode,
}

impl Default for HelperLoaderOptions {
fn default() -> Self {
Self { module_name: default_as_module_name(), mode: HelperLoaderMode::default() }
}
}

fn default_as_module_name() -> Cow<'static, str> {
Cow::Borrowed("@babel/runtime")
}
Loading

0 comments on commit bfa24af

Please sign in to comment.