css_ast/rules/container/
mod.rs

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