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