css_ast/rules/container/
mod.rs

1use crate::{Rule, Visit, VisitMut, Visitable as VisitableTrait, VisitableMut, diagnostics};
2use bumpalo::collections::Vec;
3use css_parse::{
4	AtRule, Build, ConditionKeyword, Cursor, FeatureConditionList, Kind, Parse, Parser, Peek, PreludeList,
5	Result as ParserResult, RuleList, T, atkeyword_set, keyword_set,
6};
7use csskit_derives::{Parse, Peek, ToCursors, ToSpan, Visitable};
8use csskit_proc_macro::visit;
9
10mod features;
11pub use features::*;
12
13atkeyword_set!(pub struct AtContainerKeyword "container");
14
15// https://drafts.csswg.org/css-contain-3/#container-rule
16#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
18#[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.at-rules.container"))]
19#[visit]
20pub struct ContainerRule<'a>(pub AtRule<AtContainerKeyword, ContainerConditionList<'a>, ContainerRulesBlock<'a>>);
21
22#[derive(Parse, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
24pub struct ContainerRulesBlock<'a>(RuleList<'a, Rule<'a>>);
25
26#[derive(ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
28pub struct ContainerConditionList<'a>(pub Vec<'a, ContainerCondition<'a>>);
29
30impl<'a> PreludeList<'a> for ContainerConditionList<'a> {
31	type PreludeItem = ContainerCondition<'a>;
32}
33
34impl<'a> Parse<'a> for ContainerConditionList<'a> {
35	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
36		Ok(Self(Self::parse_prelude_list(p)?))
37	}
38}
39
40#[derive(ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
42pub struct ContainerCondition<'a> {
43	#[visit(skip)]
44	pub name: Option<T![Ident]>,
45	pub condition: Option<ContainerQuery<'a>>,
46}
47
48impl<'a> Parse<'a> for ContainerCondition<'a> {
49	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
50		let mut name = None;
51		let c = p.peek_n(1);
52		if c == Kind::Ident {
53			match p.parse_str_lower(c) {
54				"none" | "and" | "not" | "or" => {}
55				_ => {
56					name = Some(p.parse::<T![Ident]>()?);
57				}
58			}
59		}
60		let condition =
61			if name.is_none() { Some(p.parse::<ContainerQuery>()?) } else { p.parse_if_peek::<ContainerQuery>()? };
62		Ok(Self { name, condition })
63	}
64}
65
66#[derive(ToCursors, ToSpan, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
68#[visit]
69pub enum ContainerQuery<'a> {
70	Is(ContainerFeature<'a>),
71	Not(ConditionKeyword, ContainerFeature<'a>),
72	And(Vec<'a, (ContainerFeature<'a>, Option<ConditionKeyword>)>),
73	Or(Vec<'a, (ContainerFeature<'a>, Option<ConditionKeyword>)>),
74}
75
76impl<'a> Peek<'a> for ContainerQuery<'a> {
77	fn peek(p: &Parser<'a>, c: Cursor) -> bool {
78		<T![Function]>::peek(p, c) || <T![Ident]>::peek(p, c)
79	}
80}
81
82impl<'a> Parse<'a> for ContainerQuery<'a> {
83	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
84		Self::parse_condition(p)
85	}
86}
87
88impl<'a> FeatureConditionList<'a> for ContainerQuery<'a> {
89	type FeatureCondition = ContainerFeature<'a>;
90	fn build_is(feature: ContainerFeature<'a>) -> Self {
91		Self::Is(feature)
92	}
93	fn build_not(keyword: ConditionKeyword, feature: ContainerFeature<'a>) -> Self {
94		Self::Not(keyword, feature)
95	}
96	fn build_and(feature: Vec<'a, (ContainerFeature<'a>, Option<ConditionKeyword>)>) -> Self {
97		Self::And(feature)
98	}
99	fn build_or(feature: Vec<'a, (ContainerFeature<'a>, Option<ConditionKeyword>)>) -> Self {
100		Self::Or(feature)
101	}
102}
103
104impl<'a> VisitableTrait for ContainerQuery<'a> {
105	fn accept<V: Visit>(&self, v: &mut V) {
106		v.visit_container_query(self);
107		match self {
108			Self::Is(feature) => feature.accept(v),
109			Self::Not(_, feature) => feature.accept(v),
110			Self::And(features) => {
111				for (feature, _) in features {
112					feature.accept(v);
113				}
114			}
115			Self::Or(features) => {
116				for (feature, _) in features {
117					feature.accept(v);
118				}
119			}
120		}
121	}
122}
123
124impl<'a> VisitableMut for ContainerQuery<'a> {
125	fn accept_mut<V: VisitMut>(&mut self, v: &mut V) {
126		v.visit_container_query(self);
127		match self {
128			Self::Is(feature) => feature.accept_mut(v),
129			Self::Not(_, feature) => feature.accept_mut(v),
130			Self::And(features) => {
131				for (feature, _) in features {
132					feature.accept_mut(v);
133				}
134			}
135			Self::Or(features) => {
136				for (feature, _) in features {
137					feature.accept_mut(v);
138				}
139			}
140		}
141	}
142}
143
144macro_rules! container_feature {
145	( $($name: ident($typ: ident): $str: tt,)+ ) => {
146		#[allow(clippy::large_enum_variant)] // TODO: refine
147		#[derive(ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
148		#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
149		#[visit]
150		pub enum ContainerFeature<'a> {
151			$($name($typ),)+
152			Style(StyleQuery<'a>),
153			ScrollState(ScrollStateQuery<'a>),
154		}
155	}
156}
157
158apply_container_features!(container_feature);
159
160macro_rules! container_feature_keyword {
161	( $($name: ident($typ: ident): $str: tt,)+) => {
162		keyword_set!(pub enum ContainerFeatureKeyword {
163			$($name: $str,)+
164		});
165	}
166}
167apply_container_features!(container_feature_keyword);
168
169impl<'a> Parse<'a> for ContainerFeature<'a> {
170	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
171		if p.peek::<T![Function]>() {
172			todo!();
173		}
174		let mut c = p.peek_n(2);
175		macro_rules! match_feature {
176			( $($name: ident($typ: ident): $str: tt,)+) => {
177				// Only peek at the token as the underlying media feature parser needs to parse the leading keyword.
178				{
179					if ContainerFeatureKeyword::peek(p, c) {
180						match ContainerFeatureKeyword::build(p, c) {
181							$(ContainerFeatureKeyword::$name(_) => {
182								let value = $typ::parse(p)?;
183								Self::$name(value)
184							},)+
185						}
186					} else {
187						let source_cursor = p.to_source_cursor(c);
188						Err(diagnostics::UnexpectedIdent(source_cursor.to_string(), c))?
189					}
190				}
191			}
192		}
193		if c == Kind::Ident {
194			Ok(apply_container_features!(match_feature))
195		} else {
196			// Styles like (1em < width < 1em) or (1em <= width <= 1em)
197			c = p.peek_n(3);
198			if c != Kind::Ident {
199				c = p.peek_n(4)
200			}
201			Ok(apply_container_features!(match_feature))
202		}
203	}
204}
205
206macro_rules! apply_container_features {
207	($macro: ident) => {
208		$macro! {
209			// https://drafts.csswg.org/css-conditional-5/#container-features
210			Width(WidthContainerFeature): "width",
211			Height(HeightContainerFeature): "height",
212			InlineSize(InlineSizeContainerFeature): "inline-size",
213			BlockSize(BlockSizeContainerFeature): "block-size",
214			AspectRatio(AspectRatioContainerFeature): "aspect-ratio",
215			Orientation(OrientationContainerFeature): "orientation",
216		}
217	};
218}
219use apply_container_features;
220
221#[cfg(test)]
222mod tests {
223	use super::*;
224	use css_parse::assert_parse;
225
226	#[test]
227	fn size_test() {
228		assert_eq!(std::mem::size_of::<ContainerRule>(), 128);
229		assert_eq!(std::mem::size_of::<ContainerConditionList>(), 32);
230		assert_eq!(std::mem::size_of::<ContainerCondition>(), 416);
231		assert_eq!(std::mem::size_of::<ContainerQuery>(), 400);
232	}
233
234	#[test]
235	fn test_writes() {
236		assert_parse!(ContainerQuery, "(width:2px)");
237		assert_parse!(ContainerCondition, "(width:2px)");
238		assert_parse!(ContainerCondition, "(inline-size>30em)");
239		assert_parse!(ContainerCondition, "(1em<width<1em)");
240		assert_parse!(ContainerRule, "@container foo{}");
241		assert_parse!(ContainerRule, "@container foo (width:2px){}");
242		assert_parse!(ContainerRule, "@container foo (10em<width<10em){}");
243		assert_parse!(ContainerRule, "@container foo (width:2px){body{color:black}}");
244	}
245}