From 01888be6af4ec39f5f07a1d234ecca2003b68430 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Sun, 21 Jul 2024 02:27:03 +0200 Subject: [PATCH 01/10] New selectors 1: rename the old. --- rsass/src/css/mod.rs | 4 +- rsass/src/css/rule.rs | 6 +- rsass/src/css/selectors.rs | 184 +++++++++++++++--------------- rsass/src/output/cssdata.rs | 4 +- rsass/src/output/cssdest.rs | 14 +-- rsass/src/output/transform.rs | 6 +- rsass/src/parser/css/selectors.rs | 40 +++---- rsass/src/sass/selectors.rs | 24 ++-- rsass/src/variablescope.rs | 12 +- 9 files changed, 147 insertions(+), 147 deletions(-) diff --git a/rsass/src/css/mod.rs b/rsass/src/css/mod.rs index 0f4fb6cc..41fe052f 100644 --- a/rsass/src/css/mod.rs +++ b/rsass/src/css/mod.rs @@ -19,11 +19,11 @@ pub use self::comment::Comment; pub use self::item::{Import, Item}; pub use self::mediarule::{MediaArgs, MediaRule}; pub use self::rule::{BodyItem, CustomProperty, Property, Rule}; -pub use self::selectors::{BadSelector, Selector, SelectorPart, Selectors}; +pub use self::selectors::{BadSelector, OldSelector, OldSelectorPart, OldSelectors}; pub use self::string::CssString; pub use self::value::{InvalidCss, Value, ValueMap, ValueToMapError}; pub(crate) use self::selectors::{ - CssSelectorSet, LogicalSelector, SelectorCtx, + CssSelectorSet, LogicalSelector, OldSelectorCtx, }; pub(crate) use self::util::{is_calc_name, is_function_name, is_not, IsNot}; diff --git a/rsass/src/css/rule.rs b/rsass/src/css/rule.rs index d2f671d2..925c6d30 100644 --- a/rsass/src/css/rule.rs +++ b/rsass/src/css/rule.rs @@ -1,4 +1,4 @@ -use super::{AtRule, Comment, CssString, Import, Selectors, Value}; +use super::{AtRule, Comment, CssString, Import, OldSelectors, Value}; use crate::output::CssBuf; use std::io::{self, Write}; @@ -8,13 +8,13 @@ use std::io::{self, Write}; /// properties with [`Value`]s). #[derive(Clone, Debug)] pub struct Rule { - pub(crate) selectors: Selectors, + pub(crate) selectors: OldSelectors, pub(crate) body: Vec, } impl Rule { /// Create a new Rule. - pub fn new(selectors: Selectors) -> Self { + pub fn new(selectors: OldSelectors) -> Self { Self { selectors, body: Vec::new(), diff --git a/rsass/src/css/selectors.rs b/rsass/src/css/selectors.rs index 56992506..31b6c35e 100644 --- a/rsass/src/css/selectors.rs +++ b/rsass/src/css/selectors.rs @@ -24,23 +24,23 @@ pub(crate) use logical::Selector as LogicalSelector; /// A full set of selectors. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd)] -pub struct Selectors { - s: Vec, +pub struct OldSelectors { + s: Vec, } -impl Selectors { +impl OldSelectors { /// Create a root (empty) selector. pub fn root() -> Self { Self { - s: vec![Selector::root()], + s: vec![OldSelector::root()], } } /// Return true if this is a root (empty) selector. pub fn is_root(&self) -> bool { - self.s == [Selector::root()] + self.s == [OldSelector::root()] } /// Create a new `Selectors` from a vec of selectors. - pub fn new(s: Vec) -> Self { + pub fn new(s: Vec) -> Self { if s.is_empty() { Self::root() } else { @@ -62,18 +62,18 @@ impl Selectors { } /// Get the first of these selectors (or the root selector if empty). - pub(crate) fn one(&self) -> Selector { - self.s.first().cloned().unwrap_or_else(Selector::root) + pub(crate) fn one(&self) -> OldSelector { + self.s.first().cloned().unwrap_or_else(OldSelector::root) } /// Create the full selector for when self is used inside a parent selector. - pub(crate) fn inside(&self, parent: &SelectorCtx) -> Self { - SelectorCtx::from(self.clone()).inside(parent).real() + pub(crate) fn inside(&self, parent: &OldSelectorCtx) -> Self { + OldSelectorCtx::from(self.clone()).inside(parent).real() } /// True if any of the selectors contains a backref (`&`). pub(crate) fn has_backref(&self) -> bool { - self.s.iter().any(Selector::has_backref) + self.s.iter().any(OldSelector::has_backref) } /// Get a vec of the non-placeholder selectors in self. @@ -81,7 +81,7 @@ impl Selectors { let s = self .s .iter() - .filter_map(Selector::no_placeholder) + .filter_map(OldSelector::no_placeholder) .collect::>(); if s.is_empty() { None @@ -102,42 +102,42 @@ impl Selectors { /// Get these selectors with a specific backref selector. /// /// Used to create `@at-root` contexts, to have `&` work in them. - pub(crate) fn with_backref(self, context: Selector) -> SelectorCtx { - SelectorCtx::from(self).inside(&SelectorCtx { + pub(crate) fn with_backref(self, context: OldSelector) -> OldSelectorCtx { + OldSelectorCtx::from(self).inside(&OldSelectorCtx { s: Self::root(), backref: context, }) } /// Return true if any of these selectors ends with a combinator pub fn has_trailing_combinator(&self) -> bool { - self.s.iter().any(Selector::has_trailing_combinator) + self.s.iter().any(OldSelector::has_trailing_combinator) } } -impl From for Value { +impl From for Value { /// Create a css `Value` representing a set of selectors. /// /// The result will be a comma-separated [list](Value::List) of /// space-separated lists of strings, or [null](Value::Null) if /// this is a root (empty) selector. - fn from(sel: Selectors) -> Self { + fn from(sel: OldSelectors) -> Self { if sel.is_root() { return Self::Null; } let content = sel .s .iter() - .map(|s: &Selector| { + .map(|s: &OldSelector| { let (mut v, last) = s.0.iter().fold( (vec![], Option::::None), |(mut v, mut last), part| { match part { - SelectorPart::Descendant => { + OldSelectorPart::Descendant => { if let Some(last) = last.take() { v.push(last.into()); } } - SelectorPart::RelOp(op) => { + OldSelectorPart::RelOp(op) => { if let Some(last) = last.take() { v.push(last.into()); } @@ -165,34 +165,34 @@ impl From for Value { /// A full set of selectors with a separate backref. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd)] -pub struct SelectorCtx { +pub struct OldSelectorCtx { /// The actual selectors. - s: Selectors, - backref: Selector, + s: OldSelectors, + backref: OldSelector, } -impl SelectorCtx { +impl OldSelectorCtx { /// Create a root (empty) selector. pub fn root() -> Self { - Selectors::root().into() + OldSelectors::root().into() } - pub(crate) fn root_with_backref(context: Selector) -> Self { + pub(crate) fn root_with_backref(context: OldSelector) -> Self { Self { - s: Selectors::root(), + s: OldSelectors::root(), backref: context, } } /// Return true if this is a root (empty) selector. pub fn is_root(&self) -> bool { - self.s.is_root() && self.backref == Selector::root() + self.s.is_root() && self.backref == OldSelector::root() } - pub(crate) fn real(&self) -> Selectors { + pub(crate) fn real(&self) -> OldSelectors { self.s.clone() } /// Remove the first of these selectors (or the root selector if empty). - pub(crate) fn one(&self) -> Selector { + pub(crate) fn one(&self) -> OldSelector { self.s.one() } @@ -205,34 +205,34 @@ impl SelectorCtx { } } Self { - s: Selectors::new(result), + s: OldSelectors::new(result), backref: parent.backref.clone(), } } } -impl From for SelectorCtx { - fn from(value: Selectors) -> Self { +impl From for OldSelectorCtx { + fn from(value: OldSelectors) -> Self { Self { s: value, - backref: Selector::root(), + backref: OldSelector::root(), } } } -impl TryFrom for SelectorCtx { +impl TryFrom for OldSelectorCtx { type Error = BadSelector; fn try_from(v: Value) -> Result { - Selectors::try_from(v).map(Into::into) + OldSelectors::try_from(v).map(Into::into) } } -impl TryFrom for Selectors { +impl TryFrom for OldSelectors { type Error = BadSelector; fn try_from(v: Value) -> Result { value_to_selectors(&v).map_err(move |e| e.ctx(v)) } } -fn value_to_selectors(v: &Value) -> Result { +fn value_to_selectors(v: &Value) -> Result { match v { Value::List(vv, s, _) => match s { Some(ListSeparator::Comma) => { @@ -240,7 +240,7 @@ fn value_to_selectors(v: &Value) -> Result { .iter() .map(value_to_selector) .collect::>()?; - Ok(Selectors::new(vv)) + Ok(OldSelectors::new(vv)) } Some(ListSeparator::Space) => { let (mut outer, last) = vv.iter().try_fold( @@ -262,14 +262,14 @@ fn value_to_selectors(v: &Value) -> Result { Result::<_, BadSelector0>::Ok((outer, a)) }, )?; - outer.push(Selector(last)); - Ok(Selectors::new(outer)) + outer.push(OldSelector(last)); + Ok(OldSelectors::new(outer)) } _ => Err(BadSelector0::Value), }, Value::Literal(s) => { if s.value().is_empty() { - Ok(Selectors::root()) + Ok(OldSelectors::root()) } else { let span = input_span(s.value()); Ok(ParseError::check(selectors(span.borrow()))?) @@ -279,11 +279,11 @@ fn value_to_selectors(v: &Value) -> Result { } } -fn check_selector_str(v: &Value) -> Result { +fn check_selector_str(v: &Value) -> Result { match v { Value::Literal(s) => { if s.value().is_empty() { - Ok(Selector::root()) + Ok(OldSelector::root()) } else { let span = input_span(s.value()); Ok(ParseError::check(selector(span.borrow()))?) @@ -292,11 +292,11 @@ fn check_selector_str(v: &Value) -> Result { _ => Err(BadSelector0::Value), } } -fn parse_selectors_str(v: &Value) -> Result { +fn parse_selectors_str(v: &Value) -> Result { match v { Value::Literal(s) => { if s.value().is_empty() { - Ok(Selectors::root()) + Ok(OldSelectors::root()) } else { let span = input_span(s.value()); Ok(ParseError::check(selectors(span.borrow()))?) @@ -306,9 +306,9 @@ fn parse_selectors_str(v: &Value) -> Result { } } -fn push_descendant(to: &mut Vec, from: &mut Selector) { +fn push_descendant(to: &mut Vec, from: &mut OldSelector) { if !to.is_empty() { - to.push(SelectorPart::Descendant); + to.push(OldSelectorPart::Descendant); } to.append(&mut from.0); } @@ -318,9 +318,9 @@ fn push_descendant(to: &mut Vec, from: &mut Selector) { /// A selector does not contain `,`. If it does, it is a `Selectors`, /// where each of the parts separated by the comma is a `Selector`. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd)] -pub struct Selector(pub(crate) Vec); +pub struct OldSelector(pub(crate) Vec); -impl Selector { +impl OldSelector { /// Get the root (empty) selector. pub fn root() -> Self { Self(vec![]) @@ -337,9 +337,9 @@ impl Selector { } else { let mut result = self.0.clone(); if !result.is_empty() - && !other.0.first().map_or(false, SelectorPart::is_operator) + && !other.0.first().map_or(false, OldSelectorPart::is_operator) { - result.push(SelectorPart::Descendant); + result.push(OldSelectorPart::Descendant); } result.extend(other.0.iter().cloned()); Self(result) @@ -360,7 +360,7 @@ impl Selector { } fn has_backref(&self) -> bool { - self.0.iter().any(SelectorPart::has_backref) + self.0.iter().any(OldSelectorPart::has_backref) } /// Return this selector without placeholders. @@ -373,7 +373,7 @@ impl Selector { let v = self .0 .iter() - .map(SelectorPart::no_placeholder) + .map(OldSelectorPart::no_placeholder) .collect::>>()?; let mut v2 = Vec::with_capacity(v.len()); let mut has_sel = false; @@ -393,20 +393,20 @@ impl Selector { } } fn has_leading_combinator(&self) -> bool { - matches!(self.0.first(), Some(SelectorPart::RelOp(_))) + matches!(self.0.first(), Some(OldSelectorPart::RelOp(_))) } /// Return true if this selector ends with a combinator pub fn has_trailing_combinator(&self) -> bool { - matches!(self.0.last(), Some(SelectorPart::RelOp(_))) + matches!(self.0.last(), Some(OldSelectorPart::RelOp(_))) } fn has_double_combinator(&self) -> bool { self.0.windows(2).any(|w| { - matches!(w, [SelectorPart::RelOp(_), SelectorPart::RelOp(_)]) + matches!(w, [OldSelectorPart::RelOp(_), OldSelectorPart::RelOp(_)]) }) } } -impl TryFrom for Selector { +impl TryFrom for OldSelector { type Error = BadSelector; fn try_from(value: Value) -> Result { @@ -414,14 +414,14 @@ impl TryFrom for Selector { } } // Internal, the api is try_into. -fn value_to_selector(v: &Value) -> Result { +fn value_to_selector(v: &Value) -> Result { match v { Value::List(list, None | Some(ListSeparator::Space), _) => { list_to_selector(list) } Value::Literal(s) => { if s.value().is_empty() { - Ok(Selector::root()) + Ok(OldSelector::root()) } else { let span = input_span(s.value()); Ok(ParseError::check(selector(span.borrow()))?) @@ -431,24 +431,24 @@ fn value_to_selector(v: &Value) -> Result { } } -fn list_to_selector(list: &[Value]) -> Result { +fn list_to_selector(list: &[Value]) -> Result { list.iter() .try_fold(vec![], |mut a, v| { let parts = value_to_selector_parts(v)?; - if !parts.first().map_or(true, SelectorPart::is_operator) - && !a.last().map_or(true, SelectorPart::is_operator) + if !parts.first().map_or(true, OldSelectorPart::is_operator) + && !a.last().map_or(true, OldSelectorPart::is_operator) { - a.push(SelectorPart::Descendant); + a.push(OldSelectorPart::Descendant); } a.extend(parts); Ok(a) }) - .map(Selector) + .map(OldSelector) } /// A selector consist of a sequence of these parts. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd)] -pub enum SelectorPart { +pub enum OldSelectorPart { /// A simple selector, eg a class, id or element name. Simple(String), /// The empty relational operator. @@ -474,20 +474,20 @@ pub enum SelectorPart { /// The name of the pseudo-element name: CssString, /// Arguments to the pseudo-element - arg: Option, + arg: Option, }, /// A pseudo-class or a css2 pseudo-element (:foo) Pseudo { /// The name of the pseudo-class name: CssString, /// Arguments to the pseudo-class - arg: Option, + arg: Option, }, /// A sass backref (`&`), to be replaced with outer selector. BackRef, } -impl SelectorPart { +impl OldSelectorPart { pub(crate) fn is_operator(&self) -> bool { match *self { Self::Descendant | Self::RelOp(_) => true, @@ -515,7 +515,7 @@ impl SelectorPart { Self::PseudoElement { ref arg, .. } | Self::Pseudo { ref arg, .. } => arg .as_ref() - .map_or(false, |a| a.s.iter().any(Selector::has_backref)), + .map_or(false, |a| a.s.iter().any(OldSelector::has_backref)), } } /// Return this selectorpart without placeholders. @@ -535,22 +535,22 @@ impl SelectorPart { Self::Pseudo { name, arg } => match name.value() { "is" => arg .as_ref() - .and_then(Selectors::no_placeholder) - .and_then(Selectors::no_leading_combinator) + .and_then(OldSelectors::no_placeholder) + .and_then(OldSelectors::no_leading_combinator) .map(|arg| Self::Pseudo { name: name.clone(), arg: Some(arg), }), "matches" | "any" | "where" | "has" => arg .as_ref() - .and_then(Selectors::no_placeholder) + .and_then(OldSelectors::no_placeholder) .map(|arg| Self::Pseudo { name: name.clone(), arg: Some(arg), }), "not" => { if let Some(arg) = - arg.as_ref().and_then(Selectors::no_placeholder) + arg.as_ref().and_then(OldSelectors::no_placeholder) { Some(Self::Pseudo { name: name.clone(), @@ -568,7 +568,7 @@ impl SelectorPart { x => Some(x.clone()), } } - fn clone_in(&self, context: &Selector) -> Vec { + fn clone_in(&self, context: &OldSelector) -> Vec { match self { s @ (Self::Descendant | Self::RelOp(_) @@ -579,7 +579,7 @@ impl SelectorPart { vec![Self::PseudoElement { name: name.clone(), arg: arg.as_ref().map(|a| { - a.inside(&SelectorCtx::root_with_backref( + a.inside(&OldSelectorCtx::root_with_backref( context.clone(), )) }), @@ -589,7 +589,7 @@ impl SelectorPart { vec![Self::Pseudo { name: name.clone(), arg: arg.as_ref().map(|a| { - a.inside(&SelectorCtx::root_with_backref( + a.inside(&OldSelectorCtx::root_with_backref( context.clone(), )) }), @@ -601,7 +601,7 @@ impl SelectorPart { fn value_to_selector_parts( v: &Value, -) -> Result, BadSelector0> { +) -> Result, BadSelector0> { match v { Value::Literal(s) => Ok(ParseError::check(selector_parts( input_span(s.value()).borrow(), @@ -611,7 +611,7 @@ fn value_to_selector_parts( } // TODO: This shoule probably be on Formatted instead. -impl fmt::Display for Selectors { +impl fmt::Display for OldSelectors { fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result { if let Some((first, rest)) = self.s.split_first() { first.fmt(out)?; @@ -625,7 +625,7 @@ impl fmt::Display for Selectors { } } -impl fmt::Display for Selector { +impl fmt::Display for OldSelector { fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result { // Note: There should be smarter whitespace-handling here, avoiding // the need to clean up afterwards. @@ -648,7 +648,7 @@ impl fmt::Display for Selector { } } -impl fmt::Display for SelectorPart { +impl fmt::Display for OldSelectorPart { fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result { match *self { Self::Simple(ref s) => write!(out, "{s}"), @@ -711,27 +711,27 @@ mod test { #[test] fn root_join() { - let s = Selector(vec![SelectorPart::Simple("foo".into())]); - assert_eq!(Selector::root().join(&s, &Selector::root()), s) + let s = OldSelector(vec![OldSelectorPart::Simple("foo".into())]); + assert_eq!(OldSelector::root().join(&s, &OldSelector::root()), s) } #[test] fn simple_join() { - let s = Selector(vec![SelectorPart::Simple("foo".into())]).join( - &Selector(vec![SelectorPart::Simple(".bar".into())]), - &Selector::root(), + let s = OldSelector(vec![OldSelectorPart::Simple("foo".into())]).join( + &OldSelector(vec![OldSelectorPart::Simple(".bar".into())]), + &OldSelector::root(), ); assert_eq!(format!("{}", s), "foo .bar") } #[test] fn backref_join() { - let s = Selector(vec![SelectorPart::Simple("foo".into())]).join( - &Selector(vec![ - SelectorPart::BackRef, - SelectorPart::Simple(".bar".into()), + let s = OldSelector(vec![OldSelectorPart::Simple("foo".into())]).join( + &OldSelector(vec![ + OldSelectorPart::BackRef, + OldSelectorPart::Simple(".bar".into()), ]), - &Selector::root(), + &OldSelector::root(), ); assert_eq!(format!("{}", s), "foo.bar") } @@ -765,7 +765,7 @@ pub enum BadSelector { /// A backref (`&`) were present but not allowed there. Backref(SourcePos), /// Cant append extenstion to base. - Append(Selector, Selector), + Append(OldSelector, OldSelector), } impl fmt::Display for BadSelector { diff --git a/rsass/src/output/cssdata.rs b/rsass/src/output/cssdata.rs index 1f6924d6..12742ef6 100644 --- a/rsass/src/output/cssdata.rs +++ b/rsass/src/output/cssdata.rs @@ -3,7 +3,7 @@ use super::cssdest::{ }; use super::{CssBuf, Format}; use crate::css::{ - Comment, CssString, Import, Item, MediaArgs, Selectors, Value, + Comment, CssString, Import, Item, MediaArgs, OldSelectors, Value, }; use crate::{Error, Invalid, ScopeRef}; use std::collections::BTreeMap; @@ -86,7 +86,7 @@ impl CssDestination for CssData { self } - fn start_rule(&mut self, selectors: Selectors) -> Result { + fn start_rule(&mut self, selectors: OldSelectors) -> Result { Ok(RuleDest::new(self, selectors)) } fn start_atmedia(&mut self, args: MediaArgs) -> AtMediaDest { diff --git a/rsass/src/output/cssdest.rs b/rsass/src/output/cssdest.rs index bc0149a5..cf3eb3bb 100644 --- a/rsass/src/output/cssdest.rs +++ b/rsass/src/output/cssdest.rs @@ -1,7 +1,7 @@ use super::CssData; use crate::css::{ AtRule, AtRuleBodyItem, Comment, CssString, CustomProperty, Import, Item, - MediaArgs, MediaRule, Property, Rule, Selectors, Value, + MediaArgs, MediaRule, Property, Rule, OldSelectors, Value, }; use crate::Invalid; @@ -10,7 +10,7 @@ type Result = std::result::Result; pub trait CssDestination { fn head(&mut self) -> &mut CssData; - fn start_rule(&mut self, selectors: Selectors) -> Result; + fn start_rule(&mut self, selectors: OldSelectors) -> Result; fn start_atmedia(&mut self, args: MediaArgs) -> AtMediaDest; fn start_atrule(&mut self, name: String, args: Value) -> AtRuleDest; fn start_nsrule(&mut self, name: String) -> Result; @@ -38,7 +38,7 @@ pub struct RuleDest<'a> { impl<'a> RuleDest<'a> { pub fn new( parent: &'a mut dyn CssDestination, - selectors: Selectors, + selectors: OldSelectors, ) -> Self { RuleDest { parent, @@ -67,7 +67,7 @@ impl<'a> CssDestination for RuleDest<'a> { fn head(&mut self) -> &mut CssData { self.parent.head() } - fn start_rule(&mut self, selectors: Selectors) -> Result { + fn start_rule(&mut self, selectors: OldSelectors) -> Result { Ok(RuleDest::new(self, selectors)) } fn start_atmedia(&mut self, args: MediaArgs) -> AtMediaDest { @@ -140,7 +140,7 @@ impl<'a> CssDestination for NsRuleDest<'a> { fn head(&mut self) -> &mut CssData { self.parent.head() } - fn start_rule(&mut self, _selectors: Selectors) -> Result { + fn start_rule(&mut self, _selectors: OldSelectors) -> Result { Err(Invalid::InNsRule) } fn start_atmedia(&mut self, args: MediaArgs) -> AtMediaDest { @@ -219,7 +219,7 @@ impl<'a> CssDestination for AtRuleDest<'a> { fn head(&mut self) -> &mut CssData { self.parent.head() } - fn start_rule(&mut self, selectors: Selectors) -> Result { + fn start_rule(&mut self, selectors: OldSelectors) -> Result { Ok(RuleDest::new(self, selectors)) } fn start_atmedia(&mut self, args: MediaArgs) -> AtMediaDest { @@ -338,7 +338,7 @@ impl<'a> CssDestination for AtMediaDest<'a> { self.parent.head() } - fn start_rule(&mut self, selectors: Selectors) -> Result { + fn start_rule(&mut self, selectors: OldSelectors) -> Result { Ok(RuleDest::new(self, selectors)) } fn start_atmedia(&mut self, args: MediaArgs) -> AtMediaDest { diff --git a/rsass/src/output/transform.rs b/rsass/src/output/transform.rs index 3ed747d8..c0fa2cfd 100644 --- a/rsass/src/output/transform.rs +++ b/rsass/src/output/transform.rs @@ -3,7 +3,7 @@ use super::cssdest::CssDestination; use super::CssData; -use crate::css::{self, AtRule, Import, SelectorCtx}; +use crate::css::{self, AtRule, Import, OldSelectorCtx}; use crate::error::ResultPos; use crate::input::{Context, Loader, Parsed, SourceKind}; use crate::sass::{get_global_module, Expose, Item, UseAs}; @@ -232,7 +232,7 @@ fn handle_item( if let Some(ref body) = *body { let mut atrule = dest.start_atrule(name.clone(), args); let local = if name == "keyframes" { - ScopeRef::sub_selectors(scope, SelectorCtx::root()) + ScopeRef::sub_selectors(scope, OldSelectorCtx::root()) } else { ScopeRef::sub(scope) }; @@ -358,7 +358,7 @@ fn handle_item( Item::Rule(ref selectors, ref body) => { check_body(body, BodyContext::Rule)?; - let selectors = SelectorCtx::from(selectors.eval(scope.clone())?) + let selectors = OldSelectorCtx::from(selectors.eval(scope.clone())?) .inside(scope.get_selectors()); let mut dest = dest.start_rule(selectors.real()).no_pos()?; let scope = ScopeRef::sub_selectors(scope, selectors); diff --git a/rsass/src/parser/css/selectors.rs b/rsass/src/parser/css/selectors.rs index 0b7f0c15..6a3f2d89 100644 --- a/rsass/src/parser/css/selectors.rs +++ b/rsass/src/parser/css/selectors.rs @@ -1,7 +1,7 @@ use super::super::util::{ignore_comments, opt_spacelike, spacelike2}; use super::super::{input_to_string, PResult, Span}; use super::strings::{css_string, css_string_any}; -use crate::css::{Selector, SelectorPart, Selectors}; +use crate::css::{OldSelector, OldSelectorPart, OldSelectors}; use nom::branch::alt; use nom::bytes::complete::tag; use nom::character::complete::one_of; @@ -9,49 +9,49 @@ use nom::combinator::{into, map, map_res, opt, value}; use nom::multi::{many1, separated_list1}; use nom::sequence::{delimited, pair, preceded, terminated, tuple}; -pub fn selectors(input: Span) -> PResult { +pub fn selectors(input: Span) -> PResult { map( separated_list1(terminated(tag(","), ignore_comments), selector), - Selectors::new, + OldSelectors::new, )(input) } -pub fn selector(input: Span) -> PResult { +pub fn selector(input: Span) -> PResult { let (input, mut s) = selector_parts(input)?; - if s.last() == Some(&SelectorPart::Descendant) { + if s.last() == Some(&OldSelectorPart::Descendant) { s.pop(); } - Ok((input, Selector(s))) + Ok((input, OldSelector(s))) } -pub(crate) fn selector_parts(input: Span) -> PResult> { +pub(crate) fn selector_parts(input: Span) -> PResult> { many1(selector_part)(input) } -fn selector_part(input: Span) -> PResult { +fn selector_part(input: Span) -> PResult { let (input, mark) = alt((tag("&"), tag("::"), tag(":"), tag("."), tag("["), tag("")))( input, )?; match mark.fragment() { - b"&" => value(SelectorPart::BackRef, tag(""))(input), + b"&" => value(OldSelectorPart::BackRef, tag(""))(input), b"::" => map( pair( into(css_string), opt(delimited(tag("("), selectors, tag(")"))), ), - |(name, arg)| SelectorPart::PseudoElement { name, arg }, + |(name, arg)| OldSelectorPart::PseudoElement { name, arg }, )(input), b":" => map( pair( into(css_string), opt(delimited(tag("("), selectors, tag(")"))), ), - |(name, arg)| SelectorPart::Pseudo { name, arg }, + |(name, arg)| OldSelectorPart::Pseudo { name, arg }, )(input), b"." => map(simple_part, |mut s| { s.insert(0, '.'); - SelectorPart::Simple(s) + OldSelectorPart::Simple(s) })(input), b"[" => delimited( opt_spacelike, @@ -82,7 +82,7 @@ fn selector_part(input: Span) -> PResult { opt_spacelike, )), )), - |(name, op, val, modifier)| SelectorPart::Attribute { + |(name, op, val, modifier)| OldSelectorPart::Attribute { name: name.into(), op, val, @@ -90,7 +90,7 @@ fn selector_part(input: Span) -> PResult { }, ), map(terminated(name_opt_ns, opt_spacelike), |name| { - SelectorPart::Attribute { + OldSelectorPart::Attribute { name: name.into(), op: "".to_string(), val: "".into(), @@ -101,18 +101,18 @@ fn selector_part(input: Span) -> PResult { tag("]"), )(input), b"" => alt(( - map(simple_part, SelectorPart::Simple), + map(simple_part, OldSelectorPart::Simple), delimited( opt_spacelike, alt(( - value(SelectorPart::RelOp(b'>'), tag(">")), - value(SelectorPart::RelOp(b'+'), tag("+")), - value(SelectorPart::RelOp(b'~'), tag("~")), - value(SelectorPart::RelOp(b'\\'), tag("\\")), + value(OldSelectorPart::RelOp(b'>'), tag(">")), + value(OldSelectorPart::RelOp(b'+'), tag("+")), + value(OldSelectorPart::RelOp(b'~'), tag("~")), + value(OldSelectorPart::RelOp(b'\\'), tag("\\")), )), opt_spacelike, ), - value(SelectorPart::Descendant, spacelike2), + value(OldSelectorPart::Descendant, spacelike2), ))(input), _ => unreachable!(), } diff --git a/rsass/src/sass/selectors.rs b/rsass/src/sass/selectors.rs index ee0a97d4..207d3f46 100644 --- a/rsass/src/sass/selectors.rs +++ b/rsass/src/sass/selectors.rs @@ -32,8 +32,8 @@ impl Selectors { } /// Evaluate any interpolation in these Selectors. - pub fn eval(&self, scope: ScopeRef) -> Result { - let s = css::Selectors::new( + pub fn eval(&self, scope: ScopeRef) -> Result { + let s = css::OldSelectors::new( self.s .iter() .map(|s| s.eval(scope.clone())) @@ -67,12 +67,12 @@ impl Selector { pub fn new(s: Vec) -> Self { Self(s) } - fn eval(&self, scope: ScopeRef) -> Result { + fn eval(&self, scope: ScopeRef) -> Result { self.0 .iter() .map(|sp| sp.eval(scope.clone())) .collect::>() - .map(css::Selector) + .map(css::OldSelector) } } @@ -120,28 +120,28 @@ pub enum SelectorPart { } impl SelectorPart { - fn eval(&self, scope: ScopeRef) -> Result { + fn eval(&self, scope: ScopeRef) -> Result { match *self { Self::Attribute { ref name, ref op, ref val, ref modifier, - } => Ok(css::SelectorPart::Attribute { + } => Ok(css::OldSelectorPart::Attribute { name: name.evaluate(scope.clone())?, op: op.clone(), val: val.evaluate(scope)?.opt_unquote(), modifier: *modifier, }), Self::Simple(ref v) => { - Ok(css::SelectorPart::Simple(v.evaluate(scope)?.to_string())) + Ok(css::OldSelectorPart::Simple(v.evaluate(scope)?.to_string())) } Self::Pseudo { ref name, ref arg } => { let arg = match &arg { Some(ref a) => Some(a.eval(scope.clone())?), None => None, }; - Ok(css::SelectorPart::Pseudo { + Ok(css::OldSelectorPart::Pseudo { name: name.evaluate(scope)?, arg, }) @@ -151,14 +151,14 @@ impl SelectorPart { Some(ref a) => Some(a.eval(scope.clone())?), None => None, }; - Ok(css::SelectorPart::PseudoElement { + Ok(css::OldSelectorPart::PseudoElement { name: name.evaluate(scope)?, arg, }) } - Self::Descendant => Ok(css::SelectorPart::Descendant), - Self::RelOp(op) => Ok(css::SelectorPart::RelOp(op)), - Self::BackRef => Ok(css::SelectorPart::BackRef), + Self::Descendant => Ok(css::OldSelectorPart::Descendant), + Self::RelOp(op) => Ok(css::OldSelectorPart::RelOp(op)), + Self::BackRef => Ok(css::OldSelectorPart::BackRef), } } } diff --git a/rsass/src/variablescope.rs b/rsass/src/variablescope.rs index 5b771b30..067bb76d 100644 --- a/rsass/src/variablescope.rs +++ b/rsass/src/variablescope.rs @@ -1,5 +1,5 @@ //! A scope is something that contains variable values. -use crate::css::{CssString, SelectorCtx, Value}; +use crate::css::{CssString, OldSelectorCtx, Value}; use crate::input::SourcePos; use crate::output::Format; use crate::sass::{Expose, Function, Item, MixinDecl, Name, UseAs}; @@ -35,7 +35,7 @@ impl ScopeRef { Self::dynamic(Scope::sub(parent)) } /// Create a new subscope of a given parent with selectors. - pub fn sub_selectors(parent: Self, selectors: SelectorCtx) -> Self { + pub fn sub_selectors(parent: Self, selectors: OldSelectorCtx) -> Self { Self::dynamic(Scope::sub_selectors(parent, selectors)) } fn dynamic(scope: Scope) -> Self { @@ -204,7 +204,7 @@ pub struct Scope { variables: Mutex>, mixins: Mutex>, functions: Mutex>, - selectors: Option, + selectors: Option, forward: Mutex>, format: Format, /// The thing to use for `@content` in a mixin. @@ -259,7 +259,7 @@ impl Scope { } } /// Create a new subscope of a given parent with selectors. - pub fn sub_selectors(parent: ScopeRef, selectors: SelectorCtx) -> Self { + pub fn sub_selectors(parent: ScopeRef, selectors: OldSelectorCtx) -> Self { let format = parent.get_format(); Self { parent: Some(parent), @@ -500,9 +500,9 @@ impl Scope { } /// Get the selectors active for this scope. - pub fn get_selectors(&self) -> &SelectorCtx { + pub fn get_selectors(&self) -> &OldSelectorCtx { lazy_static! { - static ref ROOT: SelectorCtx = SelectorCtx::root(); + static ref ROOT: OldSelectorCtx = OldSelectorCtx::root(); } self.selectors.as_ref().unwrap_or_else(|| { self.parent.as_ref().map_or(&ROOT, |p| p.get_selectors()) From 11b281cf570b1f382109a09488fae43926f6222f Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Sun, 21 Jul 2024 02:31:20 +0200 Subject: [PATCH 02/10] New selectors 2: unalias the new. --- rsass/src/css/mod.rs | 2 +- rsass/src/css/selectors.rs | 2 +- rsass/src/sass/functions/selector.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rsass/src/css/mod.rs b/rsass/src/css/mod.rs index 41fe052f..2e18373d 100644 --- a/rsass/src/css/mod.rs +++ b/rsass/src/css/mod.rs @@ -24,6 +24,6 @@ pub use self::string::CssString; pub use self::value::{InvalidCss, Value, ValueMap, ValueToMapError}; pub(crate) use self::selectors::{ - CssSelectorSet, LogicalSelector, OldSelectorCtx, + CssSelectorSet, Selector, OldSelectorCtx, }; pub(crate) use self::util::{is_calc_name, is_function_name, is_not, IsNot}; diff --git a/rsass/src/css/selectors.rs b/rsass/src/css/selectors.rs index 31b6c35e..f7651a52 100644 --- a/rsass/src/css/selectors.rs +++ b/rsass/src/css/selectors.rs @@ -20,7 +20,7 @@ mod logical; mod pseudo; mod selectorset; pub(crate) use cssselectorset::CssSelectorSet; -pub(crate) use logical::Selector as LogicalSelector; +pub(crate) use logical::Selector; /// A full set of selectors. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd)] diff --git a/rsass/src/sass/functions/selector.rs b/rsass/src/sass/functions/selector.rs index 1e016e2b..dd6da0c5 100644 --- a/rsass/src/sass/functions/selector.rs +++ b/rsass/src/sass/functions/selector.rs @@ -1,5 +1,5 @@ use super::{unnamed, CheckedArg, FunctionMap}; -use crate::css::{CssSelectorSet, LogicalSelector, Value}; +use crate::css::{CssSelectorSet, Selector, Value}; use crate::value::ListSeparator; use crate::Scope; @@ -47,7 +47,7 @@ pub fn create_module() -> Scope { Ok(selector.replace(&original, &replacement)?.into()) }); def!(f, simple_selectors(selector), |s| { - let selector: LogicalSelector = s.get(name!(selector))?; + let selector: Selector = s.get(name!(selector))?; let result = selector .simple_selectors()? .into_iter() From 0a157eed341341cee2603159d52c200372243a89 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Mon, 22 Jul 2024 20:53:55 +0200 Subject: [PATCH 03/10] fmt. --- rsass/src/css/mod.rs | 8 ++++---- rsass/src/css/selectors.rs | 34 +++++++++++++++++++++------------- rsass/src/output/cssdest.rs | 2 +- rsass/src/output/transform.rs | 5 +++-- rsass/src/sass/selectors.rs | 6 +++--- rsass/src/variablescope.rs | 5 ++++- 6 files changed, 36 insertions(+), 24 deletions(-) diff --git a/rsass/src/css/mod.rs b/rsass/src/css/mod.rs index 2e18373d..c83bcb6d 100644 --- a/rsass/src/css/mod.rs +++ b/rsass/src/css/mod.rs @@ -19,11 +19,11 @@ pub use self::comment::Comment; pub use self::item::{Import, Item}; pub use self::mediarule::{MediaArgs, MediaRule}; pub use self::rule::{BodyItem, CustomProperty, Property, Rule}; -pub use self::selectors::{BadSelector, OldSelector, OldSelectorPart, OldSelectors}; +pub use self::selectors::{ + BadSelector, OldSelector, OldSelectorPart, OldSelectors, +}; pub use self::string::CssString; pub use self::value::{InvalidCss, Value, ValueMap, ValueToMapError}; -pub(crate) use self::selectors::{ - CssSelectorSet, Selector, OldSelectorCtx, -}; +pub(crate) use self::selectors::{CssSelectorSet, OldSelectorCtx, Selector}; pub(crate) use self::util::{is_calc_name, is_function_name, is_not, IsNot}; diff --git a/rsass/src/css/selectors.rs b/rsass/src/css/selectors.rs index f7651a52..f612c899 100644 --- a/rsass/src/css/selectors.rs +++ b/rsass/src/css/selectors.rs @@ -337,7 +337,10 @@ impl OldSelector { } else { let mut result = self.0.clone(); if !result.is_empty() - && !other.0.first().map_or(false, OldSelectorPart::is_operator) + && !other + .0 + .first() + .map_or(false, OldSelectorPart::is_operator) { result.push(OldSelectorPart::Descendant); } @@ -401,7 +404,10 @@ impl OldSelector { } fn has_double_combinator(&self) -> bool { self.0.windows(2).any(|w| { - matches!(w, [OldSelectorPart::RelOp(_), OldSelectorPart::RelOp(_)]) + matches!( + w, + [OldSelectorPart::RelOp(_), OldSelectorPart::RelOp(_)] + ) }) } } @@ -717,22 +723,24 @@ mod test { #[test] fn simple_join() { - let s = OldSelector(vec![OldSelectorPart::Simple("foo".into())]).join( - &OldSelector(vec![OldSelectorPart::Simple(".bar".into())]), - &OldSelector::root(), - ); + let s = OldSelector(vec![OldSelectorPart::Simple("foo".into())]) + .join( + &OldSelector(vec![OldSelectorPart::Simple(".bar".into())]), + &OldSelector::root(), + ); assert_eq!(format!("{}", s), "foo .bar") } #[test] fn backref_join() { - let s = OldSelector(vec![OldSelectorPart::Simple("foo".into())]).join( - &OldSelector(vec![ - OldSelectorPart::BackRef, - OldSelectorPart::Simple(".bar".into()), - ]), - &OldSelector::root(), - ); + let s = OldSelector(vec![OldSelectorPart::Simple("foo".into())]) + .join( + &OldSelector(vec![ + OldSelectorPart::BackRef, + OldSelectorPart::Simple(".bar".into()), + ]), + &OldSelector::root(), + ); assert_eq!(format!("{}", s), "foo.bar") } } diff --git a/rsass/src/output/cssdest.rs b/rsass/src/output/cssdest.rs index cf3eb3bb..147988bb 100644 --- a/rsass/src/output/cssdest.rs +++ b/rsass/src/output/cssdest.rs @@ -1,7 +1,7 @@ use super::CssData; use crate::css::{ AtRule, AtRuleBodyItem, Comment, CssString, CustomProperty, Import, Item, - MediaArgs, MediaRule, Property, Rule, OldSelectors, Value, + MediaArgs, MediaRule, OldSelectors, Property, Rule, Value, }; use crate::Invalid; diff --git a/rsass/src/output/transform.rs b/rsass/src/output/transform.rs index c0fa2cfd..ca3a9bc6 100644 --- a/rsass/src/output/transform.rs +++ b/rsass/src/output/transform.rs @@ -358,8 +358,9 @@ fn handle_item( Item::Rule(ref selectors, ref body) => { check_body(body, BodyContext::Rule)?; - let selectors = OldSelectorCtx::from(selectors.eval(scope.clone())?) - .inside(scope.get_selectors()); + let selectors = + OldSelectorCtx::from(selectors.eval(scope.clone())?) + .inside(scope.get_selectors()); let mut dest = dest.start_rule(selectors.real()).no_pos()?; let scope = ScopeRef::sub_selectors(scope, selectors); handle_body(body, &mut dest, scope, file_context)?; diff --git a/rsass/src/sass/selectors.rs b/rsass/src/sass/selectors.rs index 207d3f46..b05c6fdb 100644 --- a/rsass/src/sass/selectors.rs +++ b/rsass/src/sass/selectors.rs @@ -133,9 +133,9 @@ impl SelectorPart { val: val.evaluate(scope)?.opt_unquote(), modifier: *modifier, }), - Self::Simple(ref v) => { - Ok(css::OldSelectorPart::Simple(v.evaluate(scope)?.to_string())) - } + Self::Simple(ref v) => Ok(css::OldSelectorPart::Simple( + v.evaluate(scope)?.to_string(), + )), Self::Pseudo { ref name, ref arg } => { let arg = match &arg { Some(ref a) => Some(a.eval(scope.clone())?), diff --git a/rsass/src/variablescope.rs b/rsass/src/variablescope.rs index 067bb76d..ebea5f37 100644 --- a/rsass/src/variablescope.rs +++ b/rsass/src/variablescope.rs @@ -259,7 +259,10 @@ impl Scope { } } /// Create a new subscope of a given parent with selectors. - pub fn sub_selectors(parent: ScopeRef, selectors: OldSelectorCtx) -> Self { + pub fn sub_selectors( + parent: ScopeRef, + selectors: OldSelectorCtx, + ) -> Self { let format = parent.get_format(); Self { parent: Some(parent), From caa8805f2bd92d24b01857f04714fc5d5a36699d Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Fri, 26 Jul 2024 15:12:18 +0200 Subject: [PATCH 04/10] New selectors 3: Use new in css `Rule`. * Made new-style selectors public and restrict old-style to the crate. * Improved output-formatting of new-style selectors. * Partially implmented filtering out placeholders from new-style selectors. --- rsass/src/css/mod.rs | 13 +- rsass/src/css/rule.rs | 16 +-- rsass/src/css/selectors.rs | 24 +++- rsass/src/css/selectors/cssselectorset.rs | 23 +++- rsass/src/css/selectors/logical.rs | 153 ++++++++++++++++++---- rsass/src/css/selectors/pseudo.rs | 19 +++ rsass/src/css/selectors/selectorset.rs | 29 +++- rsass/src/lib.rs | 2 +- rsass/src/output/cssdata.rs | 4 +- rsass/src/output/cssdest.rs | 19 +-- rsass/src/output/transform.rs | 7 +- rsass/src/parser/css/rule.rs | 5 +- rsass/tests/misc/compressed.rs | 5 +- 13 files changed, 254 insertions(+), 65 deletions(-) diff --git a/rsass/src/css/mod.rs b/rsass/src/css/mod.rs index c83bcb6d..0fe510fb 100644 --- a/rsass/src/css/mod.rs +++ b/rsass/src/css/mod.rs @@ -19,11 +19,16 @@ pub use self::comment::Comment; pub use self::item::{Import, Item}; pub use self::mediarule::{MediaArgs, MediaRule}; pub use self::rule::{BodyItem, CustomProperty, Property, Rule}; -pub use self::selectors::{ - BadSelector, OldSelector, OldSelectorPart, OldSelectors, -}; +pub use self::selectors::{BadSelector, Selector, SelectorSet}; pub use self::string::CssString; pub use self::value::{InvalidCss, Value, ValueMap, ValueToMapError}; -pub(crate) use self::selectors::{CssSelectorSet, OldSelectorCtx, Selector}; +pub(crate) use self::selectors::{ + CssSelectorSet, OldSelector, OldSelectorCtx, OldSelectorPart, + OldSelectors, +}; pub(crate) use self::util::{is_calc_name, is_function_name, is_not, IsNot}; + +pub(crate) mod parser { + pub(crate) use super::selectors::parser::selector_set; +} diff --git a/rsass/src/css/rule.rs b/rsass/src/css/rule.rs index 925c6d30..cb5a6cc7 100644 --- a/rsass/src/css/rule.rs +++ b/rsass/src/css/rule.rs @@ -1,20 +1,20 @@ -use super::{AtRule, Comment, CssString, Import, OldSelectors, Value}; +use super::{AtRule, Comment, CssString, Import, SelectorSet, Value}; use crate::output::CssBuf; -use std::io::{self, Write}; +use std::io; /// A css rule. /// -/// A rule binds [`Selectors`] to a body of [`BodyItem`]s (mainly +/// A rule binds a [`SelectorSet`] to a body of [`BodyItem`]s (mainly /// properties with [`Value`]s). #[derive(Clone, Debug)] pub struct Rule { - pub(crate) selectors: OldSelectors, + pub(crate) selectors: SelectorSet, pub(crate) body: Vec, } impl Rule { /// Create a new Rule. - pub fn new(selectors: OldSelectors) -> Self { + pub fn new(selectors: SelectorSet) -> Self { Self { selectors, body: Vec::new(), @@ -30,11 +30,7 @@ impl Rule { if !self.body.is_empty() { if let Some(selectors) = self.selectors.no_placeholder() { buf.do_indent_no_nl(); - if buf.format().is_compressed() { - write!(buf, "{selectors:#}")?; - } else { - write!(buf, "{selectors}")?; - } + selectors.write_to(buf)?; buf.start_block(); for item in &self.body { item.write(buf)?; diff --git a/rsass/src/css/selectors.rs b/rsass/src/css/selectors.rs index f612c899..ecfec39c 100644 --- a/rsass/src/css/selectors.rs +++ b/rsass/src/css/selectors.rs @@ -11,6 +11,7 @@ use crate::input::SourcePos; use crate::parser::css::{selector, selector_parts, selectors}; use crate::parser::{input_span, ParseError}; use crate::value::ListSeparator; +use nom::combinator::all_consuming; use std::fmt; use std::io::Write; @@ -19,8 +20,14 @@ mod cssselectorset; mod logical; mod pseudo; mod selectorset; + pub(crate) use cssselectorset::CssSelectorSet; -pub(crate) use logical::Selector; +pub use logical::Selector; +pub use selectorset::SelectorSet; + +pub(crate) mod parser { + pub(crate) use super::selectorset::parser::selector_set; +} /// A full set of selectors. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd)] @@ -187,6 +194,21 @@ impl OldSelectorCtx { self.s.is_root() && self.backref == OldSelector::root() } + // I think any backrefs is guaranteed to be already resolved in self.s! + pub(crate) fn real_new(&self) -> CssSelectorSet { + if self.s.is_root() { + return CssSelectorSet { + s: SelectorSet { + s: vec![Selector::default()], + }, + }; + } + let span = input_span(self.s.to_string()); + let (_, value) = all_consuming(parser::selector_set)(span.borrow()) + .unwrap_or_else(|e| panic!("Bad selector {span:?}: {e}")); + let msg = format!("Bad real selectors {value:?}"); + value.try_into().expect(&msg) + } pub(crate) fn real(&self) -> OldSelectors { self.s.clone() } diff --git a/rsass/src/css/selectors/cssselectorset.rs b/rsass/src/css/selectors/cssselectorset.rs index 18d67562..ec2f7278 100644 --- a/rsass/src/css/selectors/cssselectorset.rs +++ b/rsass/src/css/selectors/cssselectorset.rs @@ -2,16 +2,19 @@ use super::selectorset::SelectorSet; use super::{BadSelector, BadSelector0}; use crate::css::Value; use crate::error::Invalid; +use crate::output::CssBuf; use crate::parser::{input_span, ParseError}; use crate::sass::CallError; use crate::value::ListSeparator; use nom::Finish; +use std::io; /// A `CssSelectorset` is like a [`Selectorset`] but valid in css. /// /// The practical difference is that a `CssSelectorset` is guaranteed /// not to contain backrefs (`&`), which may be present in a /// `Selectorset`. +#[derive(Clone, Debug)] pub struct CssSelectorSet { pub(super) s: SelectorSet, } @@ -50,10 +53,9 @@ impl CssSelectorSet { let selector = join(&value).map_err(|e| e.ctx(value))?; let span = input_span(selector); - let (rest, value) = - super::selectorset::parser::selector_set(span.borrow()) - .finish() - .map_err(ParseError::from)?; + let (rest, value) = super::parser::selector_set(span.borrow()) + .finish() + .map_err(ParseError::from)?; if rest.fragment().is_empty() { value.try_into() } else { @@ -61,6 +63,9 @@ impl CssSelectorSet { } } + pub fn no_placeholder(&self) -> Option { + self.s.no_placeholder().map(|s| Self { s }) + } pub fn is_superselector(&self, sub: &Self) -> bool { self.s.is_superselector(&sub.s) } @@ -149,6 +154,16 @@ impl CssSelectorSet { }, } } + + pub fn write_to(&self, buf: &mut CssBuf) -> io::Result<()> { + self.s.write_to(buf) + } +} + +impl From for SelectorSet { + fn from(value: CssSelectorSet) -> Self { + value.s + } } impl TryFrom for CssSelectorSet { diff --git a/rsass/src/css/selectors/logical.rs b/rsass/src/css/selectors/logical.rs index cc2bc650..31ff68ac 100644 --- a/rsass/src/css/selectors/logical.rs +++ b/rsass/src/css/selectors/logical.rs @@ -9,11 +9,14 @@ use super::pseudo::Pseudo; use super::selectorset::SelectorSet; use super::{BadSelector, BadSelector0, CssSelectorSet}; use crate::css::Value; +use crate::output::CssBuf; use crate::parser::input_span; use crate::sass::CallError; use crate::value::ListSeparator; use crate::{Invalid, ParseError}; +use core::fmt; use lazy_static::lazy_static; +use std::io; use std::iter::once; type RelBox = Box<(RelKind, Selector)>; @@ -21,8 +24,8 @@ type RelBox = Box<(RelKind, Selector)>; /// A selector more aimed at making it easy to implement selector functions. /// /// A logical selector is fully resolved (cannot contain an `&` backref). -#[derive(Debug, Default, Clone, PartialEq, Eq)] -pub(crate) struct Selector { +#[derive(Default, Clone, PartialEq, Eq)] +pub struct Selector { backref: Option<()>, element: Option, placeholders: Vec, @@ -34,6 +37,31 @@ pub(crate) struct Selector { } impl Selector { + pub fn no_placeholder(&self) -> Option { + if !self.placeholders.is_empty() { + return None; + } + let rel_of = if let Some((kind, rel)) = self.rel_of.as_deref() { + if let Some(rel) = rel.no_placeholder() { + Some(Box::new((*kind, rel))) + } else { + return None; + } + } else { + None + }; + let pseudo = self + .pseudo + .iter() + .filter_map(Pseudo::no_placeholder) + .collect(); + Some(Self { + rel_of, + pseudo, + ..self.clone() + }) + } + pub(super) fn append(&self, other: &Self) -> Result { if self.is_local_empty() { return Err(AppendError::Parent); @@ -502,6 +530,24 @@ impl Selector { Ok(result) } + pub fn write_to(&self, buf: &mut CssBuf) -> io::Result<()> { + if let Some((kind, sel)) = self.rel_of.as_deref() { + sel.write_to(buf)?; + if let Some(symbol) = kind.symbol() { + if !sel.is_local_empty() { + buf.add_one(" ", ""); + } + buf.add_str(symbol); + buf.add_one(" ", ""); + } else { + buf.add_str(" "); + } + } + // TODO: This could be much more efficient! :-) + buf.add_str(&self.clone().last_compound_str()); + Ok(()) + } + pub(super) fn into_string_vec(mut self) -> Vec { let mut vec = if let Some((kind, sel)) = self.rel_of.take().map(|b| *b) { @@ -568,6 +614,8 @@ impl Selector { list.iter() .try_fold(None, |a, v| { let mut s = match v { + // TODO: This is probably broken when + // a vailue is like ["a", ">", "b"] Value::Literal(s) => { ParseError::check(parser::selector( input_span(s.value()).borrow(), @@ -774,6 +822,37 @@ impl From for Value { } } +impl fmt::Debug for Selector { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut s = f.debug_struct("Selector"); + if let Some((kind, rel)) = self.rel_of.as_deref() { + s.field(&format!("{kind:?}"), &rel); + } + if self.backref.is_some() { + s.field("backref", &"&"); + } + if let Some(elem) = &self.element { + s.field("element", elem); + } + if !self.placeholders.is_empty() { + s.field("placeholders", &self.placeholders); + } + if let Some(id) = &self.id { + s.field("id", &id); + } + if !self.classes.is_empty() { + s.field("classes", &self.classes); + } + if !self.attr.is_empty() { + s.field("attr", &self.attr); + } + if !self.pseudo.is_empty() { + s.field("pseudo", &self.pseudo); + } + s.finish() + } +} + #[derive(Clone, Debug, PartialEq, Eq)] struct ElemType { s: String, @@ -854,6 +933,8 @@ impl RelKind { } pub(crate) mod parser { + use std::str::from_utf8; + use super::super::attribute::parser::attribute; use super::super::pseudo::parser::pseudo; use super::{ElemType, RelKind, Selector}; @@ -861,13 +942,14 @@ pub(crate) mod parser { use crate::parser::util::{opt_spacelike, spacelike}; use crate::parser::{PResult, Span}; use nom::branch::alt; - use nom::bytes::complete::tag; - use nom::combinator::{map, opt, value, verify}; + use nom::bytes::complete::{is_a, tag}; + use nom::combinator::{map, opt, recognize, value, verify}; use nom::multi::fold_many0; - use nom::sequence::{delimited, pair, preceded}; + use nom::sequence::{delimited, pair, preceded, terminated, tuple}; pub(crate) fn selector(input: Span) -> PResult { - let (input, prerel) = opt(rel_kind)(input)?; + let (input, prerel) = + preceded(opt_spacelike, opt(explicit_rel_kind))(input)?; let (input, first) = if let Some(prerel) = prerel { let (input, first) = opt(compound_selector)(input)?; let mut first = first.unwrap_or_default(); @@ -876,34 +958,53 @@ pub(crate) mod parser { } else { compound_selector(input)? }; - fold_many0( - pair(rel_kind, opt(compound_selector)), - move || first.clone(), - |rel, (kind, e)| { - let mut e = e.unwrap_or_default(); - e.rel_of = Some(Box::new((kind, rel))); - e - }, + terminated( + fold_many0( + verify(pair(rel_kind, opt(compound_selector)), |(k, s)| { + k.symbol().is_some() || s.is_some() + }), + move || first.clone(), + |rel, (kind, e)| { + let mut e = e.unwrap_or_default(); + e.rel_of = Some(Box::new((kind, rel))); + e + }, + ), + opt_spacelike, + )(input) + } + + fn explicit_rel_kind(input: Span) -> PResult { + delimited( + opt_spacelike, + alt(( + value(RelKind::AdjacentSibling, tag("+")), + value(RelKind::Sibling, tag("~")), + value(RelKind::Parent, tag(">")), + )), + opt_spacelike, )(input) } fn rel_kind(input: Span) -> PResult { - alt(( - delimited( - opt_spacelike, - alt(( - value(RelKind::AdjacentSibling, tag("+")), - value(RelKind::Sibling, tag("~")), - value(RelKind::Parent, tag(">")), - )), - opt_spacelike, - ), - value(RelKind::Ancestor, spacelike), - ))(input) + alt((explicit_rel_kind, value(RelKind::Ancestor, spacelike)))(input) } pub(crate) fn compound_selector(input: Span) -> PResult { let mut result = Selector::default(); + if let PResult::Ok((rest, stop)) = recognize(tuple(( + is_a("0123456789."), + opt(tuple((is_a("eE"), opt(tag("-")), is_a("0123456789")))), + tag("%"), + )))(input) + { + // TODO: Remove this. + // It is a temporary workaround for keyframe support. + result.element = Some(ElemType { + s: dbg!(from_utf8(stop.fragment()).unwrap()).to_string(), + }); + return Ok((rest, result)); + } let (rest, backref) = opt(value((), tag("&")))(input)?; result.backref = backref; let (mut rest, elem) = opt(name_opt_ns)(rest)?; diff --git a/rsass/src/css/selectors/pseudo.rs b/rsass/src/css/selectors/pseudo.rs index eeec8d5d..70e06e85 100644 --- a/rsass/src/css/selectors/pseudo.rs +++ b/rsass/src/css/selectors/pseudo.rs @@ -12,6 +12,25 @@ pub(crate) struct Pseudo { } impl Pseudo { + // TODO: IS None match-all (ignore this) or match none (ignore container)? + pub(crate) fn no_placeholder(&self) -> Option { + let arg = match &self.arg { + Arg::Selector(s) => { + if let Some(s) = s.no_placeholder() { + Arg::Selector(s) + } else { + return None; // TODO! Positive or negative None? + } + } + arg => arg.clone(), + }; + Some(Self { + arg, + name: self.name.clone(), + element: self.element, + }) + } + pub(crate) fn is_superselector(&self, b: &Self) -> bool { if self.is_element() != b.is_element() || self.name != b.name { return false; diff --git a/rsass/src/css/selectors/selectorset.rs b/rsass/src/css/selectors/selectorset.rs index 8d59732c..980a5379 100644 --- a/rsass/src/css/selectors/selectorset.rs +++ b/rsass/src/css/selectors/selectorset.rs @@ -1,18 +1,33 @@ use super::{logical::Selector, BadSelector, BadSelector0, CssSelectorSet}; +use crate::output::CssBuf; use crate::parser::input_span; use crate::value::ListSeparator; use crate::ParseError; use crate::{css::Value, Invalid}; +use std::io; /// A set of selectors. /// This is the normal top-level selector, which can be a single /// [`Selector`] or a comma-separated list (set) of such selectors. #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct SelectorSet { +pub struct SelectorSet { pub(super) s: Vec, } impl SelectorSet { + pub fn no_placeholder(&self) -> Option { + let s = self + .s + .iter() + .filter_map(Selector::no_placeholder) + .collect::>(); + if s.is_empty() { + None + } else { + Some(Self { s }) + } + } + pub(crate) fn extend( self, extendee: &Self, @@ -60,8 +75,20 @@ impl SelectorSet { Ok(()) } + pub fn write_to(&self, buf: &mut CssBuf) -> io::Result<()> { + if let Some((first, rest)) = self.s.split_first() { + first.write_to(buf)?; + for one in rest { + buf.add_one(", ", ","); + one.write_to(buf)?; + } + } + Ok(()) + } + // TODO: Get rid of this, use the above! pub(super) fn write_to_buf(&self, buf: &mut String) { fn write_one(s: &Selector, buf: &mut String) { + // Note: this should be made much more efficient! buf.push_str(&s.clone().into_string_vec().join(" ")); } if let Some((first, rest)) = self.s.split_first() { diff --git a/rsass/src/lib.rs b/rsass/src/lib.rs index 581ea74d..fa620192 100644 --- a/rsass/src/lib.rs +++ b/rsass/src/lib.rs @@ -36,7 +36,7 @@ //! usable for my personal projects, and the number of working tests are //! improving. #![forbid(unsafe_code)] -#![forbid(missing_docs)] +// #![forbid(missing_docs)] (FIXME: temporarily ignored) pub mod css; mod error; diff --git a/rsass/src/output/cssdata.rs b/rsass/src/output/cssdata.rs index 12742ef6..482f8e1b 100644 --- a/rsass/src/output/cssdata.rs +++ b/rsass/src/output/cssdata.rs @@ -3,7 +3,7 @@ use super::cssdest::{ }; use super::{CssBuf, Format}; use crate::css::{ - Comment, CssString, Import, Item, MediaArgs, OldSelectors, Value, + Comment, CssSelectorSet, CssString, Import, Item, MediaArgs, Value, }; use crate::{Error, Invalid, ScopeRef}; use std::collections::BTreeMap; @@ -86,7 +86,7 @@ impl CssDestination for CssData { self } - fn start_rule(&mut self, selectors: OldSelectors) -> Result { + fn start_rule(&mut self, selectors: CssSelectorSet) -> Result { Ok(RuleDest::new(self, selectors)) } fn start_atmedia(&mut self, args: MediaArgs) -> AtMediaDest { diff --git a/rsass/src/output/cssdest.rs b/rsass/src/output/cssdest.rs index 147988bb..c9c34f3e 100644 --- a/rsass/src/output/cssdest.rs +++ b/rsass/src/output/cssdest.rs @@ -1,7 +1,8 @@ use super::CssData; use crate::css::{ - AtRule, AtRuleBodyItem, Comment, CssString, CustomProperty, Import, Item, - MediaArgs, MediaRule, OldSelectors, Property, Rule, Value, + AtRule, AtRuleBodyItem, Comment, CssSelectorSet, CssString, + CustomProperty, Import, Item, MediaArgs, MediaRule, Property, Rule, + Value, }; use crate::Invalid; @@ -10,7 +11,7 @@ type Result = std::result::Result; pub trait CssDestination { fn head(&mut self) -> &mut CssData; - fn start_rule(&mut self, selectors: OldSelectors) -> Result; + fn start_rule(&mut self, selectors: CssSelectorSet) -> Result; fn start_atmedia(&mut self, args: MediaArgs) -> AtMediaDest; fn start_atrule(&mut self, name: String, args: Value) -> AtRuleDest; fn start_nsrule(&mut self, name: String) -> Result; @@ -38,11 +39,11 @@ pub struct RuleDest<'a> { impl<'a> RuleDest<'a> { pub fn new( parent: &'a mut dyn CssDestination, - selectors: OldSelectors, + selectors: CssSelectorSet, ) -> Self { RuleDest { parent, - rule: Rule::new(selectors), + rule: Rule::new(selectors.into()), trail: Default::default(), } } @@ -67,7 +68,7 @@ impl<'a> CssDestination for RuleDest<'a> { fn head(&mut self) -> &mut CssData { self.parent.head() } - fn start_rule(&mut self, selectors: OldSelectors) -> Result { + fn start_rule(&mut self, selectors: CssSelectorSet) -> Result { Ok(RuleDest::new(self, selectors)) } fn start_atmedia(&mut self, args: MediaArgs) -> AtMediaDest { @@ -140,7 +141,7 @@ impl<'a> CssDestination for NsRuleDest<'a> { fn head(&mut self) -> &mut CssData { self.parent.head() } - fn start_rule(&mut self, _selectors: OldSelectors) -> Result { + fn start_rule(&mut self, _selectors: CssSelectorSet) -> Result { Err(Invalid::InNsRule) } fn start_atmedia(&mut self, args: MediaArgs) -> AtMediaDest { @@ -219,7 +220,7 @@ impl<'a> CssDestination for AtRuleDest<'a> { fn head(&mut self) -> &mut CssData { self.parent.head() } - fn start_rule(&mut self, selectors: OldSelectors) -> Result { + fn start_rule(&mut self, selectors: CssSelectorSet) -> Result { Ok(RuleDest::new(self, selectors)) } fn start_atmedia(&mut self, args: MediaArgs) -> AtMediaDest { @@ -338,7 +339,7 @@ impl<'a> CssDestination for AtMediaDest<'a> { self.parent.head() } - fn start_rule(&mut self, selectors: OldSelectors) -> Result { + fn start_rule(&mut self, selectors: CssSelectorSet) -> Result { Ok(RuleDest::new(self, selectors)) } fn start_atmedia(&mut self, args: MediaArgs) -> AtMediaDest { diff --git a/rsass/src/output/transform.rs b/rsass/src/output/transform.rs index ca3a9bc6..8f8500ff 100644 --- a/rsass/src/output/transform.rs +++ b/rsass/src/output/transform.rs @@ -152,7 +152,7 @@ fn handle_item( let selectors = scope.get_selectors(); if !selectors.is_root() { let mut rule = thead - .start_rule(selectors.real()) + .start_rule(selectors.real_new()) .at(pos)?; handle_body( &items, @@ -207,7 +207,8 @@ fn handle_item( .with_backref(scope.get_selectors().one()); let subscope = ScopeRef::sub_selectors(scope, selectors.clone()); if !selectors.is_root() { - let mut rule = dest.start_rule(selectors.real()).no_pos()?; + let mut rule = + dest.start_rule(selectors.real_new()).no_pos()?; handle_body(body, &mut rule, subscope, file_context)?; } else { handle_body(body, dest, subscope, file_context)?; @@ -361,7 +362,7 @@ fn handle_item( let selectors = OldSelectorCtx::from(selectors.eval(scope.clone())?) .inside(scope.get_selectors()); - let mut dest = dest.start_rule(selectors.real()).no_pos()?; + let mut dest = dest.start_rule(selectors.real_new()).no_pos()?; let scope = ScopeRef::sub_selectors(scope, selectors); handle_body(body, &mut dest, scope, file_context)?; } diff --git a/rsass/src/parser/css/rule.rs b/rsass/src/parser/css/rule.rs index 55b250a8..75bb628d 100644 --- a/rsass/src/parser/css/rule.rs +++ b/rsass/src/parser/css/rule.rs @@ -1,7 +1,8 @@ use super::super::util::opt_spacelike; use super::super::{PResult, Span}; use super::strings::custom_value; -use super::{comment, import2, selectors, strings, values}; +use super::{comment, import2, strings, values}; +use crate::css::parser::selector_set; use crate::css::{BodyItem, CustomProperty, Property, Rule}; use nom::branch::alt; use nom::bytes::complete::tag; @@ -13,7 +14,7 @@ use nom::sequence::{delimited, pair, preceded, terminated}; pub fn rule(input: Span) -> PResult { map( pair( - terminated(selectors, terminated(tag("{"), opt_spacelike)), + terminated(selector_set, terminated(tag("{"), opt_spacelike)), many_till(terminated(body_item, opt_spacelike), tag("}")), ), |(selectors, (body, _))| Rule { selectors, body }, diff --git a/rsass/tests/misc/compressed.rs b/rsass/tests/misc/compressed.rs index 6d55293d..2141b805 100644 --- a/rsass/tests/misc/compressed.rs +++ b/rsass/tests/misc/compressed.rs @@ -146,8 +146,8 @@ fn t10_classes_and_ids() { d #id, f ~ g.other + h, > i#grar {\n bloo: bloo;\n \ blee: blee;\n }\n}", "a+b,.class{blah:blah;bleh:bleh}\ - a+b d #id,a+b f ~ g.other+h,a+b>i#grar,.class d #id,\ - .class f ~ g.other+h,.class>i#grar{bloo:bloo;blee:blee}\n", + a+b d #id,a+b f~g.other+h,a+b>i#grar,.class d #id,\ + .class f~g.other+h,.class>i#grar{bloo:bloo;blee:blee}\n", ) } @@ -260,6 +260,7 @@ fn t49_interpolants_in_css_imports() { } #[test] +#[ignore = "argument selectors currently not compressed"] fn t50_wrapped_pseudo_selectors() { check( b"div {\n \ From 44e7f392b836c6dd06f8bffa00c91ec9350e1ff2 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Sun, 4 Aug 2024 20:45:01 +0200 Subject: [PATCH 05/10] New selectors 4: Use new in transform. The conversion from sass selectors to css selectors (i.e. the evaluation of string interpolations in selectors) are still done through the old css selectors. The selectors context in a variable scope is now new selectors. Writing new selectors is now don to `CssBuf` (handling different formats) rather than through the `Display` trait. Improved new pseudo selectors and some other selector handling using a three-state option that can be "these selectors", "match-noting", or "match anything". --- rsass/src/css/mod.rs | 3 +- rsass/src/css/rule.rs | 28 ++-- rsass/src/css/selectors.rs | 82 +++++++++++ rsass/src/css/selectors/attribute.rs | 17 +-- rsass/src/css/selectors/cssselectorset.rs | 21 ++- rsass/src/css/selectors/logical.rs | 129 ++++++++++++------ rsass/src/css/selectors/opt.rs | 64 +++++++++ rsass/src/css/selectors/pseudo.rs | 56 +++++--- rsass/src/css/selectors/selectorset.rs | 74 ++++++++-- rsass/src/lib.rs | 1 + rsass/src/output/cssbuf.rs | 7 + rsass/src/output/transform.rs | 30 ++-- rsass/src/sass/callable.rs | 8 +- rsass/src/variablescope.rs | 15 +- rsass/tests/misc/compressed.rs | 1 - .../imported/at_root_prefix.rs | 2 +- .../imported/basic_prefix.rs | 2 +- .../nested/at_root_prefix.rs | 2 +- .../base_level_parent/nested/basic_prefix.rs | 2 +- .../base_level_parent/root/at_root_prefix.rs | 2 +- .../base_level_parent/root/basic_prefix.rs | 2 +- rsass/tests/spec/libsass/propsets.rs | 1 - .../spec/libsass_closed_issues/issue_1710.rs | 1 - .../spec/libsass_closed_issues/issue_1822.rs | 2 +- .../spec/libsass_closed_issues/issue_1904.rs | 1 + .../spec/libsass_closed_issues/issue_2116.rs | 1 - .../spec/libsass_closed_issues/issue_2155.rs | 2 +- .../spec/libsass_closed_issues/issue_2358.rs | 1 + .../spec/libsass_closed_issues/issue_738.rs | 1 + .../basic/t12_pseudo_classes_and_elements.rs | 1 + .../basic/t13_back_references.rs | 1 + .../non_conformant/basic/t44_bem_selectors.rs | 1 + 32 files changed, 423 insertions(+), 138 deletions(-) create mode 100644 rsass/src/css/selectors/opt.rs diff --git a/rsass/src/css/mod.rs b/rsass/src/css/mod.rs index 0fe510fb..b03a8f8b 100644 --- a/rsass/src/css/mod.rs +++ b/rsass/src/css/mod.rs @@ -24,8 +24,7 @@ pub use self::string::CssString; pub use self::value::{InvalidCss, Value, ValueMap, ValueToMapError}; pub(crate) use self::selectors::{ - CssSelectorSet, OldSelector, OldSelectorCtx, OldSelectorPart, - OldSelectors, + CssSelectorSet, OldSelector, OldSelectorPart, OldSelectors, SelectorCtx, }; pub(crate) use self::util::{is_calc_name, is_function_name, is_not, IsNot}; diff --git a/rsass/src/css/rule.rs b/rsass/src/css/rule.rs index cb5a6cc7..3ab74faf 100644 --- a/rsass/src/css/rule.rs +++ b/rsass/src/css/rule.rs @@ -1,4 +1,6 @@ -use super::{AtRule, Comment, CssString, Import, SelectorSet, Value}; +use super::{ + selectors::Opt, AtRule, Comment, CssString, Import, SelectorSet, Value, +}; use crate::output::CssBuf; use std::io; @@ -28,15 +30,23 @@ impl Rule { /// Write this rule to a css output buffer. pub(crate) fn write(&self, buf: &mut CssBuf) -> io::Result<()> { if !self.body.is_empty() { - if let Some(selectors) = self.selectors.no_placeholder() { - buf.do_indent_no_nl(); - selectors.write_to(buf)?; - buf.start_block(); - for item in &self.body { - item.write(buf)?; - } - buf.end_block(); + let s = self.selectors.no_placeholder(); + if matches!(s, Opt::None) { + return Ok(()); } + buf.do_indent_no_nl(); + let p = buf.len(); + if let Opt::Some(s) = s { + s.write_to(buf); + } + if buf.len() == p { + buf.add_str("*"); + } + buf.start_block(); + for item in &self.body { + item.write(buf)?; + } + buf.end_block(); } Ok(()) } diff --git a/rsass/src/css/selectors.rs b/rsass/src/css/selectors.rs index ecfec39c..117f7b16 100644 --- a/rsass/src/css/selectors.rs +++ b/rsass/src/css/selectors.rs @@ -18,9 +18,11 @@ use std::io::Write; mod attribute; mod cssselectorset; mod logical; +mod opt; mod pseudo; mod selectorset; +pub(crate) use self::opt::Opt; pub(crate) use cssselectorset::CssSelectorSet; pub use logical::Selector; pub use selectorset::SelectorSet; @@ -170,6 +172,86 @@ impl From for Value { } } +/// A full set of selectors with a separate backref. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SelectorCtx { + /// The actual selectors. + s: CssSelectorSet, + backref: CssSelectorSet, +} + +impl SelectorCtx { + pub fn root() -> Self { + Self { + s: CssSelectorSet::root(), + backref: CssSelectorSet::root(), + } + } + /// Return true if this is a root (empty) selector. + pub fn is_root(&self) -> bool { + // TODO: Just s? Or really both s and backref? + self.s.is_root() && self.backref.is_root() + } + + pub fn real(&self) -> CssSelectorSet { + self.s.clone() + } + + /// Get the first of these selectors (or the root selector if empty). + pub(crate) fn one(&self) -> Selector { + self.s.s.one() + } + + /// Evaluate selectors inside this context. + pub(crate) fn nest(&self, selectors: SelectorSet) -> CssSelectorSet { + if selectors.has_backref() { + let backref = if self.s.is_root() { + &self.backref + } else { + &self.s + }; + CssSelectorSet { + s: selectors.resolve_ref(backref), + } + } else { + self.s.nest(selectors) + } + } + pub(crate) fn at_root(&self, selectors: SelectorSet) -> Self { + let backref = if self.s.is_root() { + &self.backref + } else { + &self.s + }; + let s = selectors + .s + .into_iter() + .flat_map(|s| { + if s.has_backref() { + s.resolve_ref(backref) + } else { + vec![s] + } + }) + .collect(); + Self { + s: CssSelectorSet { + s: SelectorSet { s }, + }, + backref: backref.clone(), + } + } +} + +impl From for SelectorCtx { + fn from(value: CssSelectorSet) -> Self { + Self { + s: value, + backref: CssSelectorSet::root(), + } + } +} + /// A full set of selectors with a separate backref. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd)] pub struct OldSelectorCtx { diff --git a/rsass/src/css/selectors/attribute.rs b/rsass/src/css/selectors/attribute.rs index 7ccb6596..e031df2f 100644 --- a/rsass/src/css/selectors/attribute.rs +++ b/rsass/src/css/selectors/attribute.rs @@ -1,4 +1,4 @@ -use crate::css::CssString; +use crate::{css::CssString, output::CssBuf}; /// A logical attribute selector. #[derive(Debug, Clone, PartialEq, Eq)] @@ -21,15 +21,16 @@ impl Attribute { && self.modifier == b.modifier } - pub(super) fn write_to_buf(&self, buf: &mut String) { - use std::fmt::Write; - buf.push('['); - write!(buf, "{}{}{}", self.name, self.op, self.val).unwrap(); + pub(super) fn write_to_buf(&self, buf: &mut CssBuf) { + buf.add_str("["); + buf.add_str(&self.name); + buf.add_str(&self.op); + buf.add_str(&self.val.to_string()); if let Some(m) = self.modifier { - buf.push(' '); - buf.push(m); + buf.add_str(" "); + buf.add_char(m); } - buf.push(']'); + buf.add_str("]"); } } diff --git a/rsass/src/css/selectors/cssselectorset.rs b/rsass/src/css/selectors/cssselectorset.rs index ec2f7278..05acec37 100644 --- a/rsass/src/css/selectors/cssselectorset.rs +++ b/rsass/src/css/selectors/cssselectorset.rs @@ -1,5 +1,5 @@ use super::selectorset::SelectorSet; -use super::{BadSelector, BadSelector0}; +use super::{BadSelector, BadSelector0, Opt}; use crate::css::Value; use crate::error::Invalid; use crate::output::CssBuf; @@ -7,19 +7,28 @@ use crate::parser::{input_span, ParseError}; use crate::sass::CallError; use crate::value::ListSeparator; use nom::Finish; -use std::io; /// A `CssSelectorset` is like a [`Selectorset`] but valid in css. /// /// The practical difference is that a `CssSelectorset` is guaranteed /// not to contain backrefs (`&`), which may be present in a /// `Selectorset`. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct CssSelectorSet { pub(super) s: SelectorSet, } impl CssSelectorSet { + pub(crate) fn root() -> Self { + Self { + s: SelectorSet::root(), + } + } + /// Return true if this is a root (empty) selector. + pub fn is_root(&self) -> bool { + self.s.is_root() + } + pub fn parse_value(value: Value) -> Result { fn join(value: &Value) -> Result { if let Value::List(vs, Some(ListSeparator::Comma), false) = value @@ -63,7 +72,7 @@ impl CssSelectorSet { } } - pub fn no_placeholder(&self) -> Option { + pub(crate) fn no_placeholder(&self) -> Opt { self.s.no_placeholder().map(|s| Self { s }) } pub fn is_superselector(&self, sub: &Self) -> bool { @@ -95,6 +104,8 @@ impl CssSelectorSet { self.s.extend(&extendee.s, &extender.s).map(|s| Self { s }) } + /// Nest `other` selectors inside this as though they were nested + /// within one another in the stylesheet. pub(crate) fn nest(&self, other: SelectorSet) -> Self { let mut parts = other .s @@ -155,7 +166,7 @@ impl CssSelectorSet { } } - pub fn write_to(&self, buf: &mut CssBuf) -> io::Result<()> { + pub fn write_to(&self, buf: &mut CssBuf) { self.s.write_to(buf) } } diff --git a/rsass/src/css/selectors/logical.rs b/rsass/src/css/selectors/logical.rs index 31ff68ac..1a00d44a 100644 --- a/rsass/src/css/selectors/logical.rs +++ b/rsass/src/css/selectors/logical.rs @@ -7,6 +7,7 @@ use super::attribute::Attribute; use super::pseudo::Pseudo; use super::selectorset::SelectorSet; +use super::Opt; use super::{BadSelector, BadSelector0, CssSelectorSet}; use crate::css::Value; use crate::output::CssBuf; @@ -16,7 +17,6 @@ use crate::value::ListSeparator; use crate::{Invalid, ParseError}; use core::fmt; use lazy_static::lazy_static; -use std::io; use std::iter::once; type RelBox = Box<(RelKind, Selector)>; @@ -37,30 +37,53 @@ pub struct Selector { } impl Selector { - pub fn no_placeholder(&self) -> Option { + pub(crate) fn no_placeholder(&self) -> Opt { if !self.placeholders.is_empty() { - return None; + return Opt::None; + } + if self.is_local_empty() && self.rel_of.is_some() { + eprintln!("Deprecated dobule empty relation"); + return Opt::None; } + let rel_of = if let Some((kind, rel)) = self.rel_of.as_deref() { - if let Some(rel) = rel.no_placeholder() { - Some(Box::new((*kind, rel))) - } else { - return None; + match rel.no_placeholder() { + Opt::Some(rel) => Some(Box::new((*kind, rel))), + Opt::Any => None, + Opt::None => return Opt::None, } } else { None }; - let pseudo = self - .pseudo - .iter() - .filter_map(Pseudo::no_placeholder) - .collect(); - Some(Self { + let pseudo = match Opt::collect_neg( + self.pseudo.iter().map(Pseudo::no_placeholder), + ) { + Opt::Some(p) => p, + Opt::Any => vec![], + Opt::None => return Opt::None, + }; + Opt::Some(Self { rel_of, pseudo, ..self.clone() }) } + pub(crate) fn no_leading_combinator(&self) -> Opt { + if self.has_leading_combinator() { + Opt::None + } else { + Opt::Some(self.clone()) + } + } + fn has_leading_combinator(&self) -> bool { + self.rel_of.as_deref().map_or(false, |(_k, r)| { + if r.rel_of.is_none() { + r.is_local_empty() + } else { + r.has_leading_combinator() + } + }) + } pub(super) fn append(&self, other: &Self) -> Result { if self.is_local_empty() { @@ -148,10 +171,32 @@ impl Selector { pub(super) fn nest(&self, other: &Self) -> Self { let mut result = other.clone(); - if let Some(rel) = result.rel_of.take() { - result.rel_of = Some(Box::new((rel.0, self.nest(&rel.1)))); - } else { - result.rel_of = Some(Box::new((RelKind::Ancestor, self.clone()))); + if !self.is_local_empty() || self.rel_of.is_some() { + result.rel_of = match result.rel_of.take().map(|b| *b) { + Some((kind, rel)) => { + let rel = self.nest(&rel); + if rel.is_local_empty() { + match rel.rel_of.map(|b| *b) { + Some((rk, rr)) => match (kind, rk) { + (kind, RelKind::Ancestor) => { + Some(Box::new((kind, rr))) + } + (kind, rk) => Some(Box::new(( + kind, + Selector { + rel_of: Some(Box::new((rk, rr))), + ..Default::default() + }, + ))), + }, + None => None, + } + } else { + Some(Box::new((kind, rel))) + } + } + None => Some(Box::new((RelKind::Ancestor, self.clone()))), + }; } result } @@ -176,7 +221,7 @@ impl Selector { s: format!("{}{}", a.s, b.s), }), }; - slf.unify(s) + s.unify(slf) }) .collect() } else { @@ -518,21 +563,21 @@ impl Selector { result.extend(self.classes.iter().map(|c| format!(".{c}"))); result.extend(self.id.iter().map(|id| format!("#{id}"))); result.extend(self.attr.iter().map(|a| { - let mut s = String::new(); + let mut s = CssBuf::new(Default::default()); a.write_to_buf(&mut s); - s + String::from_utf8_lossy(&s.take()).to_string() })); result.extend(self.pseudo.iter().map(|p| { - let mut s = String::new(); + let mut s = CssBuf::new(Default::default()); p.write_to_buf(&mut s); - s + String::from_utf8_lossy(&s.take()).to_string() })); Ok(result) } - pub fn write_to(&self, buf: &mut CssBuf) -> io::Result<()> { + pub fn write_to(&self, buf: &mut CssBuf) { if let Some((kind, sel)) = self.rel_of.as_deref() { - sel.write_to(buf)?; + sel.write_to(buf); if let Some(symbol) = kind.symbol() { if !sel.is_local_empty() { buf.add_one(" ", ""); @@ -543,9 +588,7 @@ impl Selector { buf.add_str(" "); } } - // TODO: This could be much more efficient! :-) - buf.add_str(&self.clone().last_compound_str()); - Ok(()) + self.write_last_compound_to(buf) } pub(super) fn into_string_vec(mut self) -> Vec { @@ -567,40 +610,42 @@ impl Selector { } fn last_compound_str(self) -> String { - let mut buf = String::new(); + let mut buf = CssBuf::new(Default::default()); + self.write_last_compound_to(&mut buf); + String::from_utf8_lossy(&buf.take()).to_string() + } + fn write_last_compound_to(&self, buf: &mut CssBuf) { if self.backref.is_some() { - buf.push('&'); + buf.add_str("&"); } - if let Some(e) = self.element { + if let Some(e) = &self.element { if !e.is_any() || (self.classes.is_empty() && self.placeholders.is_empty() && self.id.is_none() - && self.attr.is_empty() && self.pseudo.is_empty()) { - buf.push_str(&e.s); + buf.add_str(&e.s); } } for p in &self.placeholders { - buf.push('%'); - buf.push_str(p); + buf.add_str("%"); + buf.add_str(p); } if let Some(id) = &self.id { - buf.push('#'); - buf.push_str(id); + buf.add_str("#"); + buf.add_str(id); } for c in &self.classes { - buf.push('.'); - buf.push_str(c); + buf.add_str("."); + buf.add_str(c); } for attr in &self.attr { - attr.write_to_buf(&mut buf); + attr.write_to_buf(buf); } for pseudo in &self.pseudo { - pseudo.write_to_buf(&mut buf); + pseudo.write_to_buf(buf); } - buf } fn pseudo_element(&self) -> Option<&Pseudo> { @@ -832,7 +877,7 @@ impl fmt::Debug for Selector { s.field("backref", &"&"); } if let Some(elem) = &self.element { - s.field("element", elem); + s.field("element", &elem.s); } if !self.placeholders.is_empty() { s.field("placeholders", &self.placeholders); diff --git a/rsass/src/css/selectors/opt.rs b/rsass/src/css/selectors/opt.rs new file mode 100644 index 00000000..b916056f --- /dev/null +++ b/rsass/src/css/selectors/opt.rs @@ -0,0 +1,64 @@ +/// A special kind of option relevant for selectors. +/// +/// There is both a positive (match-anything) and a negative (match-nothing) +/// empty value. +#[derive(Debug)] +pub(crate) enum Opt { + Some(T), + Any, + None, +} + +impl Opt { + pub(crate) fn collect_pos( + iter: impl Iterator>, + ) -> Opt> { + let mut result = Vec::new(); + for p in iter { + match p { + Opt::Some(p) => result.push(p), + Opt::Any => return Opt::Any, + Opt::None => (), + } + } + if result.is_empty() { + Opt::None + } else { + Opt::Some(result) + } + } + pub(crate) fn collect_neg( + iter: impl Iterator>, + ) -> Opt> { + let mut result = Vec::new(); + for p in iter { + match p { + Opt::Some(p) => result.push(p), + Opt::Any => (), + Opt::None => return Opt::None, + } + } + if result.is_empty() { + Opt::Any + } else { + Opt::Some(result) + } + } + pub(crate) fn map U>(self, f: F) -> Opt { + match self { + Opt::Some(t) => Opt::Some(f(t)), + Opt::Any => Opt::Any, + Opt::None => Opt::None, + } + } +} + +impl Opt> { + pub(crate) fn positive(self) -> Option> { + match self { + Opt::Some(v) => Some(v), + Opt::Any => Some(vec![]), + Opt::None => None, + } + } +} diff --git a/rsass/src/css/selectors/pseudo.rs b/rsass/src/css/selectors/pseudo.rs index 70e06e85..b7776f24 100644 --- a/rsass/src/css/selectors/pseudo.rs +++ b/rsass/src/css/selectors/pseudo.rs @@ -1,4 +1,5 @@ -use super::{selectorset::SelectorSet, CssSelectorSet}; +use super::{CssSelectorSet, Opt, SelectorSet}; +use crate::output::CssBuf; /// A pseudo-class or a css2 pseudo-element (:foo) #[derive(Debug, Clone, PartialEq, Eq)] @@ -12,19 +13,30 @@ pub(crate) struct Pseudo { } impl Pseudo { - // TODO: IS None match-all (ignore this) or match none (ignore container)? - pub(crate) fn no_placeholder(&self) -> Option { + pub(crate) fn no_placeholder(&self) -> Opt { let arg = match &self.arg { Arg::Selector(s) => { - if let Some(s) = s.no_placeholder() { - Arg::Selector(s) - } else { - return None; // TODO! Positive or negative None? + match (s.no_placeholder(), self.name_in(&["not"])) { + (Opt::Some(t), _) => { + if self.name_in(&["is"]) { + match t.no_leading_combinator() { + Opt::Some(t) => Arg::Selector(t), + Opt::Any => return Opt::Any, + Opt::None => return Opt::None, + } + } else { + Arg::Selector(t) + } + } + (Opt::Any, false) | (Opt::None, true) => return Opt::Any, + (Opt::None, false) | (Opt::Any, true) => { + return Opt::None + } } } arg => arg.clone(), }; - Some(Self { + Opt::Some(Self { arg, name: self.name.clone(), element: self.element, @@ -98,12 +110,12 @@ impl Pseudo { self } - pub(super) fn write_to_buf(&self, buf: &mut String) { - buf.push(':'); + pub(super) fn write_to_buf(&self, buf: &mut CssBuf) { + buf.add_str(":"); if self.element { - buf.push(':'); + buf.add_str(":"); } - buf.push_str(&self.name); + buf.add_str(&self.name); // Note: This is an ugly workaround for lack of proper support // for "nth" type of pseudoclass arguments. if self.name_in(&[ @@ -112,9 +124,11 @@ impl Pseudo { "nth-last-of-type", "nth-of-type", ]) { - let mut t = String::new(); + let mut t = CssBuf::new(buf.format()); self.arg.write_to_buf(&mut t); - buf.push_str(&t.replacen(" + ", "+", 1)); + buf.add_str( + &String::from_utf8_lossy(&t.take()).replacen(" + ", "+", 1), + ); } else { self.arg.write_to_buf(buf); } @@ -167,17 +181,17 @@ impl Arg { _ => false, } } - pub(super) fn write_to_buf(&self, buf: &mut String) { + pub(super) fn write_to_buf(&self, buf: &mut CssBuf) { match self { Self::Selector(s) => { - buf.push('('); - s.write_to_buf(buf); - buf.push(')'); + buf.add_str("("); + s.write_to(buf); + buf.add_str(")"); } Self::Other(a) => { - buf.push('('); - buf.push_str(a); - buf.push(')'); + buf.add_str("("); + buf.add_str(a); + buf.add_str(")"); } Self::None => (), } diff --git a/rsass/src/css/selectors/selectorset.rs b/rsass/src/css/selectors/selectorset.rs index 980a5379..ec353501 100644 --- a/rsass/src/css/selectors/selectorset.rs +++ b/rsass/src/css/selectors/selectorset.rs @@ -1,10 +1,10 @@ use super::{logical::Selector, BadSelector, BadSelector0, CssSelectorSet}; +use super::{OldSelectors, Opt}; use crate::output::CssBuf; use crate::parser::input_span; use crate::value::ListSeparator; use crate::ParseError; use crate::{css::Value, Invalid}; -use std::io; /// A set of selectors. /// This is the normal top-level selector, which can be a single @@ -15,18 +15,24 @@ pub struct SelectorSet { } impl SelectorSet { - pub fn no_placeholder(&self) -> Option { - let s = self - .s - .iter() - .filter_map(Selector::no_placeholder) - .collect::>(); - if s.is_empty() { - None - } else { - Some(Self { s }) + pub(crate) fn root() -> Self { + Self { + s: vec![Selector::default()], } } + /// Return true if this is a root (empty) selector. + pub fn is_root(&self) -> bool { + self.s.len() == 1 && self.s[0] == Selector::default() + } + + pub(crate) fn no_placeholder(&self) -> Opt { + let s = self.s.iter().map(Selector::no_placeholder); + Opt::collect_pos(s).map(|s| Self { s }) + } + pub(crate) fn no_leading_combinator(&self) -> Opt { + let s = self.s.iter().map(Selector::no_leading_combinator); + Opt::collect_pos(s).map(|s| Self { s }) + } pub(crate) fn extend( self, @@ -75,16 +81,16 @@ impl SelectorSet { Ok(()) } - pub fn write_to(&self, buf: &mut CssBuf) -> io::Result<()> { + pub fn write_to(&self, buf: &mut CssBuf) { if let Some((first, rest)) = self.s.split_first() { - first.write_to(buf)?; + first.write_to(buf); for one in rest { buf.add_one(", ", ","); - one.write_to(buf)?; + one.write_to(buf); } } - Ok(()) } + // TODO: Get rid of this, use the above! pub(super) fn write_to_buf(&self, buf: &mut String) { fn write_one(s: &Selector, buf: &mut String) { @@ -111,10 +117,48 @@ impl SelectorSet { .collect::>(), } } + + /* // Get these selectors with a specific backref selector. + /// + /// Used to create `@at-root` contexts, to have `&` work in them. + pub(crate) fn with_backref(self, context: Selector) -> SelectorCtx { + SelectorCtx { + s: self.try_into().unwrap(), + backref: CssSelectorSet, + }.inside(&SelectorCtx { + s: Self::root().try_into().unwrap(), + backref: context, + }) + }*/ + + /// Get the first of these selectors (or the root selector if empty). + pub(crate) fn one(&self) -> Selector { + self.s.first().cloned().unwrap_or_else(Selector::default) + } +} + +impl TryFrom<&OldSelectors> for SelectorSet { + type Error = BadSelector; + + fn try_from(s: &OldSelectors) -> Result { + if s.is_root() { + return Ok(SelectorSet { + s: vec![Selector::default()], + }); + } + let formatted = s.to_string(); + let span = input_span(formatted.clone()); + ParseError::check(parser::selector_set(span.borrow()))? + .try_into() + .map_err(|_| BadSelector::Value(formatted.into())) + } } impl From for Value { fn from(value: SelectorSet) -> Self { + if value.is_root() { + return Self::Null; + } let v = value.s.into_iter().map(Self::from).collect::>(); if v.is_empty() { Self::Null diff --git a/rsass/src/lib.rs b/rsass/src/lib.rs index fa620192..c9fb5fdf 100644 --- a/rsass/src/lib.rs +++ b/rsass/src/lib.rs @@ -36,6 +36,7 @@ //! usable for my personal projects, and the number of working tests are //! improving. #![forbid(unsafe_code)] +#![allow(dead_code)] // #![forbid(missing_docs)] (FIXME: temporarily ignored) pub mod css; diff --git a/rsass/src/output/cssbuf.rs b/rsass/src/output/cssbuf.rs index 07ab7583..6b527ffc 100644 --- a/rsass/src/output/cssbuf.rs +++ b/rsass/src/output/cssbuf.rs @@ -18,6 +18,9 @@ impl CssBuf { pub fn take(self) -> Vec { self.buf } + pub(crate) fn len(&self) -> usize { + self.buf.len() + } pub(crate) fn format(&self) -> Format { self.format } @@ -59,6 +62,10 @@ impl CssBuf { pub fn add_str(&mut self, sub: &str) { self.buf.extend_from_slice(sub.as_bytes()); } + pub(crate) fn add_char(&mut self, ch: char) { + let mut buf = [b'0'; 6]; + self.add_str(ch.encode_utf8(&mut buf)) + } pub fn add_one(&mut self, normal: &str, compressed: &str) { self.add_str(if self.format.is_compressed() { compressed diff --git a/rsass/src/output/transform.rs b/rsass/src/output/transform.rs index 8f8500ff..14ffd487 100644 --- a/rsass/src/output/transform.rs +++ b/rsass/src/output/transform.rs @@ -3,7 +3,7 @@ use super::cssdest::CssDestination; use super::CssData; -use crate::css::{self, AtRule, Import, OldSelectorCtx}; +use crate::css::{self, AtRule, Import, SelectorCtx, SelectorSet}; use crate::error::ResultPos; use crate::input::{Context, Loader, Parsed, SourceKind}; use crate::sass::{get_global_module, Expose, Item, UseAs}; @@ -152,7 +152,7 @@ fn handle_item( let selectors = scope.get_selectors(); if !selectors.is_root() { let mut rule = thead - .start_rule(selectors.real_new()) + .start_rule(selectors.real()) .at(pos)?; handle_body( &items, @@ -202,13 +202,14 @@ fn handle_item( } } Item::AtRoot(ref selectors, ref body) => { - let selectors = selectors - .eval(scope.clone())? - .with_backref(scope.get_selectors().one()); - let subscope = ScopeRef::sub_selectors(scope, selectors.clone()); + let selectors = selectors.eval(scope.clone())?; + let selectors = SelectorSet::try_from(&selectors) + .map_err(|e| Error::S(e.to_string()))?; + let ctx = scope.get_selectors().at_root(selectors); + let selectors = ctx.real(); + let subscope = ScopeRef::sub_selectors(scope, ctx); if !selectors.is_root() { - let mut rule = - dest.start_rule(selectors.real_new()).no_pos()?; + let mut rule = dest.start_rule(selectors).no_pos()?; handle_body(body, &mut rule, subscope, file_context)?; } else { handle_body(body, dest, subscope, file_context)?; @@ -233,7 +234,7 @@ fn handle_item( if let Some(ref body) = *body { let mut atrule = dest.start_atrule(name.clone(), args); let local = if name == "keyframes" { - ScopeRef::sub_selectors(scope, OldSelectorCtx::root()) + ScopeRef::sub_selectors(scope, SelectorCtx::root()) } else { ScopeRef::sub(scope) }; @@ -359,11 +360,12 @@ fn handle_item( Item::Rule(ref selectors, ref body) => { check_body(body, BodyContext::Rule)?; - let selectors = - OldSelectorCtx::from(selectors.eval(scope.clone())?) - .inside(scope.get_selectors()); - let mut dest = dest.start_rule(selectors.real_new()).no_pos()?; - let scope = ScopeRef::sub_selectors(scope, selectors); + let selectors = selectors.eval(scope.clone())?; + let selectors = SelectorSet::try_from(&selectors) + .map_err(|e| Error::S(e.to_string()))?; + let selectors = scope.get_selectors().nest(selectors); + let mut dest = dest.start_rule(selectors.clone()).no_pos()?; + let scope = ScopeRef::sub_selectors(scope, selectors.into()); handle_body(body, &mut dest, scope, file_context)?; } Item::Property(ref name, ref value, ref pos) => { diff --git a/rsass/src/sass/callable.rs b/rsass/src/sass/callable.rs index c21f50de..7a971de6 100644 --- a/rsass/src/sass/callable.rs +++ b/rsass/src/sass/callable.rs @@ -57,7 +57,13 @@ impl Closure { /// This is used when the callable is a scss function. pub fn eval_value(&self, call: Call) -> Result { Ok(self - .eval_args(self.scope.clone(), call.args)? + .eval_args( + ScopeRef::sub_selectors( + self.scope.clone(), + call.scope.get_selectors().clone(), + ), + call.args, + )? .eval_body(&self.body.body) .map_err(|e| match e { Error::Invalid(err, _pos) => CallError::Invalid(err), diff --git a/rsass/src/variablescope.rs b/rsass/src/variablescope.rs index ebea5f37..5b771b30 100644 --- a/rsass/src/variablescope.rs +++ b/rsass/src/variablescope.rs @@ -1,5 +1,5 @@ //! A scope is something that contains variable values. -use crate::css::{CssString, OldSelectorCtx, Value}; +use crate::css::{CssString, SelectorCtx, Value}; use crate::input::SourcePos; use crate::output::Format; use crate::sass::{Expose, Function, Item, MixinDecl, Name, UseAs}; @@ -35,7 +35,7 @@ impl ScopeRef { Self::dynamic(Scope::sub(parent)) } /// Create a new subscope of a given parent with selectors. - pub fn sub_selectors(parent: Self, selectors: OldSelectorCtx) -> Self { + pub fn sub_selectors(parent: Self, selectors: SelectorCtx) -> Self { Self::dynamic(Scope::sub_selectors(parent, selectors)) } fn dynamic(scope: Scope) -> Self { @@ -204,7 +204,7 @@ pub struct Scope { variables: Mutex>, mixins: Mutex>, functions: Mutex>, - selectors: Option, + selectors: Option, forward: Mutex>, format: Format, /// The thing to use for `@content` in a mixin. @@ -259,10 +259,7 @@ impl Scope { } } /// Create a new subscope of a given parent with selectors. - pub fn sub_selectors( - parent: ScopeRef, - selectors: OldSelectorCtx, - ) -> Self { + pub fn sub_selectors(parent: ScopeRef, selectors: SelectorCtx) -> Self { let format = parent.get_format(); Self { parent: Some(parent), @@ -503,9 +500,9 @@ impl Scope { } /// Get the selectors active for this scope. - pub fn get_selectors(&self) -> &OldSelectorCtx { + pub fn get_selectors(&self) -> &SelectorCtx { lazy_static! { - static ref ROOT: OldSelectorCtx = OldSelectorCtx::root(); + static ref ROOT: SelectorCtx = SelectorCtx::root(); } self.selectors.as_ref().unwrap_or_else(|| { self.parent.as_ref().map_or(&ROOT, |p| p.get_selectors()) diff --git a/rsass/tests/misc/compressed.rs b/rsass/tests/misc/compressed.rs index 2141b805..c0e37b48 100644 --- a/rsass/tests/misc/compressed.rs +++ b/rsass/tests/misc/compressed.rs @@ -260,7 +260,6 @@ fn t49_interpolants_in_css_imports() { } #[test] -#[ignore = "argument selectors currently not compressed"] fn t50_wrapped_pseudo_selectors() { check( b"div {\n \ diff --git a/rsass/tests/spec/libsass/base_level_parent/imported/at_root_prefix.rs b/rsass/tests/spec/libsass/base_level_parent/imported/at_root_prefix.rs index 25fbc1f5..133489b3 100644 --- a/rsass/tests/spec/libsass/base_level_parent/imported/at_root_prefix.rs +++ b/rsass/tests/spec/libsass/base_level_parent/imported/at_root_prefix.rs @@ -8,7 +8,7 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // missing error +#[ignore] // wrong error fn test() { assert_eq!( runner().err("@import \"include.scss\";"), diff --git a/rsass/tests/spec/libsass/base_level_parent/imported/basic_prefix.rs b/rsass/tests/spec/libsass/base_level_parent/imported/basic_prefix.rs index a5bde836..90600e91 100644 --- a/rsass/tests/spec/libsass/base_level_parent/imported/basic_prefix.rs +++ b/rsass/tests/spec/libsass/base_level_parent/imported/basic_prefix.rs @@ -9,7 +9,7 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // missing error +#[ignore] // wrong error fn test() { assert_eq!( runner().err("@import \"include.scss\";"), diff --git a/rsass/tests/spec/libsass/base_level_parent/nested/at_root_prefix.rs b/rsass/tests/spec/libsass/base_level_parent/nested/at_root_prefix.rs index f8a79d15..8bdaa25a 100644 --- a/rsass/tests/spec/libsass/base_level_parent/nested/at_root_prefix.rs +++ b/rsass/tests/spec/libsass/base_level_parent/nested/at_root_prefix.rs @@ -6,7 +6,7 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // missing error +#[ignore] // wrong error fn test() { assert_eq!( runner().err( diff --git a/rsass/tests/spec/libsass/base_level_parent/nested/basic_prefix.rs b/rsass/tests/spec/libsass/base_level_parent/nested/basic_prefix.rs index 99fcc05c..1bbf4425 100644 --- a/rsass/tests/spec/libsass/base_level_parent/nested/basic_prefix.rs +++ b/rsass/tests/spec/libsass/base_level_parent/nested/basic_prefix.rs @@ -6,7 +6,7 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // missing error +#[ignore] // wrong error fn test() { assert_eq!( runner().err( diff --git a/rsass/tests/spec/libsass/base_level_parent/root/at_root_prefix.rs b/rsass/tests/spec/libsass/base_level_parent/root/at_root_prefix.rs index 44638b4a..4f2d7029 100644 --- a/rsass/tests/spec/libsass/base_level_parent/root/at_root_prefix.rs +++ b/rsass/tests/spec/libsass/base_level_parent/root/at_root_prefix.rs @@ -6,7 +6,7 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // missing error +#[ignore] // wrong error fn test() { assert_eq!( runner().err( diff --git a/rsass/tests/spec/libsass/base_level_parent/root/basic_prefix.rs b/rsass/tests/spec/libsass/base_level_parent/root/basic_prefix.rs index 91e1756e..3f9fe093 100644 --- a/rsass/tests/spec/libsass/base_level_parent/root/basic_prefix.rs +++ b/rsass/tests/spec/libsass/base_level_parent/root/basic_prefix.rs @@ -6,7 +6,7 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // missing error +#[ignore] // wrong error fn test() { assert_eq!( runner().err( diff --git a/rsass/tests/spec/libsass/propsets.rs b/rsass/tests/spec/libsass/propsets.rs index 788d6820..16580d4e 100644 --- a/rsass/tests/spec/libsass/propsets.rs +++ b/rsass/tests/spec/libsass/propsets.rs @@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn test() { assert_eq!( runner().ok("$x: ground;\ diff --git a/rsass/tests/spec/libsass_closed_issues/issue_1710.rs b/rsass/tests/spec/libsass_closed_issues/issue_1710.rs index 2bafb2c2..e6c6443e 100644 --- a/rsass/tests/spec/libsass_closed_issues/issue_1710.rs +++ b/rsass/tests/spec/libsass_closed_issues/issue_1710.rs @@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn test() { assert_eq!( runner().ok("ul, ol {\ diff --git a/rsass/tests/spec/libsass_closed_issues/issue_1822.rs b/rsass/tests/spec/libsass_closed_issues/issue_1822.rs index b5c07fb2..4a1d5128 100644 --- a/rsass/tests/spec/libsass_closed_issues/issue_1822.rs +++ b/rsass/tests/spec/libsass_closed_issues/issue_1822.rs @@ -6,7 +6,7 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // missing error +#[ignore] // wrong error fn test() { assert_eq!( runner().err( diff --git a/rsass/tests/spec/libsass_closed_issues/issue_1904.rs b/rsass/tests/spec/libsass_closed_issues/issue_1904.rs index 509391b3..66c12ad0 100644 --- a/rsass/tests/spec/libsass_closed_issues/issue_1904.rs +++ b/rsass/tests/spec/libsass_closed_issues/issue_1904.rs @@ -6,6 +6,7 @@ fn runner() -> crate::TestRunner { } #[test] +#[ignore] // wrong result fn test() { assert_eq!( runner().ok(".foo {\ diff --git a/rsass/tests/spec/libsass_closed_issues/issue_2116.rs b/rsass/tests/spec/libsass_closed_issues/issue_2116.rs index 34dc2229..370ee9b7 100644 --- a/rsass/tests/spec/libsass_closed_issues/issue_2116.rs +++ b/rsass/tests/spec/libsass_closed_issues/issue_2116.rs @@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn test() { assert_eq!( runner().ok("@function foo() {\ diff --git a/rsass/tests/spec/libsass_closed_issues/issue_2155.rs b/rsass/tests/spec/libsass_closed_issues/issue_2155.rs index a35ad3ab..680c13a9 100644 --- a/rsass/tests/spec/libsass_closed_issues/issue_2155.rs +++ b/rsass/tests/spec/libsass_closed_issues/issue_2155.rs @@ -6,7 +6,7 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // missing error +#[ignore] // wrong error fn test() { assert_eq!( runner().err( diff --git a/rsass/tests/spec/libsass_closed_issues/issue_2358.rs b/rsass/tests/spec/libsass_closed_issues/issue_2358.rs index 0571c91c..79d79cb6 100644 --- a/rsass/tests/spec/libsass_closed_issues/issue_2358.rs +++ b/rsass/tests/spec/libsass_closed_issues/issue_2358.rs @@ -6,6 +6,7 @@ fn runner() -> crate::TestRunner { } #[test] +#[ignore] // wrong result fn test() { assert_eq!( runner().ok( diff --git a/rsass/tests/spec/libsass_closed_issues/issue_738.rs b/rsass/tests/spec/libsass_closed_issues/issue_738.rs index 2b4cf736..e90611c5 100644 --- a/rsass/tests/spec/libsass_closed_issues/issue_738.rs +++ b/rsass/tests/spec/libsass_closed_issues/issue_738.rs @@ -6,6 +6,7 @@ fn runner() -> crate::TestRunner { } #[test] +#[ignore] // wrong result fn test() { assert_eq!( runner().ok(".foo {\ diff --git a/rsass/tests/spec/non_conformant/basic/t12_pseudo_classes_and_elements.rs b/rsass/tests/spec/non_conformant/basic/t12_pseudo_classes_and_elements.rs index d067c6dd..b01100cb 100644 --- a/rsass/tests/spec/non_conformant/basic/t12_pseudo_classes_and_elements.rs +++ b/rsass/tests/spec/non_conformant/basic/t12_pseudo_classes_and_elements.rs @@ -6,6 +6,7 @@ fn runner() -> crate::TestRunner { } #[test] +#[ignore] // wrong result fn test() { assert_eq!( runner().ok( diff --git a/rsass/tests/spec/non_conformant/basic/t13_back_references.rs b/rsass/tests/spec/non_conformant/basic/t13_back_references.rs index 26e084f5..08ead2ae 100644 --- a/rsass/tests/spec/non_conformant/basic/t13_back_references.rs +++ b/rsass/tests/spec/non_conformant/basic/t13_back_references.rs @@ -6,6 +6,7 @@ fn runner() -> crate::TestRunner { } #[test] +#[ignore] // wrong result fn test() { assert_eq!( runner().ok("hey, ho {\ diff --git a/rsass/tests/spec/non_conformant/basic/t44_bem_selectors.rs b/rsass/tests/spec/non_conformant/basic/t44_bem_selectors.rs index 72506cf0..89ffc365 100644 --- a/rsass/tests/spec/non_conformant/basic/t44_bem_selectors.rs +++ b/rsass/tests/spec/non_conformant/basic/t44_bem_selectors.rs @@ -6,6 +6,7 @@ fn runner() -> crate::TestRunner { } #[test] +#[ignore] // wrong result fn test() { assert_eq!( runner().ok("div {\n\ From 40dac2feb0b3ea9ccf4b9ac293f5ea8a63920dcf Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Sun, 4 Aug 2024 21:16:49 +0200 Subject: [PATCH 06/10] New selectors 5: Purge some unused stuff. --- rsass/src/css/selectors.rs | 378 ---------------------- rsass/src/css/selectors/cssselectorset.rs | 5 +- rsass/src/css/selectors/logical.rs | 3 +- rsass/src/css/selectors/opt.rs | 10 - rsass/src/css/selectors/selectorset.rs | 42 +-- rsass/src/lib.rs | 3 +- rsass/src/parser/css/mod.rs | 2 +- 7 files changed, 10 insertions(+), 433 deletions(-) diff --git a/rsass/src/css/selectors.rs b/rsass/src/css/selectors.rs index 117f7b16..e2966be5 100644 --- a/rsass/src/css/selectors.rs +++ b/rsass/src/css/selectors.rs @@ -8,10 +8,7 @@ //! leafs of simple selectors in some future release. use super::{is_not, CssString, Value}; use crate::input::SourcePos; -use crate::parser::css::{selector, selector_parts, selectors}; use crate::parser::{input_span, ParseError}; -use crate::value::ListSeparator; -use nom::combinator::all_consuming; use std::fmt; use std::io::Write; @@ -70,16 +67,6 @@ impl OldSelectors { } } - /// Get the first of these selectors (or the root selector if empty). - pub(crate) fn one(&self) -> OldSelector { - self.s.first().cloned().unwrap_or_else(OldSelector::root) - } - - /// Create the full selector for when self is used inside a parent selector. - pub(crate) fn inside(&self, parent: &OldSelectorCtx) -> Self { - OldSelectorCtx::from(self.clone()).inside(parent).real() - } - /// True if any of the selectors contains a backref (`&`). pub(crate) fn has_backref(&self) -> bool { self.s.iter().any(OldSelector::has_backref) @@ -108,70 +95,12 @@ impl OldSelectors { } } - /// Get these selectors with a specific backref selector. - /// - /// Used to create `@at-root` contexts, to have `&` work in them. - pub(crate) fn with_backref(self, context: OldSelector) -> OldSelectorCtx { - OldSelectorCtx::from(self).inside(&OldSelectorCtx { - s: Self::root(), - backref: context, - }) - } /// Return true if any of these selectors ends with a combinator pub fn has_trailing_combinator(&self) -> bool { self.s.iter().any(OldSelector::has_trailing_combinator) } } -impl From for Value { - /// Create a css `Value` representing a set of selectors. - /// - /// The result will be a comma-separated [list](Value::List) of - /// space-separated lists of strings, or [null](Value::Null) if - /// this is a root (empty) selector. - fn from(sel: OldSelectors) -> Self { - if sel.is_root() { - return Self::Null; - } - let content = sel - .s - .iter() - .map(|s: &OldSelector| { - let (mut v, last) = s.0.iter().fold( - (vec![], Option::::None), - |(mut v, mut last), part| { - match part { - OldSelectorPart::Descendant => { - if let Some(last) = last.take() { - v.push(last.into()); - } - } - OldSelectorPart::RelOp(op) => { - if let Some(last) = last.take() { - v.push(last.into()); - } - v.push(char::from(*op).to_string().into()); - } - part => { - last = Some(match last { - Some(last) => format!("{last}{part}"), - None => part.to_string(), - }); - } - } - (v, last) - }, - ); - if let Some(last) = last { - v.push(last.into()); - } - Self::List(v, Some(ListSeparator::Space), false) - }) - .collect::>(); - Self::List(content, Some(ListSeparator::Comma), false) - } -} - /// A full set of selectors with a separate backref. #[derive(Clone, Debug, PartialEq, Eq)] pub struct SelectorCtx { @@ -197,11 +126,6 @@ impl SelectorCtx { self.s.clone() } - /// Get the first of these selectors (or the root selector if empty). - pub(crate) fn one(&self) -> Selector { - self.s.s.one() - } - /// Evaluate selectors inside this context. pub(crate) fn nest(&self, selectors: SelectorSet) -> CssSelectorSet { if selectors.has_backref() { @@ -252,171 +176,6 @@ impl From for SelectorCtx { } } -/// A full set of selectors with a separate backref. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd)] -pub struct OldSelectorCtx { - /// The actual selectors. - s: OldSelectors, - backref: OldSelector, -} - -impl OldSelectorCtx { - /// Create a root (empty) selector. - pub fn root() -> Self { - OldSelectors::root().into() - } - pub(crate) fn root_with_backref(context: OldSelector) -> Self { - Self { - s: OldSelectors::root(), - backref: context, - } - } - /// Return true if this is a root (empty) selector. - pub fn is_root(&self) -> bool { - self.s.is_root() && self.backref == OldSelector::root() - } - - // I think any backrefs is guaranteed to be already resolved in self.s! - pub(crate) fn real_new(&self) -> CssSelectorSet { - if self.s.is_root() { - return CssSelectorSet { - s: SelectorSet { - s: vec![Selector::default()], - }, - }; - } - let span = input_span(self.s.to_string()); - let (_, value) = all_consuming(parser::selector_set)(span.borrow()) - .unwrap_or_else(|e| panic!("Bad selector {span:?}: {e}")); - let msg = format!("Bad real selectors {value:?}"); - value.try_into().expect(&msg) - } - pub(crate) fn real(&self) -> OldSelectors { - self.s.clone() - } - - /// Remove the first of these selectors (or the root selector if empty). - pub(crate) fn one(&self) -> OldSelector { - self.s.one() - } - - /// Create the full selector for when self is used inside a parent selector. - pub(crate) fn inside(&self, parent: &Self) -> Self { - let mut result = Vec::new(); - for p in &parent.s.s { - for s in &self.s.s { - result.push(p.join(s, &parent.backref)); - } - } - Self { - s: OldSelectors::new(result), - backref: parent.backref.clone(), - } - } -} - -impl From for OldSelectorCtx { - fn from(value: OldSelectors) -> Self { - Self { - s: value, - backref: OldSelector::root(), - } - } -} - -impl TryFrom for OldSelectorCtx { - type Error = BadSelector; - fn try_from(v: Value) -> Result { - OldSelectors::try_from(v).map(Into::into) - } -} -impl TryFrom for OldSelectors { - type Error = BadSelector; - fn try_from(v: Value) -> Result { - value_to_selectors(&v).map_err(move |e| e.ctx(v)) - } -} -fn value_to_selectors(v: &Value) -> Result { - match v { - Value::List(vv, s, _) => match s { - Some(ListSeparator::Comma) => { - let vv = vv - .iter() - .map(value_to_selector) - .collect::>()?; - Ok(OldSelectors::new(vv)) - } - Some(ListSeparator::Space) => { - let (mut outer, last) = vv.iter().try_fold( - (vec![], vec![]), - |(mut outer, mut a), v: &Value| { - if let Ok(ref mut s) = check_selector_str(v) { - push_descendant(&mut a, s); - } else { - let mut s = parse_selectors_str(v)?; - if let Some(f) = s.s.first_mut() { - push_descendant(&mut a, f); - std::mem::swap(&mut a, &mut f.0); - } - if let Some(last) = s.s.pop() { - a = last.0; - } - outer.extend(s.s); - } - Result::<_, BadSelector0>::Ok((outer, a)) - }, - )?; - outer.push(OldSelector(last)); - Ok(OldSelectors::new(outer)) - } - _ => Err(BadSelector0::Value), - }, - Value::Literal(s) => { - if s.value().is_empty() { - Ok(OldSelectors::root()) - } else { - let span = input_span(s.value()); - Ok(ParseError::check(selectors(span.borrow()))?) - } - } - _ => Err(BadSelector0::Value), - } -} - -fn check_selector_str(v: &Value) -> Result { - match v { - Value::Literal(s) => { - if s.value().is_empty() { - Ok(OldSelector::root()) - } else { - let span = input_span(s.value()); - Ok(ParseError::check(selector(span.borrow()))?) - } - } - _ => Err(BadSelector0::Value), - } -} -fn parse_selectors_str(v: &Value) -> Result { - match v { - Value::Literal(s) => { - if s.value().is_empty() { - Ok(OldSelectors::root()) - } else { - let span = input_span(s.value()); - Ok(ParseError::check(selectors(span.borrow()))?) - } - } - _ => Err(BadSelector0::Value), - } -} - -fn push_descendant(to: &mut Vec, from: &mut OldSelector) { - if !to.is_empty() { - to.push(OldSelectorPart::Descendant); - } - to.append(&mut from.0); -} - /// A single css selector. /// /// A selector does not contain `,`. If it does, it is a `Selectors`, @@ -430,29 +189,6 @@ impl OldSelector { Self(vec![]) } - fn join(&self, other: &Self, alt_context: &Self) -> Self { - if other.has_backref() { - let mut result = Vec::new(); - let context = if self.0.is_empty() { alt_context } else { self }; - for p in &other.0 { - result.extend(p.clone_in(context)); - } - Self(result) - } else { - let mut result = self.0.clone(); - if !result.is_empty() - && !other - .0 - .first() - .map_or(false, OldSelectorPart::is_operator) - { - result.push(OldSelectorPart::Descendant); - } - result.extend(other.0.iter().cloned()); - Self(result) - } - } - /// Validate that this selector is ok to use in css. /// /// `Selectors` can contain backref (`&`), but those must be @@ -516,46 +252,6 @@ impl OldSelector { } } -impl TryFrom for OldSelector { - type Error = BadSelector; - - fn try_from(value: Value) -> Result { - value_to_selector(&value).map_err(move |e| e.ctx(value)) - } -} -// Internal, the api is try_into. -fn value_to_selector(v: &Value) -> Result { - match v { - Value::List(list, None | Some(ListSeparator::Space), _) => { - list_to_selector(list) - } - Value::Literal(s) => { - if s.value().is_empty() { - Ok(OldSelector::root()) - } else { - let span = input_span(s.value()); - Ok(ParseError::check(selector(span.borrow()))?) - } - } - _ => Err(BadSelector0::Value), - } -} - -fn list_to_selector(list: &[Value]) -> Result { - list.iter() - .try_fold(vec![], |mut a, v| { - let parts = value_to_selector_parts(v)?; - if !parts.first().map_or(true, OldSelectorPart::is_operator) - && !a.last().map_or(true, OldSelectorPart::is_operator) - { - a.push(OldSelectorPart::Descendant); - } - a.extend(parts); - Ok(a) - }) - .map(OldSelector) -} - /// A selector consist of a sequence of these parts. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd)] pub enum OldSelectorPart { @@ -678,46 +374,6 @@ impl OldSelectorPart { x => Some(x.clone()), } } - fn clone_in(&self, context: &OldSelector) -> Vec { - match self { - s @ (Self::Descendant - | Self::RelOp(_) - | Self::Simple(_) - | Self::Attribute { .. }) => vec![s.clone()], - Self::BackRef => context.0.clone(), - Self::PseudoElement { name, arg } => { - vec![Self::PseudoElement { - name: name.clone(), - arg: arg.as_ref().map(|a| { - a.inside(&OldSelectorCtx::root_with_backref( - context.clone(), - )) - }), - }] - } - Self::Pseudo { name, arg } => { - vec![Self::Pseudo { - name: name.clone(), - arg: arg.as_ref().map(|a| { - a.inside(&OldSelectorCtx::root_with_backref( - context.clone(), - )) - }), - }] - } - } - } -} - -fn value_to_selector_parts( - v: &Value, -) -> Result, BadSelector0> { - match v { - Value::Literal(s) => Ok(ParseError::check(selector_parts( - input_span(s.value()).borrow(), - ))?), - _ => Err(BadSelector0::Value), - } } // TODO: This shoule probably be on Formatted instead. @@ -815,40 +471,6 @@ impl fmt::Display for OldSelectorPart { } } -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn root_join() { - let s = OldSelector(vec![OldSelectorPart::Simple("foo".into())]); - assert_eq!(OldSelector::root().join(&s, &OldSelector::root()), s) - } - - #[test] - fn simple_join() { - let s = OldSelector(vec![OldSelectorPart::Simple("foo".into())]) - .join( - &OldSelector(vec![OldSelectorPart::Simple(".bar".into())]), - &OldSelector::root(), - ); - assert_eq!(format!("{}", s), "foo .bar") - } - - #[test] - fn backref_join() { - let s = OldSelector(vec![OldSelectorPart::Simple("foo".into())]) - .join( - &OldSelector(vec![ - OldSelectorPart::BackRef, - OldSelectorPart::Simple(".bar".into()), - ]), - &OldSelector::root(), - ); - assert_eq!(format!("{}", s), "foo.bar") - } -} - enum BadSelector0 { Value, Parse(ParseError), diff --git a/rsass/src/css/selectors/cssselectorset.rs b/rsass/src/css/selectors/cssselectorset.rs index 05acec37..7de53dc3 100644 --- a/rsass/src/css/selectors/cssselectorset.rs +++ b/rsass/src/css/selectors/cssselectorset.rs @@ -1,5 +1,5 @@ use super::selectorset::SelectorSet; -use super::{BadSelector, BadSelector0, Opt}; +use super::{BadSelector, BadSelector0}; use crate::css::Value; use crate::error::Invalid; use crate::output::CssBuf; @@ -72,9 +72,6 @@ impl CssSelectorSet { } } - pub(crate) fn no_placeholder(&self) -> Opt { - self.s.no_placeholder().map(|s| Self { s }) - } pub fn is_superselector(&self, sub: &Self) -> bool { self.s.is_superselector(&sub.s) } diff --git a/rsass/src/css/selectors/logical.rs b/rsass/src/css/selectors/logical.rs index 1a00d44a..d12ff658 100644 --- a/rsass/src/css/selectors/logical.rs +++ b/rsass/src/css/selectors/logical.rs @@ -396,7 +396,7 @@ impl Selector { self._unify(other).unwrap_or_default() } - pub fn unify_extend(&self, other: &Self) -> Vec { + pub(crate) fn unify_extend(&self, other: &Self) -> Vec { self.clone()._unify(other.clone()).unwrap_or_default() } @@ -575,6 +575,7 @@ impl Selector { Ok(result) } + /// Write this `Selector` to a formatted buffer. pub fn write_to(&self, buf: &mut CssBuf) { if let Some((kind, sel)) = self.rel_of.as_deref() { sel.write_to(buf); diff --git a/rsass/src/css/selectors/opt.rs b/rsass/src/css/selectors/opt.rs index b916056f..a7b9c9e9 100644 --- a/rsass/src/css/selectors/opt.rs +++ b/rsass/src/css/selectors/opt.rs @@ -52,13 +52,3 @@ impl Opt { } } } - -impl Opt> { - pub(crate) fn positive(self) -> Option> { - match self { - Opt::Some(v) => Some(v), - Opt::Any => Some(vec![]), - Opt::None => None, - } - } -} diff --git a/rsass/src/css/selectors/selectorset.rs b/rsass/src/css/selectors/selectorset.rs index ec353501..4fe67892 100644 --- a/rsass/src/css/selectors/selectorset.rs +++ b/rsass/src/css/selectors/selectorset.rs @@ -1,5 +1,6 @@ -use super::{logical::Selector, BadSelector, BadSelector0, CssSelectorSet}; -use super::{OldSelectors, Opt}; +use super::{ + BadSelector, BadSelector0, CssSelectorSet, OldSelectors, Opt, Selector, +}; use crate::output::CssBuf; use crate::parser::input_span; use crate::value::ListSeparator; @@ -81,6 +82,7 @@ impl SelectorSet { Ok(()) } + /// Write this set of selectors to a formatted buffer. pub fn write_to(&self, buf: &mut CssBuf) { if let Some((first, rest)) = self.s.split_first() { first.write_to(buf); @@ -91,20 +93,6 @@ impl SelectorSet { } } - // TODO: Get rid of this, use the above! - pub(super) fn write_to_buf(&self, buf: &mut String) { - fn write_one(s: &Selector, buf: &mut String) { - // Note: this should be made much more efficient! - buf.push_str(&s.clone().into_string_vec().join(" ")); - } - if let Some((first, rest)) = self.s.split_first() { - write_one(first, buf); - for one in rest { - buf.push_str(", "); // TODO: Only ',' is compressed! - write_one(one, buf); - } - } - } pub(super) fn has_backref(&self) -> bool { self.s.iter().any(Selector::has_backref) } @@ -117,24 +105,6 @@ impl SelectorSet { .collect::>(), } } - - /* // Get these selectors with a specific backref selector. - /// - /// Used to create `@at-root` contexts, to have `&` work in them. - pub(crate) fn with_backref(self, context: Selector) -> SelectorCtx { - SelectorCtx { - s: self.try_into().unwrap(), - backref: CssSelectorSet, - }.inside(&SelectorCtx { - s: Self::root().try_into().unwrap(), - backref: context, - }) - }*/ - - /// Get the first of these selectors (or the root selector if empty). - pub(crate) fn one(&self) -> Selector { - self.s.first().cloned().unwrap_or_else(Selector::default) - } } impl TryFrom<&OldSelectors> for SelectorSet { @@ -148,9 +118,7 @@ impl TryFrom<&OldSelectors> for SelectorSet { } let formatted = s.to_string(); let span = input_span(formatted.clone()); - ParseError::check(parser::selector_set(span.borrow()))? - .try_into() - .map_err(|_| BadSelector::Value(formatted.into())) + Ok(ParseError::check(parser::selector_set(span.borrow()))?) } } diff --git a/rsass/src/lib.rs b/rsass/src/lib.rs index c9fb5fdf..581ea74d 100644 --- a/rsass/src/lib.rs +++ b/rsass/src/lib.rs @@ -36,8 +36,7 @@ //! usable for my personal projects, and the number of working tests are //! improving. #![forbid(unsafe_code)] -#![allow(dead_code)] -// #![forbid(missing_docs)] (FIXME: temporarily ignored) +#![forbid(missing_docs)] pub mod css; mod error; diff --git a/rsass/src/parser/css/mod.rs b/rsass/src/parser/css/mod.rs index 2ecd26e3..e7e447bc 100644 --- a/rsass/src/parser/css/mod.rs +++ b/rsass/src/parser/css/mod.rs @@ -4,7 +4,7 @@ mod selectors; pub(crate) mod strings; mod values; -pub(crate) use self::selectors::{selector, selector_parts, selectors}; +pub(crate) use self::selectors::selectors; use super::util::{opt_spacelike, spacelike}; use super::{PResult, Span}; From b790b0d5d0b93f85817efd29a280fb8c71fb3770 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Mon, 5 Aug 2024 00:41:42 +0200 Subject: [PATCH 07/10] Minor cleanup. --- rsass/src/css/selectors/attribute.rs | 6 +++--- rsass/src/css/selectors/pseudo.rs | 4 ++-- rsass/src/parser/css/selectors.rs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rsass/src/css/selectors/attribute.rs b/rsass/src/css/selectors/attribute.rs index e031df2f..c81ca3ce 100644 --- a/rsass/src/css/selectors/attribute.rs +++ b/rsass/src/css/selectors/attribute.rs @@ -22,15 +22,15 @@ impl Attribute { } pub(super) fn write_to_buf(&self, buf: &mut CssBuf) { - buf.add_str("["); + buf.add_char('['); buf.add_str(&self.name); buf.add_str(&self.op); buf.add_str(&self.val.to_string()); if let Some(m) = self.modifier { - buf.add_str(" "); + buf.add_char(' '); buf.add_char(m); } - buf.add_str("]"); + buf.add_char(']'); } } diff --git a/rsass/src/css/selectors/pseudo.rs b/rsass/src/css/selectors/pseudo.rs index b7776f24..735390f3 100644 --- a/rsass/src/css/selectors/pseudo.rs +++ b/rsass/src/css/selectors/pseudo.rs @@ -111,9 +111,9 @@ impl Pseudo { } pub(super) fn write_to_buf(&self, buf: &mut CssBuf) { - buf.add_str(":"); + buf.add_char(':'); if self.element { - buf.add_str(":"); + buf.add_char(':'); } buf.add_str(&self.name); // Note: This is an ugly workaround for lack of proper support diff --git a/rsass/src/parser/css/selectors.rs b/rsass/src/parser/css/selectors.rs index 6a3f2d89..03f97071 100644 --- a/rsass/src/parser/css/selectors.rs +++ b/rsass/src/parser/css/selectors.rs @@ -9,14 +9,14 @@ use nom::combinator::{into, map, map_res, opt, value}; use nom::multi::{many1, separated_list1}; use nom::sequence::{delimited, pair, preceded, terminated, tuple}; -pub fn selectors(input: Span) -> PResult { +pub(crate) fn selectors(input: Span) -> PResult { map( separated_list1(terminated(tag(","), ignore_comments), selector), OldSelectors::new, )(input) } -pub fn selector(input: Span) -> PResult { +pub(crate) fn selector(input: Span) -> PResult { let (input, mut s) = selector_parts(input)?; if s.last() == Some(&OldSelectorPart::Descendant) { s.pop(); From e9cd926ab24f0f6719f530d0ec75df9b5f46e2ce Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Mon, 19 Aug 2024 00:19:09 +0200 Subject: [PATCH 08/10] New selectors 5: Eval to new * Evaluate sass selectors directly to new-style css selectors. * Purge the old style css selectors from the code-base! --- rsass/src/css/mod.rs | 4 +- rsass/src/css/selectors.rs | 528 ------------------ rsass/src/css/selectors/attribute.rs | 2 +- rsass/src/css/selectors/context.rs | 62 ++ rsass/src/css/selectors/cssselectorset.rs | 4 +- rsass/src/css/selectors/error.rs | 55 ++ rsass/src/css/selectors/logical.rs | 45 +- rsass/src/css/selectors/mod.rs | 24 + rsass/src/css/selectors/pseudo.rs | 16 +- rsass/src/css/selectors/selectorset.rs | 52 +- rsass/src/output/transform.rs | 6 +- rsass/src/parser/css/mod.rs | 3 - rsass/src/parser/css/selectors.rs | 153 ----- rsass/src/parser/selectors.rs | 18 +- rsass/src/sass/selectors.rs | 160 ++++-- rsass/src/sass/value.rs | 4 +- .../imported/at_root_alone_itpl.rs | 2 +- .../imported/basic_alone_itpl.rs | 2 +- .../nested/at_root_alone_itpl.rs | 1 - .../nested/at_root_postfix_itpl.rs | 1 - .../nested/at_root_prefix_itpl.rs | 1 - .../root/at_root_alone_itpl.rs | 2 +- .../root/basic_alone_itpl.rs | 2 +- .../spec/libsass_closed_issues/issue_1043.rs | 1 - .../spec/libsass_closed_issues/issue_1904.rs | 1 - .../spec/libsass_closed_issues/issue_2358.rs | 1 - .../spec/libsass_closed_issues/issue_738.rs | 1 - .../basic/t13_back_references.rs | 1 - .../tests/spec/non_conformant/nesting/not.rs | 1 - 29 files changed, 323 insertions(+), 830 deletions(-) delete mode 100644 rsass/src/css/selectors.rs create mode 100644 rsass/src/css/selectors/context.rs create mode 100644 rsass/src/css/selectors/error.rs create mode 100644 rsass/src/css/selectors/mod.rs delete mode 100644 rsass/src/parser/css/selectors.rs diff --git a/rsass/src/css/mod.rs b/rsass/src/css/mod.rs index b03a8f8b..25b63597 100644 --- a/rsass/src/css/mod.rs +++ b/rsass/src/css/mod.rs @@ -23,9 +23,7 @@ pub use self::selectors::{BadSelector, Selector, SelectorSet}; pub use self::string::CssString; pub use self::value::{InvalidCss, Value, ValueMap, ValueToMapError}; -pub(crate) use self::selectors::{ - CssSelectorSet, OldSelector, OldSelectorPart, OldSelectors, SelectorCtx, -}; +pub(crate) use self::selectors::{CssSelectorSet, SelectorCtx}; pub(crate) use self::util::{is_calc_name, is_function_name, is_not, IsNot}; pub(crate) mod parser { diff --git a/rsass/src/css/selectors.rs b/rsass/src/css/selectors.rs deleted file mode 100644 index e2966be5..00000000 --- a/rsass/src/css/selectors.rs +++ /dev/null @@ -1,528 +0,0 @@ -//! This module contains types for the selectors of a rule. -//! -//! Basically, in a rule like `p.foo, .foo p { some: thing; }` there -//! is a `Selectors` object which contains two `Selector` objects, one -//! for `p.foo` and one for `.foo p`. -//! -//! This _may_ change to a something like a tree of operators with -//! leafs of simple selectors in some future release. -use super::{is_not, CssString, Value}; -use crate::input::SourcePos; -use crate::parser::{input_span, ParseError}; -use std::fmt; -use std::io::Write; - -mod attribute; -mod cssselectorset; -mod logical; -mod opt; -mod pseudo; -mod selectorset; - -pub(crate) use self::opt::Opt; -pub(crate) use cssselectorset::CssSelectorSet; -pub use logical::Selector; -pub use selectorset::SelectorSet; - -pub(crate) mod parser { - pub(crate) use super::selectorset::parser::selector_set; -} - -/// A full set of selectors. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd)] -pub struct OldSelectors { - s: Vec, -} - -impl OldSelectors { - /// Create a root (empty) selector. - pub fn root() -> Self { - Self { - s: vec![OldSelector::root()], - } - } - /// Return true if this is a root (empty) selector. - pub fn is_root(&self) -> bool { - self.s == [OldSelector::root()] - } - /// Create a new `Selectors` from a vec of selectors. - pub fn new(s: Vec) -> Self { - if s.is_empty() { - Self::root() - } else { - Self { s } - } - } - - /// Validate that this selector is ok to use in css. - /// - /// `Selectors` can contain backref (`&`), but those must be - /// resolved before using the `Selectors` in css. - pub fn css_ok(self) -> Result { - if self.has_backref() { - let sel = self.to_string(); - Err(BadSelector::Backref(input_span(sel))) - } else { - Ok(self) - } - } - - /// True if any of the selectors contains a backref (`&`). - pub(crate) fn has_backref(&self) -> bool { - self.s.iter().any(OldSelector::has_backref) - } - - /// Get a vec of the non-placeholder selectors in self. - pub fn no_placeholder(&self) -> Option { - let s = self - .s - .iter() - .filter_map(OldSelector::no_placeholder) - .collect::>(); - if s.is_empty() { - None - } else { - Some(Self { s }) - } - } - - fn no_leading_combinator(mut self) -> Option { - self.s.retain(|s| !s.has_leading_combinator()); - if self.s.is_empty() { - None - } else { - Some(self) - } - } - - /// Return true if any of these selectors ends with a combinator - pub fn has_trailing_combinator(&self) -> bool { - self.s.iter().any(OldSelector::has_trailing_combinator) - } -} - -/// A full set of selectors with a separate backref. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct SelectorCtx { - /// The actual selectors. - s: CssSelectorSet, - backref: CssSelectorSet, -} - -impl SelectorCtx { - pub fn root() -> Self { - Self { - s: CssSelectorSet::root(), - backref: CssSelectorSet::root(), - } - } - /// Return true if this is a root (empty) selector. - pub fn is_root(&self) -> bool { - // TODO: Just s? Or really both s and backref? - self.s.is_root() && self.backref.is_root() - } - - pub fn real(&self) -> CssSelectorSet { - self.s.clone() - } - - /// Evaluate selectors inside this context. - pub(crate) fn nest(&self, selectors: SelectorSet) -> CssSelectorSet { - if selectors.has_backref() { - let backref = if self.s.is_root() { - &self.backref - } else { - &self.s - }; - CssSelectorSet { - s: selectors.resolve_ref(backref), - } - } else { - self.s.nest(selectors) - } - } - pub(crate) fn at_root(&self, selectors: SelectorSet) -> Self { - let backref = if self.s.is_root() { - &self.backref - } else { - &self.s - }; - let s = selectors - .s - .into_iter() - .flat_map(|s| { - if s.has_backref() { - s.resolve_ref(backref) - } else { - vec![s] - } - }) - .collect(); - Self { - s: CssSelectorSet { - s: SelectorSet { s }, - }, - backref: backref.clone(), - } - } -} - -impl From for SelectorCtx { - fn from(value: CssSelectorSet) -> Self { - Self { - s: value, - backref: CssSelectorSet::root(), - } - } -} - -/// A single css selector. -/// -/// A selector does not contain `,`. If it does, it is a `Selectors`, -/// where each of the parts separated by the comma is a `Selector`. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd)] -pub struct OldSelector(pub(crate) Vec); - -impl OldSelector { - /// Get the root (empty) selector. - pub fn root() -> Self { - Self(vec![]) - } - - /// Validate that this selector is ok to use in css. - /// - /// `Selectors` can contain backref (`&`), but those must be - /// resolved before using the `Selectors` in css. - pub fn css_ok(self) -> Result { - if self.has_backref() { - let slf = self.to_string(); - Err(BadSelector::Backref(input_span(slf))) - } else { - Ok(self) - } - } - - fn has_backref(&self) -> bool { - self.0.iter().any(OldSelectorPart::has_backref) - } - - /// Return this selector without placeholders. - /// - /// For most plain selectors, this returns Some(clone of self). - /// For placeholder selectors, it returns None. - /// For some selectors containing e.g. `p:matches(%a,.foo)` it - /// returns a modified selector (in that case, `p:matches(.foo)`). - fn no_placeholder(&self) -> Option { - let v = self - .0 - .iter() - .map(OldSelectorPart::no_placeholder) - .collect::>>()?; - let mut v2 = Vec::with_capacity(v.len()); - let mut has_sel = false; - for part in v { - if has_sel && part.is_wildcard() { - continue; - } - has_sel = !part.is_operator(); - v2.push(part); - } - let result = Self(v2); - if result.has_trailing_combinator() || result.has_double_combinator() - { - None - } else { - Some(result) - } - } - fn has_leading_combinator(&self) -> bool { - matches!(self.0.first(), Some(OldSelectorPart::RelOp(_))) - } - /// Return true if this selector ends with a combinator - pub fn has_trailing_combinator(&self) -> bool { - matches!(self.0.last(), Some(OldSelectorPart::RelOp(_))) - } - fn has_double_combinator(&self) -> bool { - self.0.windows(2).any(|w| { - matches!( - w, - [OldSelectorPart::RelOp(_), OldSelectorPart::RelOp(_)] - ) - }) - } -} - -/// A selector consist of a sequence of these parts. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd)] -pub enum OldSelectorPart { - /// A simple selector, eg a class, id or element name. - Simple(String), - /// The empty relational operator. - /// - /// The thing after this is a descendant of the thing before this. - Descendant, - /// A relational operator; `>`, `+`, `~`. - RelOp(u8), - /// An attribute selector - Attribute { - /// The attribute name - // TODO: Why not a raw String? - name: CssString, - /// An operator - op: String, - /// A value to match. - val: CssString, - /// Optional modifier. - modifier: Option, - }, - /// A css3 pseudo-element (::foo) - PseudoElement { - /// The name of the pseudo-element - name: CssString, - /// Arguments to the pseudo-element - arg: Option, - }, - /// A pseudo-class or a css2 pseudo-element (:foo) - Pseudo { - /// The name of the pseudo-class - name: CssString, - /// Arguments to the pseudo-class - arg: Option, - }, - /// A sass backref (`&`), to be replaced with outer selector. - BackRef, -} - -impl OldSelectorPart { - pub(crate) fn is_operator(&self) -> bool { - match *self { - Self::Descendant | Self::RelOp(_) => true, - Self::Simple(_) - | Self::Attribute { .. } - | Self::PseudoElement { .. } - | Self::Pseudo { .. } - | Self::BackRef => false, - } - } - pub(crate) fn is_wildcard(&self) -> bool { - if let Self::Simple(s) = self { - s == "*" - } else { - false - } - } - fn has_backref(&self) -> bool { - match *self { - Self::Descendant - | Self::RelOp(_) - | Self::Simple(_) - | Self::Attribute { .. } => false, - Self::BackRef => true, - Self::PseudoElement { ref arg, .. } - | Self::Pseudo { ref arg, .. } => arg - .as_ref() - .map_or(false, |a| a.s.iter().any(OldSelector::has_backref)), - } - } - /// Return this selectorpart without placeholders. - /// - /// For most parts, this returns either Some(clone of self) or None if - /// it was a placeholder selector, but some pseudoselectors are - /// converted to a version without the placeholder parts. - fn no_placeholder(&self) -> Option { - match self { - Self::Simple(s) => { - if !s.starts_with('%') { - Some(Self::Simple(s.clone())) - } else { - None - } - } - Self::Pseudo { name, arg } => match name.value() { - "is" => arg - .as_ref() - .and_then(OldSelectors::no_placeholder) - .and_then(OldSelectors::no_leading_combinator) - .map(|arg| Self::Pseudo { - name: name.clone(), - arg: Some(arg), - }), - "matches" | "any" | "where" | "has" => arg - .as_ref() - .and_then(OldSelectors::no_placeholder) - .map(|arg| Self::Pseudo { - name: name.clone(), - arg: Some(arg), - }), - "not" => { - if let Some(arg) = - arg.as_ref().and_then(OldSelectors::no_placeholder) - { - Some(Self::Pseudo { - name: name.clone(), - arg: Some(arg), - }) - } else { - Some(Self::Simple("*".into())) - } - } - _ => Some(Self::Pseudo { - name: name.clone(), - arg: arg.clone(), - }), - }, - x => Some(x.clone()), - } - } -} - -// TODO: This shoule probably be on Formatted instead. -impl fmt::Display for OldSelectors { - fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result { - if let Some((first, rest)) = self.s.split_first() { - first.fmt(out)?; - let separator = if out.alternate() { "," } else { ", " }; - for item in rest { - out.write_str(separator)?; - item.fmt(out)?; - } - } - Ok(()) - } -} - -impl fmt::Display for OldSelector { - fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result { - // Note: There should be smarter whitespace-handling here, avoiding - // the need to clean up afterwards. - let mut buf = vec![]; - for p in &self.0 { - if out.alternate() { - write!(&mut buf, "{p:#}").map_err(|_| fmt::Error)?; - } else { - write!(&mut buf, "{p}").map_err(|_| fmt::Error)?; - } - } - if buf.ends_with(b"> ") { - buf.pop(); - } - while buf.first() == Some(&b' ') { - buf.remove(0); - } - let buf = String::from_utf8(buf).map_err(|_| fmt::Error)?; - out.write_str(&buf.replace(" ", " ")) - } -} - -impl fmt::Display for OldSelectorPart { - fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result { - match *self { - Self::Simple(ref s) => write!(out, "{s}"), - Self::Descendant => write!(out, " "), - Self::RelOp(ref c) => { - if out.alternate() && *c != b'~' { - write!(out, "{}", *c as char) - } else { - write!(out, " {} ", *c as char) - } - } - Self::Attribute { - ref name, - ref op, - ref val, - ref modifier, - } => write!( - out, - "[{name}{op}{val}{}]", - modifier.map(|m| format!(" {m}")).unwrap_or_default() - ), - Self::PseudoElement { ref name, ref arg } => { - write!(out, "::{name}")?; - if let Some(ref arg) = *arg { - if out.alternate() { - write!(out, "({arg:#})")?; - } else { - write!(out, "({arg})")?; - } - } - Ok(()) - } - Self::Pseudo { ref name, ref arg } => { - let name = name.to_string(); - if let Some(ref arg) = *arg { - // It seems some pseudo-classes should always have - // their arg in compact form. Maybe we need more - // hard-coded names here, or maybe the condition - // should be on the argument rather than the name? - if out.alternate() || name == "nth-of-type" { - write!(out, ":{name}({arg:#})",) - } else if name == "nth-child" { - let arg = format!("{arg:#}"); - write!(out, ":{name}({})", arg.replace(',', ", ")) - } else { - write!(out, ":{name}({arg})") - } - } else { - write!(out, ":{name}") - } - } - Self::BackRef => write!(out, "&"), - } - } -} - -enum BadSelector0 { - Value, - Parse(ParseError), -} -impl BadSelector0 { - fn ctx(self, v: Value) -> BadSelector { - match self { - Self::Value => BadSelector::Value(v), - Self::Parse(err) => BadSelector::Parse(err), - } - } -} -impl From for BadSelector0 { - fn from(e: ParseError) -> Self { - Self::Parse(e) - } -} - -/// The error when a [Value] cannot be converted to a [Selectors] or [Selector]. -#[derive(Debug)] -pub enum BadSelector { - /// The value was not the expected type of list or string. - Value(Value), - /// There was an error parsing a string value. - Parse(ParseError), - /// A backref (`&`) were present but not allowed there. - Backref(SourcePos), - /// Cant append extenstion to base. - Append(OldSelector, OldSelector), -} - -impl fmt::Display for BadSelector { - fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Value(v) => out.write_str(&is_not( - v, - "a valid selector: it must be a string,\ - \na list of strings, or a list of lists of strings", - )), - Self::Parse(e) => e.fmt(out), - Self::Backref(pos) => { - writeln!(out, "Parent selectors aren\'t allowed here.")?; - pos.show(out) - } - Self::Append(e, b) => { - write!(out, "Can't append {e} to {b}.") - } - } - } -} -impl From for BadSelector { - fn from(e: ParseError) -> Self { - Self::Parse(e) - } -} diff --git a/rsass/src/css/selectors/attribute.rs b/rsass/src/css/selectors/attribute.rs index c81ca3ce..521aeee2 100644 --- a/rsass/src/css/selectors/attribute.rs +++ b/rsass/src/css/selectors/attribute.rs @@ -21,7 +21,7 @@ impl Attribute { && self.modifier == b.modifier } - pub(super) fn write_to_buf(&self, buf: &mut CssBuf) { + pub(super) fn write_to(&self, buf: &mut CssBuf) { buf.add_char('['); buf.add_str(&self.name); buf.add_str(&self.op); diff --git a/rsass/src/css/selectors/context.rs b/rsass/src/css/selectors/context.rs new file mode 100644 index 00000000..75dd1eda --- /dev/null +++ b/rsass/src/css/selectors/context.rs @@ -0,0 +1,62 @@ +use super::{CssSelectorSet, SelectorSet}; + +/// A full set of selectors with a separate backref. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SelectorCtx { + /// The actual selectors. + s: CssSelectorSet, + backref: CssSelectorSet, +} + +impl SelectorCtx { + pub fn root() -> Self { + Self { + s: CssSelectorSet::root(), + backref: CssSelectorSet::root(), + } + } + /// Return true if this is a root (empty) selector. + pub fn is_root(&self) -> bool { + self.s.is_root() + } + + pub fn real(&self) -> CssSelectorSet { + self.s.clone() + } + + /// Evaluate selectors inside this context. + pub(crate) fn nest(&self, selectors: SelectorSet) -> CssSelectorSet { + if selectors.has_backref() { + CssSelectorSet { + s: selectors.resolve_ref(self.get_backref()), + } + } else { + self.s.nest(selectors) + } + } + pub(crate) fn at_root(&self, selectors: SelectorSet) -> Self { + let backref = self.get_backref(); + Self { + s: CssSelectorSet { + s: selectors.resolve_ref(backref), + }, + backref: backref.clone(), + } + } + pub(crate) fn get_backref(&self) -> &CssSelectorSet { + if self.s.is_root() { + &self.backref + } else { + &self.s + } + } +} + +impl From for SelectorCtx { + fn from(value: CssSelectorSet) -> Self { + Self { + s: value, + backref: CssSelectorSet::root(), + } + } +} diff --git a/rsass/src/css/selectors/cssselectorset.rs b/rsass/src/css/selectors/cssselectorset.rs index 7de53dc3..7246ceb6 100644 --- a/rsass/src/css/selectors/cssselectorset.rs +++ b/rsass/src/css/selectors/cssselectorset.rs @@ -1,5 +1,5 @@ -use super::selectorset::SelectorSet; -use super::{BadSelector, BadSelector0}; +use super::error::BadSelector0; +use super::{BadSelector, SelectorSet}; use crate::css::Value; use crate::error::Invalid; use crate::output::CssBuf; diff --git a/rsass/src/css/selectors/error.rs b/rsass/src/css/selectors/error.rs new file mode 100644 index 00000000..871ebf1e --- /dev/null +++ b/rsass/src/css/selectors/error.rs @@ -0,0 +1,55 @@ +use crate::css::{is_not, Value}; +use crate::input::SourcePos; +use crate::ParseError; +use std::fmt; + +pub(super) enum BadSelector0 { + Value, + Parse(ParseError), +} +impl BadSelector0 { + pub(super) fn ctx(self, v: Value) -> BadSelector { + match self { + Self::Value => BadSelector::Value(v), + Self::Parse(err) => BadSelector::Parse(err), + } + } +} +impl From for BadSelector0 { + fn from(e: ParseError) -> Self { + Self::Parse(e) + } +} + +/// The error when a [Value] cannot be converted to a [Selectors] or [Selector]. +#[derive(Debug)] +pub enum BadSelector { + /// The value was not the expected type of list or string. + Value(Value), + /// There was an error parsing a string value. + Parse(ParseError), + /// A backref (`&`) were present but not allowed there. + Backref(SourcePos), +} + +impl fmt::Display for BadSelector { + fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Value(v) => out.write_str(&is_not( + v, + "a valid selector: it must be a string,\ + \na list of strings, or a list of lists of strings", + )), + Self::Parse(e) => e.fmt(out), + Self::Backref(pos) => { + writeln!(out, "Parent selectors aren\'t allowed here.")?; + pos.show(out) + } + } + } +} +impl From for BadSelector { + fn from(e: ParseError) -> Self { + Self::Parse(e) + } +} diff --git a/rsass/src/css/selectors/logical.rs b/rsass/src/css/selectors/logical.rs index d12ff658..4ed9655d 100644 --- a/rsass/src/css/selectors/logical.rs +++ b/rsass/src/css/selectors/logical.rs @@ -5,10 +5,9 @@ //! implementation. But as that is a major breaking change, I keep //! these types internal for now. use super::attribute::Attribute; +use super::error::BadSelector0; use super::pseudo::Pseudo; -use super::selectorset::SelectorSet; -use super::Opt; -use super::{BadSelector, BadSelector0, CssSelectorSet}; +use super::{BadSelector, CssSelectorSet, Opt, SelectorSet}; use crate::css::Value; use crate::output::CssBuf; use crate::parser::input_span; @@ -211,17 +210,25 @@ impl Selector { .s .iter() .flat_map(|s| { - let mut slf = self.clone(); - let mut s = s.clone(); - slf.element = match (s.element.take(), slf.element.take()) - { - (None, None) => None, - (Some(e), None) | (None, Some(e)) => Some(e), - (Some(a), Some(b)) => Some(ElemType { - s: format!("{}{}", a.s, b.s), - }), + let mut buf = CssBuf::new(Default::default()); + s.write_last_compound_to(&mut buf); + self.write_last_compound_to(&mut buf); + let buf = buf.take(); + let mut result = if buf.is_empty() { + Selector::default() + } else { + let span = input_span( + String::from_utf8_lossy(&buf).to_string(), + ); + ParseError::check(parser::compound_selector( + span.borrow(), + )) + .unwrap() }; - s.unify(slf) + result.rel_of = self.rel_of.clone(); + let mut s2 = Selector::default(); + s2.rel_of = s.rel_of.clone(); + s2.unify(result) }) .collect() } else { @@ -564,12 +571,12 @@ impl Selector { result.extend(self.id.iter().map(|id| format!("#{id}"))); result.extend(self.attr.iter().map(|a| { let mut s = CssBuf::new(Default::default()); - a.write_to_buf(&mut s); + a.write_to(&mut s); String::from_utf8_lossy(&s.take()).to_string() })); result.extend(self.pseudo.iter().map(|p| { let mut s = CssBuf::new(Default::default()); - p.write_to_buf(&mut s); + p.write_to(&mut s); String::from_utf8_lossy(&s.take()).to_string() })); Ok(result) @@ -642,10 +649,10 @@ impl Selector { buf.add_str(c); } for attr in &self.attr { - attr.write_to_buf(buf); + attr.write_to(buf); } for pseudo in &self.pseudo { - pseudo.write_to_buf(buf); + pseudo.write_to(buf); } } @@ -979,8 +986,6 @@ impl RelKind { } pub(crate) mod parser { - use std::str::from_utf8; - use super::super::attribute::parser::attribute; use super::super::pseudo::parser::pseudo; use super::{ElemType, RelKind, Selector}; @@ -1047,7 +1052,7 @@ pub(crate) mod parser { // TODO: Remove this. // It is a temporary workaround for keyframe support. result.element = Some(ElemType { - s: dbg!(from_utf8(stop.fragment()).unwrap()).to_string(), + s: String::from_utf8_lossy(stop.fragment()).to_string(), }); return Ok((rest, result)); } diff --git a/rsass/src/css/selectors/mod.rs b/rsass/src/css/selectors/mod.rs new file mode 100644 index 00000000..be2de700 --- /dev/null +++ b/rsass/src/css/selectors/mod.rs @@ -0,0 +1,24 @@ +//! This module contains types for the selectors of a rule. +//! +//! Basically, in a rule like `p.foo, .foo p { some: thing; }` there +//! is a `SelectorSet` object which contains two `Selector` objects, one +//! for `p.foo` and one for `.foo p`. +mod attribute; +mod context; +mod cssselectorset; +mod error; +mod logical; +mod opt; +mod pseudo; +mod selectorset; + +pub(crate) use self::opt::Opt; +pub use context::SelectorCtx; +pub(crate) use cssselectorset::CssSelectorSet; +pub use error::BadSelector; +pub use logical::Selector; +pub use selectorset::SelectorSet; + +pub(crate) mod parser { + pub(crate) use super::selectorset::parser::selector_set; +} diff --git a/rsass/src/css/selectors/pseudo.rs b/rsass/src/css/selectors/pseudo.rs index 735390f3..a93ccb5b 100644 --- a/rsass/src/css/selectors/pseudo.rs +++ b/rsass/src/css/selectors/pseudo.rs @@ -110,7 +110,7 @@ impl Pseudo { self } - pub(super) fn write_to_buf(&self, buf: &mut CssBuf) { + pub(super) fn write_to(&self, buf: &mut CssBuf) { buf.add_char(':'); if self.element { buf.add_char(':'); @@ -125,12 +125,12 @@ impl Pseudo { "nth-of-type", ]) { let mut t = CssBuf::new(buf.format()); - self.arg.write_to_buf(&mut t); + self.arg.write_to(&mut t); buf.add_str( &String::from_utf8_lossy(&t.take()).replacen(" + ", "+", 1), ); } else { - self.arg.write_to_buf(buf); + self.arg.write_to(buf); } } fn name_in(&self, names: &[&str]) -> bool { @@ -181,17 +181,17 @@ impl Arg { _ => false, } } - pub(super) fn write_to_buf(&self, buf: &mut CssBuf) { + pub(super) fn write_to(&self, buf: &mut CssBuf) { match self { Self::Selector(s) => { - buf.add_str("("); + buf.add_char('('); s.write_to(buf); - buf.add_str(")"); + buf.add_char(')'); } Self::Other(a) => { - buf.add_str("("); + buf.add_char('('); buf.add_str(a); - buf.add_str(")"); + buf.add_char(')'); } Self::None => (), } diff --git a/rsass/src/css/selectors/selectorset.rs b/rsass/src/css/selectors/selectorset.rs index 4fe67892..2e116609 100644 --- a/rsass/src/css/selectors/selectorset.rs +++ b/rsass/src/css/selectors/selectorset.rs @@ -1,18 +1,17 @@ -use super::{ - BadSelector, BadSelector0, CssSelectorSet, OldSelectors, Opt, Selector, -}; +use super::error::BadSelector0; +use super::{BadSelector, CssSelectorSet, Opt, Selector}; +use crate::css::Value; use crate::output::CssBuf; use crate::parser::input_span; use crate::value::ListSeparator; -use crate::ParseError; -use crate::{css::Value, Invalid}; +use crate::{Invalid, ParseError}; /// A set of selectors. /// This is the normal top-level selector, which can be a single /// [`Selector`] or a comma-separated list (set) of such selectors. #[derive(Clone, Debug, PartialEq, Eq)] pub struct SelectorSet { - pub(super) s: Vec, + pub(crate) s: Vec, } impl SelectorSet { @@ -97,28 +96,27 @@ impl SelectorSet { self.s.iter().any(Selector::has_backref) } pub(super) fn resolve_ref(self, ctx: &CssSelectorSet) -> Self { - Self { - s: self - .s - .into_iter() - .flat_map(|o| o.resolve_ref(ctx)) - .collect::>(), - } - } -} - -impl TryFrom<&OldSelectors> for SelectorSet { - type Error = BadSelector; - - fn try_from(s: &OldSelectors) -> Result { - if s.is_root() { - return Ok(SelectorSet { - s: vec![Selector::default()], - }); + let mut resolved = self + .s + .into_iter() + .map(|s| s.resolve_ref(ctx).into_iter()) + .collect::>(); + // Now put the resolved items in the correct order. + // We have [[a1, b1], [a2, b2]] but want [a1, a2, b1, b2] + let mut s = Vec::new(); + loop { + let mut done = true; + for v in &mut resolved { + if let Some(next) = v.next() { + s.push(next); + done = false; + } + } + if done { + break; + } } - let formatted = s.to_string(); - let span = input_span(formatted.clone()); - Ok(ParseError::check(parser::selector_set(span.borrow()))?) + Self { s } } } diff --git a/rsass/src/output/transform.rs b/rsass/src/output/transform.rs index 14ffd487..71b8163c 100644 --- a/rsass/src/output/transform.rs +++ b/rsass/src/output/transform.rs @@ -3,7 +3,7 @@ use super::cssdest::CssDestination; use super::CssData; -use crate::css::{self, AtRule, Import, SelectorCtx, SelectorSet}; +use crate::css::{self, AtRule, Import, SelectorCtx}; use crate::error::ResultPos; use crate::input::{Context, Loader, Parsed, SourceKind}; use crate::sass::{get_global_module, Expose, Item, UseAs}; @@ -203,8 +203,6 @@ fn handle_item( } Item::AtRoot(ref selectors, ref body) => { let selectors = selectors.eval(scope.clone())?; - let selectors = SelectorSet::try_from(&selectors) - .map_err(|e| Error::S(e.to_string()))?; let ctx = scope.get_selectors().at_root(selectors); let selectors = ctx.real(); let subscope = ScopeRef::sub_selectors(scope, ctx); @@ -361,8 +359,6 @@ fn handle_item( Item::Rule(ref selectors, ref body) => { check_body(body, BodyContext::Rule)?; let selectors = selectors.eval(scope.clone())?; - let selectors = SelectorSet::try_from(&selectors) - .map_err(|e| Error::S(e.to_string()))?; let selectors = scope.get_selectors().nest(selectors); let mut dest = dest.start_rule(selectors.clone()).no_pos()?; let scope = ScopeRef::sub_selectors(scope, selectors.into()); diff --git a/rsass/src/parser/css/mod.rs b/rsass/src/parser/css/mod.rs index e7e447bc..7402df2d 100644 --- a/rsass/src/parser/css/mod.rs +++ b/rsass/src/parser/css/mod.rs @@ -1,11 +1,8 @@ pub(crate) mod media; mod rule; -mod selectors; pub(crate) mod strings; mod values; -pub(crate) use self::selectors::selectors; - use super::util::{opt_spacelike, spacelike}; use super::{PResult, Span}; use crate::css::{AtRule, Comment, Import, Item, MediaRule, Value}; diff --git a/rsass/src/parser/css/selectors.rs b/rsass/src/parser/css/selectors.rs deleted file mode 100644 index 03f97071..00000000 --- a/rsass/src/parser/css/selectors.rs +++ /dev/null @@ -1,153 +0,0 @@ -use super::super::util::{ignore_comments, opt_spacelike, spacelike2}; -use super::super::{input_to_string, PResult, Span}; -use super::strings::{css_string, css_string_any}; -use crate::css::{OldSelector, OldSelectorPart, OldSelectors}; -use nom::branch::alt; -use nom::bytes::complete::tag; -use nom::character::complete::one_of; -use nom::combinator::{into, map, map_res, opt, value}; -use nom::multi::{many1, separated_list1}; -use nom::sequence::{delimited, pair, preceded, terminated, tuple}; - -pub(crate) fn selectors(input: Span) -> PResult { - map( - separated_list1(terminated(tag(","), ignore_comments), selector), - OldSelectors::new, - )(input) -} - -pub(crate) fn selector(input: Span) -> PResult { - let (input, mut s) = selector_parts(input)?; - if s.last() == Some(&OldSelectorPart::Descendant) { - s.pop(); - } - Ok((input, OldSelector(s))) -} - -pub(crate) fn selector_parts(input: Span) -> PResult> { - many1(selector_part)(input) -} - -fn selector_part(input: Span) -> PResult { - let (input, mark) = - alt((tag("&"), tag("::"), tag(":"), tag("."), tag("["), tag("")))( - input, - )?; - match mark.fragment() { - b"&" => value(OldSelectorPart::BackRef, tag(""))(input), - b"::" => map( - pair( - into(css_string), - opt(delimited(tag("("), selectors, tag(")"))), - ), - |(name, arg)| OldSelectorPart::PseudoElement { name, arg }, - )(input), - b":" => map( - pair( - into(css_string), - opt(delimited(tag("("), selectors, tag(")"))), - ), - |(name, arg)| OldSelectorPart::Pseudo { name, arg }, - )(input), - b"." => map(simple_part, |mut s| { - s.insert(0, '.'); - OldSelectorPart::Simple(s) - })(input), - b"[" => delimited( - opt_spacelike, - alt(( - map( - tuple(( - terminated(name_opt_ns, opt_spacelike), - terminated( - map_res( - alt(( - tag("*="), - tag("|="), - tag("="), - tag("$="), - tag("~="), - tag("^="), - )), - input_to_string, - ), - opt_spacelike, - ), - terminated(css_string_any, opt_spacelike), - opt(terminated( - one_of( - "ABCDEFGHIJKLMNOPQRSTUVWXYZ\ - abcdefghijklmnopqrstuvwxyz", - ), - opt_spacelike, - )), - )), - |(name, op, val, modifier)| OldSelectorPart::Attribute { - name: name.into(), - op, - val, - modifier, - }, - ), - map(terminated(name_opt_ns, opt_spacelike), |name| { - OldSelectorPart::Attribute { - name: name.into(), - op: "".to_string(), - val: "".into(), - modifier: None, - } - }), - )), - tag("]"), - )(input), - b"" => alt(( - map(simple_part, OldSelectorPart::Simple), - delimited( - opt_spacelike, - alt(( - value(OldSelectorPart::RelOp(b'>'), tag(">")), - value(OldSelectorPart::RelOp(b'+'), tag("+")), - value(OldSelectorPart::RelOp(b'~'), tag("~")), - value(OldSelectorPart::RelOp(b'\\'), tag("\\")), - )), - opt_spacelike, - ), - value(OldSelectorPart::Descendant, spacelike2), - ))(input), - _ => unreachable!(), - } -} - -fn name_opt_ns(input: Span) -> PResult { - fn name_part(input: Span) -> PResult { - alt((value(String::from("*"), tag("*")), css_string))(input) - } - alt(( - map(preceded(tag("|"), name_part), |mut s| { - s.insert(0, '|'); - s - }), - map( - pair(name_part, opt(preceded(tag("|"), name_part))), - |(a, b)| { - if let Some(b) = b { - format!("{a}|{b}") - } else { - a - } - }, - ), - ))(input) -} - -fn simple_part(input: Span) -> PResult { - let (rest, (pre, mut s, post)) = - tuple((opt(tag("%")), name_opt_ns, opt(tag("%"))))(input)?; - if pre.is_some() { - s.insert(0, '%'); - } - if post.is_some() { - s.push('%'); - } - Ok((rest, s)) -} diff --git a/rsass/src/parser/selectors.rs b/rsass/src/parser/selectors.rs index 424bee14..be296da3 100644 --- a/rsass/src/parser/selectors.rs +++ b/rsass/src/parser/selectors.rs @@ -5,16 +5,22 @@ use crate::sass::{Selector, SelectorPart, Selectors}; use nom::branch::alt; use nom::bytes::complete::tag; use nom::character::complete::one_of; -use nom::combinator::{map, map_res, opt, value}; +use nom::combinator::{map, map_opt, map_res, opt, value}; use nom::multi::{many1, separated_list1}; use nom::sequence::{delimited, pair, preceded, terminated, tuple}; pub fn selectors(input: Span) -> PResult { - let (input, v) = separated_list1( - terminated(tag(","), ignore_comments), - opt(selector), - )(input)?; - Ok((input, Selectors::new(v.into_iter().flatten().collect()))) + map_opt( + separated_list1(terminated(tag(","), ignore_comments), opt(selector)), + |v| { + let v = v.into_iter().flatten().collect::>(); + if v.is_empty() { + None + } else { + Some(Selectors::new(v)) + } + }, + )(input) } pub fn selector(input: Span) -> PResult { diff --git a/rsass/src/sass/selectors.rs b/rsass/src/sass/selectors.rs index b05c6fdb..784a5f34 100644 --- a/rsass/src/sass/selectors.rs +++ b/rsass/src/sass/selectors.rs @@ -6,9 +6,12 @@ //! //! This _may_ change to a something like a tree of operators with //! leafs of simple selectors in some future release. -use crate::css; +use crate::css::parser::selector_set; +use crate::css::{self, SelectorSet}; +use crate::parser::input_span; use crate::sass::SassString; use crate::{Error, ParseError, ScopeRef}; +use std::fmt::Write; /// A full set of selectors #[derive(Clone, Debug, PartialEq, Eq, PartialOrd)] @@ -32,22 +35,26 @@ impl Selectors { } /// Evaluate any interpolation in these Selectors. - pub fn eval(&self, scope: ScopeRef) -> Result { - let s = css::OldSelectors::new( - self.s - .iter() - .map(|s| s.eval(scope.clone())) - .collect::, Error>>()?, - ); - // The "simple" parts we get from evaluating interpolations may - // contain high-level selector separators (i.e. ","), so we need to - // parse the selectors again, from a string representation. - use crate::parser::css::selectors; - use crate::parser::input_span; - // TODO: Get the span from the source of self! - Ok(ParseError::check(selectors( - input_span(format!("{s} ")).borrow(), - ))?) + pub fn eval(&self, scope: ScopeRef) -> Result { + let mut s = Vec::new(); + for sel in &self.s { + s.extend(sel.eval(scope.clone())?); + } + Ok(SelectorSet { s }) + } + fn write_eval( + &self, + f: &mut String, + scope: ScopeRef, + ) -> Result<(), Error> { + if let Some((first, rest)) = self.s.split_first() { + first.write_eval(f, scope.clone())?; + for s in rest { + f.push_str(", "); + s.write_eval(f, scope.clone())?; + } + } + Ok(()) } } @@ -67,12 +74,25 @@ impl Selector { pub fn new(s: Vec) -> Self { Self(s) } - fn eval(&self, scope: ScopeRef) -> Result { - self.0 - .iter() - .map(|sp| sp.eval(scope.clone())) - .collect::>() - .map(css::OldSelector) + fn eval(&self, scope: ScopeRef) -> Result, Error> { + if self.0.is_empty() { + Ok(vec![css::Selector::default()]) + } else { + let mut text = String::new(); + self.write_eval(&mut text, scope)?; + Ok(ParseError::check(selector_set(input_span(text).borrow()))?.s) + } + } + + fn write_eval( + &self, + f: &mut String, + scope: ScopeRef, + ) -> Result<(), Error> { + for p in &self.0 { + p.write_eval(f, scope.clone())?; + } + Ok(()) } } @@ -120,45 +140,65 @@ pub enum SelectorPart { } impl SelectorPart { - fn eval(&self, scope: ScopeRef) -> Result { - match *self { - Self::Attribute { - ref name, - ref op, - ref val, - ref modifier, - } => Ok(css::OldSelectorPart::Attribute { - name: name.evaluate(scope.clone())?, - op: op.clone(), - val: val.evaluate(scope)?.opt_unquote(), - modifier: *modifier, - }), - Self::Simple(ref v) => Ok(css::OldSelectorPart::Simple( - v.evaluate(scope)?.to_string(), - )), - Self::Pseudo { ref name, ref arg } => { - let arg = match &arg { - Some(ref a) => Some(a.eval(scope.clone())?), - None => None, - }; - Ok(css::OldSelectorPart::Pseudo { - name: name.evaluate(scope)?, - arg, - }) + fn write_eval( + &self, + f: &mut String, + scope: ScopeRef, + ) -> Result<(), Error> { + match self { + SelectorPart::Simple(s) => we(s, f, scope)?, + SelectorPart::Descendant => f.push(' '), + SelectorPart::RelOp(op) => { + if let Some(ch) = char::from_u32(u32::from(*op)) { + f.push(' '); + f.push(ch); + f.push(' '); + } + } + SelectorPart::Attribute { + name, + op, + val, + modifier, + } => { + f.push('['); + we(name, f, scope.clone())?; + f.push_str(op); + let val = val.evaluate(scope)?.opt_unquote(); + write!(f, "{val}")?; + if let Some(modifier) = modifier { + if val.quotes().is_none() { + f.push(' '); + } + f.push(*modifier); + } + f.push(']'); } - Self::PseudoElement { ref name, ref arg } => { - let arg = match &arg { - Some(ref a) => Some(a.eval(scope.clone())?), - None => None, - }; - Ok(css::OldSelectorPart::PseudoElement { - name: name.evaluate(scope)?, - arg, - }) + SelectorPart::PseudoElement { name, arg } => { + f.push_str("::"); + we(name, f, scope.clone())?; + if let Some(arg) = arg { + f.push('('); + arg.write_eval(f, scope)?; + f.push(')'); + } } - Self::Descendant => Ok(css::OldSelectorPart::Descendant), - Self::RelOp(op) => Ok(css::OldSelectorPart::RelOp(op)), - Self::BackRef => Ok(css::OldSelectorPart::BackRef), + SelectorPart::Pseudo { name, arg } => { + f.push(':'); + we(name, f, scope.clone())?; + if let Some(arg) = arg { + f.push('('); + arg.write_eval(f, scope)?; + f.push(')'); + } + } + SelectorPart::BackRef => f.push('&'), } + Ok(()) } } + +fn we(s: &SassString, f: &mut String, scope: ScopeRef) -> Result<(), Error> { + write!(f, "{}", s.evaluate(scope)?)?; + Ok(()) +} diff --git a/rsass/src/sass/value.rs b/rsass/src/sass/value.rs index 5945dd6a..f79280d5 100644 --- a/rsass/src/sass/value.rs +++ b/rsass/src/sass/value.rs @@ -185,7 +185,9 @@ impl Value { (op, v) => css::Value::UnaryOp(*op, Box::new(v)), } } - Self::HereSelector => scope.get_selectors().real().into(), + Self::HereSelector => { + scope.get_selectors().get_backref().clone().into() + } Self::UnicodeRange(s) => css::Value::UnicodeRange(s.clone()), }) } diff --git a/rsass/tests/spec/libsass/base_level_parent/imported/at_root_alone_itpl.rs b/rsass/tests/spec/libsass/base_level_parent/imported/at_root_alone_itpl.rs index 86b18c33..28f20bda 100644 --- a/rsass/tests/spec/libsass/base_level_parent/imported/at_root_alone_itpl.rs +++ b/rsass/tests/spec/libsass/base_level_parent/imported/at_root_alone_itpl.rs @@ -8,7 +8,7 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // missing error +#[ignore] // wrong error fn test() { assert_eq!( runner().err("@import \"include.scss\";"), diff --git a/rsass/tests/spec/libsass/base_level_parent/imported/basic_alone_itpl.rs b/rsass/tests/spec/libsass/base_level_parent/imported/basic_alone_itpl.rs index 036b8592..edd9187d 100644 --- a/rsass/tests/spec/libsass/base_level_parent/imported/basic_alone_itpl.rs +++ b/rsass/tests/spec/libsass/base_level_parent/imported/basic_alone_itpl.rs @@ -9,7 +9,7 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // missing error +#[ignore] // wrong error fn test() { assert_eq!( runner().err("@import \"include.scss\";"), diff --git a/rsass/tests/spec/libsass/base_level_parent/nested/at_root_alone_itpl.rs b/rsass/tests/spec/libsass/base_level_parent/nested/at_root_alone_itpl.rs index d6877e97..623a0db4 100644 --- a/rsass/tests/spec/libsass/base_level_parent/nested/at_root_alone_itpl.rs +++ b/rsass/tests/spec/libsass/base_level_parent/nested/at_root_alone_itpl.rs @@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn test() { assert_eq!( runner().ok("test {\r\ diff --git a/rsass/tests/spec/libsass/base_level_parent/nested/at_root_postfix_itpl.rs b/rsass/tests/spec/libsass/base_level_parent/nested/at_root_postfix_itpl.rs index 4985f037..f2ce2665 100644 --- a/rsass/tests/spec/libsass/base_level_parent/nested/at_root_postfix_itpl.rs +++ b/rsass/tests/spec/libsass/base_level_parent/nested/at_root_postfix_itpl.rs @@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn test() { assert_eq!( runner().ok("test {\r\ diff --git a/rsass/tests/spec/libsass/base_level_parent/nested/at_root_prefix_itpl.rs b/rsass/tests/spec/libsass/base_level_parent/nested/at_root_prefix_itpl.rs index 7b085f89..779741e6 100644 --- a/rsass/tests/spec/libsass/base_level_parent/nested/at_root_prefix_itpl.rs +++ b/rsass/tests/spec/libsass/base_level_parent/nested/at_root_prefix_itpl.rs @@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn test() { assert_eq!( runner().ok("test {\r\ diff --git a/rsass/tests/spec/libsass/base_level_parent/root/at_root_alone_itpl.rs b/rsass/tests/spec/libsass/base_level_parent/root/at_root_alone_itpl.rs index 5dd48d99..47419cca 100644 --- a/rsass/tests/spec/libsass/base_level_parent/root/at_root_alone_itpl.rs +++ b/rsass/tests/spec/libsass/base_level_parent/root/at_root_alone_itpl.rs @@ -6,7 +6,7 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // missing error +#[ignore] // wrong error fn test() { assert_eq!( runner().err( diff --git a/rsass/tests/spec/libsass/base_level_parent/root/basic_alone_itpl.rs b/rsass/tests/spec/libsass/base_level_parent/root/basic_alone_itpl.rs index 13e274f2..4cb7b47e 100644 --- a/rsass/tests/spec/libsass/base_level_parent/root/basic_alone_itpl.rs +++ b/rsass/tests/spec/libsass/base_level_parent/root/basic_alone_itpl.rs @@ -6,7 +6,7 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // missing error +#[ignore] // wrong error fn test() { assert_eq!( runner().err( diff --git a/rsass/tests/spec/libsass_closed_issues/issue_1043.rs b/rsass/tests/spec/libsass_closed_issues/issue_1043.rs index eb26a736..bd096394 100644 --- a/rsass/tests/spec/libsass_closed_issues/issue_1043.rs +++ b/rsass/tests/spec/libsass_closed_issues/issue_1043.rs @@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn test() { assert_eq!( runner().ok(".component{\ diff --git a/rsass/tests/spec/libsass_closed_issues/issue_1904.rs b/rsass/tests/spec/libsass_closed_issues/issue_1904.rs index 66c12ad0..509391b3 100644 --- a/rsass/tests/spec/libsass_closed_issues/issue_1904.rs +++ b/rsass/tests/spec/libsass_closed_issues/issue_1904.rs @@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn test() { assert_eq!( runner().ok(".foo {\ diff --git a/rsass/tests/spec/libsass_closed_issues/issue_2358.rs b/rsass/tests/spec/libsass_closed_issues/issue_2358.rs index 79d79cb6..0571c91c 100644 --- a/rsass/tests/spec/libsass_closed_issues/issue_2358.rs +++ b/rsass/tests/spec/libsass_closed_issues/issue_2358.rs @@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn test() { assert_eq!( runner().ok( diff --git a/rsass/tests/spec/libsass_closed_issues/issue_738.rs b/rsass/tests/spec/libsass_closed_issues/issue_738.rs index e90611c5..2b4cf736 100644 --- a/rsass/tests/spec/libsass_closed_issues/issue_738.rs +++ b/rsass/tests/spec/libsass_closed_issues/issue_738.rs @@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn test() { assert_eq!( runner().ok(".foo {\ diff --git a/rsass/tests/spec/non_conformant/basic/t13_back_references.rs b/rsass/tests/spec/non_conformant/basic/t13_back_references.rs index 08ead2ae..26e084f5 100644 --- a/rsass/tests/spec/non_conformant/basic/t13_back_references.rs +++ b/rsass/tests/spec/non_conformant/basic/t13_back_references.rs @@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn test() { assert_eq!( runner().ok("hey, ho {\ diff --git a/rsass/tests/spec/non_conformant/nesting/not.rs b/rsass/tests/spec/non_conformant/nesting/not.rs index 3fc37160..091bc8c8 100644 --- a/rsass/tests/spec/non_conformant/nesting/not.rs +++ b/rsass/tests/spec/non_conformant/nesting/not.rs @@ -6,7 +6,6 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result fn multiple_parent_selectors_with_trailing_ident() { assert_eq!( runner().ok("// Regression test for sass/libsass#2630\ From 284d1cf1d5c04890f183636eab31ce825ba20080 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Sun, 8 Sep 2024 00:32:34 +0200 Subject: [PATCH 09/10] Refactor CompoundSelector to separate type. --- rsass/src/css/selectors/attribute.rs | 4 +- rsass/src/css/selectors/compound.rs | 360 ++++++++++++++++ rsass/src/css/selectors/elemtype.rs | 124 ++++++ rsass/src/css/selectors/logical.rs | 576 +++++-------------------- rsass/src/css/selectors/mod.rs | 12 + rsass/src/css/selectors/pseudo.rs | 4 +- rsass/src/css/selectors/selectorset.rs | 4 +- 7 files changed, 612 insertions(+), 472 deletions(-) create mode 100644 rsass/src/css/selectors/compound.rs create mode 100644 rsass/src/css/selectors/elemtype.rs diff --git a/rsass/src/css/selectors/attribute.rs b/rsass/src/css/selectors/attribute.rs index 521aeee2..dde2e977 100644 --- a/rsass/src/css/selectors/attribute.rs +++ b/rsass/src/css/selectors/attribute.rs @@ -34,8 +34,8 @@ impl Attribute { } } -pub(crate) mod parser { - use super::super::logical::parser::name_opt_ns; +pub(super) mod parser { + use super::super::parser::name_opt_ns; use super::Attribute; use crate::parser::css::strings::css_string_any; use crate::parser::util::term_opt_space; diff --git a/rsass/src/css/selectors/compound.rs b/rsass/src/css/selectors/compound.rs new file mode 100644 index 00000000..6e73db69 --- /dev/null +++ b/rsass/src/css/selectors/compound.rs @@ -0,0 +1,360 @@ +use super::{Attribute, CssSelectorSet, ElemType, Opt, Pseudo, SelectorSet}; +use crate::output::CssBuf; +use lazy_static::lazy_static; +use std::fmt; + +#[derive(Default, Clone, PartialEq, Eq)] +pub(crate) struct CompoundSelector { + pub(super) backref: Option<()>, + element: Option, + placeholders: Vec, + classes: Vec, + id: Option, + attr: Vec, + pseudo: Vec, +} + +impl CompoundSelector { + pub fn is_empty(&self) -> bool { + self.element.is_none() + && self.backref.is_none() + && self.placeholders.is_empty() + && self.classes.is_empty() + && self.id.is_none() + && self.attr.is_empty() + && self.pseudo.is_empty() + } + + pub(super) fn has_backref(&self) -> bool { + self.backref.is_some() || self.pseudo.iter().any(Pseudo::has_backref) + } + pub(super) fn has_id(&self) -> bool { + self.id.is_some() + } + pub(super) fn is_rootish(&self) -> bool { + self.pseudo.iter().any(Pseudo::is_rootish) + } + + /// Return true if these compound selectors can't meaningfully + /// appear in the same selector. + pub(super) fn must_not_inherit(&self, other: &Self) -> bool { + if self.id.is_some() && self.id == other.id { + return true; + } + if let Some(pseudo) = self.pseudo_element() { + if other.pseudo_element() == Some(pseudo) { + return true; + } + } + false + } + + pub fn no_placeholder(&self) -> Opt { + if !self.placeholders.is_empty() { + return Opt::None; + } + let pseudo = match Opt::collect_neg( + self.pseudo.iter().map(Pseudo::no_placeholder), + ) { + Opt::Some(p) => p, + Opt::Any => vec![], + Opt::None => return Opt::None, + }; + Opt::Some(Self { + pseudo, + ..self.clone() + }) + } + + pub(super) fn dedup(&mut self, original: &Self) { + if original.element == self.element + && !self.element.as_ref().map_or(true, ElemType::is_any) + { + self.element = None; + } + self.placeholders + .retain(|p| !original.placeholders.iter().any(|o| o == p)); + if original.id == self.id { + self.id = None; + } + self.attr.retain(|a| !original.attr.iter().any(|o| a == o)); + self.classes + .retain(|c| !original.classes.iter().any(|o| c == o)); + self.pseudo + .retain(|p| !original.pseudo.iter().any(|o| p == o)); + } + + pub(super) fn resolve_ref_in_pseudo(&mut self, ctx: &CssSelectorSet) { + self.pseudo = + self.pseudo.drain(..).map(|p| p.resolve_ref(ctx)).collect(); + } + + pub(super) fn replace_in_pseudo( + &mut self, + original: &SelectorSet, + replacement: &SelectorSet, + ) { + self.pseudo = self + .pseudo + .drain(..) + .map(|p| p.replace(original, replacement)) + .collect(); + } + + pub fn is_superselector(&self, sub: &Self) -> bool { + fn elem_or_default(e: &Option) -> &ElemType { + lazy_static! { + static ref DEF: ElemType = ElemType::default(); + } + e.as_ref().unwrap_or(&DEF) + } + elem_or_default(&self.element) + .is_superselector(elem_or_default(&sub.element)) + && all_any(&self.placeholders, &sub.placeholders, PartialEq::eq) + && all_any(&self.classes, &sub.classes, PartialEq::eq) + && self.id.iter().all(|id| sub.id.as_ref() == Some(id)) + && all_any(&self.attr, &sub.attr, Attribute::is_superselector) + && all_any(&self.pseudo, &sub.pseudo, Pseudo::is_superselector) + && self.pseudo_element().as_ref().map_or_else( + || sub.pseudo_element().is_none(), + |aa| { + sub.pseudo_element() + .as_ref() + .map_or(false, |ba| aa.is_superselector(ba)) + }, + ) + } + + pub fn pseudo_element(&self) -> Option<&Pseudo> { + self.pseudo.iter().find(|p| p.is_element()) + } + + pub(crate) fn simple_selectors(&self) -> Vec { + let mut result = Vec::new(); + if let Some(element) = &self.element { + result.push(element.to_string()); + } + result.extend(self.placeholders.iter().map(|p| format!("%{p}"))); + result.extend(self.classes.iter().map(|c| format!(".{c}"))); + result.extend(self.id.iter().map(|id| format!("#{id}"))); + result.extend(self.attr.iter().map(|a| { + let mut s = CssBuf::new(Default::default()); + a.write_to(&mut s); + String::from_utf8_lossy(&s.take()).to_string() + })); + result.extend(self.pseudo.iter().map(|p| { + let mut s = CssBuf::new(Default::default()); + p.write_to(&mut s); + String::from_utf8_lossy(&s.take()).to_string() + })); + result + } + + pub fn write_to(&self, buf: &mut CssBuf) { + if self.backref.is_some() { + buf.add_str("&"); + } + if let Some(e) = &self.element { + if !e.is_any() + || (self.classes.is_empty() + && self.placeholders.is_empty() + && self.id.is_none() + && self.pseudo.is_empty()) + { + e.write_to(buf); + } + } + for p in &self.placeholders { + buf.add_str("%"); + buf.add_str(p); + } + if let Some(id) = &self.id { + buf.add_str("#"); + buf.add_str(id); + } + for c in &self.classes { + buf.add_str("."); + buf.add_str(c); + } + for attr in &self.attr { + attr.write_to(buf); + } + for pseudo in &self.pseudo { + pseudo.write_to(buf); + } + } + pub fn unify(mut self, mut other: Self) -> Option { + let pseudo_element = + match (self.pseudo_element(), other.pseudo_element()) { + (None, None) => None, + (Some(pe), None) | (None, Some(pe)) => Some(pe), + (Some(a), Some(b)) => { + if b.is_superselector(a) { + Some(a) + } else if a.is_superselector(b) { + Some(b) + } else { + return None; + } + } + } + .cloned(); + self.pseudo.retain(|p| !p.is_element()); + other.pseudo.retain(|p| !p.is_element()); + + self.element = match (self.element, other.element) { + (None, None) => None, + (None, Some(e)) | (Some(e), None) => Some(e), + (Some(a), Some(b)) => Some(a.unify(&b)?), + }; + for c in other.placeholders { + if !self.placeholders.iter().any(|sc| sc == &c) { + self.placeholders.push(c); + } + } + for c in other.classes { + if !self.classes.iter().any(|sc| sc == &c) { + self.classes.push(c); + } + } + self.id = match (self.id, other.id) { + (None, None) => None, + (None, Some(id)) | (Some(id), None) => Some(id), + (Some(s_id), Some(o_id)) => { + if s_id == o_id { + Some(s_id) + } else { + return None; + } + } + }; + + combine_vital( + &mut self.attr, + &mut other.attr, + Attribute::is_superselector, + ); + combine_vital( + &mut self.pseudo, + &mut other.pseudo, + Pseudo::is_superselector, + ); + if let Some(pseudo_element) = pseudo_element { + self.pseudo.push(pseudo_element); + } + if self.pseudo.iter().any(Pseudo::is_host) + && (self.pseudo.iter().any(Pseudo::is_hover) + || self.element.is_some() + || !self.classes.is_empty()) + { + return None; + } + Some(self) + } +} + +impl fmt::Debug for CompoundSelector { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut s = f.debug_struct("CompoundSelector"); + if self.backref.is_some() { + s.field("backref", &"&"); + } + if let Some(elem) = &self.element { + s.field("element", &elem); + } + if !self.placeholders.is_empty() { + s.field("placeholders", &self.placeholders); + } + if let Some(id) = &self.id { + s.field("id", &id); + } + if !self.classes.is_empty() { + s.field("classes", &self.classes); + } + if !self.attr.is_empty() { + s.field("attr", &self.attr); + } + if !self.pseudo.is_empty() { + s.field("pseudo", &self.pseudo); + } + s.finish() + } +} + +/// Return true if all elements of `one` has an element in `other` for +/// whitch `cond` is true. +fn all_any(one: &[T], other: &[T], cond: F) -> bool +where + F: Fn(&T, &T) -> bool, +{ + one.iter().all(|a| other.iter().any(|b| cond(a, b))) +} + +// Combine all alements from `v` that is not made redundant by `other` +// with those from `other` that is not redunant with `v`, into `v` +// (leaving `other` empty). +fn combine_vital(v: &mut Vec, other: &mut Vec, q: Q) +where + Q: Fn(&T, &T) -> bool, +{ + v.retain(|a| !other.iter().any(|b| q(b, a))); + other.retain(|a| !v.iter().any(|b| q(b, a))); + v.append(other); +} + +pub(crate) mod parser { + use super::super::parser::{attribute, elem_name, keyframe_stop, pseudo}; + use super::CompoundSelector; + use crate::parser::css::strings::css_string_nohash as css_string; + use crate::parser::{PResult, Span}; + use nom::bytes::complete::tag; + use nom::combinator::{opt, value, verify}; + use nom::sequence::preceded; + + pub(crate) fn compound_selector( + input: Span, + ) -> PResult { + let mut result = CompoundSelector::default(); + if let PResult::Ok((rest, stop)) = keyframe_stop(input) { + result.element = Some(stop); + return Ok((rest, result)); + } + let (rest, backref) = opt(value((), tag("&")))(input)?; + result.backref = backref; + let (mut rest, elem) = opt(elem_name)(rest)?; + result.element = elem; + + loop { + rest = match rest.first() { + Some(b'#') => { + let (r, id) = preceded(tag("#"), css_string)(rest)?; + result.id = Some(id); + r + } + Some(b'%') => { + let (r, p) = preceded(tag("%"), css_string)(rest)?; + result.placeholders.push(p); + r + } + Some(b'.') => { + let (r, c) = preceded(tag("."), css_string)(rest)?; + result.classes.push(c); + r + } + Some(b':') => { + let (r, p) = pseudo(rest)?; + result.pseudo.push(p); + r + } + Some(b'[') => { + let (r, c) = attribute(rest)?; + result.attr.push(c); + r + } + _ => break, + }; + } + verify(tag(""), |_| !result.is_empty())(rest)?; + Ok((rest, result)) + } +} diff --git a/rsass/src/css/selectors/elemtype.rs b/rsass/src/css/selectors/elemtype.rs new file mode 100644 index 00000000..4a307f3c --- /dev/null +++ b/rsass/src/css/selectors/elemtype.rs @@ -0,0 +1,124 @@ +use crate::output::CssBuf; +use std::fmt; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(super) struct ElemType { + s: String, +} + +impl Default for ElemType { + fn default() -> Self { + Self { s: "*".into() } + } +} + +impl ElemType { + pub fn is_any(&self) -> bool { + self.s == "*" + } + + pub fn is_superselector(&self, sub: &Self) -> bool { + let (e_ns, e_name) = self.split_ns(); + let (sub_ns, sub_name) = sub.split_ns(); + match_name(e_ns.unwrap_or("*"), sub_ns.unwrap_or("*")) + && match_name(e_name, sub_name) + } + + pub fn unify(&self, other: &Self) -> Option { + let (e_ns, e_name) = self.split_ns(); + let (o_ns, o_name) = other.split_ns(); + let ns = match (e_ns, o_ns) { + (None, None) => None, + (Some("*"), ns) | (ns, Some("*")) => ns, + (Some(e), Some(o)) if e == o => Some(e), + _ => return None, + }; + let name = match (e_name, o_name) { + ("*", name) | (name, "*") => name, + (e, o) if e == o => e, + _ => return None, + }; + Some(Self { + s: if let Some(ns) = ns { + format!("{ns}|{name}") + } else { + name.to_string() + }, + }) + } + + fn split_ns(&self) -> (Option<&str>, &str) { + let mut e = self.s.splitn(2, '|'); + match (e.next(), e.next()) { + (Some(ns), Some(elem)) => (Some(ns), elem), + (Some(elem), None) => (None, elem), + _ => unreachable!(), + } + } + + pub fn write_to(&self, buf: &mut CssBuf) { + buf.add_str(&self.s) + } +} +impl fmt::Display for ElemType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.s.fmt(f) + } +} + +fn match_name(a: &str, b: &str) -> bool { + a == "*" || a == b +} + +pub(super) mod parser { + use super::ElemType; + use crate::parser::css::strings::css_string_nohash as css_string; + use crate::parser::{PResult, Span}; + use nom::branch::alt; + use nom::bytes::complete::{is_a, tag}; + use nom::combinator::{map, opt, recognize, value}; + use nom::sequence::{pair, preceded, tuple}; + + pub(crate) fn elem_name(input: Span) -> PResult { + map(name_opt_ns, |s| ElemType { s })(input) + } + + /// Recognize a keyframe stop as an element selector. + /// + /// This is the way keyframes are currently supported, may change + /// in the future. + pub(crate) fn keyframe_stop(input: Span) -> PResult { + map( + recognize(tuple(( + is_a("0123456789."), + opt(tuple((is_a("eE"), opt(tag("-")), is_a("0123456789")))), + tag("%"), + ))), + |stop: Span| ElemType { + s: String::from_utf8_lossy(stop.fragment()).to_string(), + }, + )(input) + } + + pub(crate) fn name_opt_ns(input: Span) -> PResult { + fn name_part(input: Span) -> PResult { + alt((value(String::from("*"), tag("*")), css_string))(input) + } + alt(( + map(preceded(tag("|"), name_part), |mut s| { + s.insert(0, '|'); + s + }), + map( + pair(name_part, opt(preceded(tag("|"), name_part))), + |(a, b)| { + if let Some(b) = b { + format!("{a}|{b}") + } else { + a + } + }, + ), + ))(input) + } +} diff --git a/rsass/src/css/selectors/logical.rs b/rsass/src/css/selectors/logical.rs index 4ed9655d..fbbaab3b 100644 --- a/rsass/src/css/selectors/logical.rs +++ b/rsass/src/css/selectors/logical.rs @@ -4,10 +4,10 @@ //! In the future, I might use this as the primary (only) css selector //! implementation. But as that is a major breaking change, I keep //! these types internal for now. -use super::attribute::Attribute; +use super::compound::CompoundSelector; use super::error::BadSelector0; -use super::pseudo::Pseudo; -use super::{BadSelector, CssSelectorSet, Opt, SelectorSet}; +use super::parser::compound_selector; +use super::{BadSelector, CssSelectorSet, Opt, Pseudo, SelectorSet}; use crate::css::Value; use crate::output::CssBuf; use crate::parser::input_span; @@ -15,31 +15,28 @@ use crate::sass::CallError; use crate::value::ListSeparator; use crate::{Invalid, ParseError}; use core::fmt; -use lazy_static::lazy_static; use std::iter::once; type RelBox = Box<(RelKind, Selector)>; /// A selector more aimed at making it easy to implement selector functions. /// -/// A logical selector is fully resolved (cannot contain an `&` backref). +/// A logical selector is a sequence of compound selectors, joined by +/// relational operators (where the "ancestor" relation is just +/// whitespace in the text representation). #[derive(Default, Clone, PartialEq, Eq)] pub struct Selector { - backref: Option<()>, - element: Option, - placeholders: Vec, - classes: Vec, - id: Option, - attr: Vec, - pseudo: Vec, rel_of: Option, + compound: CompoundSelector, } impl Selector { pub(crate) fn no_placeholder(&self) -> Opt { - if !self.placeholders.is_empty() { - return Opt::None; - } + let compound = match self.compound.no_placeholder() { + Opt::Some(compound) => compound, + Opt::Any => CompoundSelector::default(), + Opt::None => return Opt::None, + }; if self.is_local_empty() && self.rel_of.is_some() { eprintln!("Deprecated dobule empty relation"); return Opt::None; @@ -54,18 +51,7 @@ impl Selector { } else { None }; - let pseudo = match Opt::collect_neg( - self.pseudo.iter().map(Pseudo::no_placeholder), - ) { - Opt::Some(p) => p, - Opt::Any => vec![], - Opt::None => return Opt::None, - }; - Opt::Some(Self { - rel_of, - pseudo, - ..self.clone() - }) + Opt::Some(Self { rel_of, compound }) } pub(crate) fn no_leading_combinator(&self) -> Opt { if self.has_leading_combinator() { @@ -94,7 +80,7 @@ impl Selector { result.rel_of = Some(Box::new((rel.0, self.append(&rel.1)?))); Ok(result) } else { - let rel = self.rel_of.clone(); + let rel_of = self.rel_of.clone(); if result.is_local_empty() { return Err(AppendError::Sub); } @@ -109,10 +95,12 @@ impl Selector { } let s = s + &other; let span = input_span(s); - let mut result = - ParseError::check(parser::compound_selector(span.borrow()))?; - result.rel_of = rel; - Ok(result) + Ok(Self { + rel_of, + compound: ParseError::check(compound_selector( + span.borrow(), + ))?, + }) } } @@ -128,29 +116,7 @@ impl Selector { .flat_map(|mut s| { if original.is_superselector(&s) { let base = s.clone(); - if original.element == s.element - && !s - .element - .as_ref() - .map_or(true, |e| e.s == "*") - { - s.element = None; - } - s.placeholders.retain(|p| { - !original.placeholders.iter().any(|o| o == p) - }); - if original.id == s.id { - s.id = None; - } - s.attr.retain(|a| { - !original.attr.iter().any(|o| a == o) - }); - s.classes.retain(|c| { - !original.classes.iter().any(|o| c == o) - }); - s.pseudo.retain(|p| { - !original.pseudo.iter().any(|o| p == o) - }); + s.compound.dedup(&original.compound); let mut result = extender .s .iter() @@ -204,31 +170,32 @@ impl Selector { self = self.resolve_ref_in_pseudo(ctx); let rel_of = self.rel_of.take(); - let result = if self.backref.is_some() { - self.backref = None; + let result = if self.compound.has_backref() { + self.compound.backref = None; ctx.s .s .iter() .flat_map(|s| { let mut buf = CssBuf::new(Default::default()); - s.write_last_compound_to(&mut buf); - self.write_last_compound_to(&mut buf); + s.compound.write_to(&mut buf); + self.compound.write_to(&mut buf); let buf = buf.take(); - let mut result = if buf.is_empty() { - Selector::default() + let compound = if buf.is_empty() { + CompoundSelector::default() } else { - let span = input_span( - String::from_utf8_lossy(&buf).to_string(), - ); - ParseError::check(parser::compound_selector( - span.borrow(), - )) - .unwrap() + let span = input_span(buf); + ParseError::check(compound_selector(span.borrow())) + .unwrap() + }; + let result = Selector { + rel_of: self.rel_of.clone(), + compound, }; - result.rel_of = self.rel_of.clone(); - let mut s2 = Selector::default(); - s2.rel_of = s.rel_of.clone(); - s2.unify(result) + Selector { + rel_of: s.rel_of.clone(), + compound: CompoundSelector::default(), + } + .unify(result) }) .collect() } else { @@ -272,13 +239,10 @@ impl Selector { rel.1 = rel.1.resolve_ref_in_pseudo(ctx); rel }); - self.pseudo = self - .pseudo - .into_iter() - .map(|p| p.resolve_ref(ctx)) - .collect(); + self.compound.resolve_ref_in_pseudo(ctx); self } + /// Return true iff this selector is a superselector of `sub`. pub(super) fn is_superselector(&self, sub: &Self) -> bool { self.is_local_superselector(sub) @@ -339,27 +303,7 @@ impl Selector { } fn is_local_superselector(&self, sub: &Self) -> bool { - fn elem_or_default(e: &Option) -> &ElemType { - lazy_static! { - static ref DEF: ElemType = ElemType::default(); - } - e.as_ref().unwrap_or(&DEF) - } - elem_or_default(&self.element) - .is_superselector(elem_or_default(&sub.element)) - && all_any(&self.placeholders, &sub.placeholders, PartialEq::eq) - && all_any(&self.classes, &sub.classes, PartialEq::eq) - && self.id.iter().all(|id| sub.id.as_ref() == Some(id)) - && all_any(&self.attr, &sub.attr, Attribute::is_superselector) - && all_any(&self.pseudo, &sub.pseudo, Pseudo::is_superselector) - && self.pseudo_element().as_ref().map_or_else( - || sub.pseudo_element().is_none(), - |aa| { - sub.pseudo_element() - .as_ref() - .map_or(false, |ba| aa.is_superselector(ba)) - }, - ) + self.compound.is_superselector(&sub.compound) } pub(super) fn replace( @@ -367,11 +311,7 @@ impl Selector { original: &SelectorSet, replacement: &SelectorSet, ) -> Vec { - self.pseudo = self - .pseudo - .into_iter() - .map(|p| p.replace(original, replacement)) - .collect(); + self.compound.replace_in_pseudo(original, replacement); let mut result = vec![self]; for original in &original.s { @@ -379,12 +319,7 @@ impl Selector { .into_iter() .flat_map(|mut s| { if original.is_superselector(&s) { - if original.element == s.element { - s.element = None; - } - s.classes.retain(|c| { - !original.classes.iter().any(|o| c == o) - }); + s.compound.dedup(&original.compound); replacement .s .iter() @@ -407,8 +342,8 @@ impl Selector { self.clone()._unify(other.clone()).unwrap_or_default() } - fn _unify(mut self, mut other: Self) -> Option> { - let rel_of = match (self.rel_of.take(), other.rel_of.take()) { + fn _unify(self, other: Self) -> Option> { + let rel_of = match (self.rel_of, other.rel_of) { (None, None) => vec![], (None, Some(rel)) | (Some(rel), None) => vec![rel], (Some(a), Some(b)) => { @@ -419,88 +354,24 @@ impl Selector { v } }; - let pseudo_element = - match (self.pseudo_element(), other.pseudo_element()) { - (None, None) => None, - (Some(pe), None) | (None, Some(pe)) => Some(pe), - (Some(a), Some(b)) => { - if b.is_superselector(a) { - Some(a) - } else if a.is_superselector(b) { - Some(b) - } else { - return None; - } - } - } - .cloned(); - self.pseudo.retain(|p| !p.is_element()); - other.pseudo.retain(|p| !p.is_element()); - - self.element = match (self.element, other.element) { - (None, None) => None, - (None, Some(e)) | (Some(e), None) => Some(e), - (Some(a), Some(b)) => Some(a.unify(&b)?), - }; - for c in other.placeholders { - if !self.placeholders.iter().any(|sc| sc == &c) { - self.placeholders.push(c); - } - } - for c in other.classes { - if !self.classes.iter().any(|sc| sc == &c) { - self.classes.push(c); - } - } - self.id = match (self.id, other.id) { - (None, None) => None, - (None, Some(id)) | (Some(id), None) => Some(id), - (Some(s_id), Some(o_id)) => { - if s_id == o_id { - Some(s_id) - } else { - return None; - } - } - }; - - combine_vital( - &mut self.attr, - &mut other.attr, - Attribute::is_superselector, - ); - combine_vital( - &mut self.pseudo, - &mut other.pseudo, - Pseudo::is_superselector, - ); - if let Some(pseudo_element) = pseudo_element { - self.pseudo.push(pseudo_element); - } - if self.pseudo.iter().any(Pseudo::is_host) - && (self.pseudo.iter().any(Pseudo::is_hover) - || self.element.is_some() - || !self.classes.is_empty() - || !rel_of.is_empty()) - { - return None; - } + let compound = self.compound.unify(other.compound)?; Some(if rel_of.is_empty() { - vec![self] - } else if self.is_local_empty() { + vec![compound.into()] + } else if compound.is_empty() { vec![] } else if rel_of.len() == 1 { let mut rel_of = rel_of; - self.rel_of = Some(rel_of.pop().unwrap()); - vec![self] + vec![Selector { + rel_of: rel_of.pop(), + compound, + }] } else { rel_of .into_iter() - .map(|r| { - let mut t = self.clone(); - t.rel_of = Some(r); - t + .map(|r| Selector { + rel_of: Some(r), + compound: compound.clone(), }) .collect() }) @@ -512,18 +383,11 @@ impl Selector { } fn is_local_empty(&self) -> bool { - self.element.is_none() - && self.backref.is_none() - && self.placeholders.is_empty() - && self.classes.is_empty() - && self.id.is_none() - && self.attr.is_empty() - && self.pseudo.is_empty() + self.compound.is_empty() } pub(super) fn has_backref(&self) -> bool { - self.backref.is_some() - || self.pseudo.iter().any(Pseudo::has_backref) + self.compound.has_backref() || self .rel_of .as_deref() @@ -548,7 +412,7 @@ impl Selector { rel_of: Some(Box::new((rel, other))), ..Self::default() }) - } else if self.pseudo.iter().any(Pseudo::is_rootish) { + } else if self.compound.is_rootish() { vec![] } else { self.rel_of = Some(Box::new((rel, other))); @@ -562,24 +426,7 @@ impl Selector { "Combinators not allowed in simple-selectors.".into(), )); } - let mut result = Vec::new(); - if let Some(element) = &self.element { - result.push(element.s.clone()); - } - result.extend(self.placeholders.iter().map(|p| format!("%{p}"))); - result.extend(self.classes.iter().map(|c| format!(".{c}"))); - result.extend(self.id.iter().map(|id| format!("#{id}"))); - result.extend(self.attr.iter().map(|a| { - let mut s = CssBuf::new(Default::default()); - a.write_to(&mut s); - String::from_utf8_lossy(&s.take()).to_string() - })); - result.extend(self.pseudo.iter().map(|p| { - let mut s = CssBuf::new(Default::default()); - p.write_to(&mut s); - String::from_utf8_lossy(&s.take()).to_string() - })); - Ok(result) + Ok(self.compound.simple_selectors()) } /// Write this `Selector` to a formatted buffer. @@ -596,7 +443,7 @@ impl Selector { buf.add_str(" "); } } - self.write_last_compound_to(buf) + self.compound.write_to(buf) } pub(super) fn into_string_vec(mut self) -> Vec { @@ -619,71 +466,32 @@ impl Selector { fn last_compound_str(self) -> String { let mut buf = CssBuf::new(Default::default()); - self.write_last_compound_to(&mut buf); + self.compound.write_to(&mut buf); String::from_utf8_lossy(&buf.take()).to_string() } - fn write_last_compound_to(&self, buf: &mut CssBuf) { - if self.backref.is_some() { - buf.add_str("&"); - } - if let Some(e) = &self.element { - if !e.is_any() - || (self.classes.is_empty() - && self.placeholders.is_empty() - && self.id.is_none() - && self.pseudo.is_empty()) - { - buf.add_str(&e.s); - } - } - for p in &self.placeholders { - buf.add_str("%"); - buf.add_str(p); - } - if let Some(id) = &self.id { - buf.add_str("#"); - buf.add_str(id); - } - for c in &self.classes { - buf.add_str("."); - buf.add_str(c); - } - for attr in &self.attr { - attr.write_to(buf); - } - for pseudo in &self.pseudo { - pseudo.write_to(buf); - } - } fn pseudo_element(&self) -> Option<&Pseudo> { - self.pseudo.iter().find(|p| p.is_element()) + self.compound.pseudo_element() } /// Internal (the api is [`TryFrom`]). pub(super) fn _try_from_value(v: &Value) -> Result { match v { - Value::List(list, None | Some(ListSeparator::Space), _) => { - list.iter() - .try_fold(None, |a, v| { - let mut s = match v { - // TODO: This is probably broken when - // a vailue is like ["a", ">", "b"] - Value::Literal(s) => { - ParseError::check(parser::selector( - input_span(s.value()).borrow(), - ))? - } - _ => return Err(BadSelector0::Value), - }; - // TODO: Handle operator at end of a! - if let Some(a) = a { - s.add_root_ancestor(a); - } - Ok(Some(s)) - }) - .map(Option::unwrap_or_default) - } + Value::List(list, None | Some(ListSeparator::Space), _) => list + .iter() + .try_fold(None, |a, v| { + let mut s = match v { + Value::Literal(s) => ParseError::check( + parser::selector(input_span(s.value()).borrow()), + )?, + _ => return Err(BadSelector0::Value), + }; + if let Some(a) = a { + s.add_root_ancestor(a); + } + Ok(Some(s)) + }) + .map(Option::unwrap_or_default), Value::Literal(s) => { if s.value().is_empty() { Ok(Self::default()) @@ -697,6 +505,15 @@ impl Selector { } } +impl From for Selector { + fn from(compound: CompoundSelector) -> Self { + Self { + rel_of: None, + compound, + } + } +} + pub(super) enum AppendError { Parent, Sub, @@ -752,10 +569,7 @@ fn unify_relbox(a: RelBox, b: RelBox) -> Option> { ) -> Vec { s.into_iter().map(|s| Box::new((kind, s))).collect() } - if a.0 == b.0 - && a.1.pseudo.iter().any(Pseudo::is_rootish) - && b.1.pseudo.iter().any(Pseudo::is_rootish) - { + if a.0 == b.0 && a.1.compound.is_rootish() && b.1.compound.is_rootish() { return Some(as_rel_vec(a.0, b.1.unify(a.1))); } @@ -766,7 +580,7 @@ fn unify_relbox(a: RelBox, b: RelBox) -> Option> { if b.is_local_superselector(&a) { as_rel_vec(Ancestor, a._unify(b)?) } else if a.is_local_superselector(&b) - || have_same(&a.id, &b.id) + || have_same(&a.compound.id, &b.compound.id) || have_same(&a.pseudo_element(), &b.pseudo_element()) { as_rel_vec(Ancestor, b._unify(a)?) @@ -785,7 +599,7 @@ fn unify_relbox(a: RelBox, b: RelBox) -> Option> { as_rel_vec(Sibling, once(b)) } else if b.is_superselector(&a) { as_rel_vec(Sibling, once(a)) - } else if !have_same(&a.id, &b.id) { + } else if !have_same(&a.compound.id, &b.compound.id) { as_rel_vec( Sibling, b.clone() @@ -802,7 +616,7 @@ fn unify_relbox(a: RelBox, b: RelBox) -> Option> { | ((Sibling, b_s), (a_k @ AdjacentSibling, a_s)) => { if b_s.is_superselector(&a_s) { as_rel_vec(a_k, once(a_s)) - } else if a_s.id.is_some() || b_s.id.is_some() { + } else if a_s.compound.id.is_some() || b_s.compound.id.is_some() { as_rel_vec(a_k, a_s.with_rel_of(Sibling, b_s)) } else { as_rel_vec( @@ -832,27 +646,6 @@ fn have_same(one: &Option, other: &Option) -> bool { one.is_some() && one == other } -/// Return true if all elements of `one` has an element in `other` for -/// whitch `cond` is true. -fn all_any(one: &[T], other: &[T], cond: F) -> bool -where - F: Fn(&T, &T) -> bool, -{ - one.iter().all(|a| other.iter().any(|b| cond(a, b))) -} - -// Combine all alements from `v` that is not made redundant by `other` -// with those from `other` that is not redunant with `v`, into `v` -// (leaving `other` empty). -fn combine_vital(v: &mut Vec, other: &mut Vec, q: Q) -where - Q: Fn(&T, &T) -> bool, -{ - v.retain(|a| !other.iter().any(|b| q(b, a))); - other.retain(|a| !v.iter().any(|b| q(b, a))); - v.append(other); -} - impl TryFrom for Selector { type Error = BadSelector; @@ -881,91 +674,19 @@ impl fmt::Debug for Selector { if let Some((kind, rel)) = self.rel_of.as_deref() { s.field(&format!("{kind:?}"), &rel); } - if self.backref.is_some() { - s.field("backref", &"&"); - } - if let Some(elem) = &self.element { - s.field("element", &elem.s); - } - if !self.placeholders.is_empty() { - s.field("placeholders", &self.placeholders); - } - if let Some(id) = &self.id { - s.field("id", &id); - } - if !self.classes.is_empty() { - s.field("classes", &self.classes); - } - if !self.attr.is_empty() { - s.field("attr", &self.attr); - } - if !self.pseudo.is_empty() { - s.field("pseudo", &self.pseudo); - } + s.field("compound", &NoAlt { t: &self.compound }); s.finish() } } - -#[derive(Clone, Debug, PartialEq, Eq)] -struct ElemType { - s: String, +struct NoAlt { + t: T, } - -impl Default for ElemType { - fn default() -> Self { - Self { s: "*".into() } - } -} - -impl ElemType { - fn is_any(&self) -> bool { - self.s == "*" - } - - fn is_superselector(&self, sub: &Self) -> bool { - let (e_ns, e_name) = self.split_ns(); - let (sub_ns, sub_name) = sub.split_ns(); - match_name(e_ns.unwrap_or("*"), sub_ns.unwrap_or("*")) - && match_name(e_name, sub_name) - } - - fn unify(&self, other: &Self) -> Option { - let (e_ns, e_name) = self.split_ns(); - let (o_ns, o_name) = other.split_ns(); - let ns = match (e_ns, o_ns) { - (None, None) => None, - (Some("*"), ns) | (ns, Some("*")) => ns, - (Some(e), Some(o)) if e == o => Some(e), - _ => return None, - }; - let name = match (e_name, o_name) { - ("*", name) | (name, "*") => name, - (e, o) if e == o => e, - _ => return None, - }; - Some(Self { - s: if let Some(ns) = ns { - format!("{ns}|{name}") - } else { - name.to_string() - }, - }) - } - - fn split_ns(&self) -> (Option<&str>, &str) { - let mut e = self.s.splitn(2, '|'); - match (e.next(), e.next()) { - (Some(ns), Some(elem)) => (Some(ns), elem), - (Some(elem), None) => (None, elem), - _ => unreachable!(), - } +impl fmt::Debug for NoAlt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.t) } } -fn match_name(a: &str, b: &str) -> bool { - a == "*" || a == b -} - #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum RelKind { Ancestor, @@ -986,28 +707,28 @@ impl RelKind { } pub(crate) mod parser { - use super::super::attribute::parser::attribute; - use super::super::pseudo::parser::pseudo; - use super::{ElemType, RelKind, Selector}; - use crate::parser::css::strings::css_string_nohash as css_string; + use super::super::parser::compound_selector; + use super::{RelKind, Selector}; use crate::parser::util::{opt_spacelike, spacelike}; use crate::parser::{PResult, Span}; use nom::branch::alt; - use nom::bytes::complete::{is_a, tag}; - use nom::combinator::{map, opt, recognize, value, verify}; + use nom::bytes::complete::tag; + use nom::combinator::{into, opt, value, verify}; use nom::multi::fold_many0; - use nom::sequence::{delimited, pair, preceded, terminated, tuple}; + use nom::sequence::{delimited, pair, preceded, terminated}; pub(crate) fn selector(input: Span) -> PResult { let (input, prerel) = preceded(opt_spacelike, opt(explicit_rel_kind))(input)?; let (input, first) = if let Some(prerel) = prerel { let (input, first) = opt(compound_selector)(input)?; - let mut first = first.unwrap_or_default(); - first.rel_of = Some(Box::new((prerel, Selector::default()))); + let first = Selector { + rel_of: Some(Box::new((prerel, Selector::default()))), + compound: first.unwrap_or_default(), + }; (input, first) } else { - compound_selector(input)? + into(compound_selector)(input)? }; terminated( fold_many0( @@ -1015,10 +736,9 @@ pub(crate) mod parser { k.symbol().is_some() || s.is_some() }), move || first.clone(), - |rel, (kind, e)| { - let mut e = e.unwrap_or_default(); - e.rel_of = Some(Box::new((kind, rel))); - e + |rel, (kind, e)| Selector { + compound: e.unwrap_or_default(), + rel_of: Some(Box::new((kind, rel))), }, ), opt_spacelike, @@ -1040,80 +760,4 @@ pub(crate) mod parser { fn rel_kind(input: Span) -> PResult { alt((explicit_rel_kind, value(RelKind::Ancestor, spacelike)))(input) } - - pub(crate) fn compound_selector(input: Span) -> PResult { - let mut result = Selector::default(); - if let PResult::Ok((rest, stop)) = recognize(tuple(( - is_a("0123456789."), - opt(tuple((is_a("eE"), opt(tag("-")), is_a("0123456789")))), - tag("%"), - )))(input) - { - // TODO: Remove this. - // It is a temporary workaround for keyframe support. - result.element = Some(ElemType { - s: String::from_utf8_lossy(stop.fragment()).to_string(), - }); - return Ok((rest, result)); - } - let (rest, backref) = opt(value((), tag("&")))(input)?; - result.backref = backref; - let (mut rest, elem) = opt(name_opt_ns)(rest)?; - result.element = elem.map(|s| ElemType { s }); - - loop { - rest = match rest.first() { - Some(b'#') => { - let (r, id) = preceded(tag("#"), css_string)(rest)?; - result.id = Some(id); - r - } - Some(b'%') => { - let (r, p) = preceded(tag("%"), css_string)(rest)?; - result.placeholders.push(p); - r - } - Some(b'.') => { - let (r, c) = preceded(tag("."), css_string)(rest)?; - result.classes.push(c); - r - } - Some(b':') => { - let (r, p) = pseudo(rest)?; - result.pseudo.push(p); - r - } - Some(b'[') => { - let (r, c) = attribute(rest)?; - result.attr.push(c); - r - } - _ => break, - }; - } - verify(tag(""), |_| !result.is_local_empty())(rest)?; - Ok((rest, result)) - } - - pub(crate) fn name_opt_ns(input: Span) -> PResult { - fn name_part(input: Span) -> PResult { - alt((value(String::from("*"), tag("*")), css_string))(input) - } - alt(( - map(preceded(tag("|"), name_part), |mut s| { - s.insert(0, '|'); - s - }), - map( - pair(name_part, opt(preceded(tag("|"), name_part))), - |(a, b)| { - if let Some(b) = b { - format!("{a}|{b}") - } else { - a - } - }, - ), - ))(input) - } } diff --git a/rsass/src/css/selectors/mod.rs b/rsass/src/css/selectors/mod.rs index be2de700..d549adb6 100644 --- a/rsass/src/css/selectors/mod.rs +++ b/rsass/src/css/selectors/mod.rs @@ -4,15 +4,20 @@ //! is a `SelectorSet` object which contains two `Selector` objects, one //! for `p.foo` and one for `.foo p`. mod attribute; +mod compound; mod context; mod cssselectorset; +mod elemtype; mod error; mod logical; mod opt; mod pseudo; mod selectorset; +use self::attribute::Attribute; +use self::elemtype::ElemType; pub(crate) use self::opt::Opt; +use self::pseudo::Pseudo; pub use context::SelectorCtx; pub(crate) use cssselectorset::CssSelectorSet; pub use error::BadSelector; @@ -20,5 +25,12 @@ pub use logical::Selector; pub use selectorset::SelectorSet; pub(crate) mod parser { + pub(super) use super::attribute::parser::attribute; + pub(super) use super::compound::parser::compound_selector; + pub(super) use super::elemtype::parser::{ + elem_name, keyframe_stop, name_opt_ns, + }; + pub(super) use super::logical::parser::selector; + pub(super) use super::pseudo::parser::pseudo; pub(crate) use super::selectorset::parser::selector_set; } diff --git a/rsass/src/css/selectors/pseudo.rs b/rsass/src/css/selectors/pseudo.rs index a93ccb5b..2ec342d4 100644 --- a/rsass/src/css/selectors/pseudo.rs +++ b/rsass/src/css/selectors/pseudo.rs @@ -198,8 +198,8 @@ impl Arg { } } -pub(crate) mod parser { - use super::super::selectorset::parser::selector_set; +pub(super) mod parser { + use super::super::parser::selector_set; use super::{Arg, Pseudo}; use crate::parser::css::strings::{ css_string_nohash, custom_value_inner, diff --git a/rsass/src/css/selectors/selectorset.rs b/rsass/src/css/selectors/selectorset.rs index 2e116609..adb8357a 100644 --- a/rsass/src/css/selectors/selectorset.rs +++ b/rsass/src/css/selectors/selectorset.rs @@ -166,7 +166,7 @@ fn value_to_selectors(v: &Value) -> Result { } } -pub(crate) mod parser { +pub(super) mod parser { use super::SelectorSet; use crate::parser::{util::opt_spacelike, PResult, Span}; use nom::bytes::complete::tag; @@ -178,7 +178,7 @@ pub(crate) mod parser { map( separated_list1( delimited(opt_spacelike, tag(","), opt_spacelike), - super::super::logical::parser::selector, + super::super::parser::selector, ), |s| SelectorSet { s }, )(input) From ce63a86aedaa3bd9f4aead7739216f8bde97829d Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Sun, 8 Sep 2024 15:16:16 +0200 Subject: [PATCH 10/10] Cleanup. Remove remains of the old selector / logical selector divide. --- CHANGELOG.md | 8 +- rsass/src/css/selectors/attribute.rs | 2 +- rsass/src/css/selectors/compound.rs | 27 +++- rsass/src/css/selectors/cssselectorset.rs | 6 +- rsass/src/css/selectors/elemtype.rs | 15 ++- rsass/src/css/selectors/error.rs | 3 +- rsass/src/css/selectors/mod.rs | 6 +- .../css/selectors/{logical.rs => selector.rs} | 122 ++++++------------ rsass/src/parser/css/values.rs | 2 +- rsass/src/sass/functions/selector.rs | 4 +- rsass/src/sass/selectors.rs | 7 +- 11 files changed, 88 insertions(+), 114 deletions(-) rename rsass/src/css/selectors/{logical.rs => selector.rs} (86%) diff --git a/CHANGELOG.md b/CHANGELOG.md index a27f305b..8087757b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,13 @@ project adheres to ## Unreleased -(Nothing yet) +### Breaking changes: + +* Replaced the css `Selector` implementation. + The new "logical" selector types that was used in selector + function in rsass 0.28 is now the only css selector implementation. + Most of the api to those types are private. + Some will probably be made public after some stabilization period. ## Release 0.28.10 diff --git a/rsass/src/css/selectors/attribute.rs b/rsass/src/css/selectors/attribute.rs index dde2e977..d745c845 100644 --- a/rsass/src/css/selectors/attribute.rs +++ b/rsass/src/css/selectors/attribute.rs @@ -1,6 +1,6 @@ use crate::{css::CssString, output::CssBuf}; -/// A logical attribute selector. +/// An attribute selector. #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct Attribute { /// The attribute name diff --git a/rsass/src/css/selectors/compound.rs b/rsass/src/css/selectors/compound.rs index 6e73db69..8b1459dc 100644 --- a/rsass/src/css/selectors/compound.rs +++ b/rsass/src/css/selectors/compound.rs @@ -1,5 +1,5 @@ use super::{Attribute, CssSelectorSet, ElemType, Opt, Pseudo, SelectorSet}; -use crate::output::CssBuf; +use crate::{output::CssBuf, parser::input_span, ParseError}; use lazy_static::lazy_static; use std::fmt; @@ -31,6 +31,23 @@ impl CompoundSelector { pub(super) fn has_id(&self) -> bool { self.id.is_some() } + pub(super) fn cant_append(&self) -> bool { + self.is_empty() + || self.element.as_ref().map_or(false, ElemType::cant_append) + } + pub(super) fn append(&self, other: &Self) -> Result { + let mut s = CssBuf::new(Default::default()); + self.write_to(&mut s); + other.write_to(&mut s); + let s = s.take(); + if s.is_empty() { + Ok(Self::default()) + } else { + let span = input_span(s); + Ok(ParseError::check(parser::compound_selector(span.borrow()))?) + } + } + pub(super) fn is_rootish(&self) -> bool { self.pseudo.iter().any(Pseudo::is_rootish) } @@ -152,7 +169,7 @@ impl CompoundSelector { pub fn write_to(&self, buf: &mut CssBuf) { if self.backref.is_some() { - buf.add_str("&"); + buf.add_char('&'); } if let Some(e) = &self.element { if !e.is_any() @@ -165,15 +182,15 @@ impl CompoundSelector { } } for p in &self.placeholders { - buf.add_str("%"); + buf.add_char('%'); buf.add_str(p); } if let Some(id) = &self.id { - buf.add_str("#"); + buf.add_char('#'); buf.add_str(id); } for c in &self.classes { - buf.add_str("."); + buf.add_char('.'); buf.add_str(c); } for attr in &self.attr { diff --git a/rsass/src/css/selectors/cssselectorset.rs b/rsass/src/css/selectors/cssselectorset.rs index 7246ceb6..a7f14a73 100644 --- a/rsass/src/css/selectors/cssselectorset.rs +++ b/rsass/src/css/selectors/cssselectorset.rs @@ -76,7 +76,7 @@ impl CssSelectorSet { self.s.is_superselector(&sub.s) } - pub(crate) fn append(self, ext: Self) -> Result { + pub(crate) fn append(self, ext: &Self) -> Result { Ok(Self { s: SelectorSet { s: self @@ -144,7 +144,7 @@ impl CssSelectorSet { .map(|s| Self { s }) } - pub(crate) fn unify(self, other: Self) -> Self { + pub(crate) fn unify(self, other: &Self) -> Self { Self { s: SelectorSet { s: self @@ -164,7 +164,7 @@ impl CssSelectorSet { } pub fn write_to(&self, buf: &mut CssBuf) { - self.s.write_to(buf) + self.s.write_to(buf); } } diff --git a/rsass/src/css/selectors/elemtype.rs b/rsass/src/css/selectors/elemtype.rs index 4a307f3c..b88f08e4 100644 --- a/rsass/src/css/selectors/elemtype.rs +++ b/rsass/src/css/selectors/elemtype.rs @@ -2,7 +2,7 @@ use crate::output::CssBuf; use std::fmt; #[derive(Clone, Debug, PartialEq, Eq)] -pub(super) struct ElemType { +pub(crate) struct ElemType { s: String, } @@ -16,6 +16,9 @@ impl ElemType { pub fn is_any(&self) -> bool { self.s == "*" } + pub(super) fn cant_append(&self) -> bool { + self.s.starts_with('*') || self.s.starts_with('|') + } pub fn is_superselector(&self, sub: &Self) -> bool { let (e_ns, e_name) = self.split_ns(); @@ -48,16 +51,14 @@ impl ElemType { } fn split_ns(&self) -> (Option<&str>, &str) { - let mut e = self.s.splitn(2, '|'); - match (e.next(), e.next()) { - (Some(ns), Some(elem)) => (Some(ns), elem), - (Some(elem), None) => (None, elem), - _ => unreachable!(), + match self.s.split_once('|') { + Some((ns, name)) => (Some(ns), name), + None => (None, &self.s), } } pub fn write_to(&self, buf: &mut CssBuf) { - buf.add_str(&self.s) + buf.add_str(&self.s); } } impl fmt::Display for ElemType { diff --git a/rsass/src/css/selectors/error.rs b/rsass/src/css/selectors/error.rs index 871ebf1e..a80055f9 100644 --- a/rsass/src/css/selectors/error.rs +++ b/rsass/src/css/selectors/error.rs @@ -21,7 +21,8 @@ impl From for BadSelector0 { } } -/// The error when a [Value] cannot be converted to a [Selectors] or [Selector]. +/// The error when a [`Value`] cannot be converted to a +/// [`SelectorSet`][super::SelectorSet] or [`Selector`][super::Selector]. #[derive(Debug)] pub enum BadSelector { /// The value was not the expected type of list or string. diff --git a/rsass/src/css/selectors/mod.rs b/rsass/src/css/selectors/mod.rs index d549adb6..3cafc377 100644 --- a/rsass/src/css/selectors/mod.rs +++ b/rsass/src/css/selectors/mod.rs @@ -9,9 +9,9 @@ mod context; mod cssselectorset; mod elemtype; mod error; -mod logical; mod opt; mod pseudo; +mod selector; mod selectorset; use self::attribute::Attribute; @@ -21,7 +21,7 @@ use self::pseudo::Pseudo; pub use context::SelectorCtx; pub(crate) use cssselectorset::CssSelectorSet; pub use error::BadSelector; -pub use logical::Selector; +pub use selector::Selector; pub use selectorset::SelectorSet; pub(crate) mod parser { @@ -30,7 +30,7 @@ pub(crate) mod parser { pub(super) use super::elemtype::parser::{ elem_name, keyframe_stop, name_opt_ns, }; - pub(super) use super::logical::parser::selector; pub(super) use super::pseudo::parser::pseudo; + pub(super) use super::selector::parser::selector; pub(crate) use super::selectorset::parser::selector_set; } diff --git a/rsass/src/css/selectors/logical.rs b/rsass/src/css/selectors/selector.rs similarity index 86% rename from rsass/src/css/selectors/logical.rs rename to rsass/src/css/selectors/selector.rs index fbbaab3b..d1ab6b6a 100644 --- a/rsass/src/css/selectors/logical.rs +++ b/rsass/src/css/selectors/selector.rs @@ -1,13 +1,6 @@ -//! A logical selector is a css selector, but representend in a way -//! that I hope make implementing the sass selector functions easier. -//! -//! In the future, I might use this as the primary (only) css selector -//! implementation. But as that is a major breaking change, I keep -//! these types internal for now. use super::compound::CompoundSelector; use super::error::BadSelector0; -use super::parser::compound_selector; -use super::{BadSelector, CssSelectorSet, Opt, Pseudo, SelectorSet}; +use super::{BadSelector, CssSelectorSet, Opt, SelectorSet}; use crate::css::Value; use crate::output::CssBuf; use crate::parser::input_span; @@ -19,9 +12,9 @@ use std::iter::once; type RelBox = Box<(RelKind, Selector)>; -/// A selector more aimed at making it easy to implement selector functions. +/// A css selector. /// -/// A logical selector is a sequence of compound selectors, joined by +/// A selector is a sequence of compound selectors, joined by /// relational operators (where the "ancestor" relation is just /// whitespace in the text representation). #[derive(Default, Clone, PartialEq, Eq)] @@ -72,34 +65,18 @@ impl Selector { pub(super) fn append(&self, other: &Self) -> Result { if self.is_local_empty() { - return Err(AppendError::Parent); - } - - let mut result = other.to_owned(); - if let Some(rel) = result.rel_of.take() { - result.rel_of = Some(Box::new((rel.0, self.append(&rel.1)?))); - Ok(result) + Err(AppendError::Parent) + } else if let Some(rel) = other.rel_of.clone() { + Ok(Self { + rel_of: Some(Box::new((rel.0, self.append(&rel.1)?))), + compound: other.compound.clone(), + }) + } else if other.compound.cant_append() { + Err(AppendError::Sub) } else { - let rel_of = self.rel_of.clone(); - if result.is_local_empty() { - return Err(AppendError::Sub); - } - let s = self.clone().last_compound_str(); - let other = result.last_compound_str(); - if other - .bytes() - .next() - .map_or(true, |c| c == b'*' || c == b'|') - { - return Err(AppendError::Sub); - } - let s = s + &other; - let span = input_span(s); Ok(Self { - rel_of, - compound: ParseError::check(compound_selector( - span.borrow(), - ))?, + rel_of: self.rel_of.clone(), + compound: self.compound.append(&other.compound)?, }) } } @@ -170,32 +147,20 @@ impl Selector { self = self.resolve_ref_in_pseudo(ctx); let rel_of = self.rel_of.take(); - let result = if self.compound.has_backref() { + let result = if self.compound.backref.is_some() { self.compound.backref = None; ctx.s .s .iter() .flat_map(|s| { - let mut buf = CssBuf::new(Default::default()); - s.compound.write_to(&mut buf); - self.compound.write_to(&mut buf); - let buf = buf.take(); - let compound = if buf.is_empty() { - CompoundSelector::default() - } else { - let span = input_span(buf); - ParseError::check(compound_selector(span.borrow())) - .unwrap() - }; - let result = Selector { - rel_of: self.rel_of.clone(), - compound, - }; Selector { rel_of: s.rel_of.clone(), compound: CompoundSelector::default(), } - .unify(result) + .unify(Selector { + rel_of: self.rel_of.clone(), + compound: s.compound.append(&self.compound).unwrap(), + }) }) .collect() } else { @@ -443,37 +408,27 @@ impl Selector { buf.add_str(" "); } } - self.compound.write_to(buf) + self.compound.write_to(buf); } - pub(super) fn into_string_vec(mut self) -> Vec { - let mut vec = - if let Some((kind, sel)) = self.rel_of.take().map(|b| *b) { - let mut vec = sel.into_string_vec(); - if let Some(symbol) = kind.symbol() { - vec.push(symbol.to_string()); - } - vec - } else { - Vec::new() - }; - let last = self.last_compound_str(); - if !last.is_empty() { - vec.push(last); + pub(super) fn into_string_vec(self) -> Vec { + let mut vec = if let Some((kind, sel)) = self.rel_of.map(|b| *b) { + let mut vec = sel.into_string_vec(); + if let Some(symbol) = kind.symbol() { + vec.push(symbol.to_string()); + } + vec + } else { + Vec::new() + }; + if !self.compound.is_empty() { + let mut buf = CssBuf::new(Default::default()); + self.compound.write_to(&mut buf); + vec.push(String::from_utf8_lossy(&buf.take()).to_string()); } vec } - fn last_compound_str(self) -> String { - let mut buf = CssBuf::new(Default::default()); - self.compound.write_to(&mut buf); - String::from_utf8_lossy(&buf.take()).to_string() - } - - fn pseudo_element(&self) -> Option<&Pseudo> { - self.compound.pseudo_element() - } - /// Internal (the api is [`TryFrom`]). pub(super) fn _try_from_value(v: &Value) -> Result { match v { @@ -580,8 +535,7 @@ fn unify_relbox(a: RelBox, b: RelBox) -> Option> { if b.is_local_superselector(&a) { as_rel_vec(Ancestor, a._unify(b)?) } else if a.is_local_superselector(&b) - || have_same(&a.compound.id, &b.compound.id) - || have_same(&a.pseudo_element(), &b.pseudo_element()) + || a.compound.must_not_inherit(&b.compound) { as_rel_vec(Ancestor, b._unify(a)?) } else { @@ -599,7 +553,7 @@ fn unify_relbox(a: RelBox, b: RelBox) -> Option> { as_rel_vec(Sibling, once(b)) } else if b.is_superselector(&a) { as_rel_vec(Sibling, once(a)) - } else if !have_same(&a.compound.id, &b.compound.id) { + } else if !a.compound.must_not_inherit(&b.compound) { as_rel_vec( Sibling, b.clone() @@ -616,7 +570,7 @@ fn unify_relbox(a: RelBox, b: RelBox) -> Option> { | ((Sibling, b_s), (a_k @ AdjacentSibling, a_s)) => { if b_s.is_superselector(&a_s) { as_rel_vec(a_k, once(a_s)) - } else if a_s.compound.id.is_some() || b_s.compound.id.is_some() { + } else if a_s.compound.has_id() || b_s.compound.has_id() { as_rel_vec(a_k, a_s.with_rel_of(Sibling, b_s)) } else { as_rel_vec( @@ -642,10 +596,6 @@ fn unify_relbox(a: RelBox, b: RelBox) -> Option> { }) } -fn have_same(one: &Option, other: &Option) -> bool { - one.is_some() && one == other -} - impl TryFrom for Selector { type Error = BadSelector; diff --git a/rsass/src/parser/css/values.rs b/rsass/src/parser/css/values.rs index 05f3b3ae..1672b52e 100644 --- a/rsass/src/parser/css/values.rs +++ b/rsass/src/parser/css/values.rs @@ -179,6 +179,6 @@ fn ext_arg_as_string(input: Span) -> PResult { fn spaced<'a>( the_tag: &'static str, -) -> impl FnMut(Span<'a>) -> PResult> { +) -> impl FnMut(Span<'a>) -> PResult<'a, Span<'a>> { delimited(opt_spacelike, tag(the_tag), opt_spacelike) } diff --git a/rsass/src/sass/functions/selector.rs b/rsass/src/sass/functions/selector.rs index dd6da0c5..98280bf2 100644 --- a/rsass/src/sass/functions/selector.rs +++ b/rsass/src/sass/functions/selector.rs @@ -14,7 +14,7 @@ pub fn create_module() -> Scope { let mut s = unnamed(s.get_va::(name!(selectors)))? .into_iter(); if let Some(base) = s.next() { - Ok(s.try_fold(base, |base, s| base.append(s))?.into()) + Ok(s.try_fold(base, |base, s| base.append(&s))?.into()) } else { Err("At least one selector must be passed.") .named(name!(selectors)) @@ -58,7 +58,7 @@ pub fn create_module() -> Scope { def!(f, unify(selector1, selector2), |s| { let a: CssSelectorSet = s.get(name!(selector1))?; let b: CssSelectorSet = s.get(name!(selector2))?; - Ok(a.unify(b).into()) + Ok(a.unify(&b).into()) }); f diff --git a/rsass/src/sass/selectors.rs b/rsass/src/sass/selectors.rs index 784a5f34..0578dfe1 100644 --- a/rsass/src/sass/selectors.rs +++ b/rsass/src/sass/selectors.rs @@ -6,8 +6,7 @@ //! //! This _may_ change to a something like a tree of operators with //! leafs of simple selectors in some future release. -use crate::css::parser::selector_set; -use crate::css::{self, SelectorSet}; +use crate::css::{self, parser::selector_set}; use crate::parser::input_span; use crate::sass::SassString; use crate::{Error, ParseError, ScopeRef}; @@ -35,12 +34,12 @@ impl Selectors { } /// Evaluate any interpolation in these Selectors. - pub fn eval(&self, scope: ScopeRef) -> Result { + pub fn eval(&self, scope: ScopeRef) -> Result { let mut s = Vec::new(); for sel in &self.s { s.extend(sel.eval(scope.clone())?); } - Ok(SelectorSet { s }) + Ok(css::SelectorSet { s }) } fn write_eval( &self,