diff --git a/crates/oxc_transformer/src/options/babel/env/data/mod.rs b/crates/oxc_transformer/src/options/babel/env/data/mod.rs index 43d0e1339..81f3bb19f 100644 --- a/crates/oxc_transformer/src/options/babel/env/data/mod.rs +++ b/crates/oxc_transformer/src/options/babel/env/data/mod.rs @@ -2,7 +2,7 @@ use std::sync::OnceLock; use rustc_hash::FxHashMap; -use super::{Targets, Version}; +use super::Targets; /// Reference: pub fn features() -> &'static FxHashMap { @@ -20,19 +20,12 @@ pub fn features() -> &'static FxHashMap { map.into_iter() .map(|(feature, mut versions)| { - (feature, { - let version = versions.get("safari"); - if version.is_some_and(|v| v == "tp") { - versions.remove("safari"); - } - - Targets::new( - versions - .into_iter() - .map(|(k, v)| (k, v.parse::().unwrap())) - .collect::>(), - ) - }) + let version = versions.get("safari"); + if version.is_some_and(|v| v == "tp") { + versions.remove("safari"); + } + let versions = versions.into_iter().collect::>(); + (feature, Targets::parse_versions(versions)) }) .collect() }) diff --git a/crates/oxc_transformer/src/options/babel/env/mod.rs b/crates/oxc_transformer/src/options/babel/env/mod.rs index 5cead9a02..3d9132f33 100644 --- a/crates/oxc_transformer/src/options/babel/env/mod.rs +++ b/crates/oxc_transformer/src/options/babel/env/mod.rs @@ -3,7 +3,7 @@ mod query; mod targets; pub use data::{bugfix_features, features}; -pub use targets::{Targets, Version}; +pub use targets::Targets; use serde::Deserialize; diff --git a/crates/oxc_transformer/src/options/babel/env/query.rs b/crates/oxc_transformer/src/options/babel/env/query.rs index b8a98cdb6..c02c1721f 100644 --- a/crates/oxc_transformer/src/options/babel/env/query.rs +++ b/crates/oxc_transformer/src/options/babel/env/query.rs @@ -1,5 +1,3 @@ -//! Module for `browserslist` queries. - use std::sync::OnceLock; use dashmap::DashMap; @@ -16,15 +14,13 @@ pub enum Query { Multiple(Vec), } -type QueryResult = Result; - fn cache() -> &'static DashMap { static CACHE: OnceLock> = OnceLock::new(); CACHE.get_or_init(DashMap::new) } impl Query { - pub fn exec(&self) -> QueryResult { + pub fn exec(&self) -> Result { if let Some(v) = cache().get(self) { return Ok(v.clone()); } @@ -47,7 +43,13 @@ impl Query { }; let result = match result { - Ok(distribs) => Targets::parse_versions(distribs), + Ok(distribs) => { + let versions = distribs + .into_iter() + .map(|d| (d.name().to_string(), d.version().to_string())) + .collect::>(); + Targets::parse_versions(versions) + } Err(err) => { return Err(OxcDiagnostic::error(format!("failed to resolve query: {err}")).into()) } diff --git a/crates/oxc_transformer/src/options/babel/env/targets.rs b/crates/oxc_transformer/src/options/babel/env/targets.rs index acff86323..b7cc0a51d 100644 --- a/crates/oxc_transformer/src/options/babel/env/targets.rs +++ b/crates/oxc_transformer/src/options/babel/env/targets.rs @@ -1,12 +1,3 @@ -//! Base crate for `preset-env`-like crates. -//! -//! This crate provides an interface to convert `browserslist` query to -//! something usable. -//! -//! This file is copied from - -use std::ops::Deref; - use oxc_diagnostics::Error; use rustc_hash::FxHashMap; use serde::Deserialize; @@ -19,87 +10,104 @@ use super::query::Query; /// /// This type mainly stores `minimum version for each browsers with support for /// a feature`. -#[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 Targets { - type Target = FxHashMap; - - fn deref(&self) -> &Self::Target { - &self.0 - } +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize)] +#[serde(try_from = "BabelTargets")] +pub struct Targets { + chrome: Option, + deno: Option, + edge: Option, + firefox: Option, + hermes: Option, + ie: Option, + ios: Option, + node: Option, + opera: Option, + rhino: Option, + safari: Option, } 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) + Query::Single(query.to_string()).exec() } /// Returns true if all fields are [None]. pub fn is_any_target(&self) -> bool { - self.0.is_empty() + *self == Self::default() + } + + pub fn should_enable(&self, targets: &Targets) -> bool { + if let (Some(v1), Some(v2)) = (&self.chrome, &targets.chrome) { + return v1 < v2; + } + if let (Some(v1), Some(v2)) = (&self.deno, &targets.deno) { + return v1 < v2; + } + if let (Some(v1), Some(v2)) = (&self.edge, &targets.edge) { + return v1 < v2; + } + if let (Some(v1), Some(v2)) = (&self.firefox, &targets.firefox) { + return v1 < v2; + } + if let (Some(v1), Some(v2)) = (&self.hermes, &targets.hermes) { + return v1 < v2; + } + if let (Some(v1), Some(v2)) = (&self.ie, &targets.ie) { + return v1 < v2; + } + if let (Some(v1), Some(v2)) = (&self.ios, &targets.ios) { + return v1 < v2; + } + if let (Some(v1), Some(v2)) = (&self.node, &targets.node) { + return v1 < v2; + } + if let (Some(v1), Some(v2)) = (&self.opera, &targets.opera) { + return v1 < v2; + } + if let (Some(v1), Some(v2)) = (&self.rhino, &targets.rhino) { + return v1 < v2; + } + if let (Some(v1), Some(v2)) = (&self.safari, &targets.safari) { + return v1 < v2; + } + false } /// Parses the value returned from `browserslist`. - pub fn parse_versions(distribs: Vec) -> Self { - fn remap(key: &str) -> &str { - match key { - "and_chr" => "chrome", - "and_ff" => "firefox", - "ie_mob" => "ie", - "ios_saf" => "ios", - "op_mob" => "opera", - _ => key, + pub fn parse_versions(versions: Vec<(String, String)>) -> Self { + let mut targets = Self::default(); + for (name, version) in versions { + let Ok(browser) = targets.get_version_mut(&name) else { + continue; + }; + let Ok(version) = version.parse::() else { + continue; + }; + if browser.is_none() || browser.is_some_and(|v| version < v) { + browser.replace(version); } } - - let mut data = FxHashMap::default(); - for dist in distribs { - let browser = dist.name(); - let browser = remap(browser); - let version = dist.version(); - match browser { - "and_qq" | "and_uc" | "baidu" | "bb" | "kaios" | "op_mini" => continue, - - _ => {} - } - - let version = - version.split_once('-').map_or(version, |(version, _)| version).parse::(); - - let Ok(version) = version else { continue }; - - // lowest version - let is_lowest = data.get(browser).map_or(true, |v| v > &version); - if is_lowest { - data.insert(browser.to_string(), version); - } - } - - Self(data) + targets } - 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() { - // Fall back to Chrome versions if Android browser data - // is missing from the feature data. It appears the - // Android browser has aligned its versioning with Chrome. - "android" => feature.get("chrome"), - _ => None, - }) - .map_or(false, |feature_version| feature_version > target_version) - }) + fn get_version_mut(&mut self, key: &str) -> Result<&mut Option, ()> { + match key { + "chrome" | "and_chr" => Ok(&mut self.chrome), + "deno" => Ok(&mut self.deno), + "edge" => Ok(&mut self.edge), + "firefox" | "and_ff" => Ok(&mut self.firefox), + "hermes" => Ok(&mut self.hermes), + "ie" | "ie_mob" => Ok(&mut self.ie), + "ios" | "ios_saf" => Ok(&mut self.ios), + "node" => Ok(&mut self.node), + "opera" | "op_mob" => Ok(&mut self.opera), + "rhino" => Ok(&mut self.rhino), + "safari" => Ok(&mut self.safari), + _ => Err(()), + } } } @@ -132,49 +140,49 @@ 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::String(s) => Query::Single(s).exec(), + BabelTargets::Array(v) => Query::Multiple(v).exec(), BabelTargets::Map(map) => { - let mut new_map = FxHashMap::default(); - for (k, v) in map { + let mut targets = Self::default(); + for (key, value) in map { // TODO: Implement these targets. - if matches!(k.as_str(), "esmodules" | "node" | "safari" | "browsers" | "deno") { + if matches!(key.as_str(), "esmodules" | "browsers") { continue; } // TODO: Implement `Version::from_number` - if matches!(v, BabelTargetsValue::Int(_) | BabelTargetsValue::Float(_)) { + if matches!(value, BabelTargetsValue::Int(_) | BabelTargetsValue::Float(_)) { continue; }; - let BabelTargetsValue::String(v) = v else { - return Err(Error::msg(format!("{v:?} is not a string for {k}."))); + let BabelTargetsValue::String(v) = value else { + return Err(Error::msg(format!("{value:?} is not a string for {key}."))); + }; + // TODO: Implement this target. + if key == "node" && v == "current" { + continue; + } + // TODO: Implement this target. + if key == "safari" && v == "tp" { + continue; + } + // TODO: Some keys are not implemented yet. + // : + // Supported environments: android, chrome, deno, edge, electron, firefox, ie, ios, node, opera, rhino, safari, samsung. + let Ok(target) = targets.get_version_mut(&key) else { + continue; }; match Version::parse(&v) { - Ok(v) => { - new_map.insert(k, v); + Ok(version) => { + target.replace(version); } Err(err) => { return Err(oxc_diagnostics::Error::msg(format!( - "Failed to parse `{v}` for `{k}`\n{err:?}" + "Failed to parse `{v}` for `{key}`\n{err:?}" ))) } } } - Ok(Self(new_map)) + Ok(targets) } } } } - -#[cfg(test)] -mod tests { - use super::{Targets, Version}; - - #[test] - fn should_enable_android_falls_back_to_chrome() { - 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)); - } -}