mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
feat(transformer): support from_babel_options in TransformOptions (#3301)
Move `BabelOptions` to Transformer. The `output.json` is a standard babel configuration. We can reuse BabelOptions to read [babel.config.json](https://babeljs.io/docs/configuration#babelconfigjson) or our configuration(maybe oxc.config.json) The current `from_babel_options` implementation is copied from the `transform_options` in `test_case.rs`, which I'll completely reimplement next
This commit is contained in:
parent
bd8a0ddb7f
commit
9ee962add8
10 changed files with 201 additions and 202 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -1659,8 +1659,6 @@ version = "0.0.0"
|
|||
dependencies = [
|
||||
"console",
|
||||
"project-root",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"similar",
|
||||
"ureq",
|
||||
"url",
|
||||
|
|
@ -1679,7 +1677,6 @@ dependencies = [
|
|||
"oxc_tasks_common",
|
||||
"oxc_transformer",
|
||||
"pico-args",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"walkdir",
|
||||
]
|
||||
|
|
@ -1700,6 +1697,7 @@ dependencies = [
|
|||
"ropey",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ oxc_traverse = { workspace = true }
|
|||
rustc-hash = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
ropey = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ use oxc_span::SourceType;
|
|||
use oxc_traverse::{traverse_mut, Traverse, TraverseCtx};
|
||||
|
||||
pub use crate::{
|
||||
compiler_assumptions::CompilerAssumptions, es2015::ES2015Options, options::TransformOptions,
|
||||
react::ReactOptions, typescript::TypeScriptOptions,
|
||||
compiler_assumptions::CompilerAssumptions, es2015::ES2015Options, options::BabelOptions,
|
||||
options::TransformOptions, react::ReactOptions, typescript::TypeScriptOptions,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde::{de::DeserializeOwned, Deserialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
compiler_assumptions::CompilerAssumptions, es2015::ES2015Options, react::ReactOptions,
|
||||
|
|
@ -28,3 +31,188 @@ pub struct TransformOptions {
|
|||
|
||||
pub es2015: ES2015Options,
|
||||
}
|
||||
|
||||
impl TransformOptions {
|
||||
/// # Panics
|
||||
/// Panics if the options are invalid.
|
||||
/// # Errors
|
||||
pub fn from_babel_options(options: &BabelOptions) -> serde_json::Result<Self> {
|
||||
fn get_options<T: Default + DeserializeOwned>(
|
||||
value: Option<Value>,
|
||||
) -> serde_json::Result<T> {
|
||||
match value {
|
||||
Some(v) => serde_json::from_value::<T>(v),
|
||||
None => Ok(T::default()),
|
||||
}
|
||||
}
|
||||
|
||||
let react = if let Some(options) = options.get_preset("react") {
|
||||
get_options::<ReactOptions>(options)?
|
||||
} else {
|
||||
let jsx_plugin = options.get_plugin("transform-react-jsx");
|
||||
let jsx_development_plugin = options.get_plugin("transform-react-jsx-development");
|
||||
let has_jsx_plugin =
|
||||
jsx_plugin.as_ref().is_some() || jsx_development_plugin.as_ref().is_some();
|
||||
let mut react_options = jsx_plugin
|
||||
.map(get_options::<ReactOptions>)
|
||||
.or_else(|| jsx_development_plugin.map(get_options::<ReactOptions>))
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
react_options.development =
|
||||
options.get_plugin("transform-react-jsx-development").is_some();
|
||||
react_options.jsx_plugin = has_jsx_plugin;
|
||||
react_options.display_name_plugin =
|
||||
options.get_plugin("transform-react-display-name").is_some();
|
||||
react_options.jsx_self_plugin =
|
||||
options.get_plugin("transform-react-jsx-self").is_some();
|
||||
react_options.jsx_source_plugin =
|
||||
options.get_plugin("transform-react-jsx-source").is_some();
|
||||
react_options
|
||||
};
|
||||
|
||||
let es2015 = ES2015Options {
|
||||
arrow_function: options
|
||||
.get_plugin("transform-arrow-functions")
|
||||
.map(get_options)
|
||||
.transpose()?,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
cwd: options.cwd.clone().unwrap(),
|
||||
assumptions: serde_json::from_value(options.assumptions.clone()).unwrap_or_default(),
|
||||
typescript: options
|
||||
.get_plugin("transform-typescript")
|
||||
.map(get_options::<TypeScriptOptions>)
|
||||
.transpose()?
|
||||
.unwrap_or_default(),
|
||||
react,
|
||||
es2015,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Babel options
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BabelOptions {
|
||||
pub cwd: Option<PathBuf>,
|
||||
pub source_type: Option<String>,
|
||||
#[serde(default)]
|
||||
pub plugins: Vec<Value>, // Can be a string or an array
|
||||
#[serde(default)]
|
||||
pub presets: Vec<Value>, // Can be a string or an array
|
||||
#[serde(default)]
|
||||
pub assumptions: Value,
|
||||
// Test options
|
||||
pub throws: Option<String>,
|
||||
#[serde(rename = "BABEL_8_BREAKING")]
|
||||
pub babel_8_breaking: Option<bool>,
|
||||
/// Babel test helper for running tests on specific operating systems
|
||||
pub os: Option<Vec<TestOs>>,
|
||||
// Parser options for babel-parser
|
||||
#[serde(default)]
|
||||
pub allow_return_outside_function: bool,
|
||||
#[serde(default)]
|
||||
pub allow_await_outside_function: bool,
|
||||
#[serde(default)]
|
||||
pub allow_undeclared_exports: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum TestOs {
|
||||
Linux,
|
||||
Win32,
|
||||
Windows,
|
||||
Darwin,
|
||||
}
|
||||
|
||||
impl TestOs {
|
||||
pub fn is_windows(&self) -> bool {
|
||||
matches!(self, Self::Win32 | Self::Windows)
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
if existing_json.throws.is_none() {
|
||||
if let Some(throws) = new_json.throws {
|
||||
existing_json.throws = Some(throws);
|
||||
}
|
||||
}
|
||||
existing_json.plugins.extend(new_json.plugins);
|
||||
} else {
|
||||
options_json = Some(new_json);
|
||||
}
|
||||
}
|
||||
options_json.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn is_jsx(&self) -> bool {
|
||||
self.plugins.iter().any(|v| v.as_str().is_some_and(|v| v == "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
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_module(&self) -> bool {
|
||||
self.source_type.as_ref().map_or(false, |s| matches!(s.as_str(), "module" | "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))
|
||||
}
|
||||
|
||||
#[allow(clippy::option_option)]
|
||||
fn get_value(value: &Value, name: &str) -> Option<Option<Value>> {
|
||||
match value {
|
||||
Value::String(s) if s == name => Some(None),
|
||||
Value::Array(a) if a.first().and_then(Value::as_str).is_some_and(|s| s == name) => {
|
||||
Some(a.get(1).cloned())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@ doctest = false
|
|||
console = { workspace = true }
|
||||
|
||||
project-root = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
similar = { workspace = true }
|
||||
|
||||
ureq = { workspace = true, features = ["tls", "json"] }
|
||||
|
|
|
|||
|
|
@ -1,128 +0,0 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
/// Babel options.json for tests
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BabelOptions {
|
||||
pub cwd: Option<PathBuf>,
|
||||
#[serde(rename = "BABEL_8_BREAKING")]
|
||||
pub babel_8_breaking: Option<bool>,
|
||||
pub source_type: Option<String>,
|
||||
pub throws: Option<String>,
|
||||
#[serde(default)]
|
||||
pub plugins: Vec<Value>, // Can be a string or an array
|
||||
#[serde(default)]
|
||||
pub presets: Vec<Value>, // Can be a string or an array
|
||||
#[serde(default)]
|
||||
pub allow_return_outside_function: bool,
|
||||
#[serde(default)]
|
||||
pub allow_await_outside_function: bool,
|
||||
#[serde(default)]
|
||||
pub allow_undeclared_exports: bool,
|
||||
#[serde(default)]
|
||||
pub assumptions: Value,
|
||||
/// Babel test helper for running tests on specific operating systems
|
||||
pub os: Option<Vec<TestOs>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum TestOs {
|
||||
Linux,
|
||||
Win32,
|
||||
Windows,
|
||||
Darwin,
|
||||
}
|
||||
|
||||
impl TestOs {
|
||||
pub fn is_windows(&self) -> bool {
|
||||
matches!(self, Self::Win32 | Self::Windows)
|
||||
}
|
||||
}
|
||||
|
||||
impl BabelOptions {
|
||||
/// Read options.json and merge them with options.json from ancestors directories.
|
||||
/// # Panics
|
||||
pub fn from_path(path: &Path) -> Self {
|
||||
let mut options_json: Option<Self> = 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);
|
||||
}
|
||||
}
|
||||
if existing_json.throws.is_none() {
|
||||
if let Some(throws) = new_json.throws {
|
||||
existing_json.throws = Some(throws);
|
||||
}
|
||||
}
|
||||
existing_json.plugins.extend(new_json.plugins);
|
||||
} else {
|
||||
options_json = Some(new_json);
|
||||
}
|
||||
}
|
||||
options_json.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn is_jsx(&self) -> bool {
|
||||
self.plugins.iter().any(|v| v.as_str().is_some_and(|v| v == "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
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_module(&self) -> bool {
|
||||
self.source_type.as_ref().map_or(false, |s| matches!(s.as_str(), "module" | "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))
|
||||
}
|
||||
|
||||
#[allow(clippy::option_option)]
|
||||
fn get_value(value: &Value, name: &str) -> Option<Option<Value>> {
|
||||
match value {
|
||||
Value::String(s) if s == name => Some(None),
|
||||
Value::Array(a) if a.first().and_then(Value::as_str).is_some_and(|s| s == name) => {
|
||||
Some(a.get(1).cloned())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,11 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
mod babel;
|
||||
mod diff;
|
||||
mod request;
|
||||
mod snapshot;
|
||||
mod test_file;
|
||||
|
||||
pub use crate::{
|
||||
babel::{BabelOptions, TestOs},
|
||||
request::agent,
|
||||
snapshot::Snapshot,
|
||||
test_file::*,
|
||||
};
|
||||
pub use crate::{request::agent, snapshot::Snapshot, test_file::*};
|
||||
pub use diff::print_diff_in_terminal;
|
||||
|
||||
/// # Panics
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use oxc_transformer::BabelOptions;
|
||||
use serde::{de::DeserializeOwned, Deserialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use oxc_span::SourceType;
|
||||
use oxc_tasks_common::BabelOptions;
|
||||
|
||||
use crate::{
|
||||
project_root,
|
||||
|
|
@ -128,7 +128,7 @@ impl Case for BabelCase {
|
|||
/// # Panics
|
||||
fn new(path: PathBuf, code: String) -> Self {
|
||||
let dir = project_root().join(FIXTURES_PATH).join(&path);
|
||||
let options = BabelOptions::from_path(dir.parent().unwrap());
|
||||
let options = BabelOptions::from_test_path(dir.parent().unwrap());
|
||||
let source_type = SourceType::from_path(&path)
|
||||
.unwrap()
|
||||
.with_script(true)
|
||||
|
|
|
|||
|
|
@ -32,5 +32,4 @@ oxc_diagnostics = { workspace = true }
|
|||
walkdir = { workspace = true }
|
||||
pico-args = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -3,18 +3,13 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::Value;
|
||||
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_codegen::{Codegen, CodegenOptions};
|
||||
use oxc_diagnostics::{Error, OxcDiagnostic};
|
||||
use oxc_parser::Parser;
|
||||
use oxc_span::{SourceType, VALID_EXTENSIONS};
|
||||
use oxc_tasks_common::{normalize_path, print_diff_in_terminal, BabelOptions};
|
||||
use oxc_transformer::{
|
||||
ES2015Options, ReactOptions, TransformOptions, Transformer, TypeScriptOptions,
|
||||
};
|
||||
use oxc_tasks_common::{normalize_path, print_diff_in_terminal};
|
||||
use oxc_transformer::{BabelOptions, TransformOptions, Transformer};
|
||||
|
||||
use crate::{
|
||||
constants::{PLUGINS_NOT_SUPPORTED_YET, SKIP_TESTS},
|
||||
|
|
@ -75,53 +70,7 @@ impl TestCaseKind {
|
|||
}
|
||||
|
||||
fn transform_options(options: &BabelOptions) -> serde_json::Result<TransformOptions> {
|
||||
fn get_options<T: Default + DeserializeOwned>(value: Option<Value>) -> serde_json::Result<T> {
|
||||
match value {
|
||||
Some(v) => serde_json::from_value::<T>(v),
|
||||
None => Ok(T::default()),
|
||||
}
|
||||
}
|
||||
|
||||
let react = if let Some(options) = options.get_preset("react") {
|
||||
get_options::<ReactOptions>(options)?
|
||||
} else {
|
||||
let jsx_plugin = options.get_plugin("transform-react-jsx");
|
||||
let jsx_development_plugin = options.get_plugin("transform-react-jsx-development");
|
||||
let has_jsx_plugin =
|
||||
jsx_plugin.as_ref().is_some() || jsx_development_plugin.as_ref().is_some();
|
||||
let mut react_options = jsx_plugin
|
||||
.map(get_options::<ReactOptions>)
|
||||
.or_else(|| jsx_development_plugin.map(get_options::<ReactOptions>))
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
react_options.development = options.get_plugin("transform-react-jsx-development").is_some();
|
||||
react_options.jsx_plugin = has_jsx_plugin;
|
||||
react_options.display_name_plugin =
|
||||
options.get_plugin("transform-react-display-name").is_some();
|
||||
react_options.jsx_self_plugin = options.get_plugin("transform-react-jsx-self").is_some();
|
||||
react_options.jsx_source_plugin =
|
||||
options.get_plugin("transform-react-jsx-source").is_some();
|
||||
react_options
|
||||
};
|
||||
|
||||
let es2015 = ES2015Options {
|
||||
arrow_function: options
|
||||
.get_plugin("transform-arrow-functions")
|
||||
.map(get_options)
|
||||
.transpose()?,
|
||||
};
|
||||
|
||||
Ok(TransformOptions {
|
||||
cwd: options.cwd.clone().unwrap(),
|
||||
assumptions: serde_json::from_value(options.assumptions.clone()).unwrap_or_default(),
|
||||
typescript: options
|
||||
.get_plugin("transform-typescript")
|
||||
.map(get_options::<TypeScriptOptions>)
|
||||
.transpose()?
|
||||
.unwrap_or_default(),
|
||||
react,
|
||||
es2015,
|
||||
})
|
||||
TransformOptions::from_babel_options(options)
|
||||
}
|
||||
|
||||
pub trait TestCase {
|
||||
|
|
@ -242,7 +191,7 @@ pub struct ConformanceTestCase {
|
|||
|
||||
impl TestCase for ConformanceTestCase {
|
||||
fn new(cwd: &Path, path: &Path) -> Self {
|
||||
let mut options = BabelOptions::from_path(path.parent().unwrap());
|
||||
let mut options = BabelOptions::from_test_path(path.parent().unwrap());
|
||||
options.cwd.replace(cwd.to_path_buf());
|
||||
let transform_options = transform_options(&options);
|
||||
Self { path: path.to_path_buf(), options, transform_options }
|
||||
|
|
@ -437,7 +386,7 @@ impl ExecTestCase {
|
|||
|
||||
impl TestCase for ExecTestCase {
|
||||
fn new(cwd: &Path, path: &Path) -> Self {
|
||||
let mut options = BabelOptions::from_path(path.parent().unwrap());
|
||||
let mut options = BabelOptions::from_test_path(path.parent().unwrap());
|
||||
options.cwd.replace(cwd.to_path_buf());
|
||||
let transform_options = transform_options(&options);
|
||||
Self { path: path.to_path_buf(), options, transform_options }
|
||||
|
|
|
|||
Loading…
Reference in a new issue