#![allow(missing_docs)]
use std::borrow::Borrow;
use crate::vendor_prefix::VendorPrefix;
use bitflags::bitflags;
#[cfg(any(feature = "serde", feature = "nodejs"))]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[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")]
pub use browserslist::Opts as BrowserslistConfig;
#[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> {
Self::from_browserslist_with_config(query, BrowserslistConfig::default())
}
pub fn from_browserslist_with_config<S: AsRef<str>, I: IntoIterator<Item = S>>(
query: I,
config: BrowserslistConfig,
) -> Result<Option<Browsers>, browserslist::Error> {
use browserslist::resolve;
Self::from_distribs(resolve(query, &config)?)
}
#[cfg(not(target_arch = "wasm32"))]
pub fn load_browserslist() -> Result<Option<Browsers>, browserslist::Error> {
use browserslist::{execute, Opts};
Self::from_distribs(execute(&Opts::default())?)
}
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, Hash, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
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 LightDark = 1 << 20;
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() | Self::LightDark.bits();
}
}
pub(crate) trait FeaturesIterator: Sized + Iterator {
fn try_union_all<T>(&mut self) -> Option<Features>
where
Self: Iterator<Item = Option<T>>,
T: Borrow<Features>,
{
self.try_fold(Features::empty(), |a, b| b.map(|b| a | *b.borrow()))
}
}
impl<I> FeaturesIterator for I where I: Iterator {}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
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
}
}
}
#[derive(Debug)]
pub(crate) struct TargetsWithSupportsScope {
stack: Vec<Features>,
pub(crate) current: Targets,
}
impl TargetsWithSupportsScope {
pub fn new(targets: Targets) -> Self {
Self {
stack: Vec::new(),
current: targets,
}
}
pub fn enter_supports(&mut self, features: Features) -> bool {
if features.is_empty() || self.current.exclude.contains(features) {
return false;
}
let newly_excluded = features - self.current.exclude;
self.stack.push(newly_excluded);
self.current.exclude.insert(newly_excluded);
true
}
pub fn exit_supports(&mut self) {
if let Some(last) = self.stack.pop() {
self.current.exclude.remove(last);
}
}
}
#[test]
fn supports_scope_correctly() {
let mut targets = TargetsWithSupportsScope::new(Targets::default());
assert!(!targets.current.exclude.contains(Features::OklabColors));
assert!(!targets.current.exclude.contains(Features::LabColors));
assert!(!targets.current.exclude.contains(Features::P3Colors));
targets.enter_supports(Features::OklabColors | Features::LabColors);
assert!(targets.current.exclude.contains(Features::OklabColors));
assert!(targets.current.exclude.contains(Features::LabColors));
targets.enter_supports(Features::P3Colors | Features::LabColors);
assert!(targets.current.exclude.contains(Features::OklabColors));
assert!(targets.current.exclude.contains(Features::LabColors));
assert!(targets.current.exclude.contains(Features::P3Colors));
targets.exit_supports();
assert!(targets.current.exclude.contains(Features::OklabColors));
assert!(targets.current.exclude.contains(Features::LabColors));
assert!(!targets.current.exclude.contains(Features::P3Colors));
targets.exit_supports();
assert!(!targets.current.exclude.contains(Features::OklabColors));
assert!(!targets.current.exclude.contains(Features::LabColors));
}
macro_rules! should_compile {
($targets: expr, $feature: ident) => {
$targets.should_compile(
crate::compat::Feature::$feature,
crate::targets::Features::$feature,
)
};
}
pub(crate) use should_compile;