css_ast/
metadata.rs

1use crate::traits::{AppliesTo, BoxPortion, BoxSide, PropertyGroup};
2use bitmask_enum::bitmask;
3use css_lexer::{Span, ToSpan};
4use css_parse::{NodeMetadata, SemanticEq, ToCursors};
5
6#[bitmask(u32)]
7#[bitmask_config(vec_debug)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub enum AtRuleId {
10	Charset,
11	ColorProfile,
12	Container,
13	CounterStyle,
14	FontFace,
15	FontFeatureValues,
16	FontPaletteValues,
17	Import,
18	Keyframes,
19	Layer,
20	Media,
21	Namespace,
22	Page,
23	Property,
24	Scope,
25	StartingStyle,
26	Supports,
27	Document,
28	WebkitKeyframes,
29	MozDocument,
30}
31
32#[bitmask(u8)]
33#[bitmask_config(vec_debug)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
35pub enum VendorPrefixes {
36	Moz,
37	WebKit,
38	O,
39	Ms,
40}
41
42#[bitmask(u8)]
43#[bitmask_config(vec_debug)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
45pub enum DeclarationKind {
46	/// If an unknown declaration was used
47	Unknown,
48	/// If a declaration has !important
49	Important,
50	/// If a declaration used a css-wide keyword, e.g. `inherit` or `revert-layer`.
51	CssWideKeywords,
52	/// If a declaration is custom, e.g `--foo`
53	Custom,
54	/// If a declaration is computed-time, e.g. using `calc()` or `var()`
55	Computed,
56	/// If a declaration is shorthand
57	Shorthands,
58	/// If a declaration is longhand
59	Longhands,
60}
61
62#[bitmask(u16)]
63#[bitmask_config(vec_debug)]
64#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
65pub enum RuleKind {
66	/// If a rule is unknown
67	Unknown,
68	/// If a rule is a nested at-rules
69	NestedAtRule,
70	/// If a rule is a nested style-rule
71	NestedStyleRules,
72}
73
74/// Aggregated metadata computed from declarations within a block.
75/// This allows efficient checking of what types of properties a block contains
76/// without iterating through all declarations.
77#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
78#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
79pub struct CssMetadata {
80	/// Bitwise OR of all PropertyGroup values
81	pub property_groups: PropertyGroup,
82	/// Bitwise OR of all AppliesTo values
83	pub applies_to: AppliesTo,
84	/// Bitwise OR of all BoxSide values
85	pub box_sides: BoxSide,
86	/// Bitwise OR of all BoxPortion values
87	pub box_portions: BoxPortion,
88	/// Bitwise OR of all DeclarationKind values
89	pub declaration_kinds: DeclarationKind,
90	/// Bitwise OR of all RuleKind values
91	pub rule_kinds: RuleKind,
92	/// Bitwise OR of all AtRuleIds in a Node
93	pub used_at_rules: AtRuleId,
94	/// Bitwise OR of all VendorPrefixes in a Node
95	pub vendor_prefixes: VendorPrefixes,
96}
97
98impl Default for CssMetadata {
99	fn default() -> Self {
100		Self {
101			property_groups: PropertyGroup::none(),
102			applies_to: AppliesTo::none(),
103			box_sides: BoxSide::none(),
104			box_portions: BoxPortion::none(),
105			declaration_kinds: DeclarationKind::none(),
106			rule_kinds: RuleKind::none(),
107			used_at_rules: AtRuleId::none(),
108			vendor_prefixes: VendorPrefixes::none(),
109		}
110	}
111}
112
113impl CssMetadata {
114	/// Returns true if this metadata is empty (contains no properties or at-rules)
115	#[inline]
116	pub fn is_empty(&self) -> bool {
117		self.property_groups == PropertyGroup::none()
118			&& self.applies_to == AppliesTo::none()
119			&& self.box_sides == BoxSide::none()
120			&& self.box_portions == BoxPortion::none()
121			&& self.declaration_kinds == DeclarationKind::none()
122			&& self.rule_kinds == RuleKind::none()
123			&& self.used_at_rules == AtRuleId::none()
124			&& self.vendor_prefixes == VendorPrefixes::none()
125	}
126
127	/// Returns true if this block modifies any positioning-related properties.
128	#[inline]
129	pub fn modifies_box(&self) -> bool {
130		!self.box_portions.is_none()
131	}
132
133	/// Returns true if this block contains any color-related properties.
134	#[inline]
135	pub fn has_colors(&self) -> bool {
136		self.property_groups.intersects(PropertyGroup::Color | PropertyGroup::ColorHdr | PropertyGroup::ColorAdjust)
137	}
138}
139
140impl NodeMetadata for CssMetadata {
141	#[inline]
142	fn merge(&mut self, other: &Self) {
143		self.property_groups |= other.property_groups;
144		self.applies_to |= other.applies_to;
145		self.box_sides |= other.box_sides;
146		self.box_portions |= other.box_portions;
147		self.declaration_kinds |= other.declaration_kinds;
148		self.rule_kinds |= other.rule_kinds;
149		self.used_at_rules |= other.used_at_rules;
150		self.vendor_prefixes |= other.vendor_prefixes;
151	}
152}
153
154// Metadata is not serialized to tokens but providing these simplifies ToCursors/ToSpan impls
155impl ToCursors for CssMetadata {
156	fn to_cursors(&self, _: &mut impl css_parse::CursorSink) {}
157}
158impl ToSpan for CssMetadata {
159	fn to_span(&self) -> Span {
160		Span::DUMMY
161	}
162}
163
164impl SemanticEq for CssMetadata {
165	fn semantic_eq(&self, other: &Self) -> bool {
166		self == other
167	}
168}
169
170#[cfg(test)]
171mod tests {
172	use super::*;
173	use crate::{CssAtomSet, StyleSheet};
174	use css_lexer::Lexer;
175	use css_parse::{NodeWithMetadata, Parser};
176
177	#[test]
178	fn test_block_metadata_merge() {
179		let mut meta1 = CssMetadata::default();
180		meta1.property_groups = PropertyGroup::Color;
181		meta1.declaration_kinds = DeclarationKind::Important;
182
183		let mut meta2 = CssMetadata::default();
184		meta2.property_groups = PropertyGroup::Position;
185		meta2.declaration_kinds = DeclarationKind::Custom;
186
187		meta1.merge(&meta2);
188
189		assert!(meta1.property_groups.contains(PropertyGroup::Color));
190		assert!(meta1.property_groups.contains(PropertyGroup::Position));
191		assert!(meta1.declaration_kinds.contains(DeclarationKind::Important));
192		assert!(meta1.declaration_kinds.contains(DeclarationKind::Custom));
193	}
194
195	#[test]
196	fn test_stylesheet_metadata_simple() {
197		let css = "body { color: red; width: 100px; }";
198		let bump = bumpalo::Bump::new();
199		let lexer = Lexer::new(&CssAtomSet::ATOMS, css);
200		let mut parser = Parser::new(&bump, css, lexer);
201		let stylesheet = parser.parse::<StyleSheet>().unwrap();
202
203		let metadata = stylesheet.metadata();
204
205		dbg!(metadata);
206		assert!(metadata.property_groups.contains(PropertyGroup::Color));
207		assert!(metadata.property_groups.contains(PropertyGroup::Sizing));
208		assert!(metadata.modifies_box());
209		assert!(metadata.has_colors());
210		assert!(metadata.declaration_kinds.is_none());
211	}
212
213	#[test]
214	fn test_stylesheet_metadata_with_important() {
215		let css = "body { color: red !important; }";
216		let bump = bumpalo::Bump::new();
217		let lexer = Lexer::new(&CssAtomSet::ATOMS, css);
218		let mut parser = Parser::new(&bump, css, lexer);
219		let stylesheet = parser.parse::<StyleSheet>().unwrap();
220
221		let metadata = stylesheet.metadata();
222
223		assert!(metadata.declaration_kinds.contains(DeclarationKind::Important));
224		assert!(metadata.property_groups.contains(PropertyGroup::Color));
225	}
226
227	#[test]
228	fn test_stylesheet_metadata_custom_properties() {
229		let css = "body { --custom: value; }";
230		let bump = bumpalo::Bump::new();
231		let lexer = Lexer::new(&CssAtomSet::ATOMS, css);
232		let mut parser = Parser::new(&bump, css, lexer);
233		let stylesheet = parser.parse::<StyleSheet>().unwrap();
234
235		let metadata = stylesheet.metadata();
236
237		assert!(metadata.declaration_kinds.contains(DeclarationKind::Custom));
238	}
239
240	#[test]
241	fn test_stylesheet_metadata_nested_media() {
242		let css = "@media screen { body { color: red; } }";
243		let bump = bumpalo::Bump::new();
244		let lexer = Lexer::new(&CssAtomSet::ATOMS, css);
245		let mut parser = Parser::new(&bump, css, lexer);
246		let stylesheet = parser.parse::<StyleSheet>().unwrap();
247
248		let metadata = stylesheet.metadata();
249
250		assert!(metadata.property_groups.contains(PropertyGroup::Color));
251		assert!(metadata.used_at_rules.contains(AtRuleId::Media));
252		assert!(metadata.has_colors());
253	}
254}