diff --git a/crates/oxc_transformer/examples/transformer.rs b/crates/oxc_transformer/examples/transformer.rs index fc1185258b2a2..67afa6bce31cd 100644 --- a/crates/oxc_transformer/examples/transformer.rs +++ b/crates/oxc_transformer/examples/transformer.rs @@ -56,7 +56,7 @@ fn main() { let transform_options = if let Some(targets) = &targets { TransformOptions::try_from(&EnvOptions { - targets: Targets::from_query(targets), + targets: Targets::try_from_query(targets).unwrap(), ..EnvOptions::default() }) .unwrap() diff --git a/crates/oxc_transformer/src/env/data/babel.rs b/crates/oxc_transformer/src/env/data/babel.rs index 56362bb253915..ce1719352b38c 100644 --- a/crates/oxc_transformer/src/env/data/babel.rs +++ b/crates/oxc_transformer/src/env/data/babel.rs @@ -2,21 +2,20 @@ use std::sync::OnceLock; use rustc_hash::FxHashMap; -use crate::env::{targets::version::Version, Versions}; +use crate::env::targets::{version::Version, Targets}; /// Reference: -fn features() -> &'static FxHashMap { - static FEATURES: OnceLock> = OnceLock::new(); +fn features() -> &'static FxHashMap { + static FEATURES: OnceLock> = OnceLock::new(); FEATURES.get_or_init(|| { let mut map: FxHashMap> = - serde_json::from_str(include_str!("./@babel/compat_data/data/plugins.json")) - .expect("failed to parse json"); + serde_json::from_str(include_str!("./@babel/compat_data/data/plugins.json")).unwrap(); map.extend( serde_json::from_str::>>(include_str!( "./esbuild/features.json" )) - .expect("failed to parse json"), + .unwrap(), ); map.into_iter() @@ -27,7 +26,7 @@ fn features() -> &'static FxHashMap { versions.remove("safari"); } - Versions( + Targets::new( versions .into_iter() .map(|(k, v)| (k, v.parse::().unwrap())) @@ -40,17 +39,18 @@ fn features() -> &'static FxHashMap { } /// Reference: -fn bugfix_features() -> &'static FxHashMap { - static BUGFIX_FEATURES: OnceLock> = OnceLock::new(); +fn bugfix_features() -> &'static FxHashMap { + static BUGFIX_FEATURES: OnceLock> = OnceLock::new(); BUGFIX_FEATURES.get_or_init(|| { - let map: FxHashMap = - serde_json::from_str(include_str!("./@babel/compat_data/data/plugin_bugfixes.json")) - .expect("failed to parse json"); + let map = serde_json::from_str::>(include_str!( + "./@babel/compat_data/data/plugin_bugfixes.json" + )) + .unwrap(); features().clone().into_iter().chain(map).collect() }) } -pub fn can_enable_plugin(name: &str, targets: Option<&Versions>, bugfixes: bool) -> bool { +pub fn can_enable_plugin(name: &str, targets: Option<&Targets>, bugfixes: bool) -> bool { let versions = if bugfixes { bugfix_features().get(name).unwrap_or_else(|| &features()[name]) } else { diff --git a/crates/oxc_transformer/src/env/mod.rs b/crates/oxc_transformer/src/env/mod.rs index 0567dfccd9e9a..70808ff1e1944 100644 --- a/crates/oxc_transformer/src/env/mod.rs +++ b/crates/oxc_transformer/src/env/mod.rs @@ -4,4 +4,4 @@ mod targets; pub use data::can_enable_plugin; pub use options::EnvOptions; -pub use targets::{Targets, Versions}; +pub use targets::Targets; diff --git a/crates/oxc_transformer/src/env/options.rs b/crates/oxc_transformer/src/env/options.rs index 5908c533cd509..1a0f43c87db28 100644 --- a/crates/oxc_transformer/src/env/options.rs +++ b/crates/oxc_transformer/src/env/options.rs @@ -1,7 +1,7 @@ use serde::Deserialize; use serde_json::Value; -use super::targets::query::Targets; +use crate::env::Targets; fn default_as_true() -> bool { true diff --git a/crates/oxc_transformer/src/env/targets/mod.rs b/crates/oxc_transformer/src/env/targets/mod.rs index 38039c1d4c262..a7ba28cb121dc 100644 --- a/crates/oxc_transformer/src/env/targets/mod.rs +++ b/crates/oxc_transformer/src/env/targets/mod.rs @@ -5,24 +5,27 @@ //! //! This file is copied from -use std::ops::{Deref, DerefMut}; +use std::{ops::Deref, str::FromStr}; +use oxc_diagnostics::Error; use rustc_hash::FxHashMap; use serde::Deserialize; pub mod query; pub mod version; -pub use query::Targets; -use version::Version; + +pub use query::Query; +pub use version::Version; /// A map of browser names to data for feature support in browser. /// /// This type mainly stores `minimum version for each browsers with support for /// a feature`. -#[derive(Debug, Clone, Default, Deserialize)] -pub struct Versions(pub FxHashMap); +#[derive(Debug, Default, Clone, Deserialize)] +#[serde(try_from = "BabelTargets")] // https://github.com/serde-rs/serde/issues/642#issuecomment-683276351 +pub struct Targets(FxHashMap); -impl Deref for Versions { +impl Deref for Targets { type Target = FxHashMap; fn deref(&self) -> &Self::Target { @@ -30,19 +33,24 @@ impl Deref for Versions { } } -impl DerefMut for Versions { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 +impl Targets { + pub fn new(map: FxHashMap) -> Self { + Self(map) + } + + /// # Errors + /// + /// * Query is invalid. + pub fn try_from_query(query: &str) -> Result { + Query::Single(query.to_string()).exec().map(|v| v.0).map(Self) } -} -impl Versions { /// Returns true if all fields are [None]. pub fn is_any_target(&self) -> bool { self.0.is_empty() } - /// Parses the value returned from `browserslist` as [Versions]. + /// Parses the value returned from `browserslist`. pub fn parse_versions(distribs: Vec) -> Self { fn remap(key: &str) -> &str { match key { @@ -55,7 +63,7 @@ impl Versions { } } - let mut data: Versions = Versions::default(); + let mut data = FxHashMap::default(); for dist in distribs { let browser = dist.name(); let browser = remap(browser); @@ -78,11 +86,11 @@ impl Versions { } } - data + Self(data) } - pub fn should_enable(&self, feature: &Versions) -> bool { - self.iter().any(|(target_name, target_version)| { + pub fn should_enable(&self, feature: &Targets) -> bool { + self.0.iter().any(|(target_name, target_version)| { feature .get(target_name) .or_else(|| match target_name.as_str() { @@ -97,16 +105,78 @@ impl Versions { } } +/// +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum BabelTargets { + String(String), + Array(Vec), + /// For Deserializing + /// * `esmodules`: `boolean` + /// * `node`: `string | "current" | true` + /// * `safari`: `string | "tp"` + /// * `browsers`: `string | Array.` + /// * `deno`: `string` + Map(FxHashMap), +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum BabelTargetsValue { + String(String), + Array(Vec), + Bool(bool), + Int(u32), + Float(f64), +} + +impl TryFrom for Targets { + type Error = Error; + fn try_from(value: BabelTargets) -> Result { + match value { + BabelTargets::String(s) => Query::Single(s).exec().map(|v| v.0).map(Self), + BabelTargets::Array(v) => Query::Multiple(v).exec().map(|v| v.0).map(Self), + BabelTargets::Map(map) => { + let mut new_map = FxHashMap::default(); + for (k, v) in map { + // TODO: Implement these targets. + if matches!(k.as_str(), "esmodules" | "node" | "safari" | "browsers" | "deno") { + continue; + } + // TODO: Implement `Version::from_number` + if matches!(v, BabelTargetsValue::Int(_) | BabelTargetsValue::Float(_)) { + continue; + }; + let BabelTargetsValue::String(v) = v else { + return Err(Error::msg(format!("{v:?} is not a string for {k}."))); + }; + match Version::from_str(&v) { + Ok(v) => { + new_map.insert(k, v); + } + Err(()) => { + return Err(oxc_diagnostics::Error::msg(format!( + "Failed to parse `{v}` for `{k}`" + ))) + } + } + } + Ok(Self(new_map)) + } + } + } +} + #[cfg(test)] mod tests { - use crate::env::{targets::version::Version, Versions}; + use crate::env::{targets::version::Version, Targets}; #[test] fn should_enable_android_falls_back_to_chrome() { - let mut targets = Versions::default(); - targets.insert("android".to_string(), "51.0.0".parse::().unwrap()); - let mut feature = Versions::default(); - feature.insert("chrome".to_string(), "51.0.0".parse::().unwrap()); + let mut targets = Targets::default(); + targets.0.insert("android".to_string(), "51.0.0".parse::().unwrap()); + let mut feature = Targets::default(); + feature.0.insert("chrome".to_string(), "51.0.0".parse::().unwrap()); assert!(!targets.should_enable(&feature)); } } diff --git a/crates/oxc_transformer/src/env/targets/query.rs b/crates/oxc_transformer/src/env/targets/query.rs index 1f46a89ccaa35..f7fbec77bd0c5 100644 --- a/crates/oxc_transformer/src/env/targets/query.rs +++ b/crates/oxc_transformer/src/env/targets/query.rs @@ -5,114 +5,11 @@ use std::sync::OnceLock; use dashmap::DashMap; -use rustc_hash::FxHashMap; use serde::Deserialize; use oxc_diagnostics::{Error, OxcDiagnostic}; -use super::{version::Version, Versions}; - -#[derive(Debug, Clone, Deserialize)] -#[serde(untagged)] -#[allow(clippy::large_enum_variant)] -pub enum Targets { - Query(Query), - EsModules(EsModules), - Versions(Versions), - HashMap(FxHashMap), -} - -impl Default for Targets { - fn default() -> Self { - Targets::Query(Query::Single("defaults".into())) - } -} - -impl Targets { - /// Create a `Targets` from a browserslist query. - /// - /// The usage refer to the [browserslist](https://github.com/browserslist/browserslist?tab=readme-ov-file#queries) documentation. - pub fn from_query(query: &str) -> Self { - Targets::Query(Query::Single(query.into())) - } - - /// Parse the query and return the parsed Versions. - /// - /// # Errors - /// - /// This function returns an error if: - /// * The query is not supported. - /// * The query is invalid. - pub fn get_targets(self) -> Result { - match self { - Targets::Versions(v) => Ok(v), - Targets::Query(q) => q.exec(), - Targets::HashMap(mut map) => { - let q = map.remove("browsers").map(|q| match q { - QueryOrVersion::Query(q) => q.exec(), - QueryOrVersion::Version(_) => unreachable!(), - }); - - let node = match map.remove("node") { - Some(QueryOrVersion::Version(v)) => Some(v), - Some(QueryOrVersion::Query(v)) => { - // We cannot get `current` node version - return Err(OxcDiagnostic::error(format!( - "Targets: node `{}` is not supported", - v.get_value() - )) - .into()); - } - None => None, - }; - - if map.is_empty() { - if let Some(q) = q { - let mut q = q?; - if let Some(node) = node { - q.insert("node".to_string(), node); - } - return Ok(q); - } - } - - let mut result = Versions::default(); - for (k, v) in &map { - match v { - QueryOrVersion::Query(q) => { - let v = q.exec()?; - - for (k, v) in v.iter() { - result.insert(k.to_string(), *v); - } - } - QueryOrVersion::Version(v) => { - result.insert(k.to_string(), *v); - } - } - } - - Err(OxcDiagnostic::error(format!("Targets: {result:?}")).into()) - } - Targets::EsModules(_) => { - Err(OxcDiagnostic::error("Targets: The `esmodules` is not supported").into()) - } - } - } -} - -#[derive(Debug, Clone, Copy, Deserialize)] -pub struct EsModules { - #[allow(dead_code)] - esmodules: bool, -} - -#[derive(Debug, Clone, Deserialize)] -#[serde(untagged)] -pub enum QueryOrVersion { - Query(Query), - Version(Version), -} +use super::Targets; #[derive(Debug, Clone, Deserialize, Eq, PartialEq, PartialOrd, Ord, Hash)] #[serde(untagged)] @@ -121,22 +18,15 @@ pub enum Query { Multiple(Vec), } -type QueryResult = Result; +type QueryResult = Result; -fn cache() -> &'static DashMap { - static CACHE: OnceLock> = OnceLock::new(); +fn cache() -> &'static DashMap { + static CACHE: OnceLock> = OnceLock::new(); CACHE.get_or_init(DashMap::new) } impl Query { - fn get_value(&self) -> String { - match self { - Query::Single(s) => s.clone(), - Query::Multiple(s) => s.join(","), - } - } - - fn exec(&self) -> QueryResult { + pub fn exec(&self) -> QueryResult { fn query(s: &[T]) -> QueryResult where T: AsRef, @@ -150,7 +40,7 @@ impl Query { }, ) { Ok(distribs) => { - let versions = Versions::parse_versions(distribs); + let versions = Targets::parse_versions(distribs); Ok(versions) } diff --git a/crates/oxc_transformer/src/options/transformer.rs b/crates/oxc_transformer/src/options/transformer.rs index 28b8d78e83e1c..70f614c49f106 100644 --- a/crates/oxc_transformer/src/options/transformer.rs +++ b/crates/oxc_transformer/src/options/transformer.rs @@ -7,7 +7,7 @@ use oxc_diagnostics::{Error, OxcDiagnostic}; use crate::{ common::helper_loader::{HelperLoaderMode, HelperLoaderOptions}, compiler_assumptions::CompilerAssumptions, - env::{can_enable_plugin, EnvOptions, Versions}, + env::{can_enable_plugin, EnvOptions, Targets}, es2015::{ArrowFunctionsOptions, ES2015Options}, es2016::ES2016Options, es2017::options::ES2017Options, @@ -116,12 +116,8 @@ impl TryFrom<&EnvOptions> for TransformOptions { /// If there are any errors in the `options.targets``, they will be returned as a list of errors. fn try_from(options: &EnvOptions) -> Result { - let targets = match options.targets.clone().get_targets() { - Ok(targets) => Some(targets), - Err(err) => return Err(vec![err]), - }; + let targets = Some(&options.targets); let bugfixes = options.bugfixes; - let targets = targets.as_ref(); Ok(Self { regexp: RegExpOptions { sticky_flag: can_enable_plugin("transform-sticky-regex", targets, bugfixes), @@ -263,18 +259,10 @@ impl TryFrom<&BabelOptions> for TransformOptions { .ok() }); - let targets = env.as_ref().and_then(|env| { - env.targets - .clone() - .get_targets() - .inspect_err(|err| errors.push(OxcDiagnostic::error(err.to_string()).into())) - .ok() - }); + let targets = env.as_ref().map(|env| &env.targets); let bugfixes = env.as_ref().is_some_and(|o| o.bugfixes); - let targets = targets.as_ref(); - let regexp = RegExpOptions { sticky_flag: can_enable_plugin("transform-sticky-regex", targets, bugfixes) || options.has_plugin("transform-sticky-regex"), @@ -425,7 +413,7 @@ fn get_plugin_options(name: &str, babel_options: &BabelOptions) -> Value { fn get_enabled_plugin_options( plugin_name: &str, babel_options: &BabelOptions, - targets: Option<&Versions>, + targets: Option<&Targets>, bugfixes: bool, ) -> Option { let can_enable = diff --git a/crates/oxc_wasm/src/lib.rs b/crates/oxc_wasm/src/lib.rs index 53d95be8275b0..d0dd9e70f17a8 100644 --- a/crates/oxc_wasm/src/lib.rs +++ b/crates/oxc_wasm/src/lib.rs @@ -243,7 +243,7 @@ impl Oxc { if run_options.transform.unwrap_or_default() { if let Ok(options) = TransformOptions::try_from(&EnvOptions { - targets: Targets::from_query("chrome 51"), + targets: Targets::try_from_query("chrome 51").unwrap_or_default(), ..EnvOptions::default() }) { let result = Transformer::new(&allocator, &path, options) diff --git a/tasks/transform_conformance/snapshots/babel.snap.md b/tasks/transform_conformance/snapshots/babel.snap.md index c1c834685e373..c0e172b9e43ce 100644 --- a/tasks/transform_conformance/snapshots/babel.snap.md +++ b/tasks/transform_conformance/snapshots/babel.snap.md @@ -1,6 +1,6 @@ commit: d20b314c -Passed: 357/1058 +Passed: 361/1058 # All Passed: * babel-plugin-transform-class-static-block @@ -12,7 +12,7 @@ Passed: 357/1058 * babel-plugin-transform-react-jsx-source -# babel-preset-env (110/585) +# babel-preset-env (114/585) * .plugins-overlapping/chrome-49/input.js x Output mismatch @@ -23,10 +23,10 @@ x Output mismatch x Output mismatch * bugfixes/_esmodules/input.js -Targets: The `esmodules` is not supported +x Output mismatch * bugfixes/_esmodules-no-bugfixes/input.js -Targets: The `esmodules` is not supported +x Output mismatch * bugfixes/edge-default-params-chrome-40/input.js x Output mismatch @@ -170,7 +170,7 @@ x Output mismatch x Output mismatch * corejs2-babel-7/usage-browserslist-config-ignore/input.mjs -Targets: The `esmodules` is not supported +x Output mismatch * corejs2-babel-7/usage-destructuring-assignment/input.mjs x Output mismatch @@ -401,7 +401,7 @@ x Output mismatch x Output mismatch * corejs3/usage-browserslist-config-ignore/input.mjs -Targets: The `esmodules` is not supported +x Output mismatch * corejs3/usage-determanated-instance-methods/input.mjs x Output mismatch @@ -614,7 +614,7 @@ x Output mismatch x Output mismatch * corejs3-babel-7/usage-browserslist-config-ignore/input.mjs -Targets: The `esmodules` is not supported +x Output mismatch * corejs3-babel-7/usage-built-in-from-global-object/input.mjs x Output mismatch @@ -1280,7 +1280,7 @@ x Output mismatch x Output mismatch * preset-options/browserslist-defaults-not-ie/input.mjs -Targets: node `current` is not supported +x Output mismatch * preset-options/deno-1_0/input.mjs x Output mismatch @@ -1294,9 +1294,6 @@ x Output mismatch * preset-options/empty-options/input.mjs x Output mismatch -* preset-options/esmodules-async-functions/input.mjs -Targets: The `esmodules` is not supported - * preset-options/include/input.mjs x Output mismatch @@ -1324,9 +1321,6 @@ x Output mismatch * preset-options/safari-tagged-template-literals/input.js x Output mismatch -* preset-options/safari-tp/input.js -failed to resolve query: failed to parse the rest of input: ...'' - * preset-options/unicode-property-regex-chrome-49/input.js x Output mismatch @@ -1343,7 +1337,7 @@ x Output mismatch x Output mismatch * preset-options-babel-7/browserslist-defaults-not-ie/input.mjs -Targets: node `current` is not supported +x Output mismatch * preset-options-babel-7/deno-1_0/input.mjs x Output mismatch @@ -1358,7 +1352,7 @@ x Output mismatch x Output mismatch * preset-options-babel-7/esmodules-async-functions/input.mjs -Targets: The `esmodules` is not supported +x Output mismatch * preset-options-babel-7/include/input.mjs x Output mismatch @@ -1399,9 +1393,6 @@ x Output mismatch * preset-options-babel-7/safari-tagged-template-literals/input.js x Output mismatch -* preset-options-babel-7/safari-tp/input.js -failed to resolve query: failed to parse the rest of input: ...'' - * preset-options-babel-7/shippedProposals/input.js x Output mismatch @@ -1432,9 +1423,6 @@ x Output mismatch * shipped-proposals/new-class-features-chrome-90/input.js x Output mismatch -* shipped-proposals/new-class-features-chrome-94/input.js -x Output mismatch - * shipped-proposals/new-class-features-firefox-70/input.js x Output mismatch