css_ast/
stylesheet.rs

1use crate::{CssAtomSet, CssMetadata, RuleKind, StyleValue, rules, stylerule::StyleRule};
2use bumpalo::collections::Vec;
3use css_parse::{
4	ComponentValues, Cursor, Diagnostic, NodeWithMetadata, Parse, Parser, QualifiedRule, Result as ParserResult,
5	RuleVariants, StyleSheet as StyleSheetTrait, T,
6};
7use csskit_derives::{Parse, Peek, SemanticEq, ToCursors, ToSpan};
8
9// https://drafts.csswg.org/cssom-1/#the-cssstylesheet-interface
10#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
12#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
13pub struct StyleSheet<'a> {
14	pub rules: Vec<'a, Rule<'a>>,
15	#[to_cursors(skip)]
16	#[cfg_attr(feature = "serde", serde(skip))]
17	#[cfg_attr(feature = "visitable", visit(skip))]
18	meta: CssMetadata,
19}
20
21impl<'a> NodeWithMetadata<CssMetadata> for StyleSheet<'a> {
22	fn metadata(&self) -> CssMetadata {
23		self.meta
24	}
25}
26
27// A StyleSheet represents the root node of a CSS-like language.
28// The StyleSheet trait represents an abstraction of this, which allows for
29// alternate implementations such as SCSS.
30// AtRules vs QualifiedRules are differentiated by two different functions.
31impl<'a> Parse<'a> for StyleSheet<'a> {
32	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
33	where
34		I: Iterator<Item = Cursor> + Clone,
35	{
36		let (rules, meta) = Self::parse_stylesheet(p)?;
37		Ok(Self { rules, meta })
38	}
39}
40
41impl<'a> StyleSheetTrait<'a, CssMetadata> for StyleSheet<'a> {
42	type Rule = Rule<'a>;
43}
44
45macro_rules! apply_rules {
46	($macro: ident) => {
47		$macro! {
48			Charset(CharsetRule): CssAtomSet::Charset,
49			ColorProfile(ColorProfileRule): CssAtomSet::ColorProfile,
50			Container(ContainerRule<'a>): CssAtomSet::Container,
51			CounterStyle(CounterStyleRule): CssAtomSet::CounterStyle,
52			FontFace(FontFaceRule<'a>): CssAtomSet::FontFace,
53			FontFeatureValues(FontFeatureValuesRule): CssAtomSet::FontFeatureValues,
54			FontPaletteValues(FontPaletteValuesRule): CssAtomSet::FontPaletteValues,
55			Import(ImportRule): CssAtomSet::Import,
56			Keyframes(KeyframesRule<'a>): CssAtomSet::Keyframes,
57			Layer(LayerRule<'a>): CssAtomSet::Layer,
58			Media(MediaRule<'a>): CssAtomSet::Media,
59			Namespace(NamespaceRule): CssAtomSet::Namespace,
60			Page(PageRule<'a>): CssAtomSet::Page,
61			Property(PropertyRule<'a>): CssAtomSet::Property,
62			Scope(ScopeRule): CssAtomSet::Scope,
63			StartingStyle(StartingStyleRule): CssAtomSet::StartingStyle,
64			Supports(SupportsRule<'a>): CssAtomSet::Supports,
65
66			// Deprecated Rules
67			Document(DocumentRule<'a>): CssAtomSet::Document,
68
69			// Vendor Prefixed
70			WebkitKeyframes(WebkitKeyframesRule<'a>): CssAtomSet::_WebkitKeyframes,
71
72			// https://developer.mozilla.org/en-US/docs/Web/CSS/Mozilla_Extensions#at-rules
73			MozDocument(MozDocumentRule<'a>): CssAtomSet::_MozDocument,
74		}
75	};
76}
77
78#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
79#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
80#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
81pub struct UnknownAtRule<'a> {
82	name: T![AtKeyword],
83	prelude: ComponentValues<'a>,
84	block: ComponentValues<'a>,
85}
86
87impl<'a> NodeWithMetadata<CssMetadata> for UnknownAtRule<'a> {
88	fn metadata(&self) -> CssMetadata {
89		CssMetadata { rule_kinds: RuleKind::Unknown, ..Default::default() }
90	}
91}
92
93#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
94#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
95#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
96pub struct UnknownQualifiedRule<'a>(
97	QualifiedRule<'a, ComponentValues<'a>, StyleValue<'a>, ComponentValues<'a>, CssMetadata>,
98);
99
100impl<'a> NodeWithMetadata<CssMetadata> for UnknownQualifiedRule<'a> {
101	fn metadata(&self) -> CssMetadata {
102		let mut meta = self.0.metadata();
103		meta.rule_kinds |= RuleKind::Unknown;
104		meta
105	}
106}
107
108macro_rules! rule {
109    ( $(
110        $name: ident($ty: ident$(<$a: lifetime>)?): $str: pat,
111    )+ ) => {
112		#[allow(clippy::large_enum_variant)] // TODO: Box?
113		// https://drafts.csswg.org/cssom-1/#the-cssrule-interface
114		#[derive(ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
115		#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
116		#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))]
117		pub enum Rule<'a> {
118			$(
119				$name(rules::$ty$(<$a>)?),
120			)+
121			UnknownAt(UnknownAtRule<'a>),
122			Style(StyleRule<'a>),
123			Unknown(UnknownQualifiedRule<'a>)
124		}
125	}
126}
127
128apply_rules!(rule);
129
130impl<'a> NodeWithMetadata<CssMetadata> for Rule<'a> {
131	fn metadata(&self) -> CssMetadata {
132		macro_rules! match_rule {
133			( $(
134				$name: ident($ty: ident$(<$a: lifetime>)?): $atoms: pat,
135			)+ ) => {
136				match self {
137					$(Self::$name(r) => r.metadata(),)+
138					Self::Style(r) => r.metadata(),
139					Self::UnknownAt(r) => r.metadata(),
140					Self::Unknown(r) => r.metadata(),
141				}
142			}
143		}
144		apply_rules!(match_rule)
145	}
146}
147
148impl<'a> RuleVariants<'a> for Rule<'a> {
149	fn parse_at_rule<I>(p: &mut Parser<'a, I>, c: Cursor) -> ParserResult<Self>
150	where
151		I: Iterator<Item = Cursor> + Clone,
152	{
153		macro_rules! parse_rule {
154			( $(
155				$name: ident($ty: ident$(<$a: lifetime>)?): $atoms: pat,
156			)+ ) => {
157				match p.to_atom::<CssAtomSet>(c) {
158					$($atoms => p.parse::<rules::$ty>().map(Self::$name),)+
159					_ => Err(Diagnostic::new(p.next(), Diagnostic::unexpected))?,
160				}
161			}
162		}
163		apply_rules!(parse_rule)
164	}
165
166	fn parse_unknown_at_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
167	where
168		I: Iterator<Item = Cursor> + Clone,
169	{
170		p.parse::<UnknownAtRule>().map(Self::UnknownAt)
171	}
172
173	fn parse_qualified_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
174	where
175		I: Iterator<Item = Cursor> + Clone,
176	{
177		p.parse::<StyleRule>().map(Self::Style)
178	}
179
180	fn parse_unknown_qualified_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
181	where
182		I: Iterator<Item = Cursor> + Clone,
183	{
184		p.parse::<UnknownQualifiedRule>().map(Self::Unknown)
185	}
186}
187
188impl<'a> Parse<'a> for Rule<'a> {
189	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
190	where
191		I: Iterator<Item = Cursor> + Clone,
192	{
193		Self::parse_rule_variants(p)
194	}
195}
196
197#[cfg(test)]
198mod tests {
199	use super::*;
200	use crate::CssAtomSet;
201	use css_parse::assert_parse;
202
203	#[test]
204	fn size_test() {
205		assert_eq!(std::mem::size_of::<StyleSheet>(), 56);
206		assert_eq!(std::mem::size_of::<Rule>(), 520);
207	}
208
209	#[test]
210	fn test_writes() {
211		assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body{}");
212		assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body{color:red;}");
213		assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body,tr:nth-child(n-1){}");
214		assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body{width:1px;}");
215		assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body{width:1px;}.a{width:2px;}");
216		assert_parse!(CssAtomSet::ATOMS, StyleSheet, "one:1;a{two:2}");
217		assert_parse!(CssAtomSet::ATOMS, Rule, "@media screen{}", Rule::Media(_));
218		assert_parse!(CssAtomSet::ATOMS, Rule, "@layer foo{}", Rule::Layer(_));
219	}
220}