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, SimpleBlock, 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 CustomMedia(CustomMediaRule<'a>): CssAtomSet::CustomMedia,
66 FontFace(FontFaceRule<'a>): CssAtomSet::FontFace,
67 FontFeatureValues(FontFeatureValuesRule<'a>): CssAtomSet::FontFeatureValues,
68 FontPaletteValues(FontPaletteValuesRule<'a>): CssAtomSet::FontPaletteValues,
69 Keyframes(KeyframesRule<'a>): CssAtomSet::Keyframes,
70 Layer(LayerRule<'a>): CssAtomSet::Layer,
71 Media(MediaRule<'a>): CssAtomSet::Media,
72 Namespace(NamespaceRule): CssAtomSet::Namespace,
73 Page(PageRule<'a>): CssAtomSet::Page,
74 Property(PropertyRule<'a>): CssAtomSet::Property,
75 Scope(ScopeRule<'a>): CssAtomSet::Scope,
76 StartingStyle(StartingStyleRule<'a>): CssAtomSet::StartingStyle,
77
78 Document(DocumentRule<'a>): CssAtomSet::Document,
80
81 WebkitKeyframes(WebkitKeyframesRule<'a>): CssAtomSet::_WebkitKeyframes,
83
84 MozDocument(MozDocumentRule<'a>): CssAtomSet::_MozDocument,
86 }
87 };
88}
89
90#[node]
91#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
93#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self), queryable(skip))]
94#[derive(csskit_derives::NodeWithMetadata)]
95#[metadata(node_kinds = Unknown | Effective, property_kinds = Name)]
96pub struct UnknownAtRule<'a> {
97 name: T![AtKeyword],
98 prelude: UnknownAtRulePrelude<'a>,
99 block: Option<SimpleBlock<'a>>,
100 #[semantic_eq(skip)]
101 semicolon: Option<T![;]>,
102}
103
104#[node]
105#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
106#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
107#[parse(stop = KindSet::LEFT_CURLY_OR_SEMICOLON)]
108#[derive(csskit_derives::NodeWithMetadata)]
109pub struct UnknownAtRulePrelude<'a>(ComponentValues<'a>);
110
111#[cfg(feature = "visitable")]
112impl<'a> QueryableNode for UnknownAtRule<'a> {
113 const NODE_ID: NodeId = NodeId::UnknownAtRule;
114
115 fn get_property(&self, kind: PropertyKind) -> Option<Cursor> {
116 match kind {
117 PropertyKind::Name => Some(self.name.into()),
118 _ => None,
119 }
120 }
121}
122
123#[node]
124#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
125#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
126#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
127#[derive(csskit_derives::NodeWithMetadata)]
128#[metadata(node_kinds = Unknown | Effective)]
129pub struct UnknownQualifiedRule<'a>(
130 QualifiedRule<
131 'a,
132 UnknownRuleBlock<'a>,
133 StyleValue<'a>,
134 UnknownRuleBlock<'a, StyleValue<'a>, CssMetadata>,
135 CssMetadata,
136 >,
137);
138
139macro_rules! rule {
140 ( $(
141 $name: ident($ty: ident$(<$a: lifetime>)?): $str: pat,
142 )+ ) => {
143 #[node]
145 #[derive(ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
146 #[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
147 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))]
148 #[derive(csskit_derives::NodeWithMetadata)]
149 pub enum Rule<'a> {
150 $(
151 $name(Box<'a, rules::$ty$(<$a>)?>),
152 )+
153 Import(Box<'a, rules::ImportRule<'a>>),
154 Supports(Box<'a, rules::SupportsRule<'a>>),
155
156 UnknownAt(Box<'a, UnknownAtRule<'a>>),
157 Style(Box<'a, StyleRule<'a>>),
158 Unknown(Box<'a, UnknownQualifiedRule<'a>>)
159 }
160 }
161}
162
163apply_rules!(rule);
164
165impl<'a> RuleVariants<'a> for Rule<'a> {
166 type DeclarationValue = StyleValue<'a>;
167 type Metadata = CssMetadata;
168
169 fn parse_at_rule<I>(p: &mut Parser<'a, I>, c: Cursor) -> ParserResult<Self>
170 where
171 I: Iterator<Item = Cursor> + Clone,
172 {
173 macro_rules! parse_rule {
174 ( $(
175 $name: ident($ty: ident$(<$a: lifetime>)?): $atoms: pat,
176 )+ ) => {
177 match p.to_atom::<CssAtomSet>(c) {
178 $($atoms => p.parse::<Box<rules::$ty>>().map(Self::$name),)+
179 CssAtomSet::Import => p.parse::<rules::ImportRule>().map(|r| Self::Import(Box::new_in(p.alloc(), r))),
180 CssAtomSet::Supports => p.parse::<rules::SupportsRule>().map(|r| Self::Supports(Box::new_in(p.alloc(), r))),
181 _ => Err(Diagnostic::new(p.next(), Diagnostic::unexpected))?,
182 }
183 }
184 }
185 apply_rules!(parse_rule)
186 }
187
188 fn parse_unknown_at_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
189 where
190 I: Iterator<Item = Cursor> + Clone,
191 {
192 p.parse::<Box<UnknownAtRule>>().map(Self::UnknownAt)
193 }
194
195 fn parse_qualified_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
196 where
197 I: Iterator<Item = Cursor> + Clone,
198 {
199 p.parse::<Box<StyleRule>>().map(Self::Style)
200 }
201
202 fn parse_unknown_qualified_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
203 where
204 I: Iterator<Item = Cursor> + Clone,
205 {
206 p.parse::<Box<UnknownQualifiedRule>>().map(Self::Unknown)
207 }
208}
209
210impl<'a> Peek<'a> for Rule<'a> {
211 const PEEK_KINDSET: KindSet = KindSet::ANY;
212}
213
214impl<'a> Parse<'a> for Rule<'a> {
215 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
216 where
217 I: Iterator<Item = Cursor> + Clone,
218 {
219 Self::parse_rule_variants(p)
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226 use crate::CssAtomSet;
227 use css_parse::assert_parse;
228
229 #[test]
230 fn test_writes() {
231 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body{}");
232 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body{color:red;}");
233 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body,tr:nth-child(n-1){}");
234 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body{width:1px;}");
235 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body{width:1px;}.a{width:2px;}");
236 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "one:1;a{two:2}");
237 assert_parse!(CssAtomSet::ATOMS, Rule, "@media screen{}", Rule::Media(_));
238 assert_parse!(CssAtomSet::ATOMS, Rule, "@layer foo{}", Rule::Layer(_));
239 }
240
241 #[test]
242 fn cdc_in_unknown_rule_block() {
243 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "={-->}");
244 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "={-->");
245 }
246
247 #[test]
248 fn stray_close_bracket_in_block() {
249 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "%{)");
250 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "t% {90)");
251 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "{--ty-2% {90)");
252 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "%{]");
253 }
254
255 #[test]
256 fn unknown_at_rule_ends_at_semicolon_or_block() {
257 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "@foo bar;@media screen{}", |sheet| {
258 assert!(matches!(sheet.rules[..], [Rule::UnknownAt(_), Rule::Media(_)]))
259 });
260 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "@foo bar{baz}@media screen{}", |sheet| {
261 assert!(matches!(sheet.rules[..], [Rule::UnknownAt(_), Rule::Media(_)]))
262 });
263 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "a{@foo bar;color:red}", |sheet| {
264 assert!(matches!(sheet.rules[..], [Rule::Style(_)]))
265 });
266 }
267}