#![allow(missing_docs)]
use crate::vendor_prefix::VendorPrefix;
use bitflags::bitflags;
#[cfg(any(feature = "serde", feature = "nodejs"))]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Default)]
#[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(Serialize, Deserialize))]
#[allow(missing_docs)]
pub struct Browsers {
pub android: Option<u32>,
pub chrome: Option<u32>,
pub edge: Option<u32>,
pub firefox: Option<u32>,
pub ie: Option<u32>,
pub ios_saf: Option<u32>,
pub opera: Option<u32>,
pub safari: Option<u32>,
pub samsung: Option<u32>,
}
#[cfg(feature = "browserslist")]
#[cfg_attr(docsrs, doc(cfg(feature = "browserslist")))]
impl Browsers {
pub fn from_browserslist<S: AsRef<str>, I: IntoIterator<Item = S>>(
query: I,
) -> Result<Option<Browsers>, browserslist::Error> {
use browserslist::{resolve, Opts};
Self::from_distribs(resolve(query, &Opts::new())?)
}
pub fn load_browserslist() -> Result<Option<Browsers>, browserslist::Error> {
use browserslist::{execute, Opts};
Self::from_distribs(execute(&Opts::new())?)
}
fn from_distribs(distribs: Vec<browserslist::Distrib>) -> Result<Option<Browsers>, browserslist::Error> {
let mut browsers = Browsers::default();
let mut has_any = false;
for distrib in distribs {
macro_rules! browser {
($browser: ident) => {{
if let Some(v) = parse_version(distrib.version()) {
if browsers.$browser.is_none() || v < browsers.$browser.unwrap() {
browsers.$browser = Some(v);
has_any = true;
}
}
}};
}
match distrib.name() {
"android" => browser!(android),
"chrome" | "and_chr" => browser!(chrome),
"edge" => browser!(edge),
"firefox" | "and_ff" => browser!(firefox),
"ie" => browser!(ie),
"ios_saf" => browser!(ios_saf),
"opera" | "op_mob" => browser!(opera),
"safari" => browser!(safari),
"samsung" => browser!(samsung),
_ => {}
}
}
if !has_any {
return Ok(None);
}
Ok(Some(browsers))
}
}
#[cfg(feature = "browserslist")]
fn parse_version(version: &str) -> Option<u32> {
let version = version.split('-').next();
if version.is_none() {
return None;
}
let mut version = version.unwrap().split('.');
let major = version.next().and_then(|v| v.parse::<u32>().ok());
if let Some(major) = major {
let minor = version.next().and_then(|v| v.parse::<u32>().ok()).unwrap_or(0);
let patch = version.next().and_then(|v| v.parse::<u32>().ok()).unwrap_or(0);
let v: u32 = (major & 0xff) << 16 | (minor & 0xff) << 8 | (patch & 0xff);
return Some(v);
}
None
}
bitflags! {
#[derive(Debug, Default, Clone, Copy)]
pub struct Features: u32 {
const Nesting = 1 << 0;
const NotSelectorList = 1 << 1;
const DirSelector = 1 << 2;
const LangSelectorList = 1 << 3;
const IsSelector = 1 << 4;
const TextDecorationThicknessPercent = 1 << 5;
const MediaIntervalSyntax = 1 << 6;
const MediaRangeSyntax = 1 << 7;
const CustomMediaQueries = 1 << 8;
const ClampFunction = 1 << 9;
const ColorFunction = 1 << 10;
const OklabColors = 1 << 11;
const LabColors = 1 << 12;
const P3Colors = 1 << 13;
const HexAlphaColors = 1 << 14;
const SpaceSeparatedColorNotation = 1 << 15;
const FontFamilySystemUi = 1 << 16;
const DoublePositionGradients = 1 << 17;
const VendorPrefixes = 1 << 18;
const LogicalProperties = 1 << 19;
const Selectors = Self::Nesting.bits() | Self::NotSelectorList.bits() | Self::DirSelector.bits() | Self::LangSelectorList.bits() | Self::IsSelector.bits();
const MediaQueries = Self::MediaIntervalSyntax.bits() | Self::MediaRangeSyntax.bits() | Self::CustomMediaQueries.bits();
const Colors = Self::ColorFunction.bits() | Self::OklabColors.bits() | Self::LabColors.bits() | Self::P3Colors.bits() | Self::HexAlphaColors.bits() | Self::SpaceSeparatedColorNotation.bits();
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Targets {
pub browsers: Option<Browsers>,
pub include: Features,
pub exclude: Features,
}
impl From<Browsers> for Targets {
fn from(browsers: Browsers) -> Self {
Self {
browsers: Some(browsers),
..Default::default()
}
}
}
impl From<Option<Browsers>> for Targets {
fn from(browsers: Option<Browsers>) -> Self {
Self {
browsers,
..Default::default()
}
}
}
impl Targets {
pub(crate) fn is_compatible(&self, feature: crate::compat::Feature) -> bool {
self.browsers.map(|targets| feature.is_compatible(targets)).unwrap_or(true)
}
pub(crate) fn should_compile(&self, feature: crate::compat::Feature, flag: Features) -> bool {
self.include.contains(flag) || (!self.exclude.contains(flag) && !self.is_compatible(feature))
}
pub(crate) fn should_compile_logical(&self, feature: crate::compat::Feature) -> bool {
self.should_compile(feature, Features::LogicalProperties)
}
pub(crate) fn should_compile_selectors(&self) -> bool {
self.include.intersects(Features::Selectors)
|| (!self.exclude.intersects(Features::Selectors) && self.browsers.is_some())
}
pub(crate) fn prefixes(&self, prefix: VendorPrefix, feature: crate::prefixes::Feature) -> VendorPrefix {
if prefix.contains(VendorPrefix::None) && !self.exclude.contains(Features::VendorPrefixes) {
if self.include.contains(Features::VendorPrefixes) {
VendorPrefix::all()
} else {
self.browsers.map(|browsers| feature.prefixes_for(browsers)).unwrap_or(prefix)
}
} else {
prefix
}
}
}
macro_rules! should_compile {
($targets: expr, $feature: ident) => {
$targets.should_compile(
crate::compat::Feature::$feature,
crate::targets::Features::$feature,
)
};
}
pub(crate) use should_compile;