css_ast 0.0.25

CSS Abstract Syntax Trees with visitable nodes and style value types.
Documentation
use crate::{CssAtomSet, CssDiagnostic};
use css_parse::{Cursor, Diagnostic, Parse, Parser, Result as ParserResult, T};
use csskit_derives::*;

use super::{moz::MozPseudoClass, ms::MsPseudoClass, o::OPseudoClass, webkit::WebkitPseudoClass};

macro_rules! apply_pseudo_class {
	($macro: ident) => {
		$macro! {
			Active: CssAtomSet::Active,
			AnyLink: CssAtomSet::AnyLink,
			Autofill: CssAtomSet::Autofill,
			Blank: CssAtomSet::Blank,
			Buffering: CssAtomSet::Buffering,
			Checked: CssAtomSet::Checked,
			Current: CssAtomSet::Current,
			Default: CssAtomSet::Default,
			Defined: CssAtomSet::Defined,
			Disabled: CssAtomSet::Disabled,
			Empty: CssAtomSet::Empty,
			Enabled: CssAtomSet::Enabled,
			First: CssAtomSet::First,
			FirstChild: CssAtomSet::FirstChild,
			FirstOfType: CssAtomSet::FirstOfType,
			Focus: CssAtomSet::Focus,
			FocusVisible: CssAtomSet::FocusVisible,
			FocusWithin: CssAtomSet::FocusWithin,
			Fullscreen: CssAtomSet::Fullscreen,
			Future: CssAtomSet::Future,
			HasSlotted: CssAtomSet::HasSlotted,
			Host: CssAtomSet::Host,
			Heading: CssAtomSet::Heading,
			Hover: CssAtomSet::Hover,
			InRange: CssAtomSet::InRange,
			Indeterminate: CssAtomSet::Indeterminate,
			Invalid: CssAtomSet::Invalid,
			LastChild: CssAtomSet::LastChild,
			LastOfType: CssAtomSet::LastOfType,
			Left: CssAtomSet::Left,
			Link: CssAtomSet::Link,
			LocalLink: CssAtomSet::LocalLink,
			Modal: CssAtomSet::Modal,
			Muted: CssAtomSet::Muted,
			OnlyChild: CssAtomSet::OnlyChild,
			OnlyOfType: CssAtomSet::OnlyOfType,
			Open: CssAtomSet::Open,
			Optional: CssAtomSet::Optional,
			OutOfRange: CssAtomSet::OutOfRange,
			Past: CssAtomSet::Past,
			Paused: CssAtomSet::Paused,
			PictureInPicture: CssAtomSet::PictureInPicture,
			PlaceholderShown: CssAtomSet::PlaceholderShown,
			Playing: CssAtomSet::Playing,
			PopoverOpen: CssAtomSet::PopoverOpen,
			ReadOnly: CssAtomSet::ReadOnly,
			ReadWrite: CssAtomSet::ReadWrite,
			Required: CssAtomSet::Required,
			Right: CssAtomSet::Right,
			Root: CssAtomSet::Root,
			Scope: CssAtomSet::Scope,
			Seeking: CssAtomSet::Seeking,
			Stalled: CssAtomSet::Stalled,
			Target: CssAtomSet::Target,
			TargetCurrent: CssAtomSet::TargetCurrent,
			TargetWithin: CssAtomSet::TargetWithin,
			UserInvalid: CssAtomSet::UserInvalid,
			Valid: CssAtomSet::Valid,
			Visited: CssAtomSet::Visited,
			VolumeLocked: CssAtomSet::VolumeLocked,
		}
	};
}

macro_rules! define_pseudo_class {
	( $($(#[$meta:meta])* $ident: ident: $pat: pat $(,)*)+ ) => {
		#[derive(Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
		#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
		#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
#[derive(csskit_derives::NodeWithMetadata)]
		#[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.selectors"))]
		pub enum PseudoClass {
			$($(#[$meta])* $ident(T![:], T![Ident]),)+
			Webkit(WebkitPseudoClass),
			Moz(MozPseudoClass),
			Ms(MsPseudoClass),
			O(OPseudoClass),
		}
	};
}
apply_pseudo_class!(define_pseudo_class);

impl<'a> Parse<'a> for PseudoClass {
	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
	where
		I: Iterator<Item = Cursor> + Clone,
	{
		let c = p.peek_n(2);
		macro_rules! match_keyword {
			( $($(#[$meta:meta])* $ident: ident: $pat: pat $(,)*)+ ) => {
				match p.to_atom::<CssAtomSet>(c) {
					$($pat => {
						let colon = p.parse::<T![:]>()?;
						let ident = p.parse::<T![Ident]>()?;
						Ok(Self::$ident(colon, ident))
					})+
					_ => {
						if let Ok(psuedo) = p.try_parse::<WebkitPseudoClass>() {
							return Ok(Self::Webkit(psuedo));
						}
						if let Ok(psuedo) = p.try_parse::<MozPseudoClass>() {
							return Ok(Self::Moz(psuedo));
						}
						if let Ok(psuedo) = p.try_parse::<MsPseudoClass>() {
							return Ok(Self::Ms(psuedo));
						}
						if let Ok(psuedo) = p.try_parse::<OPseudoClass>() {
							return Ok(Self::O(psuedo));
						}
						Err(Diagnostic::new(c, Diagnostic::unexpected_pseudo_class))?
					}
				}
			};
		}
		apply_pseudo_class!(match_keyword)
	}
}

#[cfg(test)]
mod tests {
	use super::*;
	use crate::CssAtomSet;
	use css_parse::assert_parse;

	#[test]
	fn size_test() {
		assert_eq!(std::mem::size_of::<PseudoClass>(), 32);
	}

	#[test]
	fn test_writes() {
		assert_parse!(CssAtomSet::ATOMS, PseudoClass, ":target");
		assert_parse!(CssAtomSet::ATOMS, PseudoClass, ":scope");
		assert_parse!(CssAtomSet::ATOMS, PseudoClass, ":valid");
	}

	#[cfg(feature = "css_feature_data")]
	#[test]
	fn test_feature_data() {
		use crate::assert_feature_id;
		assert_feature_id!(":hover", PseudoClass, "css.selectors.hover");
		assert_feature_id!(":future", PseudoClass, "css.selectors.future");
		assert_feature_id!(":volume-locked", PseudoClass, "css.selectors.volume-locked");
	}
}