From 76ef3db2227205f95dfec076fb5fcbf10555d440 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Sun, 7 Jan 2024 22:14:49 +0100 Subject: [PATCH] Use logical selectors in nest. This adds supports for sass backref (`&`) is `LogicalSelectorSet` and introduces a new type, `CssSelectorSet`, which is guaranteed to not contain backrefs. Functions that are intended to not accept backrefs are moved to `CssSelectorSet`. The `nest` function is also created as a `CssSelectorSet` method, as the first argument may not contain backrefs. The other argument is a `LogicalSelectorSet`, though. --- CHANGELOG.md | 6 +- rsass/src/css/mod.rs | 2 +- rsass/src/css/selectors.rs | 4 +- rsass/src/css/selectors/cssselectorset.rs | 106 ++++++ rsass/src/css/selectors/logical.rs | 316 ++++++++---------- rsass/src/css/selectors/pseudo.rs | 153 +++++++++ rsass/src/parser/error.rs | 2 +- rsass/src/sass/functions/mod.rs | 12 + rsass/src/sass/functions/resolvedargs.rs | 29 ++ rsass/src/sass/functions/selector.rs | 30 +- .../core_functions/selector/nest/error.rs | 2 - .../spec/core_functions/selector/nest/list.rs | 4 - 12 files changed, 470 insertions(+), 196 deletions(-) create mode 100644 rsass/src/css/selectors/cssselectorset.rs create mode 100644 rsass/src/css/selectors/pseudo.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ba3356691..c17bacd15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,11 @@ project adheres to ## Unreleased -* (Oops: Removed a missing dbg call.) +* Improve support for the `selector.nest` function (PR #189). +* Some internal cleanup and improvements in the next-generation css + selector implementation (which is currently internal and used only + for selector functions, but should replace the old css selector + implementation in release 0.29). ## Release 0.28.8 diff --git a/rsass/src/css/mod.rs b/rsass/src/css/mod.rs index d54879127..c1f61bb70 100644 --- a/rsass/src/css/mod.rs +++ b/rsass/src/css/mod.rs @@ -23,5 +23,5 @@ pub use self::selectors::{BadSelector, Selector, SelectorPart, Selectors}; pub use self::string::CssString; pub use self::value::{InvalidCss, Value, ValueMap, ValueToMapError}; -pub(crate) use self::selectors::{LogicalSelectorSet, SelectorCtx}; +pub(crate) use self::selectors::{CssSelectorSet, SelectorCtx}; pub(crate) use self::util::{is_calc_name, is_function_name, is_not}; diff --git a/rsass/src/css/selectors.rs b/rsass/src/css/selectors.rs index 789eb7443..a2f2bc766 100644 --- a/rsass/src/css/selectors.rs +++ b/rsass/src/css/selectors.rs @@ -14,8 +14,10 @@ use crate::value::ListSeparator; use std::fmt; use std::io::Write; +mod cssselectorset; mod logical; -pub(crate) use logical::SelectorSet as LogicalSelectorSet; +pub(crate) use cssselectorset::CssSelectorSet; +mod pseudo; /// A full set of selectors. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd)] diff --git a/rsass/src/css/selectors/cssselectorset.rs b/rsass/src/css/selectors/cssselectorset.rs new file mode 100644 index 000000000..bfbd83e97 --- /dev/null +++ b/rsass/src/css/selectors/cssselectorset.rs @@ -0,0 +1,106 @@ +use crate::{css::Value, parser::input_span, Invalid}; + +use super::{logical::SelectorSet, BadSelector}; + +/// 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. +pub struct CssSelectorSet { + pub(super) s: SelectorSet, +} + +impl CssSelectorSet { + pub fn is_superselector(&self, sub: &CssSelectorSet) -> bool { + self.s.is_superselector(&sub.s) + } + + pub(crate) fn nest(&self, other: SelectorSet) -> Self { + let mut parts = other + .s + .into_iter() + .map(|o| { + if o.has_backref() { + o.resolve_ref(self) + } else { + self.s.s.iter().map(|s| s.nest(&o)).collect() + } + }) + .map(Vec::into_iter) + .collect::>(); + + let mut result = Vec::new(); + let mut empty = false; + while !empty { + empty = true; + for i in &mut parts { + if let Some(next) = i.next() { + result.push(next); + empty = false; + } + } + } + + CssSelectorSet { + s: SelectorSet { s: result }, + } + } + + pub(crate) fn replace( + self, + original: &Self, + replacement: &Self, + ) -> Result { + self.s + .replace(&original.s, &replacement.s) + .map(|s| CssSelectorSet { s }) + } + + pub(crate) fn unify(self, other: Self) -> Self { + CssSelectorSet { + s: SelectorSet { + s: self + .s + .s + .into_iter() + .flat_map(|s| { + other + .s + .s + .iter() + .flat_map(move |o| s.clone().unify(o.clone())) + }) + .collect(), + }, + } + } +} + +impl TryFrom for CssSelectorSet { + type Error = BadSelector; + + fn try_from(value: SelectorSet) -> Result { + for s in &value.s { + if s.has_backref() { + let sel = s.clone().into_string_vec().join(" "); + return Err(BadSelector::Backref(input_span(sel))); + } + } + Ok(CssSelectorSet { s: value }) + } +} + +impl TryFrom for CssSelectorSet { + type Error = BadSelector; + + fn try_from(value: Value) -> Result { + SelectorSet::try_from(value)?.try_into() + } +} + +impl From for Value { + fn from(value: CssSelectorSet) -> Self { + value.s.into() + } +} diff --git a/rsass/src/css/selectors/logical.rs b/rsass/src/css/selectors/logical.rs index 64cd00066..54fd4df25 100644 --- a/rsass/src/css/selectors/logical.rs +++ b/rsass/src/css/selectors/logical.rs @@ -4,11 +4,12 @@ //! 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::{BadSelector, SelectorPart, Selectors}; +use super::pseudo::Pseudo; +use super::{BadSelector, CssSelectorSet, SelectorPart, Selectors}; use crate::css::{CssString, Value}; -use crate::parser::input_span; +use crate::input::{SourceFile, SourceName, SourcePos}; use crate::value::ListSeparator; -use crate::Invalid; +use crate::{Invalid, ParseError}; use lazy_static::lazy_static; use std::iter::once; use std::mem::swap; @@ -16,13 +17,13 @@ use std::mem::swap; /// 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)] +#[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct SelectorSet { - s: Vec, + pub(super) s: Vec, } impl SelectorSet { - pub fn is_superselector(&self, other: &Self) -> bool { + pub(super) fn is_superselector(&self, other: &Self) -> bool { other .s .iter() @@ -48,19 +49,28 @@ impl SelectorSet { .collect(); Ok(Self { s: result }) } - - pub fn unify(self, other: SelectorSet) -> SelectorSet { + pub(super) fn write_to_buf(&self, buf: &mut String) { + fn write_one(s: &Selector, buf: &mut String) { + 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) + } + pub(super) fn resolve_ref(self, ctx: &CssSelectorSet) -> Self { SelectorSet { s: self .s .into_iter() - .flat_map(|s| { - other - .s - .iter() - .flat_map(move |o| s.clone().unify(o.clone())) - }) - .collect(), + .flat_map(|o| o.resolve_ref(ctx)) + .collect::>(), } } } @@ -69,7 +79,7 @@ impl TryFrom for SelectorSet { type Error = BadSelector; fn try_from(value: Value) -> Result { - Self::try_from(&Selectors::try_from(value)?.css_ok()?) + Self::try_from(&Selectors::try_from(value)?) } } impl TryFrom<&Selectors> for SelectorSet { @@ -108,8 +118,9 @@ 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)] +#[derive(Debug, Default, Clone, PartialEq, Eq)] pub(crate) struct Selector { + backref: Option<()>, element: Option, placeholders: Vec, classes: Vec, @@ -120,6 +131,87 @@ pub(crate) struct Selector { } impl Selector { + pub(super) fn nest(&self, other: &Self) -> Self { + let mut result = other.clone(); + /*if result.has_backref() { + result = Self::resolve_ref(result, self); + } else*/ + 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()))); + } + result + } + + pub(super) fn resolve_ref(mut self, ctx: &CssSelectorSet) -> Vec { + self = self.resolve_ref_in_pseudo(ctx); + let rel_of = self.rel_of.take(); + + let result = if self.backref.is_some() { + self.backref = None; + ctx.s + .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), + }), + }; + slf.unify(s) + }) + .collect() + } else { + vec![self] + }; + if let Some(rel_of) = rel_of { + let rels = rel_of.1.resolve_ref(ctx); + rels.into_iter() + .flat_map(|rel| { + result + .iter() + .map(|r| { + let mut r = r.to_owned(); + let mut t = &mut r.rel_of; + loop { + if let Some(next) = t { + t = &mut next.1.rel_of; + } else { + let _ = t.insert(Box::new(( + rel_of.0, + rel.clone(), + ))); + break; + } + } + r + }) + .collect::>() + }) + .collect() + } else { + result + } + } + + pub(super) fn resolve_ref_in_pseudo( + mut self, + ctx: &CssSelectorSet, + ) -> Self { + // FIXME: Handle rel_of! + self.pseudo = self + .pseudo + .into_iter() + .map(|p| p.resolve_ref(ctx)) + .collect(); + self + } /// Return true iff this selector is a superselector of `sub`. fn is_superselector(&self, sub: &Self) -> bool { self.is_local_superselector(sub) @@ -240,7 +332,7 @@ impl Selector { result } - fn unify(self, other: Selector) -> Vec { + pub(super) fn unify(self, other: Selector) -> Vec { self._unify(other).unwrap_or_default() } @@ -315,7 +407,7 @@ impl Selector { self.pseudo.push(pseudo_element); } if self.pseudo.iter().any(Pseudo::is_host) - && (self.pseudo.iter().any(|p| p.name.value() == "hover") + && (self.pseudo.iter().any(Pseudo::is_hover) || self.element.is_some() || !self.classes.is_empty() || !rel_of.is_empty()) @@ -352,6 +444,15 @@ impl Selector { && self.pseudo.is_empty() } + pub(super) fn has_backref(&self) -> bool { + self.backref.is_some() + || self.pseudo.iter().any(Pseudo::has_backref) + || self + .rel_of + .as_deref() + .map_or(false, |(_, s)| s.has_backref()) + } + fn with_rel_of(mut self, rel: RelKind, other: Selector) -> Vec { if self.rel_of.is_some() { self.unify(Selector { @@ -366,7 +467,7 @@ impl Selector { } } - fn into_string_vec(mut self) -> Vec { + 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(); @@ -383,6 +484,9 @@ impl Selector { fn last_compound_str(self) -> String { let mut buf = String::new(); + if self.backref.is_some() { + buf.push('&'); + } if let Some(e) = self.element { if !e.is_any() || (self.classes.is_empty() @@ -537,7 +641,7 @@ impl TryFrom for Selector { type Error = BadSelector; fn try_from(value: Value) -> Result { - Self::try_from(&super::Selector::try_from(value)?.css_ok()?) + Self::try_from(&super::Selector::try_from(value)?) } } @@ -582,19 +686,11 @@ impl TryFrom<&[SelectorPart]> for Selector { }); } SelectorPart::Pseudo { name, arg } => { - result.pseudo.push(Pseudo { - name: name.clone(), - arg: arg.clone(), - element: false, - }); + result.pseudo.push(Pseudo::class(name, arg)); } SelectorPart::PseudoElement { name, arg } => { - assert!(result.pseudo_element().is_none()); // ? - result.pseudo.push(Pseudo { - name: name.clone(), - arg: arg.clone(), - element: true, - }); + assert!(result.pseudo_element().is_none()); // FIXME: Error or ignore? + result.pseudo.push(Pseudo::element(name, arg)); } SelectorPart::Descendant => { let mut t = Self::default(); @@ -619,9 +715,18 @@ impl TryFrom<&[SelectorPart]> for Selector { result.rel_of = Some(Box::new((kind, t))); } SelectorPart::BackRef => { - // I hope backrefs should be resolved before here. - // The real span should be retained in the selector. - return Err(BadSelector::Backref(input_span("&"))); + if !result.is_local_empty() { + let msg = "\"&\" may only used at the beginning of a compound selector."; + let s = format!("{}&", result.last_compound_str()); + let l = s.len(); + let f = + SourceFile::css_bytes(s, SourceName::root("-")); + let pos = SourcePos::new_range(f, l - 1..l); + return Err(BadSelector::Parse(ParseError::new( + msg, pos, + ))); + } + result.backref = Some(()); } } } @@ -643,7 +748,7 @@ impl From for Value { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] struct ElemType { s: String, } @@ -728,7 +833,7 @@ impl RelKind { } /// A logical attribute selector. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] struct Attribute { /// The attribute name // TODO: Why not a raw String? @@ -760,140 +865,3 @@ impl Attribute { buf.push(']'); } } - -/// A pseudo-class or a css2 pseudo-element (:foo) -#[derive(Debug, Clone, PartialEq, Eq)] -struct Pseudo { - /// The name of the pseudo-class - name: CssString, - /// Arguments to the pseudo-class - arg: Option, - /// True if this is a `::psedu-element`, false for a `:pseudo-class`. - element: bool, -} - -impl Pseudo { - fn is_superselector(&self, b: &Self) -> bool { - fn map_sel(s: &Option) -> Option { - s.as_ref().and_then(|s| SelectorSet::try_from(s).ok()) - } - if self.is_element() != b.is_element() || self.name != b.name { - return false; - } - // Note: A better implementetation of is/matches/any would be - // different from has, host, and host-context. - if name_in( - self.name.value(), - &[ - "is", - "matches", - "any", - "where", - "has", - "host", - "host-context", - ], - ) { - if self.arg == b.arg { - true - } else if let (Some(a), Some(b)) = - (map_sel(&self.arg), map_sel(&b.arg)) - { - a.is_superselector(&b) - } else { - false - } - } else if name_in(self.name.value(), &["not"]) { - if let (Some(a), Some(b)) = (map_sel(&self.arg), map_sel(&b.arg)) - { - b.is_superselector(&a) // NOTE: Reversed! - } else { - false - } - } else { - self.name == b.name && self.arg == b.arg - } - } - - fn is_element(&self) -> bool { - self.element || is_pseudo_element(self.name.value()) - } - fn is_host(&self) -> bool { - name_in(self.name.value(), &["host", "host-context"]) - } - fn is_rootish(&self) -> bool { - name_in( - self.name.value(), - &["host", "host-context", "root", "scope"], - ) - } - - pub fn replace( - mut self, - original: &SelectorSet, - replacement: &SelectorSet, - ) -> Self { - if name_in( - self.name.value(), - &[ - "is", - "matches", - "not", - "any", - "where", - "has", - "host", - "host-context", - ], - ) { - self.arg = self.arg.map(|s| { - SelectorSet::try_from(&s) - .unwrap() - .replace(original, replacement) - .unwrap() - .try_into() - .unwrap() - }); - } - self - } - - fn write_to_buf(&self, buf: &mut String) { - buf.push(':'); - if self.element { - buf.push(':'); - } - buf.push_str(self.name.value()); - if let Some(arg) = &self.arg { - use std::fmt::Write; - write!(buf, "({arg})").unwrap(); - } - } -} - -fn is_pseudo_element(n: &str) -> bool { - let pse = [ - "after", - "before", - "file-selector-button", - "first-letter", - "first-line", - "grammar-error", - "marker", - "placeholder", - "selection", - "spelling-error", - "target-text", - ]; - pse.binary_search(&n).is_ok() -} - -fn name_in(name: &str, known: &[&str]) -> bool { - if name.starts_with('-') { - known.iter().any(|end| { - name.strip_suffix(end).map_or(false, |s| s.ends_with('-')) - }) - } else { - known.iter().any(|known| name == *known) - } -} diff --git a/rsass/src/css/selectors/pseudo.rs b/rsass/src/css/selectors/pseudo.rs new file mode 100644 index 000000000..ed731cbe5 --- /dev/null +++ b/rsass/src/css/selectors/pseudo.rs @@ -0,0 +1,153 @@ +use super::{logical::SelectorSet, CssSelectorSet}; +use crate::css::{CssString, Selectors}; + +/// A pseudo-class or a css2 pseudo-element (:foo) +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct Pseudo { + /// The name of the pseudo-class + name: String, + /// Arguments to the pseudo-class + arg: Option, + /// True if this is a `::psedu-element`, false for a `:pseudo-class`. + element: bool, +} + +impl Pseudo { + /// Named constructor for pseudo classes. + pub(crate) fn class(name: &CssString, arg: &Option) -> Self { + Pseudo { + name: name.value().into(), + arg: arg.as_ref().and_then(|s| SelectorSet::try_from(s).ok()), + element: false, + } + } + + /// Named constructor for psedo elements. + pub(crate) fn element(name: &CssString, arg: &Option) -> Self { + Pseudo { + name: name.value().into(), + arg: arg.as_ref().and_then(|s| SelectorSet::try_from(s).ok()), + element: true, + } + } + + pub(crate) fn is_superselector(&self, b: &Self) -> bool { + if self.is_element() != b.is_element() || self.name != b.name { + return false; + } + // Note: A better implementetation of is/matches/any would be + // different from has, host, and host-context. + if name_in( + &self.name, + &[ + "is", + "matches", + "any", + "where", + "has", + "host", + "host-context", + ], + ) { + if self.arg == b.arg { + true + } else if let (Some(a), Some(b)) = (&self.arg, &b.arg) { + a.is_superselector(b) + } else { + false + } + } else if name_in(&self.name, &["not"]) { + if let (Some(a), Some(b)) = (&self.arg, &b.arg) { + b.is_superselector(a) // NOTE: Reversed! + } else { + false + } + } else { + self.name == b.name && self.arg == b.arg + } + } + + pub(super) fn is_element(&self) -> bool { + self.element || is_pseudo_element(&self.name) + } + pub(super) fn is_hover(&self) -> bool { + self.name == "hover" + } + pub(super) fn is_host(&self) -> bool { + name_in(&self.name, &["host", "host-context"]) + } + pub(super) fn is_rootish(&self) -> bool { + name_in(&self.name, &["host", "host-context", "root", "scope"]) + } + + pub(super) fn has_backref(&self) -> bool { + self.arg.as_ref().map_or(false, SelectorSet::has_backref) + } + pub(super) fn resolve_ref(mut self, ctx: &CssSelectorSet) -> Self { + self.arg = self.arg.map(|arg| arg.resolve_ref(ctx)); + self + } + pub(super) fn replace( + mut self, + original: &SelectorSet, + replacement: &SelectorSet, + ) -> Self { + if name_in( + &self.name, + &[ + "is", + "matches", + "not", + "any", + "where", + "has", + "host", + "host-context", + ], + ) { + self.arg = + self.arg.map(|s| s.replace(original, replacement).unwrap()); + } + self + } + + pub(super) fn write_to_buf(&self, buf: &mut String) { + buf.push(':'); + if self.element { + buf.push(':'); + } + buf.push_str(&self.name); + if let Some(arg) = &self.arg { + buf.push('('); + arg.write_to_buf(buf); + buf.push(')'); + } + } +} + +fn is_pseudo_element(n: &str) -> bool { + let pse = [ + "after", + "before", + "file-selector-button", + "first-letter", + "first-line", + "grammar-error", + "marker", + "placeholder", + "selection", + "spelling-error", + "target-text", + ]; + pse.binary_search(&n).is_ok() +} + +fn name_in(name: &str, known: &[&str]) -> bool { + if name.starts_with('-') { + known.iter().any(|end| { + name.strip_suffix(end).map_or(false, |s| s.ends_with('-')) + }) + } else { + known.iter().any(|known| name == *known) + } +} diff --git a/rsass/src/parser/error.rs b/rsass/src/parser/error.rs index efb4b17ae..fe6ba3b5e 100644 --- a/rsass/src/parser/error.rs +++ b/rsass/src/parser/error.rs @@ -31,7 +31,7 @@ impl ParseError { } } - fn new(msg: Msg, pos: SourcePos) -> Self + pub(crate) fn new(msg: Msg, pos: SourcePos) -> Self where Msg: Into, { diff --git a/rsass/src/sass/functions/mod.rs b/rsass/src/sass/functions/mod.rs index b6d697179..a8e45c0a7 100644 --- a/rsass/src/sass/functions/mod.rs +++ b/rsass/src/sass/functions/mod.rs @@ -416,6 +416,18 @@ fn looks_like_call(s: &CssString) -> bool { && s.value().ends_with(')') } +/// Convert an error for a specific argument to not mentioning the argument. +/// +/// Ok results and other errors are returned unmodified. +/// This is used in some cases to make errors from rsass the same as +/// the expected errors for dart sass. +fn unnamed(result: Result) -> Result { + match result { + Err(CallError::BadArgument(_name, msg)) => Err(CallError::msg(msg)), + other => other, + } +} + mod check { use super::{expected_to, is_not}; use crate::css::Value; diff --git a/rsass/src/sass/functions/resolvedargs.rs b/rsass/src/sass/functions/resolvedargs.rs index 8faa483f7..7c71d5d12 100644 --- a/rsass/src/sass/functions/resolvedargs.rs +++ b/rsass/src/sass/functions/resolvedargs.rs @@ -1,5 +1,6 @@ use super::{CallError, CheckedArg, Name}; use crate::css::Value; +use crate::value::ListSeparator; use crate::ScopeRef; /// The arguments to a builtin function. @@ -25,6 +26,34 @@ impl ResolvedArgs { self.get_map(name, |v| T::try_from(v).map_err(|e| e.to_string())) } + /// Get a checked var-args parameter as a Vec of a given type. + pub fn get_va(&self, name: Name) -> Result, CallError> + where + T: TryFrom, + >::Error: ToString, + { + fn inner(value: Value) -> Result, String> + where + T: TryFrom, + >::Error: ToString, + { + let check = |value: Value| -> Result { + T::try_from(value).map_err(|e| e.to_string()) + }; + match value { + Value::ArgList(args) => { + args.check_no_named().map_err(|e| e.to_string())?; + args.positional.into_iter().map(check).collect() + } + Value::List(v, Some(ListSeparator::Comma), false) => { + v.into_iter().map(check).collect() + } + single => Ok(vec![check(single)?]), + } + } + inner(self.scope.get(&name)?).named(name) + } + /// Get an optional named argument. pub fn get_opt(&self, name: Name) -> Result, CallError> where diff --git a/rsass/src/sass/functions/selector.rs b/rsass/src/sass/functions/selector.rs index 125d331ac..9b4a977ef 100644 --- a/rsass/src/sass/functions/selector.rs +++ b/rsass/src/sass/functions/selector.rs @@ -1,13 +1,15 @@ -use super::{check, CallError, FunctionMap, ResolvedArgs}; -use crate::css::{BadSelector, LogicalSelectorSet, Selectors, Value}; +use super::{ + check, unnamed, CallError, CheckedArg, FunctionMap, ResolvedArgs, +}; +use crate::css::{BadSelector, CssSelectorSet, Selectors, Value}; use crate::sass::Name; use crate::Scope; pub fn create_module() -> Scope { let mut f = Scope::builtin_module("sass:selector"); def!(f, is_superselector(super, sub), |s| { - let sup: LogicalSelectorSet = s.get(name!(super))?; - let sub: LogicalSelectorSet = s.get(name!(sub))?; + let sup: CssSelectorSet = s.get(name!(super))?; + let sub: CssSelectorSet = s.get(name!(sub))?; Ok(sup.is_superselector(&sub).into()) }); def_va!(f, append(selectors), |s| { @@ -24,23 +26,27 @@ pub fn create_module() -> Scope { }); // TODO: extend def_va!(f, nest(selectors), |s| { - let mut v = get_selectors(s, name!(selectors))?.into_iter(); - let first = v.next().unwrap().css_ok()?; - Ok(v.fold(first, |b, e| e.inside(&b.into())).into()) + let mut v = unnamed(s.get_va(name!(selectors)))?.into_iter(); + let first = v + .next() + .ok_or("At least one selector must be passed.") + .named(name!(selectors))?; + let first = CssSelectorSet::try_from(first)?; + Ok(v.fold(first, |b, e| b.nest(e)).into()) }); def!(f, parse(selector), |s| { Ok(s.get_map(name!(selector), parse_selectors_x)?.into()) }); def!(f, replace(selector, original, replacement), |s| { - let selector: LogicalSelectorSet = s.get(name!(selector))?; - let original: LogicalSelectorSet = s.get(name!(original))?; - let replacement: LogicalSelectorSet = s.get(name!(replacement))?; + let selector: CssSelectorSet = s.get(name!(selector))?; + let original: CssSelectorSet = s.get(name!(original))?; + let replacement: CssSelectorSet = s.get(name!(replacement))?; Ok(selector.replace(&original, &replacement)?.into()) }); // TODO: simple_selectors def!(f, unify(selector1, selector2), |s| { - let a: LogicalSelectorSet = s.get(name!(selector1))?; - let b: LogicalSelectorSet = s.get(name!(selector2))?; + let a: CssSelectorSet = s.get(name!(selector1))?; + let b: CssSelectorSet = s.get(name!(selector2))?; Ok(a.unify(b).into()) }); diff --git a/rsass/tests/spec/core_functions/selector/nest/error.rs b/rsass/tests/spec/core_functions/selector/nest/error.rs index 0bed07a74..5063a4b59 100644 --- a/rsass/tests/spec/core_functions/selector/nest/error.rs +++ b/rsass/tests/spec/core_functions/selector/nest/error.rs @@ -66,7 +66,6 @@ mod parent { ); } #[test] - #[ignore] // missing error fn non_initial() { assert_eq!( runner().err( @@ -86,7 +85,6 @@ mod parent { ); } #[test] - #[ignore] // missing error fn prefix() { assert_eq!( runner().err( diff --git a/rsass/tests/spec/core_functions/selector/nest/list.rs b/rsass/tests/spec/core_functions/selector/nest/list.rs index 57a0b685c..b4193875b 100644 --- a/rsass/tests/spec/core_functions/selector/nest/list.rs +++ b/rsass/tests/spec/core_functions/selector/nest/list.rs @@ -78,7 +78,6 @@ mod list { ); } #[test] - #[ignore] // wrong result fn multiple() { assert_eq!( runner().ok("a {b: selector-nest(\"c, d\", \"&.e &.f\")}\n"), @@ -92,7 +91,6 @@ mod list { use super::runner; #[test] - #[ignore] // wrong result fn is() { assert_eq!( runner() @@ -103,7 +101,6 @@ mod list { ); } #[test] - #[ignore] // wrong result fn matches() { assert_eq!( runner().ok( @@ -115,7 +112,6 @@ mod list { ); } #[test] - #[ignore] // wrong result fn test_where() { assert_eq!( runner().ok(