1#![allow(missing_docs)]
4
5use std::borrow::Borrow;
6
7use crate::vendor_prefix::VendorPrefix;
8use bitflags::bitflags;
9#[cfg(any(feature = "serde", feature = "nodejs"))]
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
30#[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(Serialize, Deserialize))]
31#[allow(missing_docs)]
32pub struct Browsers {
33 pub android: Option<u32>,
34 pub chrome: Option<u32>,
35 pub edge: Option<u32>,
36 pub firefox: Option<u32>,
37 pub ie: Option<u32>,
38 pub ios_saf: Option<u32>,
39 pub opera: Option<u32>,
40 pub safari: Option<u32>,
41 pub samsung: Option<u32>,
42}
43
44#[cfg(feature = "browserslist")]
45pub use browserslist::Opts as BrowserslistConfig;
46
47#[cfg(feature = "browserslist")]
48#[cfg_attr(docsrs, doc(cfg(feature = "browserslist")))]
49impl Browsers {
50 pub fn from_browserslist<S: AsRef<str>, I: IntoIterator<Item = S>>(
52 query: I,
53 ) -> Result<Option<Browsers>, browserslist::Error> {
54 Self::from_browserslist_with_config(query, BrowserslistConfig::default())
55 }
56
57 pub fn from_browserslist_with_config<S: AsRef<str>, I: IntoIterator<Item = S>>(
59 query: I,
60 config: BrowserslistConfig,
61 ) -> Result<Option<Browsers>, browserslist::Error> {
62 use browserslist::resolve;
63
64 Self::from_distribs(resolve(query, &config)?)
65 }
66
67 #[cfg(not(target_arch = "wasm32"))]
68 pub fn load_browserslist() -> Result<Option<Browsers>, browserslist::Error> {
90 use browserslist::{execute, Opts};
91
92 Self::from_distribs(execute(&Opts::default())?)
93 }
94
95 fn from_distribs(distribs: Vec<browserslist::Distrib>) -> Result<Option<Browsers>, browserslist::Error> {
96 let mut browsers = Browsers::default();
97 let mut has_any = false;
98 for distrib in distribs {
99 macro_rules! browser {
100 ($browser: ident) => {{
101 if let Some(v) = parse_version(distrib.version()) {
102 if browsers.$browser.is_none() || v < browsers.$browser.unwrap() {
103 browsers.$browser = Some(v);
104 has_any = true;
105 }
106 }
107 }};
108 }
109
110 match distrib.name() {
111 "android" => browser!(android),
112 "chrome" | "and_chr" => browser!(chrome),
113 "edge" => browser!(edge),
114 "firefox" | "and_ff" => browser!(firefox),
115 "ie" => browser!(ie),
116 "ios_saf" => browser!(ios_saf),
117 "opera" | "op_mob" => browser!(opera),
118 "safari" => browser!(safari),
119 "samsung" => browser!(samsung),
120 _ => {}
121 }
122 }
123
124 if !has_any {
125 return Ok(None);
126 }
127
128 Ok(Some(browsers))
129 }
130}
131
132#[cfg(feature = "browserslist")]
133fn parse_version(version: &str) -> Option<u32> {
134 let version = version.split('-').next();
135 if version.is_none() {
136 return None;
137 }
138
139 let mut version = version.unwrap().split('.');
140 let major = version.next().and_then(|v| v.parse::<u32>().ok());
141 if let Some(major) = major {
142 let minor = version.next().and_then(|v| v.parse::<u32>().ok()).unwrap_or(0);
143 let patch = version.next().and_then(|v| v.parse::<u32>().ok()).unwrap_or(0);
144 let v: u32 = (major & 0xff) << 16 | (minor & 0xff) << 8 | (patch & 0xff);
145 return Some(v);
146 }
147
148 None
149}
150
151bitflags! {
152 #[derive(Debug, Default, Clone, Copy, Hash, Eq, PartialEq)]
154 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
155 pub struct Features: u32 {
156 const Nesting = 1 << 0;
157 const NotSelectorList = 1 << 1;
158 const DirSelector = 1 << 2;
159 const LangSelectorList = 1 << 3;
160 const IsSelector = 1 << 4;
161 const TextDecorationThicknessPercent = 1 << 5;
162 const MediaIntervalSyntax = 1 << 6;
163 const MediaRangeSyntax = 1 << 7;
164 const CustomMediaQueries = 1 << 8;
165 const ClampFunction = 1 << 9;
166 const ColorFunction = 1 << 10;
167 const OklabColors = 1 << 11;
168 const LabColors = 1 << 12;
169 const P3Colors = 1 << 13;
170 const HexAlphaColors = 1 << 14;
171 const SpaceSeparatedColorNotation = 1 << 15;
172 const FontFamilySystemUi = 1 << 16;
173 const DoublePositionGradients = 1 << 17;
174 const VendorPrefixes = 1 << 18;
175 const LogicalProperties = 1 << 19;
176 const LightDark = 1 << 20;
177 const Selectors = Self::Nesting.bits() | Self::NotSelectorList.bits() | Self::DirSelector.bits() | Self::LangSelectorList.bits() | Self::IsSelector.bits();
178 const MediaQueries = Self::MediaIntervalSyntax.bits() | Self::MediaRangeSyntax.bits() | Self::CustomMediaQueries.bits();
179 const Colors = Self::ColorFunction.bits() | Self::OklabColors.bits() | Self::LabColors.bits() | Self::P3Colors.bits() | Self::HexAlphaColors.bits() | Self::SpaceSeparatedColorNotation.bits() | Self::LightDark.bits();
180 }
181}
182
183pub(crate) trait FeaturesIterator: Sized + Iterator {
184 fn try_union_all<T>(&mut self) -> Option<Features>
185 where
186 Self: Iterator<Item = Option<T>>,
187 T: Borrow<Features>,
188 {
189 self.try_fold(Features::empty(), |a, b| b.map(|b| a | *b.borrow()))
190 }
191}
192
193impl<I> FeaturesIterator for I where I: Iterator {}
194
195#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
197#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
198pub struct Targets {
199 pub browsers: Option<Browsers>,
201 pub include: Features,
203 pub exclude: Features,
205}
206
207impl From<Browsers> for Targets {
208 fn from(browsers: Browsers) -> Self {
209 Self {
210 browsers: Some(browsers),
211 ..Default::default()
212 }
213 }
214}
215
216impl From<Option<Browsers>> for Targets {
217 fn from(browsers: Option<Browsers>) -> Self {
218 Self {
219 browsers,
220 ..Default::default()
221 }
222 }
223}
224
225impl Targets {
226 pub(crate) fn is_compatible(&self, feature: crate::compat::Feature) -> bool {
227 self.browsers.map(|targets| feature.is_compatible(targets)).unwrap_or(true)
228 }
229
230 pub(crate) fn should_compile(&self, feature: crate::compat::Feature, flag: Features) -> bool {
231 self.include.contains(flag) || (!self.exclude.contains(flag) && !self.is_compatible(feature))
232 }
233
234 pub(crate) fn should_compile_logical(&self, feature: crate::compat::Feature) -> bool {
235 self.should_compile(feature, Features::LogicalProperties)
236 }
237
238 pub(crate) fn should_compile_selectors(&self) -> bool {
239 self.include.intersects(Features::Selectors)
240 || (!self.exclude.intersects(Features::Selectors) && self.browsers.is_some())
241 }
242
243 pub(crate) fn prefixes(&self, prefix: VendorPrefix, feature: crate::prefixes::Feature) -> VendorPrefix {
244 if prefix.contains(VendorPrefix::None) && !self.exclude.contains(Features::VendorPrefixes) {
245 if self.include.contains(Features::VendorPrefixes) {
246 VendorPrefix::all()
247 } else {
248 self.browsers.map(|browsers| feature.prefixes_for(browsers)).unwrap_or(prefix)
249 }
250 } else {
251 prefix
252 }
253 }
254}
255
256#[derive(Debug)]
257pub(crate) struct TargetsWithSupportsScope {
258 stack: Vec<Features>,
259 pub(crate) current: Targets,
260}
261
262impl TargetsWithSupportsScope {
263 pub fn new(targets: Targets) -> Self {
264 Self {
265 stack: Vec::new(),
266 current: targets,
267 }
268 }
269
270 pub fn enter_supports(&mut self, features: Features) -> bool {
272 if features.is_empty() || self.current.exclude.contains(features) {
273 return false;
275 }
276
277 let newly_excluded = features - self.current.exclude;
278 self.stack.push(newly_excluded);
279 self.current.exclude.insert(newly_excluded);
280 true
281 }
282
283 pub fn exit_supports(&mut self) {
285 if let Some(last) = self.stack.pop() {
286 self.current.exclude.remove(last);
287 }
288 }
289}
290
291#[test]
292fn supports_scope_correctly() {
293 let mut targets = TargetsWithSupportsScope::new(Targets::default());
294 assert!(!targets.current.exclude.contains(Features::OklabColors));
295 assert!(!targets.current.exclude.contains(Features::LabColors));
296 assert!(!targets.current.exclude.contains(Features::P3Colors));
297
298 targets.enter_supports(Features::OklabColors | Features::LabColors);
299 assert!(targets.current.exclude.contains(Features::OklabColors));
300 assert!(targets.current.exclude.contains(Features::LabColors));
301
302 targets.enter_supports(Features::P3Colors | Features::LabColors);
303 assert!(targets.current.exclude.contains(Features::OklabColors));
304 assert!(targets.current.exclude.contains(Features::LabColors));
305 assert!(targets.current.exclude.contains(Features::P3Colors));
306
307 targets.exit_supports();
308 assert!(targets.current.exclude.contains(Features::OklabColors));
309 assert!(targets.current.exclude.contains(Features::LabColors));
310 assert!(!targets.current.exclude.contains(Features::P3Colors));
311
312 targets.exit_supports();
313 assert!(!targets.current.exclude.contains(Features::OklabColors));
314 assert!(!targets.current.exclude.contains(Features::LabColors));
315}
316
317macro_rules! should_compile {
318 ($targets: expr, $feature: ident) => {
319 $targets.should_compile(
320 crate::compat::Feature::$feature,
321 crate::targets::Features::$feature,
322 )
323 };
324}
325
326pub(crate) use should_compile;