#[cfg(feature = "visitable")]
use crate::visit::NodeId;
use crate::{
CssAtomSet,
traits::{AppliesTo, BoxPortion, BoxSide, PropertyGroup},
};
use bitmask_enum::bitmask;
use css_lexer::{Span, ToSpan};
use css_parse::{NodeMetadata, SemanticEq, ToCursors};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum UnitlessZeroResolves {
#[default]
Length,
Number,
}
#[bitmask(u32)]
#[bitmask_config(vec_debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AtRuleId {
Charset,
ColorProfile,
Container,
CounterStyle,
FontFace,
FontFeatureValues,
FontPaletteValues,
Import,
Keyframes,
Layer,
Media,
Namespace,
Page,
Property,
Scope,
StartingStyle,
Supports,
Document,
WebkitKeyframes,
MozDocument,
}
#[cfg(feature = "visitable")]
impl NodeId {
pub fn to_at_rule_id(self) -> Option<AtRuleId> {
match self {
Self::CharsetRule => Some(AtRuleId::Charset),
Self::ContainerRule => Some(AtRuleId::Container),
Self::CounterStyleRule => Some(AtRuleId::CounterStyle),
Self::DocumentRule => Some(AtRuleId::Document),
Self::FontFaceRule => Some(AtRuleId::FontFace),
Self::KeyframesRule => Some(AtRuleId::Keyframes),
Self::LayerRule => Some(AtRuleId::Layer),
Self::MediaRule => Some(AtRuleId::Media),
Self::MozDocumentRule => Some(AtRuleId::MozDocument),
Self::NamespaceRule => Some(AtRuleId::Namespace),
Self::PageRule => Some(AtRuleId::Page),
Self::PropertyRule => Some(AtRuleId::Property),
Self::StartingStyleRule => Some(AtRuleId::StartingStyle),
Self::SupportsRule => Some(AtRuleId::Supports),
Self::WebkitKeyframesRule => Some(AtRuleId::WebkitKeyframes),
_ => None,
}
}
}
#[bitmask(u8)]
#[bitmask_config(vec_debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum VendorPrefixes {
Moz,
WebKit,
O,
Ms,
}
impl TryFrom<CssAtomSet> for VendorPrefixes {
type Error = ();
fn try_from(atom: CssAtomSet) -> Result<Self, Self::Error> {
const VENDOR_FLAG: u32 = 0b00000000_10000000_00000000_00000000;
const VENDORS: [VendorPrefixes; 4] =
[VendorPrefixes::WebKit, VendorPrefixes::Moz, VendorPrefixes::Ms, VendorPrefixes::O];
let atom_bits = atom as u32;
if atom_bits & VENDOR_FLAG == 0 {
return Err(());
}
let index = (atom_bits >> 21) & 0b11;
Ok(VENDORS[index as usize])
}
}
#[bitmask(u8)]
#[bitmask_config(vec_debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DeclarationKind {
Important,
CssWideKeywords,
Custom,
Computed,
Shorthands,
Longhands,
}
#[bitmask(u16)]
#[bitmask_config(vec_debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum NodeKinds {
Unknown,
StyleRule,
AtRule,
Declaration,
Function,
EmptyPrelude,
EmptyBlock,
Nested,
Deprecated,
Experimental,
NonStandard,
Dimension,
Custom,
}
#[bitmask(u8)]
#[bitmask_config(vec_debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PropertyKind {
Name,
}
pub const PROPERTY_KIND_VARIANTS: &[PropertyKind] = &[PropertyKind::Name];
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CssMetadata {
pub property_groups: PropertyGroup,
pub applies_to: AppliesTo,
pub box_sides: BoxSide,
pub box_portions: BoxPortion,
pub declaration_kinds: DeclarationKind,
pub used_at_rules: AtRuleId,
pub vendor_prefixes: VendorPrefixes,
pub node_kinds: NodeKinds,
pub property_kinds: PropertyKind,
pub unitless_zero_resolves: UnitlessZeroResolves,
pub size: u16,
}
impl Default for CssMetadata {
fn default() -> Self {
Self {
property_groups: PropertyGroup::none(),
applies_to: AppliesTo::none(),
box_sides: BoxSide::none(),
box_portions: BoxPortion::none(),
declaration_kinds: DeclarationKind::none(),
used_at_rules: AtRuleId::none(),
vendor_prefixes: VendorPrefixes::none(),
node_kinds: NodeKinds::none(),
property_kinds: PropertyKind::none(),
unitless_zero_resolves: UnitlessZeroResolves::default(),
size: 0,
}
}
}
impl CssMetadata {
#[inline]
pub fn is_empty(&self) -> bool {
self.property_groups == PropertyGroup::none()
&& self.applies_to == AppliesTo::none()
&& self.box_sides == BoxSide::none()
&& self.box_portions == BoxPortion::none()
&& self.declaration_kinds == DeclarationKind::none()
&& self.used_at_rules == AtRuleId::none()
&& self.vendor_prefixes == VendorPrefixes::none()
&& self.node_kinds == NodeKinds::none()
&& self.property_kinds == PropertyKind::none()
&& self.unitless_zero_resolves == UnitlessZeroResolves::Length
&& self.size == 0
}
#[inline]
pub fn modifies_box(&self) -> bool {
!self.box_portions.is_none()
}
#[inline]
pub fn has_important(&self) -> bool {
self.declaration_kinds.contains(DeclarationKind::Important)
}
#[inline]
pub fn has_custom_properties(&self) -> bool {
self.declaration_kinds.contains(DeclarationKind::Custom)
}
#[inline]
pub fn has_computed(&self) -> bool {
self.declaration_kinds.contains(DeclarationKind::Computed)
}
#[inline]
pub fn has_shorthands(&self) -> bool {
self.declaration_kinds.contains(DeclarationKind::Shorthands)
}
#[inline]
pub fn has_longhands(&self) -> bool {
self.declaration_kinds.contains(DeclarationKind::Longhands)
}
#[inline]
pub fn has_unknown(&self) -> bool {
self.node_kinds.contains(NodeKinds::Unknown)
}
#[inline]
pub fn has_vendor_prefixes(&self) -> bool {
!self.vendor_prefixes.is_none()
}
#[inline]
pub fn single_vendor_prefix(&self) -> Option<VendorPrefixes> {
if self.vendor_prefixes.is_none() || self.vendor_prefixes.bits().count_ones() != 1 {
None
} else {
Some(self.vendor_prefixes)
}
}
#[inline]
pub fn has_rules(&self) -> bool {
self.node_kinds.intersects(NodeKinds::StyleRule | NodeKinds::AtRule)
}
#[inline]
pub fn has_style_rules(&self) -> bool {
self.node_kinds.contains(NodeKinds::StyleRule)
}
#[inline]
pub fn has_at_rules(&self) -> bool {
self.node_kinds.contains(NodeKinds::AtRule)
}
#[inline]
pub fn has_functions(&self) -> bool {
self.node_kinds.contains(NodeKinds::Function)
}
#[inline]
pub fn is_deprecated(&self) -> bool {
self.node_kinds.contains(NodeKinds::Deprecated)
}
#[inline]
pub fn is_experimental(&self) -> bool {
self.node_kinds.contains(NodeKinds::Experimental)
}
#[inline]
pub fn is_non_standard(&self) -> bool {
self.node_kinds.contains(NodeKinds::NonStandard)
}
#[inline]
pub fn is_dimension(&self) -> bool {
self.node_kinds.contains(NodeKinds::Dimension)
}
#[inline]
pub fn has_property_kind(&self, kind: PropertyKind) -> bool {
self.property_kinds.contains(kind)
}
#[inline]
pub fn is_empty_container(&self) -> bool {
self.node_kinds.contains(NodeKinds::EmptyBlock)
}
#[inline]
pub fn can_be_empty(&self) -> bool {
self.node_kinds.intersects(NodeKinds::StyleRule | NodeKinds::AtRule)
}
}
impl NodeMetadata for CssMetadata {
#[inline]
fn merge(mut self, other: Self) -> Self {
self.property_groups |= other.property_groups;
self.applies_to |= other.applies_to;
self.box_sides |= other.box_sides;
self.box_portions |= other.box_portions;
self.declaration_kinds |= other.declaration_kinds;
self.used_at_rules |= other.used_at_rules;
self.vendor_prefixes |= other.vendor_prefixes;
self.node_kinds |= other.node_kinds;
self.property_kinds |= other.property_kinds;
if other.unitless_zero_resolves == UnitlessZeroResolves::Number {
self.unitless_zero_resolves = UnitlessZeroResolves::Number;
}
self.size = self.size.max(other.size);
self
}
#[inline]
fn with_size(mut self, size: u16) -> Self {
self.size = size;
self
}
}
impl ToCursors for CssMetadata {
fn to_cursors(&self, _: &mut impl css_parse::CursorSink) {}
}
impl ToSpan for CssMetadata {
fn to_span(&self) -> Span {
Span::DUMMY
}
}
impl SemanticEq for CssMetadata {
fn semantic_eq(&self, other: &Self) -> bool {
self == other
}
}
macro_rules! impl_token_metadata {
($($token:tt),* $(,)?) => {
$(
impl css_parse::NodeWithMetadata<CssMetadata> for css_parse::T![$token] {
fn metadata(&self) -> CssMetadata {
CssMetadata::default()
}
}
)*
};
}
impl_token_metadata!(Ident, Number, Dimension, Hash, AtKeyword, String, Function, Url);
impl css_parse::NodeWithMetadata<CssMetadata> for css_parse::token_macros::RightParen {
fn metadata(&self) -> CssMetadata {
CssMetadata::default()
}
}
impl<'a, T: css_parse::NodeWithMetadata<CssMetadata>> css_parse::NodeWithMetadata<CssMetadata>
for bumpalo::collections::Vec<'a, T>
{
fn metadata(&self) -> CssMetadata {
self.iter().fold(CssMetadata::default(), |acc, item| NodeMetadata::merge(acc, item.metadata()))
}
}
impl<'a, T: css_parse::NodeWithMetadata<CssMetadata>, const MIN: usize> css_parse::NodeWithMetadata<CssMetadata>
for css_parse::CommaSeparated<'a, T, MIN>
{
fn metadata(&self) -> CssMetadata {
self.into_iter().fold(CssMetadata::default(), |acc, (item, _comma)| NodeMetadata::merge(acc, item.metadata()))
}
}
macro_rules! impl_optionals_metadata {
($name:ident, $($T:ident => $v:ident),+) => {
impl<$($T: css_parse::NodeWithMetadata<CssMetadata>),+>
css_parse::NodeWithMetadata<CssMetadata> for css_parse::$name<$($T),+>
{
fn metadata(&self) -> CssMetadata {
let css_parse::$name($($v),+) = self;
let mut meta = CssMetadata::default();
$(
if let Some(val) = $v {
meta = NodeMetadata::merge(meta, val.metadata());
}
)+
meta
}
}
};
}
impl_optionals_metadata!(Optionals2, A => a, B => b);
impl_optionals_metadata!(Optionals3, A => a, B => b, C => c);
impl_optionals_metadata!(Optionals4, A => a, B => b, C => c, D => d);
impl_optionals_metadata!(Optionals5, A => a, B => b, C => c, D => d, E => e);
macro_rules! impl_tuple_metadata {
($($T:ident),+) => {
impl<$($T: css_parse::NodeWithMetadata<CssMetadata>),+>
css_parse::NodeWithMetadata<CssMetadata> for ($($T,)+)
{
#[allow(non_snake_case)]
fn metadata(&self) -> CssMetadata {
let ($($T,)+) = self;
let mut meta = CssMetadata::default();
$(
meta = NodeMetadata::merge(meta, $T.metadata());
)+
meta
}
}
};
}
impl_tuple_metadata!(A, B);
impl_tuple_metadata!(A, B, C);
impl_tuple_metadata!(A, B, C, D);
#[cfg(test)]
mod tests {
use super::*;
use crate::{CssAtomSet, StyleSheet};
use css_lexer::Lexer;
use css_parse::{NodeMetadata, NodeWithMetadata, Parser};
#[test]
fn test_block_metadata_merge() {
let mut meta1 = CssMetadata::default();
meta1.property_groups = PropertyGroup::Color;
meta1.declaration_kinds = DeclarationKind::Important;
let mut meta2 = CssMetadata::default();
meta2.property_groups = PropertyGroup::Position;
meta2.declaration_kinds = DeclarationKind::Custom;
let merged = meta1.merge(meta2);
assert!(merged.property_groups.contains(PropertyGroup::Color));
assert!(merged.property_groups.contains(PropertyGroup::Position));
assert!(merged.declaration_kinds.contains(DeclarationKind::Important));
assert!(merged.declaration_kinds.contains(DeclarationKind::Custom));
}
#[test]
fn test_stylesheet_metadata_simple() {
let css = "body { color: red; width: 100px; }";
let bump = bumpalo::Bump::new();
let lexer = Lexer::new(&CssAtomSet::ATOMS, css);
let mut parser = Parser::new(&bump, css, lexer);
let stylesheet = parser.parse::<StyleSheet>().unwrap();
let metadata = stylesheet.metadata();
assert!(metadata.property_groups.contains(PropertyGroup::Color));
assert!(metadata.property_groups.contains(PropertyGroup::Sizing));
assert!(metadata.modifies_box());
assert!(metadata.has_longhands());
}
#[test]
fn test_stylesheet_metadata_with_important() {
let css = "body { color: red !important; }";
let bump = bumpalo::Bump::new();
let lexer = Lexer::new(&CssAtomSet::ATOMS, css);
let mut parser = Parser::new(&bump, css, lexer);
let stylesheet = parser.parse::<StyleSheet>().unwrap();
let metadata = stylesheet.metadata();
assert!(metadata.has_important());
assert!(metadata.property_groups.contains(PropertyGroup::Color));
}
#[test]
fn test_stylesheet_metadata_custom_properties() {
let css = "body { --custom: value; }";
let bump = bumpalo::Bump::new();
let lexer = Lexer::new(&CssAtomSet::ATOMS, css);
let mut parser = Parser::new(&bump, css, lexer);
let stylesheet = parser.parse::<StyleSheet>().unwrap();
let metadata = stylesheet.metadata();
assert!(metadata.has_custom_properties());
}
#[test]
fn test_stylesheet_metadata_nested_media() {
let css = "@media screen { body { color: red; } }";
let bump = bumpalo::Bump::new();
let lexer = Lexer::new(&CssAtomSet::ATOMS, css);
let mut parser = Parser::new(&bump, css, lexer);
let stylesheet = parser.parse::<StyleSheet>().unwrap();
let metadata = stylesheet.metadata();
assert!(metadata.property_groups.contains(PropertyGroup::Color));
assert!(metadata.used_at_rules.contains(AtRuleId::Media));
}
#[test]
fn test_vendor_prefixes_try_from() {
assert_eq!(VendorPrefixes::try_from(CssAtomSet::_WebkitTransform), Ok(VendorPrefixes::WebKit));
assert_eq!(VendorPrefixes::try_from(CssAtomSet::_WebkitAnimation), Ok(VendorPrefixes::WebKit));
assert_eq!(VendorPrefixes::try_from(CssAtomSet::_WebkitLineClamp), Ok(VendorPrefixes::WebKit));
assert_eq!(VendorPrefixes::try_from(CssAtomSet::_MozAppearance), Ok(VendorPrefixes::Moz));
assert_eq!(VendorPrefixes::try_from(CssAtomSet::_MozAny), Ok(VendorPrefixes::Moz));
assert_eq!(VendorPrefixes::try_from(CssAtomSet::_MsFullscreen), Ok(VendorPrefixes::Ms));
assert_eq!(VendorPrefixes::try_from(CssAtomSet::_MsBackdrop), Ok(VendorPrefixes::Ms));
assert_eq!(VendorPrefixes::try_from(CssAtomSet::_OPlaceholder), Ok(VendorPrefixes::O));
assert_eq!(VendorPrefixes::try_from(CssAtomSet::_OScrollbar), Ok(VendorPrefixes::O));
assert_eq!(VendorPrefixes::try_from(CssAtomSet::Px), Err(()));
assert_eq!(VendorPrefixes::try_from(CssAtomSet::Em), Err(()));
assert_eq!(VendorPrefixes::try_from(CssAtomSet::Auto), Err(()));
assert_eq!(VendorPrefixes::try_from(CssAtomSet::Transform), Err(()));
}
}