use crate::{
CssAtomSet, CssDiagnostic, CssMetadata, NodeKinds, SelectorList, StyleValue, UnknownAtRule, UnknownQualifiedRule,
rules,
};
use css_parse::{
BumpBox, Cursor, DeclarationGroup, Diagnostic, NodeMetadata, NodeWithMetadata, Parse, Parser, QualifiedRule,
Result as ParserResult, RuleVariants,
};
use csskit_derives::*;
#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
pub struct StyleRule<'a> {
pub rule: QualifiedRule<'a, SelectorList<'a>, StyleValue<'a>, NestedGroupRule<'a>, CssMetadata>,
}
impl<'a> NodeWithMetadata<CssMetadata> for StyleRule<'a> {
fn self_metadata(&self) -> CssMetadata {
let child_meta = self.rule.metadata();
let is_empty = child_meta.declaration_kinds.is_none() && !child_meta.has_rules();
let mut node_kinds = NodeKinds::StyleRule;
if is_empty {
node_kinds |= NodeKinds::EmptyBlock;
}
CssMetadata { node_kinds, ..Default::default() }
}
fn metadata(&self) -> CssMetadata {
self.rule.metadata().merge(self.self_metadata())
}
}
macro_rules! apply_rules {
($macro: ident) => {
$macro! {
Container(ContainerRule<'a>): "container",
Layer(LayerRule<'a>): "layer",
Media(MediaRule<'a>): "media",
Scope(ScopeRule): "scope",
}
};
}
macro_rules! nested_group_rule {
( $(
$name: ident($ty: ident$(<$a: lifetime>)?): $str: pat,
)+ ) => {
#[derive(ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))]
#[derive(csskit_derives::NodeWithMetadata)]
#[metadata(delegate)]
pub enum NestedGroupRule<'a> {
$(
$name(rules::$ty$(<$a>)?),
)+
Supports(BumpBox<'a, rules::SupportsRule<'a>>),
UnknownAt(UnknownAtRule<'a>),
Style(StyleRule<'a>),
Unknown(UnknownQualifiedRule<'a>),
Declarations(DeclarationGroup<'a, StyleValue<'a>, CssMetadata>),
}
}
}
apply_rules!(nested_group_rule);
impl<'a> RuleVariants<'a> for NestedGroupRule<'a> {
type DeclarationValue = StyleValue<'a>;
type Metadata = CssMetadata;
fn parse_at_rule<I>(p: &mut Parser<'a, I>, name: Cursor) -> ParserResult<Self>
where
I: Iterator<Item = Cursor> + Clone,
{
macro_rules! parse_rule {
( $(
$name: ident($ty: ident$(<$a: lifetime>)?): $str: pat,
)+ ) => {
match p.to_atom::<CssAtomSet>(name) {
$(CssAtomSet::$name => p.parse::<rules::$ty>().map(Self::$name),)+
CssAtomSet::Supports => p.parse::<rules::SupportsRule>().map(|r| Self::Supports(BumpBox::new_in(p.bump(), r))),
_ => Err(Diagnostic::new(name.into(), Diagnostic::unexpected_at_rule))?,
}
}
}
apply_rules!(parse_rule)
}
fn parse_unknown_at_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
where
I: Iterator<Item = Cursor> + Clone,
{
p.parse::<UnknownAtRule>().map(Self::UnknownAt)
}
fn parse_qualified_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
where
I: Iterator<Item = Cursor> + Clone,
{
p.parse::<StyleRule>().map(Self::Style)
}
fn parse_unknown_qualified_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
where
I: Iterator<Item = Cursor> + Clone,
{
p.parse::<UnknownQualifiedRule>().map(Self::Unknown)
}
fn is_unknown(&self) -> bool {
matches!(self, Self::UnknownAt(_) | Self::Unknown(_))
}
fn from_declaration_group(
group: css_parse::DeclarationGroup<'a, Self::DeclarationValue, Self::Metadata>,
) -> Option<Self> {
Some(Self::Declarations(group))
}
}
impl<'a> Parse<'a> for NestedGroupRule<'a> {
fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
where
I: Iterator<Item = Cursor> + Clone,
{
Self::parse_rule_variants(p)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::CssAtomSet;
use css_parse::assert_parse;
#[cfg(feature = "visitable")]
use crate::assert_visits;
#[test]
fn size_test() {
assert_eq!(std::mem::size_of::<StyleRule>(), 192);
}
#[test]
fn test_writes() {
assert_parse!(CssAtomSet::ATOMS, StyleRule, "body{}");
assert_parse!(CssAtomSet::ATOMS, StyleRule, "body,body{}");
assert_parse!(CssAtomSet::ATOMS, StyleRule, "body{width:1px;}");
assert_parse!(CssAtomSet::ATOMS, StyleRule, "body{opacity:0;}");
assert_parse!(CssAtomSet::ATOMS, StyleRule, ".foo *{}");
assert_parse!(CssAtomSet::ATOMS, StyleRule, ":nth-child(1){opacity:0;}");
assert_parse!(CssAtomSet::ATOMS, StyleRule, ".foo{--bar:(baz);}");
assert_parse!(CssAtomSet::ATOMS, StyleRule, ".foo{width: calc(1px + (var(--foo)) + 1px);}");
assert_parse!(CssAtomSet::ATOMS, StyleRule, ".foo{--bar:1}");
assert_parse!(CssAtomSet::ATOMS, StyleRule, ":root{--custom:{width:0;height:0;};}");
assert_parse!(CssAtomSet::ATOMS, StyleRule, ":root{a;b{}}");
assert_parse!(CssAtomSet::ATOMS, StyleRule, ":root{$(var)-size: 100%;}");
}
#[test]
#[cfg(feature = "visitable")]
fn test_visits() {
assert_visits!(
":root{html:has(&[open]){overflow:hidden}}",
StyleRule,
SelectorList,
CompoundSelector,
PseudoClass,
StyleRule,
SelectorList,
CompoundSelector,
Tag,
HtmlTag,
HasPseudoFunction,
SelectorList,
CompoundSelector,
Combinator,
Attribute,
StyleValue,
OverflowStyleValue,
OverflowBlockStyleValue
);
}
}