refactor(transform): refactor Babel Targets (#7026)

Found a trick from serde to get us from `BabelTargets` to `Targets`.
This commit is contained in:
Boshen 2024-10-30 14:08:19 +00:00
parent b02114783a
commit 76947e2aec
9 changed files with 128 additions and 192 deletions

View file

@ -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()

View file

@ -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()
@ -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()))
@ -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 {

View file

@ -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;

View file

@ -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

View file

@ -5,24 +5,27 @@
//!
//! 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 {
@ -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<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 {
@ -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 {
}
}
/// <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));
}
}

View file

@ -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<String, QueryOrVersion>),
}
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<Versions, Error> {
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<String>),
}
type QueryResult = Result<Versions, Error>;
type QueryResult = Result<Targets, Error>;
fn cache() -> &'static DashMap<Query, Versions> {
static CACHE: OnceLock<DashMap<Query, Versions>> = OnceLock::new();
fn cache() -> &'static DashMap<Query, Targets> {
static CACHE: OnceLock<DashMap<Query, Targets>> = 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<T>(s: &[T]) -> QueryResult
where
T: AsRef<str>,
@ -150,7 +40,7 @@ impl Query {
},
) {
Ok(distribs) => {
let versions = Versions::parse_versions(distribs);
let versions = Targets::parse_versions(distribs);
Ok(versions)
}

View file

@ -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<Self, Self::Error> {
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<Value> {
let can_enable =

View file

@ -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)

View file

@ -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