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..5350fe7b --- /dev/null +++ b/rsass/src/css/selectors/context.rs @@ -0,0 +1,76 @@ +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 { + // 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(), + } + } +} 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..22d0d02e 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; @@ -564,12 +563,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 +641,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 +978,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 +1044,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..8ed937dc 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 { @@ -107,21 +106,6 @@ impl SelectorSet { } } -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()); - Ok(ParseError::check(parser::selector_set(span.borrow()))?) - } -} - impl From for Value { fn from(value: SelectorSet) -> Self { if value.is_root() { diff --git a/rsass/src/output/transform.rs b/rsass/src/output/transform.rs index 14ffd487..9247bd3a 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}; @@ -202,9 +202,7 @@ 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 selectors = selectors.eval_new(scope.clone())?; let ctx = scope.get_selectors().at_root(selectors); let selectors = ctx.real(); let subscope = ScopeRef::sub_selectors(scope, ctx); @@ -360,9 +358,7 @@ 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 = selectors.eval_new(scope.clone())?; 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..9d0e4a99 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_new(&self, scope: ScopeRef) -> Result { + let mut s = Vec::new(); + for sel in &self.s { + s.extend(sel.eval_new(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_new(&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/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..b51b801b 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,7 @@ fn runner() -> crate::TestRunner { } #[test] -#[ignore] // wrong result +#[ignore] // unexepected error 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(