Skip to content

Commit

Permalink
refactor(transform): refactor Babel Targets (#7026)
Browse files Browse the repository at this point in the history
Found a trick from serde to get us from `BabelTargets` to `Targets`.
  • Loading branch information
Boshen committed Oct 30, 2024
1 parent b021147 commit 76947e2
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 192 deletions.
2 changes: 1 addition & 1 deletion crates/oxc_transformer/examples/transformer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
26 changes: 13 additions & 13 deletions crates/oxc_transformer/src/env/data/babel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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: <https://github.com/swc-project/swc/blob/ea14fc8e5996dcd736b8deb4cc99262d07dfff44/crates/swc_ecma_preset_env/src/transform_data.rs#L194-L218>
fn features() -> &'static FxHashMap<String, Versions> {
static FEATURES: OnceLock<FxHashMap<String, Versions>> = OnceLock::new();
fn features() -> &'static FxHashMap<String, Targets> {
static FEATURES: OnceLock<FxHashMap<String, Targets>> = OnceLock::new();
FEATURES.get_or_init(|| {
let mut map: FxHashMap<String, FxHashMap<String, String>> =
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::<FxHashMap<String, FxHashMap<String, String>>>(include_str!(
"./esbuild/features.json"
))
.expect("failed to parse json"),
.unwrap(),
);

map.into_iter()
Expand All @@ -27,7 +26,7 @@ fn features() -> &'static FxHashMap<String, Versions> {
versions.remove("safari");
}

Versions(
Targets::new(
versions
.into_iter()
.map(|(k, v)| (k, v.parse::<Version>().unwrap()))
Expand All @@ -40,17 +39,18 @@ fn features() -> &'static FxHashMap<String, Versions> {
}

/// Reference: <https://github.com/swc-project/swc/blob/ea14fc8e5996dcd736b8deb4cc99262d07dfff44/crates/swc_ecma_preset_env/src/transform_data.rs#L220-L237>
fn bugfix_features() -> &'static FxHashMap<String, Versions> {
static BUGFIX_FEATURES: OnceLock<FxHashMap<String, Versions>> = OnceLock::new();
fn bugfix_features() -> &'static FxHashMap<String, Targets> {
static BUGFIX_FEATURES: OnceLock<FxHashMap<String, Targets>> = OnceLock::new();
BUGFIX_FEATURES.get_or_init(|| {
let map: FxHashMap<String, Versions> =
serde_json::from_str(include_str!("./@babel/compat_data/data/plugin_bugfixes.json"))
.expect("failed to parse json");
let map = serde_json::from_str::<FxHashMap<String, Targets>>(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 {
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_transformer/src/env/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
2 changes: 1 addition & 1 deletion crates/oxc_transformer/src/env/options.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down
112 changes: 91 additions & 21 deletions crates/oxc_transformer/src/env/targets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,52 @@
//!
//! This file is copied from <https://github.com/swc-project/swc/blob/ea14fc8e5996dcd736b8deb4cc99262d07dfff44/crates/preset_env_base/src/lib.rs>
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<String, Version>);
#[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 Versions {
impl Deref for Targets {
type Target = FxHashMap<String, Version>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl DerefMut for Versions {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
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, oxc_diagnostics::Error> {
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<browserslist::Distrib>) -> Self {
fn remap(key: &str) -> &str {
match key {
Expand All @@ -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);
Expand All @@ -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() {
Expand All @@ -97,16 +105,78 @@ impl Versions {
}
}

/// <https://babel.dev/docs/babel-preset-env#targets>
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum BabelTargets {
String(String),
Array(Vec<String>),
/// For Deserializing
/// * `esmodules`: `boolean`
/// * `node`: `string | "current" | true`
/// * `safari`: `string | "tp"`
/// * `browsers`: `string | Array<string>.`
/// * `deno`: `string`
Map(FxHashMap<String, BabelTargetsValue>),
}

#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum BabelTargetsValue {
String(String),
Array(Vec<String>),
Bool(bool),
Int(u32),
Float(f64),
}

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::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::<Version>().unwrap());
let mut feature = Versions::default();
feature.insert("chrome".to_string(), "51.0.0".parse::<Version>().unwrap());
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));
}
}
Loading

0 comments on commit 76947e2

Please sign in to comment.