use std::{ fmt::{self, Write}, hash::{Hash, Hasher}, }; use codemap::Span; use crate::{common::unvendor, error::SassResult}; use super::{ Attribute, ComplexSelector, ComplexSelectorComponent, CompoundSelector, Namespace, QualifiedName, SelectorList, Specificity, }; const SUBSELECTOR_PSEUDOS: [&str; 6] = [ "matches", "where", "is", "any", "nth-child", "nth-last-child", ]; const BASE_SPECIFICITY: i32 = 1000; #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub(crate) enum SimpleSelector { /// * Universal(Namespace), /// A pseudo-class or pseudo-element selector. /// /// The semantics of a specific pseudo selector depends on its name. Some /// selectors take arguments, including other selectors. Sass manually encodes /// logic for each pseudo selector that takes a selector as an argument, to /// ensure that extension and other selector operations work properly. Pseudo(Pseudo), /// A type selector. /// /// This selects elements whose name equals the given name. Type(QualifiedName), /// A placeholder selector. /// /// This doesn't match any elements. It's intended to be extended using /// `@extend`. It's not a plain CSS selector—it should be removed before /// emitting a CSS document. Placeholder(String), /// A selector that matches the parent in the Sass stylesheet. /// `&` /// /// This is not a plain CSS selector—it should be removed before emitting a CSS /// document. /// /// The parameter is the suffix that will be added to the parent selector after /// it's been resolved. /// /// This is assumed to be a valid identifier suffix. It may be `None`, /// indicating that the parent selector will not be modified. Parent(Option), Id(String), /// A class selector. /// /// This selects elements whose `class` attribute contains an identifier with /// the given name. Class(String), Attribute(Box), } impl fmt::Display for SimpleSelector { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Id(name) => write!(f, "#{}", name), Self::Class(name) => write!(f, ".{}", name), Self::Placeholder(name) => write!(f, "%{}", name), Self::Universal(namespace) => write!(f, "{}*", namespace), Self::Pseudo(pseudo) => write!(f, "{}", pseudo), Self::Type(name) => write!(f, "{}", name), Self::Attribute(attr) => write!(f, "{}", attr), Self::Parent(..) => unreachable!("It should not be possible to format `&`."), } } } impl SimpleSelector { /// The minimum possible specificity that this selector can have. /// /// Pseudo selectors that contain selectors, like `:not()` and `:matches()`, /// can have a range of possible specificities. /// /// Specifity is represented in base 1000. The spec says this should be /// "sufficiently high"; it's extremely unlikely that any single selector /// sequence will contain 1000 simple selectors. pub fn min_specificity(&self) -> i32 { match self { Self::Universal(..) => 0, Self::Type(..) => 1, Self::Pseudo(pseudo) => pseudo.min_specificity(), Self::Id(..) => BASE_SPECIFICITY.pow(2_u32), _ => BASE_SPECIFICITY, } } /// The maximum possible specificity that this selector can have. /// /// Pseudo selectors that contain selectors, like `:not()` and `:matches()`, /// can have a range of possible specificities. pub fn max_specificity(&self) -> i32 { match self { Self::Universal(..) => 0, Self::Pseudo(pseudo) => pseudo.max_specificity(), _ => self.min_specificity(), } } pub fn is_invisible(&self) -> bool { match self { Self::Universal(..) | Self::Type(..) | Self::Id(..) | Self::Class(..) | Self::Attribute(..) => false, Self::Pseudo(Pseudo { name, selector, .. }) => { name != "not" && selector.as_ref().map_or(false, |sel| sel.is_invisible()) } Self::Placeholder(..) => true, Self::Parent(..) => unreachable!("parent selectors should be resolved at this point"), } } pub fn add_suffix(&mut self, suffix: &str, span: Span) -> SassResult<()> { match self { Self::Type(name) => name.ident.push_str(suffix), Self::Placeholder(name) | Self::Id(name) | Self::Class(name) | Self::Pseudo(Pseudo { name, argument: None, selector: None, .. }) => name.push_str(suffix), // todo: add test for this? _ => return Err((format!("Invalid parent selector \"{}\"", self), span).into()), }; Ok(()) } pub fn is_universal(&self) -> bool { matches!(self, Self::Universal(..)) } pub fn is_pseudo(&self) -> bool { matches!(self, Self::Pseudo { .. }) } pub fn is_parent(&self) -> bool { matches!(self, Self::Parent(..)) } pub fn is_id(&self) -> bool { matches!(self, Self::Id(..)) } pub fn is_type(&self) -> bool { matches!(self, Self::Type(..)) } pub fn unify(self, compound: Vec) -> Option> { match self { Self::Type(..) => self.unify_type(compound), Self::Universal(..) => self.unify_universal(compound), Self::Pseudo { .. } => self.unify_pseudo(compound), Self::Id(..) => { if compound .iter() .any(|simple| simple.is_id() && simple != &self) { return None; } self.unify_default(compound) } _ => self.unify_default(compound), } } /// Returns the components of a `CompoundSelector` that matches only elements /// matched by both this and `compound`. /// /// By default, this just returns a copy of `compound` with this selector /// added to the end, or returns the original array if this selector already /// exists in it. /// /// Returns `None` if unification is impossible—for example, if there are /// multiple ID selectors. fn unify_default(self, mut compound: Vec) -> Option> { if compound.len() == 1 && compound[0].is_universal() { return compound.swap_remove(0).unify(vec![self]); } if compound.contains(&self) { return Some(compound); } let mut result: Vec = Vec::new(); let mut added_this = false; for simple in compound { if !added_this && simple.is_pseudo() { result.push(self.clone()); added_this = true; } result.push(simple); } if !added_this { result.push(self); } Some(result) } fn unify_universal(self, mut compound: Vec) -> Option> { if let Self::Universal(..) | Self::Type(..) = compound[0] { let mut unified = vec![self.unify_universal_and_element(&compound[0])?]; unified.extend(compound.into_iter().skip(1)); return Some(unified); } if self != Self::Universal(Namespace::Asterisk) && self != Self::Universal(Namespace::None) { let mut v = vec![self]; v.append(&mut compound); return Some(v); } if !compound.is_empty() { return Some(compound); } Some(vec![self]) } /// Returns a `SimpleSelector` that matches only elements that are matched by /// both `selector1` and `selector2`, which must both be either /// `SimpleSelector::Universal`s or `SimpleSelector::Type`s. /// /// If no such selector can be produced, returns `None`. fn unify_universal_and_element(&self, other: &Self) -> Option { let namespace1; let name1; if let SimpleSelector::Type(name) = self.clone() { namespace1 = name.namespace; name1 = name.ident; } else if let SimpleSelector::Universal(namespace) = self.clone() { namespace1 = namespace; name1 = String::new(); } else { unreachable!("{:?} must be a universal selector or a type selector", self); } let namespace2; let mut name2 = String::new(); if let SimpleSelector::Universal(namespace) = other { namespace2 = namespace.clone(); } else if let SimpleSelector::Type(name) = other { namespace2 = name.namespace.clone(); name2 = name.ident.clone(); } else { unreachable!( "{:?} must be a universal selector or a type selector", other ); } let namespace = if namespace1 == namespace2 || namespace2 == Namespace::Asterisk { namespace1 } else if namespace1 == Namespace::Asterisk { namespace2 } else { return None; }; let name = if name1 == name2 || name2.is_empty() { name1 } else if name1.is_empty() || name1 == "*" { name2 } else { return None; }; Some(if name.is_empty() { SimpleSelector::Universal(namespace) } else { SimpleSelector::Type(QualifiedName { namespace, ident: name, }) }) } fn unify_type(self, mut compound: Vec) -> Option> { if let Self::Universal(..) | Self::Type(..) = compound[0] { let mut unified = vec![self.unify_universal_and_element(&compound[0])?]; unified.extend(compound.into_iter().skip(1)); Some(unified) } else { let mut unified = vec![self]; unified.append(&mut compound); Some(unified) } } fn unify_pseudo(self, mut compound: Vec) -> Option> { if compound.len() == 1 && compound[0].is_universal() { return compound.remove(0).unify(vec![self]); } if compound.contains(&self) { return Some(compound); } let mut result = Vec::new(); let mut added_self = false; for simple in compound { if let Self::Pseudo(Pseudo { is_class: false, .. }) = simple { // A given compound selector may only contain one pseudo element. If // `compound` has a different one than `self`, unification fails. if let Self::Pseudo(Pseudo { is_class: false, .. }) = self { return None; } // Otherwise, this is a pseudo selector and should come before pseduo // elements. result.push(self.clone()); added_self = true; } result.push(simple); } if !added_self { result.push(self); } Some(result) } pub fn is_super_selector_of_compound(&self, compound: &CompoundSelector) -> bool { compound.components.iter().any(|their_simple| { if self == their_simple { return true; } if let SimpleSelector::Pseudo(Pseudo { selector: Some(sel), name, .. }) = their_simple { if SUBSELECTOR_PSEUDOS.contains(&unvendor(name)) { return sel.components.iter().all(|complex| { if complex.components.len() != 1 { return false; }; complex .components .first() .unwrap() .as_compound() .components .contains(self) }); } false } else { false } }) } } #[derive(Clone, Debug)] pub(crate) struct Pseudo { /// The name of this selector. pub name: String, /// Whether this is a pseudo-class selector. /// /// If this is false, this is a pseudo-element selector pub is_class: bool, /// Whether this is syntactically a pseudo-class selector. /// /// This is the same as `is_class` unless this selector is a pseudo-element /// that was written syntactically as a pseudo-class (`:before`, `:after`, /// `:first-line`, or `:first-letter`). /// /// If this is false, it is syntactically a psuedo-element pub is_syntactic_class: bool, /// The non-selector argument passed to this selector. /// /// This is `None` if there's no argument. If `argument` and `selector` are /// both non-`None`, the selector follows the argument. pub argument: Option>, /// The selector argument passed to this selector. /// /// This is `None` if there's no selector. If `argument` and `selector` are /// both non-`None`, the selector follows the argument. pub selector: Option>, pub span: Span, } impl PartialEq for Pseudo { fn eq(&self, other: &Pseudo) -> bool { self.name == other.name && self.is_class == other.is_class && self.argument == other.argument && self.selector == other.selector } } impl Eq for Pseudo {} impl Hash for Pseudo { fn hash(&self, state: &mut H) { self.name.hash(state); self.is_class.hash(state); self.argument.hash(state); self.selector.hash(state); } } impl fmt::Display for Pseudo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(sel) = &self.selector { if self.name == "not" && sel.is_invisible() { return Ok(()); } } f.write_char(':')?; if !self.is_syntactic_class { f.write_char(':')?; } f.write_str(&self.name)?; if self.argument.is_none() && self.selector.is_none() { return Ok(()); } f.write_char('(')?; if let Some(arg) = &self.argument { f.write_str(arg)?; if self.selector.is_some() { f.write_char(' ')?; } } if let Some(sel) = &self.selector { write!(f, "{}", sel)?; } f.write_char(')') } } impl Pseudo { /// Returns whether `pseudo1` is a superselector of `compound2`. /// /// That is, whether `pseudo1` matches every element that `compound2` matches, as well /// as possibly additional elements. /// /// This assumes that `pseudo1`'s `selector` argument is not `None`. /// /// If `parents` is passed, it represents the parents of `compound`. This is /// relevant for pseudo selectors with selector arguments, where we may need to /// know if the parent selectors in the selector argument match `parents`. pub fn is_super_selector( &self, compound: &CompoundSelector, parents: Option>, ) -> bool { debug_assert!(self.selector.is_some()); match self.normalized_name() { "matches" | "is" | "any" | "where" => { selector_pseudos_named(compound.clone(), &self.name, true).any(move |pseudo2| { self.selector .as_ref() .unwrap() .is_superselector(&pseudo2.selector.unwrap()) }) || self .selector .as_ref() .unwrap() .components .iter() .any(move |complex1| { let mut components = parents.clone().unwrap_or_default(); components.push(ComplexSelectorComponent::Compound(compound.clone())); complex1.is_super_selector(&ComplexSelector::new(components, false)) }) } "has" | "host" | "host-context" => { selector_pseudos_named(compound.clone(), &self.name, true).any(|pseudo2| { self.selector .as_ref() .unwrap() .is_superselector(&pseudo2.selector.unwrap()) }) } "slotted" => { selector_pseudos_named(compound.clone(), &self.name, false).any(|pseudo2| { self.selector .as_ref() .unwrap() .is_superselector(pseudo2.selector.as_ref().unwrap()) }) } "not" => self .selector .as_ref() .unwrap() .components .iter() .all(|complex| { compound.components.iter().any(|simple2| { if let SimpleSelector::Type(..) = simple2 { let compound1 = complex.components.last(); if let Some(ComplexSelectorComponent::Compound(c)) = compound1 { c.components .iter() .any(|simple1| simple1.is_type() && simple1 != simple2) } else { false } } else if let SimpleSelector::Id(..) = simple2 { let compound1 = complex.components.last(); if let Some(ComplexSelectorComponent::Compound(c)) = compound1 { c.components .iter() .any(|simple1| simple1.is_id() && simple1 != simple2) } else { false } } else if let SimpleSelector::Pseudo(Pseudo { selector: Some(sel), name, .. }) = simple2 { if name != &self.name { return false; } sel.is_superselector(&SelectorList { components: vec![complex.clone()], span: self.span, }) } else { false } }) }), "current" => selector_pseudos_named(compound.clone(), &self.name, self.is_class) .any(|pseudo2| self.selector == pseudo2.selector), "nth-child" | "nth-last-child" => compound.components.iter().any(|pseudo2| { if let SimpleSelector::Pseudo( pseudo @ Pseudo { selector: Some(..), .. }, ) = pseudo2 { pseudo.name == self.name && pseudo.argument == self.argument && self .selector .as_ref() .unwrap() .is_superselector(pseudo.selector.as_ref().unwrap()) } else { false } }), _ => unreachable!(), } } #[allow(clippy::missing_const_for_fn)] pub fn with_selector(self, selector: Option>) -> Self { Self { selector, ..self } } pub fn max_specificity(&self) -> i32 { self.specificity().max } pub fn min_specificity(&self) -> i32 { self.specificity().min } pub fn specificity(&self) -> Specificity { if !self.is_class { return Specificity { min: 1, max: 1 }; } let selector = match &self.selector { Some(sel) => sel, None => { return Specificity { min: BASE_SPECIFICITY, max: BASE_SPECIFICITY, } } }; if self.name == "not" { let mut min = 0; let mut max = 0; for complex in &selector.components { min = min.max(complex.min_specificity()); max = max.max(complex.max_specificity()); } Specificity { min, max } } else { // This is higher than any selector's specificity can actually be. let mut min = BASE_SPECIFICITY.pow(3_u32); let mut max = 0; for complex in &selector.components { min = min.min(complex.min_specificity()); max = max.max(complex.max_specificity()); } Specificity { min, max } } } /// Like `name`, but without any vendor prefixes. pub fn normalized_name(&self) -> &str { unvendor(&self.name) } } /// Returns all pseudo selectors in `compound` that have a selector argument, /// and that have the given `name`. fn selector_pseudos_named( compound: CompoundSelector, name: &str, is_class: bool, ) -> impl Iterator + '_ { compound .components .into_iter() .filter_map(|c| { if let SimpleSelector::Pseudo(p) = c { Some(p) } else { None } }) .filter(move |p| p.is_class == is_class && p.selector.is_some() && p.name == name) }