//! Visitors for traversing the values in a StyleSheet. //! //! The [Visitor](Visitor) trait includes methods for visiting and transforming rules, properties, and values within a StyleSheet. //! Each value implements the [Visit](Visit) trait, which knows how to visit the value itself, as well as its children. //! A Visitor is configured to only visit specific types of values using [VisitTypes](VisitTypes) flags. This enables //! entire branches to be skipped when a type does not contain any relevant values. //! //! # Example //! //! This example transforms a stylesheet, adding a prefix to all URLs, and converting pixels to rems. //! //! ``` //! use std::convert::Infallible; //! use lightningcss::{ //! stylesheet::{StyleSheet, ParserOptions, PrinterOptions}, //! visitor::{Visitor, Visit, VisitTypes}, //! visit_types, //! values::length::LengthValue, //! values::url::Url //! }; //! //! let mut stylesheet = StyleSheet::parse( //! r#" //! .foo { //! background: url(bg.png); //! width: 32px; //! } //! "#, //! ParserOptions::default() //! ).unwrap(); //! //! struct MyVisitor; //! impl<'i> Visitor<'i> for MyVisitor { //! type Error = Infallible; //! //! fn visit_types(&self) -> VisitTypes { //! visit_types!(URLS | LENGTHS) //! } //! //! fn visit_url(&mut self, url: &mut Url<'i>) -> Result<(), Self::Error> { //! url.url = format!("https://mywebsite.com/{}", url.url).into(); //! Ok(()) //! } //! //! fn visit_length(&mut self, length: &mut LengthValue) -> Result<(), Self::Error> { //! match length { //! LengthValue::Px(px) => *length = LengthValue::Rem(*px / 16.0), //! _ => {} //! } //! //! Ok(()) //! } //! } //! //! stylesheet.visit(&mut MyVisitor).unwrap(); //! //! let res = stylesheet.to_css(PrinterOptions { minify: true, ..Default::default() }).unwrap(); //! assert_eq!(res.code, ".foo{background:url(https://mywebsite.com/bg.png);width:2rem}"); //! ``` use crate::{ declaration::DeclarationBlock, media_query::{MediaFeature, MediaFeatureValue, MediaList, MediaQuery}, parser::DefaultAtRule, properties::{ custom::{EnvironmentVariable, Function, TokenList, TokenOrValue, Variable}, Property, }, rules::{supports::SupportsCondition, CssRule, CssRuleList}, selector::{Selector, SelectorList}, stylesheet::StyleSheet, values::{ angle::Angle, color::CssColor, ident::{CustomIdent, DashedIdent}, image::Image, length::LengthValue, ratio::Ratio, resolution::Resolution, time::Time, url::Url, }, }; use bitflags::bitflags; use indexmap::IndexMap; use smallvec::SmallVec; pub(crate) use lightningcss_derive::Visit; bitflags! { /// Describes what a [Visitor](Visitor) will visit when traversing a StyleSheet. /// /// Flags may be combined to visit multiple types. The [visit_types](visit_types) macro allows /// combining flags in a `const` expression. #[derive(PartialEq, Eq, Clone, Copy)] pub struct VisitTypes: u32 { /// Visit rules. const RULES = 1 << 0; /// Visit properties; const PROPERTIES = 1 << 1; /// Visit urls. const URLS = 1 << 2; /// Visit colors. const COLORS = 1 << 3; /// Visit images. const IMAGES = 1 << 4; /// Visit lengths. const LENGTHS = 1 << 5; /// Visit angles. const ANGLES = 1 << 6; /// Visit ratios. const RATIOS = 1 << 7; /// Visit resolutions. const RESOLUTIONS = 1 << 8; /// Visit times. const TIMES = 1 << 9; /// Visit custom identifiers. const CUSTOM_IDENTS = 1 << 10; /// Visit dashed identifiers. const DASHED_IDENTS = 1 << 11; /// Visit variables. const VARIABLES = 1 << 12; /// Visit environment variables. const ENVIRONMENT_VARIABLES = 1 << 13; /// Visit media queries. const MEDIA_QUERIES = 1 << 14; /// Visit supports conditions. const SUPPORTS_CONDITIONS = 1 << 15; /// Visit selectors. const SELECTORS = 1 << 16; /// Visit custom functions. const FUNCTIONS = 1 << 17; /// Visit a token. const TOKENS = 1 << 18; } } /// Constructs a constant [VisitTypes](VisitTypes) from flags. #[macro_export] macro_rules! visit_types { ($( $flag: ident )|+) => { $crate::visitor::VisitTypes::from_bits_truncate(0 $(| $crate::visitor::VisitTypes::$flag.bits())+) } } /// A trait for visiting or transforming rules, properties, and values in a StyleSheet. pub trait Visitor<'i, T: Visit<'i, T, Self> = DefaultAtRule> { /// The `Err` value for `Result`s returned by `visit_*` methods. type Error; /// Returns the types of values that this visitor should visit. By default, it returns /// `Self::TYPES`, but this can be overridden to change the value at runtime. fn visit_types(&self) -> VisitTypes; /// Visits a stylesheet. #[inline] fn visit_stylesheet<'o>(&mut self, stylesheet: &mut StyleSheet<'i, 'o, T>) -> Result<(), Self::Error> { stylesheet.visit_children(self) } /// Visits a rule list. #[inline] fn visit_rule_list(&mut self, rules: &mut CssRuleList<'i, T>) -> Result<(), Self::Error> { rules.visit_children(self) } /// Visits a rule. #[inline] fn visit_rule(&mut self, rule: &mut CssRule<'i, T>) -> Result<(), Self::Error> { rule.visit_children(self) } /// Visits a declaration block. #[inline] fn visit_declaration_block(&mut self, decls: &mut DeclarationBlock<'i>) -> Result<(), Self::Error> { decls.visit_children(self) } /// Visits a property. #[inline] fn visit_property(&mut self, property: &mut Property<'i>) -> Result<(), Self::Error> { property.visit_children(self) } /// Visits a url. fn visit_url(&mut self, _url: &mut Url<'i>) -> Result<(), Self::Error> { Ok(()) } /// Visits a color. #[allow(unused_variables)] fn visit_color(&mut self, color: &mut CssColor) -> Result<(), Self::Error> { Ok(()) } /// Visits an image. #[inline] fn visit_image(&mut self, image: &mut Image<'i>) -> Result<(), Self::Error> { image.visit_children(self) } /// Visits a length. #[allow(unused_variables)] fn visit_length(&mut self, length: &mut LengthValue) -> Result<(), Self::Error> { Ok(()) } /// Visits an angle. #[allow(unused_variables)] fn visit_angle(&mut self, angle: &mut Angle) -> Result<(), Self::Error> { Ok(()) } /// Visits a ratio. #[allow(unused_variables)] fn visit_ratio(&mut self, ratio: &mut Ratio) -> Result<(), Self::Error> { Ok(()) } /// Visits a resolution. #[allow(unused_variables)] fn visit_resolution(&mut self, resolution: &mut Resolution) -> Result<(), Self::Error> { Ok(()) } /// Visits a time. #[allow(unused_variables)] fn visit_time(&mut self, time: &mut Time) -> Result<(), Self::Error> { Ok(()) } /// Visits a custom ident. #[allow(unused_variables)] fn visit_custom_ident(&mut self, ident: &mut CustomIdent) -> Result<(), Self::Error> { Ok(()) } /// Visits a dashed ident. #[allow(unused_variables)] fn visit_dashed_ident(&mut self, ident: &mut DashedIdent) -> Result<(), Self::Error> { Ok(()) } /// Visits a variable reference. #[inline] fn visit_variable(&mut self, var: &mut Variable<'i>) -> Result<(), Self::Error> { var.visit_children(self) } /// Visits an environment variable reference. #[inline] fn visit_environment_variable(&mut self, env: &mut EnvironmentVariable<'i>) -> Result<(), Self::Error> { env.visit_children(self) } /// Visits a media query list. #[inline] fn visit_media_list(&mut self, media: &mut MediaList<'i>) -> Result<(), Self::Error> { media.visit_children(self) } /// Visits a media query. #[inline] fn visit_media_query(&mut self, query: &mut MediaQuery<'i>) -> Result<(), Self::Error> { query.visit_children(self) } /// Visits a media feature. #[inline] fn visit_media_feature(&mut self, feature: &mut MediaFeature<'i>) -> Result<(), Self::Error> { feature.visit_children(self) } /// Visits a media feature value. #[inline] fn visit_media_feature_value(&mut self, value: &mut MediaFeatureValue<'i>) -> Result<(), Self::Error> { value.visit_children(self) } /// Visits a supports condition. #[inline] fn visit_supports_condition(&mut self, condition: &mut SupportsCondition<'i>) -> Result<(), Self::Error> { condition.visit_children(self) } /// Visits a selector list. #[inline] fn visit_selector_list(&mut self, selectors: &mut SelectorList<'i>) -> Result<(), Self::Error> { selectors.visit_children(self) } /// Visits a selector. #[allow(unused_variables)] fn visit_selector(&mut self, selector: &mut Selector<'i>) -> Result<(), Self::Error> { Ok(()) } /// Visits a custom function. #[inline] fn visit_function(&mut self, function: &mut Function<'i>) -> Result<(), Self::Error> { function.visit_children(self) } /// Visits a token list. #[inline] fn visit_token_list(&mut self, tokens: &mut TokenList<'i>) -> Result<(), Self::Error> { tokens.visit_children(self) } /// Visits a token or value in an unparsed property. #[inline] fn visit_token(&mut self, token: &mut TokenOrValue<'i>) -> Result<(), Self::Error> { token.visit_children(self) } } /// A trait for values that can be visited by a [Visitor](Visitor). pub trait Visit<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>> { /// The types of values contained within this value and its children. /// This is used to skip branches that don't have any values requested /// by the Visitor. const CHILD_TYPES: VisitTypes; /// Visits the value by calling an appropriate method on the Visitor. /// If no corresponding visitor method exists, then the children are visited. #[inline] fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> { self.visit_children(visitor) } /// Visit the children of this value. fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error>; } impl<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>, U: Visit<'i, T, V>> Visit<'i, T, V> for Option { const CHILD_TYPES: VisitTypes = U::CHILD_TYPES; fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> { if let Some(v) = self { v.visit(visitor) } else { Ok(()) } } fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> { if let Some(v) = self { v.visit_children(visitor) } else { Ok(()) } } } impl<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>, U: Visit<'i, T, V>> Visit<'i, T, V> for Box { const CHILD_TYPES: VisitTypes = U::CHILD_TYPES; fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> { self.as_mut().visit(visitor) } fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> { self.as_mut().visit_children(visitor) } } impl<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>, U: Visit<'i, T, V>> Visit<'i, T, V> for Vec { const CHILD_TYPES: VisitTypes = U::CHILD_TYPES; fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> { self.iter_mut().try_for_each(|v| v.visit(visitor)) } fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> { self.iter_mut().try_for_each(|v| v.visit_children(visitor)) } } impl<'i, A: smallvec::Array, U: Visit<'i, T, V>, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>> Visit<'i, T, V> for SmallVec { const CHILD_TYPES: VisitTypes = U::CHILD_TYPES; fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> { self.iter_mut().try_for_each(|v| v.visit(visitor)) } fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> { self.iter_mut().try_for_each(|v| v.visit_children(visitor)) } } impl<'i, T, V, U, W> Visit<'i, T, V> for IndexMap where T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>, W: Visit<'i, T, V>, { const CHILD_TYPES: VisitTypes = W::CHILD_TYPES; fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> { self.iter_mut().try_for_each(|(_k, v)| v.visit(visitor)) } fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> { self.iter_mut().try_for_each(|(_k, v)| v.visit_children(visitor)) } } macro_rules! impl_visit { ($t: ty) => { impl<'i, V: ?Sized + Visitor<'i, T>, T: Visit<'i, T, V>> Visit<'i, T, V> for $t { const CHILD_TYPES: VisitTypes = VisitTypes::empty(); fn visit_children(&mut self, _: &mut V) -> Result<(), V::Error> { Ok(()) } } }; } impl_visit!(u8); impl_visit!(u16); impl_visit!(u32); impl_visit!(i32); impl_visit!(f32); impl_visit!(bool); impl_visit!(char); impl_visit!(str); impl_visit!(String); impl_visit!((f32, f32));