oxc_compat/
engine_targets.rs1use std::{
2 fmt::{Debug, Display},
3 ops::{Deref, DerefMut},
4 str::FromStr,
5};
6
7pub use browserslist::Version;
8use oxc_syntax::es_target::ESTarget;
9use rustc_hash::FxHashMap;
10use serde::Deserialize;
11
12use crate::browserslist_query::BrowserslistQuery;
13use crate::{babel_targets::BabelTargets, es_target::ESVersion};
14
15use super::{
16 Engine,
17 es_features::{ESFeature, features},
18};
19
20#[derive(Debug, Default, Clone, Deserialize)]
22#[serde(try_from = "BabelTargets")]
23pub struct EngineTargets(FxHashMap<Engine, Version>);
24
25impl Deref for EngineTargets {
26 type Target = FxHashMap<Engine, Version>;
27
28 fn deref(&self) -> &Self::Target {
29 &self.0
30 }
31}
32
33impl DerefMut for EngineTargets {
34 fn deref_mut(&mut self) -> &mut Self::Target {
35 &mut self.0
36 }
37}
38
39impl Display for EngineTargets {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 for (idx, (engine, version)) in self.iter().enumerate() {
42 if idx > 0 {
43 f.write_str(",")?;
44 }
45 f.write_str(&engine.to_string())?;
46 if *engine == Engine::Es {
47 f.write_str(&version.0.to_string())?;
48 } else {
49 f.write_str(&version.to_string())?;
50 }
51 }
52 Ok(())
53 }
54}
55
56impl EngineTargets {
57 pub fn new(map: FxHashMap<Engine, Version>) -> Self {
58 Self(map)
59 }
60
61 pub fn try_from_query(query: &str) -> Result<Self, String> {
65 BrowserslistQuery::Single(query.to_string()).exec()
66 }
67
68 pub fn is_any_target(&self) -> bool {
70 self.0.is_empty()
71 }
72
73 pub fn has_feature(&self, feature: ESFeature) -> bool {
78 let feature_engine_targets = &features()[&feature];
79 for (engine, feature_version) in feature_engine_targets.iter() {
80 if let Some(target_version) = self.get(engine) {
81 if *engine == Engine::Es {
82 return target_version.0 < feature_version.0;
83 }
84 if target_version < feature_version {
85 return true;
86 }
87 }
88 }
89 false
90 }
91
92 pub fn parse_versions(versions: Vec<(String, String)>) -> Self {
94 let mut engine_targets = Self::default();
95 for (engine, version) in versions {
96 let Ok(engine) = Engine::from_str(&engine) else {
97 continue;
98 };
99 let Ok(version) = Version::from_str(&version) else {
100 continue;
101 };
102 engine_targets
103 .0
104 .entry(engine)
105 .and_modify(|v| {
106 if version < *v {
107 *v = version;
108 }
109 })
110 .or_insert(version);
111 }
112 engine_targets
113 }
114
115 pub fn from_target(s: &str) -> Result<Self, String> {
119 if s.contains(',') {
120 Self::from_target_list(&s.split(',').collect::<Vec<_>>())
121 } else {
122 Self::from_target_list(&[s])
123 }
124 }
125
126 pub fn from_target_list<S: AsRef<str>>(list: &[S]) -> Result<Self, String> {
130 let mut es_target = None;
131 let mut engine_targets = EngineTargets::default();
132
133 for s in list {
134 let s = s.as_ref();
135 if let Ok(target) = ESTarget::from_str(s) {
137 if let Some(target) = es_target {
138 return Err(format!("'{target}' is already specified."));
139 }
140 es_target = Some(target);
141 } else {
142 let (engine, version) = Engine::parse_name_and_version(s)?;
144 if engine_targets.insert(engine, version).is_some() {
145 return Err(format!("'{s}' is already specified."));
146 }
147 }
148 }
149 engine_targets.insert(Engine::Es, es_target.unwrap_or(ESTarget::default()).version());
150 Ok(engine_targets)
151 }
152}
153
154#[test]
155fn test_displayed_value_is_parsable() {
156 let target = EngineTargets::new(FxHashMap::from_iter([
157 (Engine::Chrome, Version(139, 0, 0)),
158 (Engine::Deno, Version(2, 5, 1)),
159 (Engine::Es, Version(2024, 0, 0)),
160 ]));
161 let s = target.to_string();
162 let parsed = EngineTargets::from_target(&s).unwrap();
163 assert_eq!(target.0, parsed.0);
164}