use std::borrow::Cow;
use rustc_hash::FxHashMap;
use serde::Deserialize;
use oxc_allocator::Vec as ArenaVec;
use oxc_ast::{
NONE,
ast::{Argument, CallExpression, Expression},
};
use oxc_semantic::{ReferenceFlags, SymbolFlags};
use oxc_span::SPAN;
use oxc_str::Str;
use oxc_traverse::BoundIdentifier;
use crate::context::TraverseCtx;
#[derive(Default, Clone, Copy, Debug, Deserialize)]
pub enum HelperLoaderMode {
Inline,
External,
#[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("@oxc-project/runtime")
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Helper {
AwaitAsyncGenerator,
AsyncGeneratorDelegate,
AsyncIterator,
AsyncToGenerator,
ObjectSpread2,
WrapAsyncGenerator,
Extends,
ObjectDestructuringEmpty,
ObjectWithoutProperties,
ToPropertyKey,
DefineProperty,
ClassPrivateFieldInitSpec,
ClassPrivateMethodInitSpec,
ClassPrivateFieldGet2,
ClassPrivateFieldSet2,
AssertClassBrand,
ToSetter,
ClassPrivateFieldLooseKey,
ClassPrivateFieldLooseBase,
SuperPropGet,
SuperPropSet,
ReadOnlyError,
WriteOnlyError,
CheckInRHS,
Decorate,
DecorateParam,
DecorateMetadata,
UsingCtx,
TaggedTemplateLiteral,
}
impl Helper {
pub const fn name(self) -> &'static str {
match self {
Self::AwaitAsyncGenerator => "awaitAsyncGenerator",
Self::AsyncGeneratorDelegate => "asyncGeneratorDelegate",
Self::AsyncIterator => "asyncIterator",
Self::AsyncToGenerator => "asyncToGenerator",
Self::ObjectSpread2 => "objectSpread2",
Self::WrapAsyncGenerator => "wrapAsyncGenerator",
Self::Extends => "extends",
Self::ObjectDestructuringEmpty => "objectDestructuringEmpty",
Self::ObjectWithoutProperties => "objectWithoutProperties",
Self::ToPropertyKey => "toPropertyKey",
Self::DefineProperty => "defineProperty",
Self::ClassPrivateFieldInitSpec => "classPrivateFieldInitSpec",
Self::ClassPrivateMethodInitSpec => "classPrivateMethodInitSpec",
Self::ClassPrivateFieldGet2 => "classPrivateFieldGet2",
Self::ClassPrivateFieldSet2 => "classPrivateFieldSet2",
Self::AssertClassBrand => "assertClassBrand",
Self::ToSetter => "toSetter",
Self::ClassPrivateFieldLooseKey => "classPrivateFieldLooseKey",
Self::ClassPrivateFieldLooseBase => "classPrivateFieldLooseBase",
Self::SuperPropGet => "superPropGet",
Self::SuperPropSet => "superPropSet",
Self::ReadOnlyError => "readOnlyError",
Self::WriteOnlyError => "writeOnlyError",
Self::CheckInRHS => "checkInRHS",
Self::Decorate => "decorate",
Self::DecorateParam => "decorateParam",
Self::DecorateMetadata => "decorateMetadata",
Self::UsingCtx => "usingCtx",
Self::TaggedTemplateLiteral => "taggedTemplateLiteral",
}
}
pub const fn pure(self) -> bool {
matches!(self, Self::ClassPrivateFieldLooseKey)
}
}
pub struct HelperLoaderStore<'a> {
module_name: Cow<'static, str>,
mode: HelperLoaderMode,
loaded_helpers: FxHashMap<Helper, BoundIdentifier<'a>>,
pub(crate) used_helpers: FxHashMap<Helper, String>,
}
impl HelperLoaderStore<'_> {
pub fn new(options: &HelperLoaderOptions) -> Self {
Self {
module_name: options.module_name.clone(),
mode: options.mode,
loaded_helpers: FxHashMap::default(),
used_helpers: FxHashMap::default(),
}
}
}
pub fn helper_call<'a>(
helper: Helper,
arguments: ArenaVec<'a, Argument<'a>>,
ctx: &mut TraverseCtx<'a>,
) -> CallExpression<'a> {
let callee = helper_load(helper, ctx);
let pure = helper.pure();
ctx.ast.call_expression_with_pure(SPAN, callee, NONE, arguments, false, pure)
}
pub fn helper_call_expr<'a>(
helper: Helper,
arguments: ArenaVec<'a, Argument<'a>>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
let callee = helper_load(helper, ctx);
let pure = helper.pure();
ctx.ast.expression_call_with_pure(SPAN, callee, NONE, arguments, false, pure)
}
pub fn helper_load<'a>(helper: Helper, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
let source = ctx.state.helper_loader.get_runtime_source(helper, ctx);
ctx.state.helper_loader.used_helpers.entry(helper).or_insert_with(|| source.to_string());
let mode = ctx.state.helper_loader.mode;
match mode {
HelperLoaderMode::Runtime => {
let existing = ctx.state.helper_loader.loaded_helpers.get(&helper).cloned();
if let Some(binding) = existing {
return binding.create_read_expression(ctx);
}
let is_module = ctx.state.source_type.is_module();
let flag =
if is_module { SymbolFlags::Import } else { SymbolFlags::FunctionScopedVariable };
let binding = ctx.generate_uid_in_root_scope(helper.name(), flag);
ctx.state.module_imports.add_default_import(source, binding.clone(), false);
ctx.state.helper_loader.loaded_helpers.insert(helper, binding.clone());
binding.create_read_expression(ctx)
}
HelperLoaderMode::External => HelperLoaderStore::transform_for_external_helper(helper, ctx),
HelperLoaderMode::Inline => {
unreachable!("Inline helpers are not supported yet");
}
}
}
impl<'a> HelperLoaderStore<'a> {
fn get_runtime_source(&self, helper: Helper, ctx: &TraverseCtx<'a>) -> Str<'a> {
ctx.ast.str_from_strs_array([&self.module_name, "/helpers/", helper.name()])
}
fn transform_for_external_helper(helper: Helper, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
static HELPER_VAR: &str = "babelHelpers";
let helper_var = ctx.ast.ident(HELPER_VAR);
let symbol_id = ctx.scoping().find_binding(ctx.current_scope_id(), helper_var);
let object = ctx.create_ident_expr(SPAN, helper_var, symbol_id, ReferenceFlags::Read);
let property = ctx.ast.identifier_name(SPAN, helper.name());
Expression::from(ctx.ast.member_expression_static(SPAN, object, property, false))
}
}