Skip to main content

css_ast/
stylesheet.rs

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