1#![allow(missing_docs)]
4
5use crate::vendor_prefix::VendorPrefix;
6use bitflags::bitflags;
7#[cfg(any(feature = "serde", feature = "nodejs"))]
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Copy, Default)]
28#[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(Serialize, Deserialize))]
29#[allow(missing_docs)]
30pub struct Browsers {
31 pub android: Option<u32>,
32 pub chrome: Option<u32>,
33 pub edge: Option<u32>,
34 pub firefox: Option<u32>,
35 pub ie: Option<u32>,
36 pub ios_saf: Option<u32>,
37 pub opera: Option<u32>,
38 pub safari: Option<u32>,
39 pub samsung: Option<u32>,
40}
41
42#[cfg(feature = "browserslist")]
43#[cfg_attr(docsrs, doc(cfg(feature = "browserslist")))]
44impl Browsers {
45 pub fn from_browserslist<S: AsRef<str>, I: IntoIterator<Item = S>>(
47 query: I,
48 ) -> Result<Option<Browsers>, browserslist::Error> {
49 use browserslist::{resolve, Opts};
50
51 Self::from_distribs(resolve(query, &Opts::default())?)
52 }
53
54 pub fn load_browserslist() -> Result<Option<Browsers>, browserslist::Error> {
76 use browserslist::{execute, Opts};
77
78 Self::from_distribs(execute(&Opts::default())?)
79 }
80
81 fn from_distribs(distribs: Vec<browserslist::Distrib>) -> Result<Option<Browsers>, browserslist::Error> {
82 let mut browsers = Browsers::default();
83 let mut has_any = false;
84 for distrib in distribs {
85 macro_rules! browser {
86 ($browser: ident) => {{
87 if let Some(v) = parse_version(distrib.version()) {
88 if browsers.$browser.is_none() || v < browsers.$browser.unwrap() {
89 browsers.$browser = Some(v);
90 has_any = true;
91 }
92 }
93 }};
94 }
95
96 match distrib.name() {
97 "android" => browser!(android),
98 "chrome" | "and_chr" => browser!(chrome),
99 "edge" => browser!(edge),
100 "firefox" | "and_ff" => browser!(firefox),
101 "ie" => browser!(ie),
102 "ios_saf" => browser!(ios_saf),
103 "opera" | "op_mob" => browser!(opera),
104 "safari" => browser!(safari),
105 "samsung" => browser!(samsung),
106 _ => {}
107 }
108 }
109
110 if !has_any {
111 return Ok(None);
112 }
113
114 Ok(Some(browsers))
115 }
116}
117
118#[cfg(feature = "browserslist")]
119fn parse_version(version: &str) -> Option<u32> {
120 let version = version.split('-').next();
121 if version.is_none() {
122 return None;
123 }
124
125 let mut version = version.unwrap().split('.');
126 let major = version.next().and_then(|v| v.parse::<u32>().ok());
127 if let Some(major) = major {
128 let minor = version.next().and_then(|v| v.parse::<u32>().ok()).unwrap_or(0);
129 let patch = version.next().and_then(|v| v.parse::<u32>().ok()).unwrap_or(0);
130 let v: u32 = (major & 0xff) << 16 | (minor & 0xff) << 8 | (patch & 0xff);
131 return Some(v);
132 }
133
134 None
135}
136
137bitflags! {
138 #[derive(Debug, Default, Clone, Copy)]
140 pub struct Features: u32 {
141 const Nesting = 1 << 0;
142 const NotSelectorList = 1 << 1;
143 const DirSelector = 1 << 2;
144 const LangSelectorList = 1 << 3;
145 const IsSelector = 1 << 4;
146 const TextDecorationThicknessPercent = 1 << 5;
147 const MediaIntervalSyntax = 1 << 6;
148 const MediaRangeSyntax = 1 << 7;
149 const CustomMediaQueries = 1 << 8;
150 const ClampFunction = 1 << 9;
151 const ColorFunction = 1 << 10;
152 const OklabColors = 1 << 11;
153 const LabColors = 1 << 12;
154 const P3Colors = 1 << 13;
155 const HexAlphaColors = 1 << 14;
156 const SpaceSeparatedColorNotation = 1 << 15;
157 const FontFamilySystemUi = 1 << 16;
158 const DoublePositionGradients = 1 << 17;
159 const VendorPrefixes = 1 << 18;
160 const LogicalProperties = 1 << 19;
161 const Selectors = Self::Nesting.bits() | Self::NotSelectorList.bits() | Self::DirSelector.bits() | Self::LangSelectorList.bits() | Self::IsSelector.bits();
162 const MediaQueries = Self::MediaIntervalSyntax.bits() | Self::MediaRangeSyntax.bits() | Self::CustomMediaQueries.bits();
163 const Colors = Self::ColorFunction.bits() | Self::OklabColors.bits() | Self::LabColors.bits() | Self::P3Colors.bits() | Self::HexAlphaColors.bits() | Self::SpaceSeparatedColorNotation.bits();
164 }
165}
166
167#[derive(Debug, Clone, Copy, Default)]
169pub struct Targets {
170 pub browsers: Option<Browsers>,
172 pub include: Features,
174 pub exclude: Features,
176}
177
178impl From<Browsers> for Targets {
179 fn from(browsers: Browsers) -> Self {
180 Self {
181 browsers: Some(browsers),
182 ..Default::default()
183 }
184 }
185}
186
187impl From<Option<Browsers>> for Targets {
188 fn from(browsers: Option<Browsers>) -> Self {
189 Self {
190 browsers,
191 ..Default::default()
192 }
193 }
194}
195
196impl Targets {
197 pub(crate) fn is_compatible(&self, feature: crate::compat::Feature) -> bool {
198 self.browsers.map(|targets| feature.is_compatible(targets)).unwrap_or(true)
199 }
200
201 pub(crate) fn should_compile(&self, feature: crate::compat::Feature, flag: Features) -> bool {
202 self.include.contains(flag) || (!self.exclude.contains(flag) && !self.is_compatible(feature))
203 }
204
205 pub(crate) fn should_compile_logical(&self, feature: crate::compat::Feature) -> bool {
206 self.should_compile(feature, Features::LogicalProperties)
207 }
208
209 pub(crate) fn should_compile_selectors(&self) -> bool {
210 self.include.intersects(Features::Selectors)
211 || (!self.exclude.intersects(Features::Selectors) && self.browsers.is_some())
212 }
213
214 pub(crate) fn prefixes(&self, prefix: VendorPrefix, feature: crate::prefixes::Feature) -> VendorPrefix {
215 if prefix.contains(VendorPrefix::None) && !self.exclude.contains(Features::VendorPrefixes) {
216 if self.include.contains(Features::VendorPrefixes) {
217 VendorPrefix::all()
218 } else {
219 self.browsers.map(|browsers| feature.prefixes_for(browsers)).unwrap_or(prefix)
220 }
221 } else {
222 prefix
223 }
224 }
225}
226
227macro_rules! should_compile {
228 ($targets: expr, $feature: ident) => {
229 $targets.should_compile(
230 crate::compat::Feature::$feature,
231 crate::targets::Features::$feature,
232 )
233 };
234}
235
236pub(crate) use should_compile;