Skip to content

Commit

Permalink
refactor(transformer): deserialize BabelOptions::plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
Boshen committed Nov 1, 2024
1 parent 4bef99c commit 268e7f3
Show file tree
Hide file tree
Showing 9 changed files with 287 additions and 219 deletions.
2 changes: 1 addition & 1 deletion crates/oxc_transformer/src/es2015/arrow_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ use oxc_syntax::{
};
use oxc_traverse::{Ancestor, BoundIdentifier, Traverse, TraverseCtx};

#[derive(Debug, Default, Clone, Deserialize)]
#[derive(Debug, Default, Clone, Copy, Deserialize)]
pub struct ArrowFunctionsOptions {
/// This option enables the following:
/// * Wrap the generated function in .bind(this) and keeps uses of this inside the function as-is, instead of using a renamed this.
Expand Down
4 changes: 1 addition & 3 deletions crates/oxc_transformer/src/es2015/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ pub struct ES2015<'a> {
impl<'a> ES2015<'a> {
pub fn new(options: ES2015Options) -> Self {
Self {
arrow_functions: ArrowFunctions::new(
options.arrow_function.clone().unwrap_or_default(),
),
arrow_functions: ArrowFunctions::new(options.arrow_function.unwrap_or_default()),
options,
}
}
Expand Down
252 changes: 205 additions & 47 deletions crates/oxc_transformer/src/options/babel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ mod env;

use std::path::{Path, PathBuf};

use serde::Deserialize;
use serde::{de::DeserializeOwned, Deserialize};
use serde_json::Value;

use crate::{
es2015::ArrowFunctionsOptions, es2018::ObjectRestSpreadOptions, es2022::ClassPropertiesOptions,
jsx::JsxOptions, TypeScriptOptions,
};

pub use env::{BabelEnvOptions, Targets};

/// Babel options
Expand All @@ -20,7 +25,7 @@ pub struct BabelOptions {

// Plugin and Preset options
#[serde(default)]
pub plugins: Vec<Value>, // Can be a string or an array
pub plugins: BabelPlugins,

#[serde(default)]
pub presets: Vec<Value>, // Can be a string or an array
Expand Down Expand Up @@ -77,61 +82,62 @@ impl BabelOptions {
/// Read options.json and merge them with options.json from ancestors directories.
/// # Panics
pub fn from_test_path(path: &Path) -> Self {
let mut options_json: Option<Self> = None;
let mut babel_options: Option<Self> = None;
let mut plugins_json = None;

for path in path.ancestors().take(3) {
let file = path.join("options.json");
if !file.exists() {
continue;
}
let file = std::fs::read_to_string(&file).unwrap();
let new_json: Self = serde_json::from_str(&file).unwrap();
if let Some(existing_json) = options_json.as_mut() {
if existing_json.source_type.is_none() {
if let Some(source_type) = new_json.source_type {
existing_json.source_type = Some(source_type);

let content = std::fs::read_to_string(&file).unwrap();
let mut new_value = serde_json::from_str::<serde_json::Value>(&content).unwrap();

let new_plugins = new_value.as_object_mut().unwrap().remove("plugins");
if plugins_json.is_none() {
plugins_json = new_plugins;
}

let new_options: Self = serde_json::from_value::<BabelOptions>(new_value)
.unwrap_or_else(|err| panic!("{err:?}\n{file:?}\n{content}"));

if let Some(existing_options) = babel_options.as_mut() {
if existing_options.source_type.is_none() {
if let Some(source_type) = new_options.source_type {
existing_options.source_type = Some(source_type);
}
}
if existing_json.throws.is_none() {
if let Some(throws) = new_json.throws {
existing_json.throws = Some(throws);
if existing_options.throws.is_none() {
if let Some(throws) = new_options.throws {
existing_options.throws = Some(throws);
}
}
if existing_json.plugins.is_empty() {
existing_json.plugins = new_json.plugins;
}
if existing_json.presets.is_empty() {
existing_json.presets = new_json.presets;
if existing_options.presets.is_empty() {
existing_options.presets = new_options.presets;
}
} else {
options_json = Some(new_json);
babel_options = Some(new_options);
}
}
options_json.unwrap_or_default()

let mut options = babel_options.unwrap_or_default();
if let Some(plugins_json) = plugins_json {
options.plugins = serde_json::from_value::<BabelPlugins>(plugins_json).unwrap();
}
options
}

pub fn is_jsx(&self) -> bool {
self.plugins.iter().any(|v| v.as_str().is_some_and(|v| v == "jsx"))
self.plugins.syntax_jsx
}

pub fn is_typescript(&self) -> bool {
self.plugins.iter().any(|v| {
let string_value = v.as_str().is_some_and(|v| v == "typescript");
let array_value = v.get(0).and_then(Value::as_str).is_some_and(|s| s == "typescript");
string_value || array_value
})
self.plugins.syntax_typescript.is_some()
}

pub fn is_typescript_definition(&self) -> bool {
self.plugins.iter().filter_map(Value::as_array).any(|p| {
let typescript = p.first().and_then(Value::as_str).is_some_and(|s| s == "typescript");
let dts = p
.get(1)
.and_then(Value::as_object)
.and_then(|v| v.get("dts"))
.and_then(Value::as_bool)
.is_some_and(|v| v);
typescript && dts
})
self.plugins.syntax_typescript.is_some_and(|o| o.dts)
}

pub fn is_module(&self) -> bool {
Expand All @@ -142,22 +148,10 @@ impl BabelOptions {
self.source_type.as_ref().map_or(false, |s| s.as_str() == "unambiguous")
}

/// Returns
/// * `Some<None>` if the plugin exists without a config
/// * `Some<Some<Value>>` if the plugin exists with a config
/// * `None` if the plugin does not exist
pub fn get_plugin(&self, name: &str) -> Option<Option<Value>> {
self.plugins.iter().find_map(|v| Self::get_value(v, name))
}

pub fn get_preset(&self, name: &str) -> Option<Option<Value>> {
self.presets.iter().find_map(|v| Self::get_value(v, name))
}

pub fn has_plugin(&self, name: &str) -> bool {
self.get_plugin(name).is_some()
}

pub fn has_preset(&self, name: &str) -> bool {
self.get_preset(name).is_some()
}
Expand All @@ -173,3 +167,167 @@ impl BabelOptions {
}
}
}

#[derive(Debug, Default, Clone, Copy, Deserialize)]
pub struct SyntaxTypeScriptOptions {
#[serde(default)]
pub dts: bool,
}

#[derive(Debug, Default, Clone, Deserialize)]
pub struct SyntaxDecoratorOptions {
#[serde(default)]
pub version: String,
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(try_from = "PluginPresetEntries")]
pub struct BabelPlugins {
pub errors: Vec<String>,
pub unsupported: Vec<String>,
// syntax
pub syntax_typescript: Option<SyntaxTypeScriptOptions>,
pub syntax_jsx: bool,
// decorators
pub syntax_decorators: Option<SyntaxDecoratorOptions>,
pub proposal_decorators: Option<SyntaxDecoratorOptions>,
// ts
pub typescript: Option<TypeScriptOptions>,
// jsx
pub react_jsx: Option<JsxOptions>,
pub react_jsx_dev: Option<JsxOptions>,
pub react_jsx_self: bool,
pub react_jsx_source: bool,
pub react_display_name: bool,
// regexp
pub sticky_flag: bool,
pub unicode_flag: bool,
pub dot_all_flag: bool,
pub look_behind_assertions: bool,
pub named_capture_groups: bool,
pub unicode_property_escapes: bool,
pub match_indices: bool,
/// Enables plugin to transform the RegExp literal has `v` flag
pub set_notation: bool,
// ES2015
pub arrow_function: Option<ArrowFunctionsOptions>,
// ES2016
pub exponentiation_operator: bool,
// ES2017
pub async_to_generator: bool,
// ES2018
pub object_rest_spread: Option<ObjectRestSpreadOptions>,
pub async_generator_functions: bool,
// ES2019
pub optional_catch_binding: bool,
// ES2020
pub nullish_coalescing_operator: bool,
// ES2021
pub logical_assignment_operators: bool,
// ES2022
pub class_static_block: bool,
pub class_properties: Option<ClassPropertiesOptions>,
}

/// <https://babeljs.io/docs/options#pluginpreset-entries>
#[derive(Debug, Deserialize)]
struct PluginPresetEntries(Vec<PluginPresetEntry>);

impl TryFrom<PluginPresetEntries> for BabelPlugins {
type Error = String;

fn try_from(entries: PluginPresetEntries) -> Result<Self, Self::Error> {
let mut p = BabelPlugins::default();
for entry in entries.0 {
match entry.name() {
"typescript" | "syntax-typescript" => {
p.syntax_typescript = Some(entry.value::<SyntaxTypeScriptOptions>()?);
}
"jsx" | "syntax-jsx" => p.syntax_jsx = true,
"syntax-decorators" => {
p.syntax_decorators = Some(entry.value::<SyntaxDecoratorOptions>()?);
}
"proposal-decorators" => {
p.proposal_decorators = Some(entry.value::<SyntaxDecoratorOptions>()?);
}
"transform-typescript" => {
p.typescript =
entry.value::<TypeScriptOptions>().map_err(|err| p.errors.push(err)).ok();
}
"transform-react-jsx" => {
p.react_jsx =
entry.value::<JsxOptions>().map_err(|err| p.errors.push(err)).ok();
}
"transform-react-jsx-development" => {
p.react_jsx_dev =
entry.value::<JsxOptions>().map_err(|err| p.errors.push(err)).ok();
}
"transform-react-display-name" => p.react_display_name = true,
"transform-react-jsx-self" => p.react_jsx_self = true,
"transform-react-jsx-source" => p.react_jsx_source = true,
"transform-sticky-regex" => p.sticky_flag = true,
"transform-unicode-regex" => p.unicode_flag = true,
"transform-dotall-regex" => p.dot_all_flag = true,
"esbuild-regexp-lookbehind-assertions" => p.look_behind_assertions = true,
"transform-named-capturing-groups-regex" => p.named_capture_groups = true,
"transform-unicode-property-regex" => p.unicode_property_escapes = true,
"esbuild-regexp-match-indices" => p.match_indices = true,
"transform-unicode-sets-regex" => p.set_notation = true,
"transform-arrow-functions" => {
p.arrow_function = entry
.value::<ArrowFunctionsOptions>()
.map_err(|err| p.errors.push(err))
.ok();
}
"transform-exponentiation-operator" => p.exponentiation_operator = true,
"transform-async-to-generator" => p.async_to_generator = true,
"transform-object-rest-spread" => {
p.object_rest_spread = entry
.value::<ObjectRestSpreadOptions>()
.inspect_err(|err| p.errors.push(err.to_string()))
.ok();
}
"transform-async-generator-functions" => p.async_generator_functions = true,
"transform-optional-catch-binding" => p.optional_catch_binding = true,
"transform-nullish-coalescing-operator" => p.nullish_coalescing_operator = true,
"transform-logical-assignment-operators" => p.logical_assignment_operators = true,
"transform-class-static-block" => p.class_static_block = true,
"transform-class-properties" => {
p.class_properties = entry
.value::<ClassPropertiesOptions>()
.inspect_err(|err| p.errors.push(err.to_string()))
.ok();
}
s => p.unsupported.push(s.to_string()),
}
}
Ok(p)
}
}

/// <https://babeljs.io/docs/options#pluginpreset-entries>
#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum PluginPresetEntry {
String(String),
Vec1([String; 1]),
Tuple(String, serde_json::Value),
}

impl PluginPresetEntry {
fn name(&self) -> &str {
match self {
Self::String(s) | Self::Tuple(s, _) => s,
Self::Vec1(s) => &s[0],
}
}

fn value<T: DeserializeOwned + Default>(self) -> Result<T, String> {
match self {
Self::String(_) | Self::Vec1(_) => Ok(T::default()),
Self::Tuple(name, v) => {
serde_json::from_value::<T>(v).map_err(|err| format!("{name}: {err}"))
}
}
}
}
Loading

0 comments on commit 268e7f3

Please sign in to comment.