css_ast/
stylerule.rs

1use crate::{StyleValue, selector::SelectorList};
2use css_parse::{
3	Cursor, Parse, Parser, QualifiedRule, Result as ParserResult, RuleVariants, atkeyword_set, syntax::BadDeclaration,
4};
5use csskit_derives::{Parse, Peek, ToCursors, ToSpan, Visitable};
6use csskit_proc_macro::visit;
7
8use super::{UnknownAtRule, UnknownQualifiedRule, rules};
9
10/// Represents a "Style Rule", such as `body { width: 100% }`. See also the CSS-OM [CSSStyleRule][1] interface.
11///
12/// The Style Rule is comprised of two child nodes: the [SelectorList] represents the selectors of the rule.
13/// Each [Declaration][css_parse::Declaration] will have a [StyleValue], and each rule will be a [NestedGroupRule].
14///
15/// [1]: https://drafts.csswg.org/cssom-1/#the-cssstylerule-interface
16#[derive(Parse, Peek, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
18#[visit]
19pub struct StyleRule<'a>(pub QualifiedRule<'a, SelectorList<'a>, StyleValue<'a>, NestedGroupRule<'a>>);
20
21// https://drafts.csswg.org/css-nesting/#conditionals
22macro_rules! apply_rules {
23	($macro: ident) => {
24		$macro! {
25			Container(ContainerRule<'a>): "container",
26			Layer(LayerRule<'a>): "layer",
27			Media(MediaRule<'a>): "media",
28			Scope(ScopeRule): "scope",
29			Supports(SupportsRule<'a>): "supports",
30		}
31	};
32}
33
34macro_rules! nested_group_rule {
35    ( $(
36        $name: ident($ty: ident$(<$a: lifetime>)?): $str: pat,
37    )+ ) => {
38		#[allow(clippy::large_enum_variant)] // TODO: Box?
39		// https://drafts.csswg.org/cssom-1/#the-cssrule-interface
40		#[derive(ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
41		#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))]
42		pub enum NestedGroupRule<'a> {
43			$(
44				$name(rules::$ty$(<$a>)?),
45			)+
46			UnknownAt(UnknownAtRule<'a>),
47			Style(StyleRule<'a>),
48			Unknown(UnknownQualifiedRule<'a>),
49			BadDeclaration(BadDeclaration<'a>),
50		}
51	}
52}
53apply_rules!(nested_group_rule);
54
55macro_rules! define_atkeyword_set {
56	( $(
57		$name:ident($ty:ty): $str:tt,
58	)+ ) => {
59		atkeyword_set!(
60			enum AtRuleKeywords {
61				$($name: $str),+
62			}
63		);
64	}
65}
66
67apply_rules!(define_atkeyword_set);
68
69impl<'a> RuleVariants<'a> for NestedGroupRule<'a> {
70	fn parse_at_rule(p: &mut Parser<'a>, _name: Cursor) -> ParserResult<Self> {
71		let kw = p.parse::<AtRuleKeywords>()?;
72		macro_rules! parse_rule {
73			( $(
74				$name: ident($ty: ident$(<$a: lifetime>)?): $str: pat,
75			)+ ) => {
76				match kw {
77					$(AtRuleKeywords::$name(_) => p.parse::<rules::$ty>().map(Self::$name),)+
78				}
79			}
80		}
81		apply_rules!(parse_rule)
82	}
83
84	fn parse_unknown_at_rule(p: &mut Parser<'a>, _name: Cursor) -> ParserResult<Self> {
85		p.parse::<UnknownAtRule>().map(Self::UnknownAt)
86	}
87
88	fn parse_qualified_rule(p: &mut Parser<'a>, _name: Cursor) -> ParserResult<Self> {
89		p.parse::<StyleRule>().map(Self::Style)
90	}
91
92	fn parse_unknown_qualified_rule(p: &mut Parser<'a>, _name: Cursor) -> ParserResult<Self> {
93		p.parse::<UnknownQualifiedRule>().map(Self::Unknown)
94	}
95
96	fn bad_declaration(b: BadDeclaration<'a>) -> Option<Self> {
97		Some(Self::BadDeclaration(b))
98	}
99}
100
101impl<'a> Parse<'a> for NestedGroupRule<'a> {
102	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
103		Self::parse_rule_variants(p)
104	}
105}
106
107#[cfg(test)]
108mod tests {
109	use super::*;
110	use css_parse::assert_parse;
111
112	#[test]
113	fn size_test() {
114		assert_eq!(std::mem::size_of::<StyleRule>(), 128);
115	}
116
117	#[test]
118	fn test_writes() {
119		assert_parse!(StyleRule, "body{}");
120		assert_parse!(StyleRule, "body,body{}");
121		assert_parse!(StyleRule, "body{width:1px;}");
122		assert_parse!(StyleRule, "body{opacity:0;}");
123		assert_parse!(StyleRule, ".foo *{}", ".foo *{}");
124		assert_parse!(StyleRule, ":nth-child(1){opacity:0;}");
125		assert_parse!(StyleRule, ".foo{--bar:(baz);}");
126		assert_parse!(StyleRule, ".foo{width: calc(1px + (var(--foo)) + 1px);}");
127		assert_parse!(StyleRule, ".foo{--bar:1}");
128		assert_parse!(StyleRule, ":root{--custom:{width:0;height:0;};}");
129		// Semicolons are "allowed" in geneirc preludes
130		assert_parse!(StyleRule, ":root{a;b{}}");
131		// Bad Declarations should be parsable.
132		assert_parse!(StyleRule, ":root{$(var)-size: 100%;}");
133	}
134}