Skip to content

Commit

Permalink
refactor(transformer): deserialize engine target strings to specific …
Browse files Browse the repository at this point in the history
…keys (#7139)
  • Loading branch information
Boshen committed Nov 5, 2024
1 parent 9aa34b4 commit 55e6989
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 118 deletions.
21 changes: 7 additions & 14 deletions crates/oxc_transformer/src/options/babel/env/data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::sync::OnceLock;

use rustc_hash::FxHashMap;

use super::{Targets, Version};
use super::Targets;

/// Reference: <https://github.com/swc-project/swc/blob/ea14fc8e5996dcd736b8deb4cc99262d07dfff44/crates/swc_ecma_preset_env/src/transform_data.rs#L194-L218>
pub fn features() -> &'static FxHashMap<String, Targets> {
Expand All @@ -20,19 +20,12 @@ pub fn features() -> &'static FxHashMap<String, Targets> {

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::<Version>().unwrap()))
.collect::<FxHashMap<String, Version>>(),
)
})
let version = versions.get("safari");
if version.is_some_and(|v| v == "tp") {
versions.remove("safari");
}
let versions = versions.into_iter().collect::<Vec<_>>();
(feature, Targets::parse_versions(versions))
})
.collect()
})
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_transformer/src/options/babel/env/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
14 changes: 8 additions & 6 deletions crates/oxc_transformer/src/options/babel/env/query.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//! Module for `browserslist` queries.
use std::sync::OnceLock;

use dashmap::DashMap;
Expand All @@ -16,15 +14,13 @@ pub enum Query {
Multiple(Vec<String>),
}

type QueryResult = Result<Targets, Error>;

fn cache() -> &'static DashMap<Query, Targets> {
static CACHE: OnceLock<DashMap<Query, Targets>> = OnceLock::new();
CACHE.get_or_init(DashMap::new)
}

impl Query {
pub fn exec(&self) -> QueryResult {
pub fn exec(&self) -> Result<Targets, Error> {
if let Some(v) = cache().get(self) {
return Ok(v.clone());
}
Expand All @@ -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::<Vec<_>>();
Targets::parse_versions(versions)
}
Err(err) => {
return Err(OxcDiagnostic::error(format!("failed to resolve query: {err}")).into())
}
Expand Down
202 changes: 105 additions & 97 deletions crates/oxc_transformer/src/options/babel/env/targets.rs
Original file line number Diff line number Diff line change
@@ -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 <https://github.com/swc-project/swc/blob/ea14fc8e5996dcd736b8deb4cc99262d07dfff44/crates/preset_env_base/src/lib.rs>
use std::ops::Deref;

use oxc_diagnostics::Error;
use rustc_hash::FxHashMap;
use serde::Deserialize;
Expand All @@ -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<String, Version>);

impl Deref for Targets {
type Target = FxHashMap<String, Version>;

fn deref(&self) -> &Self::Target {
&self.0
}
#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize)]
#[serde(try_from = "BabelTargets")]
pub struct Targets {
chrome: Option<Version>,
deno: Option<Version>,
edge: Option<Version>,
firefox: Option<Version>,
hermes: Option<Version>,
ie: Option<Version>,
ios: Option<Version>,
node: Option<Version>,
opera: Option<Version>,
rhino: Option<Version>,
safari: Option<Version>,
}

impl Targets {
pub fn new(map: FxHashMap<String, Version>) -> Self {
Self(map)
}

/// # Errors
///
/// * Query is invalid.
pub fn try_from_query(query: &str) -> Result<Self, Error> {
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()
}

/// Parses the value returned from `browserslist`.
pub fn parse_versions(distribs: Vec<browserslist::Distrib>) -> 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 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
}

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::<Version>();

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);
/// Parses the value returned from `browserslist`.
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::<Version>() else {
continue;
};
if browser.is_none() || browser.is_some_and(|v| version < v) {
browser.replace(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<Version>, ()> {
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(()),
}
}
}

Expand Down Expand Up @@ -132,49 +140,49 @@ impl TryFrom<BabelTargets> for Targets {
type Error = Error;
fn try_from(value: BabelTargets) -> Result<Self, Self::Error> {
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.
// <https://babel.dev/docs/options#targets>:
// 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::<Version>().unwrap());
let mut feature = Targets::default();
feature.0.insert("chrome".to_string(), "51.0.0".parse::<Version>().unwrap());
assert!(!targets.should_enable(&feature));
}
}

0 comments on commit 55e6989

Please sign in to comment.