repo_description: An extremely fast CSS parser, transformer, bundler, and minifier written in Rust.
repo_topics: build-tool compiler css minifier parser rust
code: //! Browser target options.
  
  #![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};
  
  /// Browser versions to compile CSS for.
  ///
  /// Versions are represented as a single 24-bit integer, with one byte
  /// per `major.minor.patch` component.
  ///
  /// # Example
  ///
  /// This example represents a target of Safari 13.2.0.
  ///
  /// ```
  /// use lightningcss::targets::Browsers;
  ///
  /// let targets = Browsers {
  ///   safari: Some((13 << 16) | (2 << 8)),
  ///   ..Browsers::default()
  /// };
  /// ```
  #[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 {
    /// Parses a list of browserslist queries into Lightning CSS targets.
    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())
    }
  
    /// Parses a list of browserslist queries into Lightning CSS targets.
    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"))]
    /// Finds browserslist configuration, selects queries by environment and loads the resulting queries into LightningCSS targets.
    ///
    /// Configuration resolution is modeled after the original `browserslist` nodeJS package.
    /// The configuration is resolved in the following order:
    ///
    /// - If a `BROWSERSLIST` environment variable is present, then load targets from its value. This is analog to the `--targets` CLI option.
    ///   Example: `BROWSERSLIST="firefox ESR" lightningcss [OPTIONS] <INPUT_FILE>`
    /// - If a `BROWSERSLIST_CONFIG` environment variable is present, then resolve the file at the provided path.
    ///   Then parse and use targets from `package.json` or any browserslist configuration file pointed to by the environment variable.
    ///   Example: `BROWSERSLIST_CONFIG="../config/browserslist" lightningcss [OPTIONS] <INPUT_FILE>`
    /// - If none of the above apply, then find, parse and use targets from the first `browserslist`, `.browserslistrc`
    ///   or `package.json` configuration file in any parent directory.
    ///
    /// When using parsed configuration from `browserslist`, `.browserslistrc` or `package.json` configuration files,
    /// the environment determined by:
    ///
    /// - the `BROWSERSLIST_ENV` environment variable if present,
    /// - otherwise the `NODE_ENV` environment varialbe if present,
    /// - otherwise `production` is used.
    ///
    /// If no targets are found for the resulting environment, then the `defaults` configuration section is used.
    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! {
    /// Features to explicitly enable or disable.
    #[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 {}
  
  /// Target browsers and features to compile.
  #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
  #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
  pub struct Targets {
    /// Browser targets to compile the CSS for.
    pub browsers: Option<Browsers>,
    /// Features that should always be compiled, even when supported by targets.
    pub include: Features,
    /// Features that should never be compiled, even when unsupported by targets.
    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,
      }
    }
  
    /// Returns true if inserted
    pub fn enter_supports(&mut self, features: Features) -> bool {
      if features.is_empty() || self.current.exclude.contains(features) {
        // Already excluding all features
        return false;
      }
  
      let newly_excluded = features - self.current.exclude;
      self.stack.push(newly_excluded);
      self.current.exclude.insert(newly_excluded);
      true
    }
  
    /// Should be only called if inserted
    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;
  