From c2f4014a1a3be047b33855e38fe0a0927962644b Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 31 May 2020 04:48:21 -0400 Subject: [PATCH 01/15] resolve test errors --- tests/equality.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/equality.rs b/tests/equality.rs index d6fb7d9..b68bc52 100644 --- a/tests/equality.rs +++ b/tests/equality.rs @@ -50,11 +50,11 @@ test!( ); test!( different_quoting_inside_list_eq, - "a {\n color: ("foo",) == (foo,);\n}\n", + "a {\n color: (\"foo\",) == (foo,);\n}\n", "a {\n color: true;\n}\n" ); test!( different_quoting_inside_list_ne, - "a {\n color: ("foo",) != (foo,);\n}\n", + "a {\n color: (\"foo\",) != (foo,);\n}\n", "a {\n color: false;\n}\n" ); From f3a58e0fa3401ad3a856f2f09aa5b5842ce8713f Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 31 May 2020 04:51:41 -0400 Subject: [PATCH 02/15] initial implementation of selector-* builtin fns --- src/args.rs | 2 +- src/atrule/mixin.rs | 5 +- src/atrule/mod.rs | 1 + src/atrule/parse.rs | 4 +- src/builtin/list.rs | 20 +- src/builtin/mod.rs | 1 + src/builtin/selector.rs | 213 ++++++++++ src/common.rs | 15 - src/lib.rs | 1 + src/output.rs | 4 +- src/selector/attribute.rs | 26 +- src/selector/common.rs | 55 +++ src/selector/complex.rs | 279 +++++++++++++ src/selector/compound.rs | 219 +++++++++++ src/selector/extend/mod.rs | 775 +++++++++++++++++++++++++++++++++++++ src/selector/list.rs | 261 +++++++++++++ src/selector/mod.rs | 573 +++------------------------ src/selector/parse.rs | 583 ++++++++++++++++++++++++++++ src/selector/simple.rs | 582 ++++++++++++++++++++++++++++ src/stylesheet.rs | 3 +- src/value/mod.rs | 79 +++- src/value/parse.rs | 2 +- tests/at-root.rs | 126 +++--- tests/error.rs | 1 + tests/is-superselector.rs | 75 ++++ tests/selector-append.rs | 78 ++++ tests/selector-nest.rs | 158 ++++++++ tests/selector-parse.rs | 100 +++++ tests/selector-unify.rs | 602 ++++++++++++++++++++++++++++ tests/selectors.rs | 21 +- 30 files changed, 4228 insertions(+), 636 deletions(-) create mode 100644 src/selector/common.rs create mode 100644 src/selector/complex.rs create mode 100644 src/selector/compound.rs create mode 100644 src/selector/extend/mod.rs create mode 100644 src/selector/list.rs create mode 100644 src/selector/parse.rs create mode 100644 src/selector/simple.rs create mode 100644 tests/is-superselector.rs create mode 100644 tests/selector-append.rs create mode 100644 tests/selector-nest.rs create mode 100644 tests/selector-parse.rs create mode 100644 tests/selector-unify.rs diff --git a/src/args.rs b/src/args.rs index 0ae0583..7cf5e38 100644 --- a/src/args.rs +++ b/src/args.rs @@ -5,7 +5,6 @@ use codemap::{Span, Spanned}; use peekmore::PeekMoreIterator; -use crate::Cow; use crate::common::Identifier; use crate::error::SassResult; use crate::scope::Scope; @@ -15,6 +14,7 @@ use crate::utils::{ read_until_closing_paren, read_until_closing_quote, read_until_closing_square_brace, }; use crate::value::Value; +use crate::Cow; use crate::Token; #[derive(Debug, Clone, Eq, PartialEq)] diff --git a/src/atrule/mixin.rs b/src/atrule/mixin.rs index 5a82ab5..a31cbbe 100644 --- a/src/atrule/mixin.rs +++ b/src/atrule/mixin.rs @@ -155,7 +155,10 @@ impl Mixin { return Err(("Mixins may not contain mixin declarations.", span).into()) } Expr::Selector(selector) => { - let rules = self.eval(&super_selector.zip(&selector), content)?; + let rules = self.eval( + &selector.resolve_parent_selectors(&super_selector, true), + content, + )?; stmts.push(Spanned { node: Stmt::RuleSet(RuleSet { super_selector: super_selector.clone(), diff --git a/src/atrule/mod.rs b/src/atrule/mod.rs index f364173..300fc88 100644 --- a/src/atrule/mod.rs +++ b/src/atrule/mod.rs @@ -159,6 +159,7 @@ impl AtRule { &mut read_until_open_curly_brace(toks)?.into_iter().peekmore(), scope, super_selector, + true, )?, ); let mut is_some = true; diff --git a/src/atrule/parse.rs b/src/atrule/parse.rs index 609b95d..33f058c 100644 --- a/src/atrule/parse.rs +++ b/src/atrule/parse.rs @@ -33,7 +33,7 @@ pub(crate) fn eat_stmts>( let rules = eat_stmts( toks, scope, - &super_selector.zip(&selector), + &selector.resolve_parent_selectors(&super_selector, true), at_root, content, )?; @@ -82,7 +82,7 @@ pub(crate) fn eat_stmts_at_root>( Expr::MixinDecl(..) | Expr::FunctionDecl(..) => todo!(), Expr::Selector(mut selector) => { if nesting > 1 || is_some { - selector = super_selector.zip(&selector); + selector = selector.resolve_parent_selectors(&super_selector, true); } else { selector = Selector::replace(super_selector, selector); } diff --git a/src/builtin/list.rs b/src/builtin/list.rs index b245256..c9e17de 100644 --- a/src/builtin/list.rs +++ b/src/builtin/list.rs @@ -22,11 +22,7 @@ fn length(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassR fn nth(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult { args.max_args(2)?; - let mut list = match arg!(args, scope, super_selector, 0, "list") { - Value::List(v, ..) => v, - Value::Map(m) => m.entries(), - v => vec![v], - }; + let mut list = arg!(args, scope, super_selector, 0, "list").as_list(); let n = match arg!(args, scope, super_selector, 1, "n") { Value::Dimension(num, _) => num, v => { @@ -260,11 +256,7 @@ fn is_bracketed(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> fn index(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult { args.max_args(2)?; - let list = match arg!(args, scope, super_selector, 0, "list") { - Value::List(v, ..) => v, - Value::Map(m) => m.entries(), - v => vec![v], - }; + let list = arg!(args, scope, super_selector, 0, "list").as_list(); let value = arg!(args, scope, super_selector, 1, "value"); // TODO: find a way around this unwrap. // It should be impossible to hit as the arg is @@ -288,13 +280,7 @@ fn zip(args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult v, - Value::Map(m) => m.entries(), - v => vec![v], - }) - }) + .map(|x| Ok(x.node.eval(span)?.node.as_list())) .collect::>>>()?; let len = lists.iter().map(Vec::len).min().unwrap_or(0); diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index d1898ac..8e37009 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -50,6 +50,7 @@ pub(crate) static GLOBAL_FUNCTIONS: Lazy = Lazy::new(|| { map::declare(&mut m); math::declare(&mut m); meta::declare(&mut m); + selector::declare(&mut m); string::declare(&mut m); m }); diff --git a/src/builtin/selector.rs b/src/builtin/selector.rs index 8b13789..f3c56d3 100644 --- a/src/builtin/selector.rs +++ b/src/builtin/selector.rs @@ -1 +1,214 @@ +#![allow(unused_variables, unused_mut)] +use super::{Builtin, GlobalFunctionMap}; + +use crate::args::CallArgs; +use crate::error::SassResult; +use crate::scope::Scope; +use crate::selector::{ComplexSelector, ComplexSelectorComponent, Selector, SelectorList}; +use crate::value::Value; + +fn is_superselector( + mut args: CallArgs, + scope: &Scope, + super_selector: &Selector, +) -> SassResult { + args.max_args(2)?; + let parent_selector = arg!(args, scope, super_selector, 0, "super").to_selector( + args.span(), + scope, + super_selector, + "super", + false, + )?; + let child_selector = arg!(args, scope, super_selector, 1, "sub").to_selector( + args.span(), + scope, + super_selector, + "sub", + false, + )?; + + Ok(Value::bool( + parent_selector.is_super_selector(&child_selector), + )) +} + +fn simple_selectors( + mut args: CallArgs, + scope: &Scope, + super_selector: &Selector, +) -> SassResult { + args.max_args(1)?; + todo!("built-in fn simple-selectors") +} + +fn selector_parse( + mut args: CallArgs, + scope: &Scope, + super_selector: &Selector, +) -> SassResult { + args.max_args(1)?; + Ok(arg!(args, scope, super_selector, 0, "selector") + .to_selector(args.span(), scope, super_selector, "selector", false)? + .into_value()) +} + +fn selector_nest(args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult { + let span = args.span(); + let selectors = args.get_variadic(scope, super_selector)?; + if selectors.is_empty() { + return Err(("$selectors: At least one selector must be passed.", span).into()); + } + + Ok(selectors + .into_iter() + .map(|sel| { + sel.node + .to_selector(span, scope, super_selector, "selectors", true) + }) + .collect::>>()? + .into_iter() + .fold(Selector::new(), |parent, child| { + child.resolve_parent_selectors(&parent, true) + }) + .into_value()) +} + +fn selector_append( + mut args: CallArgs, + scope: &Scope, + super_selector: &Selector, +) -> SassResult { + let span = args.span(); + let selectors = args.get_variadic(scope, super_selector)?; + if selectors.is_empty() { + return Err(("$selectors: At least one selector must be passed.", span).into()); + } + + let mut parsed_selectors = selectors + .into_iter() + .map(|s| { + let tmp = s + .node + .to_selector(span, scope, super_selector, "selectors", false)?; + if tmp.contains_parent_selector() { + return Err(("Parent selectors aren't allowed here.", span).into()); + } else { + Ok(tmp) + } + }) + .collect::>>()?; + + let first = parsed_selectors.remove(0); + Ok(parsed_selectors + .into_iter() + .try_fold(first, |parent, child| -> SassResult { + Ok(Selector(SelectorList { + components: child + .0 + .components + .into_iter() + .map(|complex| -> SassResult { + let compound = complex.components.first(); + if let Some(ComplexSelectorComponent::Compound(compound)) = compound { + let mut components = vec![match compound.clone().prepend_parent() { + Some(v) => ComplexSelectorComponent::Compound(v), + None => { + return Err(( + format!("Can't append {} to {}.", complex, parent), + span, + ) + .into()) + } + }]; + components.extend(complex.components.into_iter().skip(1)); + Ok(ComplexSelector { + components, + line_break: false, + }) + } else { + return Err( + (format!("Can't append {} to {}.", complex, parent), span).into() + ); + } + }) + .collect::>>()?, + }) + .resolve_parent_selectors(&parent, false)) + })? + .into_value()) +} + +fn selector_extend( + mut args: CallArgs, + scope: &Scope, + super_selector: &Selector, +) -> SassResult { + args.max_args(3)?; + todo!("built-in fn selector-extend") +} + +fn selector_replace( + mut args: CallArgs, + scope: &Scope, + super_selector: &Selector, +) -> SassResult { + args.max_args(3)?; + todo!("built-in fn selector-replace") +} + +fn selector_unify( + mut args: CallArgs, + scope: &Scope, + super_selector: &Selector, +) -> SassResult { + args.max_args(2)?; + let selector1 = arg!(args, scope, super_selector, 0, "selector1").to_selector( + args.span(), + scope, + super_selector, + "selector1", + false, + )?; + + if selector1.contains_parent_selector() { + return Err(( + "$selector1: Parent selectors aren't allowed here.", + args.span(), + ) + .into()); + } + + let selector2 = arg!(args, scope, super_selector, 1, "selector2").to_selector( + args.span(), + scope, + super_selector, + "selector2", + false, + )?; + + if selector2.contains_parent_selector() { + return Err(( + "$selector2: Parent selectors aren't allowed here.", + args.span(), + ) + .into()); + } + + Ok(match selector1.unify(selector2) { + Some(sel) => sel.into_value(), + None => Value::Null, + }) +} + +pub(crate) fn declare(f: &mut GlobalFunctionMap) { + f.insert("is-superselector", Builtin::new(is_superselector)); + f.insert("simple-selectors", Builtin::new(simple_selectors)); + f.insert("selector-parse", Builtin::new(selector_parse)); + f.insert("selector-nest", Builtin::new(selector_nest)); + f.insert("selector-append", Builtin::new(selector_append)); + f.insert("selector-extend", Builtin::new(selector_extend)); + f.insert("selector-replace", Builtin::new(selector_replace)); + f.insert("selector-unify", Builtin::new(selector_unify)); +} diff --git a/src/common.rs b/src/common.rs index 458f11b..09cc3da 100644 --- a/src/common.rs +++ b/src/common.rs @@ -105,21 +105,6 @@ impl ListSeparator { } } -#[derive(Clone, Debug, Eq, PartialEq)] -pub(crate) struct QualifiedName { - pub ident: String, - pub namespace: Option, -} - -impl Display for QualifiedName { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(namespace) = &self.namespace { - write!(f, "{}|", namespace)?; - } - f.write_str(&self.ident) - } -} - #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub(crate) struct Identifier(String); diff --git a/src/lib.rs b/src/lib.rs index e94fb13..e372f44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -278,6 +278,7 @@ pub(crate) fn eat_expr>( &mut values.into_iter().peekmore(), scope, super_selector, + true, )?), span, })); diff --git a/src/output.rs b/src/output.rs index 95bdc7c..0ba38a3 100644 --- a/src/output.rs +++ b/src/output.rs @@ -75,7 +75,9 @@ impl Css { super_selector, rules, }) => { - let selector = super_selector.zip(&selector).remove_placeholders(); + let selector = selector + .resolve_parent_selectors(&super_selector, true) + .remove_placeholders(); if selector.is_empty() { return Ok(Vec::new()); } diff --git a/src/selector/attribute.rs b/src/selector/attribute.rs index 38736b9..7de0a70 100644 --- a/src/selector/attribute.rs +++ b/src/selector/attribute.rs @@ -4,8 +4,8 @@ use peekmore::PeekMoreIterator; use codemap::Span; -use super::{Selector, SelectorKind}; -use crate::common::{QualifiedName, QuoteKind}; +use super::{Namespace, QualifiedName, Selector}; +use crate::common::QuoteKind; use crate::error::SassResult; use crate::scope::Scope; use crate::utils::{devour_whitespace, eat_ident, is_ident, parse_quoted_string}; @@ -40,7 +40,7 @@ fn attribute_name>( let ident = eat_ident(toks, scope, super_selector, span_before)?.node; return Ok(QualifiedName { ident, - namespace: Some('*'.to_string()), + namespace: Namespace::Asterisk, }); } let span_before = next.pos; @@ -49,7 +49,7 @@ fn attribute_name>( Some(v) if v.kind != '|' => { return Ok(QualifiedName { ident: name_or_namespace.node, - namespace: None, + namespace: Namespace::None, }); } Some(..) => {} @@ -57,14 +57,14 @@ fn attribute_name>( } match toks.peek_forward(1) { Some(v) if v.kind == '=' => { - toks.peek_backward(1).unwrap(); + toks.reset_view(); return Ok(QualifiedName { ident: name_or_namespace.node, - namespace: None, + namespace: Namespace::None, }); } Some(..) => { - toks.peek_backward(1).unwrap(); + toks.reset_view(); } None => return Err(("expected more input.", name_or_namespace.span).into()), } @@ -72,7 +72,7 @@ fn attribute_name>( let ident = eat_ident(toks, scope, super_selector, span_before)?.node; Ok(QualifiedName { ident, - namespace: Some(name_or_namespace.node), + namespace: Namespace::Other(name_or_namespace.node), }) } @@ -100,19 +100,19 @@ impl Attribute { scope: &Scope, super_selector: &Selector, start: Span, - ) -> SassResult { + ) -> SassResult { devour_whitespace(toks); let attr = attribute_name(toks, scope, super_selector, start)?; devour_whitespace(toks); if toks.peek().ok_or(("expected more input.", start))?.kind == ']' { toks.next(); - return Ok(SelectorKind::Attribute(Attribute { + return Ok(Attribute { attr, value: String::new(), modifier: None, op: AttributeOp::Any, span: start, - })); + }); } let op = attribute_operator(toks, start)?; @@ -152,13 +152,13 @@ impl Attribute { toks.next(); - Ok(SelectorKind::Attribute(Attribute { + Ok(Attribute { op, attr, value, modifier, span: start, - })) + }) } } diff --git a/src/selector/common.rs b/src/selector/common.rs new file mode 100644 index 0000000..a8fc201 --- /dev/null +++ b/src/selector/common.rs @@ -0,0 +1,55 @@ +use std::fmt::{self, Display}; + +/// The selector namespace. +/// +/// If this is `None`, this matches all elements in the default namespace. If +/// it's `Empty`, this matches all elements that aren't in any +/// namespace. If it's `Asterisk`, this matches all elements in any namespace. +/// Otherwise, it matches all elements in the given namespace. +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) enum Namespace { + Empty, + Asterisk, + Other(String), + None, +} + +impl Display for Namespace { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Empty => write!(f, "|"), + Self::Asterisk => write!(f, "*|"), + Self::Other(namespace) => write!(f, "{}|", namespace), + Self::None => Ok(()), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct QualifiedName { + pub ident: String, + pub namespace: Namespace, +} + +impl Display for QualifiedName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.namespace)?; + f.write_str(&self.ident) + } +} + +pub(crate) struct Specificity(u32, u32); + +impl Specificity { + pub const fn new(min: u32, max: u32) -> Self { + Specificity(min, max) + } + + pub const fn min(&self) -> u32 { + self.0 + } + + pub const fn max(&self) -> u32 { + self.1 + } +} diff --git a/src/selector/complex.rs b/src/selector/complex.rs new file mode 100644 index 0000000..2904e2f --- /dev/null +++ b/src/selector/complex.rs @@ -0,0 +1,279 @@ +use super::{CompoundSelector, Pseudo, SelectorList, SimpleSelector}; +use std::fmt::{self, Display, Write}; + +/// A complex selector. +/// +/// A complex selector is composed of `CompoundSelector`s separated by +/// `Combinator`s. It selects elements based on their parent selectors. +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct ComplexSelector { + /// The components of this selector. + /// + /// This is never empty. + /// + /// Descendant combinators aren't explicitly represented here. If two + /// `CompoundSelector`s are adjacent to one another, there's an implicit + /// descendant combinator between them. + /// + /// It's possible for multiple `Combinator`s to be adjacent to one another. + /// This isn't valid CSS, but Sass supports it for CSS hack purposes. + pub components: Vec, + + /// Whether a line break should be emitted *before* this selector. + pub line_break: bool, +} + +impl fmt::Display for ComplexSelector { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut last_component = None; + + for component in &self.components { + if let Some(c) = last_component { + if !omit_spaces_around(c) && !omit_spaces_around(component) { + f.write_char(' ')?; + } + } + write!(f, "{}", component)?; + last_component = Some(component); + } + Ok(()) + } +} + +/// When `style` is `OutputStyle::compressed`, omit spaces around combinators. +fn omit_spaces_around(component: &ComplexSelectorComponent) -> bool { + // todo: compressed + let is_compressed = false; + is_compressed && matches!(component, ComplexSelectorComponent::Combinator(..)) +} + +impl ComplexSelector { + // pub fn specificity(&self) -> Specificity { + // let mut min = 0; + // let mut max = 0; + // for component in self.components.iter() { + // todo!() + // // min += simple.min_specificity(); + // // max += simple.max_specificity(); + // } + // Specificity::new(min, max) + // } + + pub fn is_invisible(&self) -> bool { + self.components + .iter() + .any(|component| component.is_invisible()) + } + + /// Returns whether `self` is a superselector of `other`. + /// + /// That is, whether `self` matches every element that `other` matches, as well + /// as possibly additional elements. + pub fn is_super_selector(&self, other: &Self) -> bool { + if let Some(ComplexSelectorComponent::Combinator(..)) = self.components.last() { + return false; + } + if let Some(ComplexSelectorComponent::Combinator(..)) = other.components.last() { + return false; + } + + let mut i1 = 0; + let mut i2 = 0; + + loop { + let remaining1 = self.components.len() - i1; + let remaining2 = other.components.len() - i2; + + if remaining1 == 0 || remaining2 == 0 || remaining1 > remaining2 { + return false; + } + + let compound1 = match self.components.get(i1) { + Some(ComplexSelectorComponent::Compound(c)) => c, + Some(ComplexSelectorComponent::Combinator(..)) => return false, + None => unreachable!(), + }; + + if let ComplexSelectorComponent::Combinator(..) = other.components[i2] { + return false; + } + + if remaining1 == 1 { + let parents = other + .components + .iter() + .take(other.components.len() - 1) + .skip(i2) + .cloned() + .collect(); + return compound1.is_super_selector( + other.components.last().unwrap().as_compound(), + Some(parents), + ); + } + + let mut after_super_selector = i2 + 1; + while after_super_selector < other.components.len() { + if let Some(ComplexSelectorComponent::Compound(compound2)) = + other.components.get(after_super_selector - 1) + { + if compound1.is_super_selector( + compound2, + Some( + other + .components + .iter() + .take(after_super_selector - 1) + .skip(i2 + 1) + .cloned() + .collect(), + ), + ) { + break; + } + } + + after_super_selector += 1; + } + + if after_super_selector == other.components.len() { + return false; + } + + if let Some(ComplexSelectorComponent::Combinator(combinator1)) = + self.components.get(i1 + 1) + { + let combinator2 = match other.components.get(after_super_selector) { + Some(ComplexSelectorComponent::Combinator(c)) => c, + Some(ComplexSelectorComponent::Compound(..)) => return false, + None => unreachable!(), + }; + + if combinator1 == &Combinator::FollowingSibling { + if combinator2 == &Combinator::Child { + return false; + } + } else if combinator1 != combinator2 { + return false; + } + + if remaining1 == 3 && remaining2 > 3 { + return false; + } + + i1 += 2; + i2 = after_super_selector + 1; + } else if let Some(ComplexSelectorComponent::Combinator(combinator2)) = + other.components.get(after_super_selector) + { + if combinator2 != &Combinator::Child { + return false; + } + i1 += 1; + i2 = after_super_selector + 1; + } else { + i1 += 1; + i2 = after_super_selector; + } + } + } + + pub fn contains_parent_selector(&self) -> bool { + self.components.iter().any(|c| { + if let ComplexSelectorComponent::Compound(compound) = c { + compound.components.iter().any(|simple| { + if simple.is_parent() { + return true; + } + if let SimpleSelector::Pseudo(Pseudo { + selector: Some(sel), + .. + }) = simple + { + return sel.contains_parent_selector(); + } + false + }) + } else { + false + } + }) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Copy)] +pub(crate) enum Combinator { + /// Matches the right-hand selector if it's immediately adjacent to the + /// left-hand selector in the DOM tree. + /// + /// `'+'` + NextSibling, + + /// Matches the right-hand selector if it's a direct child of the left-hand + /// selector in the DOM tree. + /// + /// `'>'` + Child, + + /// Matches the right-hand selector if it comes after the left-hand selector + /// in the DOM tree. + /// + /// `'~'` + FollowingSibling, +} + +impl Display for Combinator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_char(match self { + Self::NextSibling => '+', + Self::Child => '>', + Self::FollowingSibling => '~', + }) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) enum ComplexSelectorComponent { + Combinator(Combinator), + Compound(CompoundSelector), +} + +impl ComplexSelectorComponent { + pub fn is_invisible(&self) -> bool { + match self { + Self::Combinator(..) => false, + Self::Compound(c) => c.is_invisible(), + } + } + + pub fn is_compound(&self) -> bool { + matches!(self, Self::Compound(..)) + } + + pub fn is_combinator(&self) -> bool { + matches!(self, Self::Combinator(..)) + } + + pub fn resolve_parent_selectors(self, parent: SelectorList) -> Option> { + match self { + Self::Compound(c) => c.resolve_parent_selectors(parent), + Self::Combinator(..) => todo!(), + } + } + + pub fn as_compound(&self) -> &CompoundSelector { + match self { + Self::Compound(c) => c, + Self::Combinator(..) => unreachable!(), + } + } +} + +impl Display for ComplexSelectorComponent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Compound(c) => write!(f, "{}", c), + Self::Combinator(c) => write!(f, "{}", c), + } + } +} diff --git a/src/selector/compound.rs b/src/selector/compound.rs new file mode 100644 index 0000000..61fcba2 --- /dev/null +++ b/src/selector/compound.rs @@ -0,0 +1,219 @@ +use std::fmt::{self, Write}; + +use super::{ + ComplexSelector, ComplexSelectorComponent, Namespace, Pseudo, SelectorList, SimpleSelector, + Specificity, +}; + +/// A compound selector is composed of several +/// simple selectors +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct CompoundSelector { + pub components: Vec, +} + +impl fmt::Display for CompoundSelector { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut did_write = false; + for simple in &self.components { + if !did_write { + let s = simple.to_string(); + if !s.is_empty() { + did_write = true; + } + write!(f, "{}", s)?; + } else { + write!(f, "{}", simple)?; + } + } + + // If we emit an empty compound, it's because all of the components got + // optimized out because they match all selectors, so we just emit the + // universal selector. + if !did_write { + f.write_char('*')?; + } + + Ok(()) + } +} + +impl CompoundSelector { + /// Returns tuple of (min, max) specificity + pub fn specificity(&self) -> Specificity { + let mut min = 0; + let mut max = 0; + for simple in self.components.iter() { + todo!() + // min += simple.min_specificity; + // max += simple.max_specificity; + } + Specificity::new(min, max) + } + + pub fn is_invisible(&self) -> bool { + self.components + .iter() + .any(|component| component.is_invisible()) + } + + pub fn is_super_selector( + &self, + other: &Self, + parents: Option>, + ) -> bool { + for simple1 in &self.components { + if let SimpleSelector::Pseudo( + pseudo @ Pseudo { + selector: Some(..), .. + }, + ) = simple1 + { + if !pseudo.is_super_selector(other, parents.clone()) { + return false; + } + } else if !simple1.is_super_selector_of_compound(other) { + return false; + } + } + + for simple2 in &other.components { + if let SimpleSelector::Pseudo(Pseudo { + is_class: false, + selector: None, + .. + }) = simple2 + { + if simple2.is_super_selector_of_compound(&self) { + return false; + } + } + } + + return true; + } + + /// Returns a new `CompoundSelector` based on `compound` with all + /// `SimpleSelector::Parent`s replaced with `parent`. + /// + /// Returns `None` if `compound` doesn't contain any `SimpleSelector::Parent`s. + pub fn resolve_parent_selectors(self, parent: SelectorList) -> Option> { + let contains_selector_pseudo = self.components.iter().any(|simple| { + if let SimpleSelector::Pseudo(Pseudo { + selector: Some(sel), + .. + }) = simple + { + sel.contains_parent_selector() + } else { + false + } + }); + + if !contains_selector_pseudo && !self.components[0].is_parent() { + return None; + } + + let resolved_members: Vec = if contains_selector_pseudo { + self.components + .clone() + .into_iter() + .map(|simple| { + if let SimpleSelector::Pseudo(mut pseudo) = simple { + if let Some(sel) = pseudo.selector.clone() { + if !sel.contains_parent_selector() { + return SimpleSelector::Pseudo(pseudo); + } + pseudo.selector = + Some(sel.resolve_parent_selectors(Some(parent.clone()), false)); + SimpleSelector::Pseudo(pseudo) + } else { + SimpleSelector::Pseudo(pseudo) + } + } else { + simple + } + }) + .collect() + } else { + self.components.clone() + }; + + if let Some(SimpleSelector::Parent(suffix)) = self.components.first() { + if self.components.len() == 1 && suffix.is_none() { + return Some(parent.components); + } + } else { + return Some(vec![ComplexSelector { + components: vec![ComplexSelectorComponent::Compound(CompoundSelector { + components: resolved_members, + })], + line_break: false, + }]); + } + + Some(parent.components.into_iter().map(move |mut complex| { + let last_component = complex.components.last(); + let last = if let Some(ComplexSelectorComponent::Compound(c)) = last_component { + c.clone() + } else { + todo!("throw SassScriptException('Parent \"$complex\" is incompatible with this selector.');") + }; + + let last = if let Some(SimpleSelector::Parent(Some(suffix))) = self.components.first() { + let mut components = last.components; + let mut end = components.pop().unwrap(); + end.add_suffix(suffix); + components.push(end); + components.extend(resolved_members.clone().into_iter().skip(1)); + CompoundSelector { components } + } else { + let mut components = last.components; + components.extend(resolved_members.clone().into_iter().skip(1)); + CompoundSelector { components } + }; + + complex.components.pop(); + + let mut components = complex.components; + components.push(ComplexSelectorComponent::Compound(last)); + + ComplexSelector { components, line_break: complex.line_break } + }).collect()) + } + + /// Returns a `CompoundSelector` that matches only elements that are matched by + /// both `compound1` and `compound2`. + /// + /// If no such selector can be produced, returns `None`. + pub fn unify(self, other: Self) -> Option { + let mut components = other.components; + for simple in self.components { + components = simple.unify(std::mem::take(&mut components))?; + } + + Some(Self { components }) + } + + /// Adds a `SimpleSelector::Parent` to the beginning of `compound`, or returns `None` if + /// that wouldn't produce a valid selector. + pub fn prepend_parent(mut self) -> Option { + Some(match self.components.first()? { + SimpleSelector::Universal(..) => return None, + SimpleSelector::Type(name) => { + if name.namespace != Namespace::None { + return None; + } + let mut components = vec![SimpleSelector::Parent(Some(name.ident.clone()))]; + components.extend(self.components.into_iter().skip(1)); + + Self { components } + } + simple => { + let mut components = vec![SimpleSelector::Parent(None)]; + components.append(&mut self.components); + Self { components } + } + }) + } +} diff --git a/src/selector/extend/mod.rs b/src/selector/extend/mod.rs new file mode 100644 index 0000000..d5ddf2c --- /dev/null +++ b/src/selector/extend/mod.rs @@ -0,0 +1,775 @@ +use std::collections::VecDeque; + +use super::{ + Combinator, ComplexSelector, ComplexSelectorComponent, CompoundSelector, Pseudo, SimpleSelector, +}; + +/// Returns the contents of a `SelectorList` that matches only elements that are +/// matched by both `complex1` and `complex2`. +/// +/// If no such list can be produced, returns `None`. +pub(crate) fn unify_complex( + complexes: Vec>, +) -> Option>> { + debug_assert!(!complexes.is_empty()); + + if complexes.len() == 1 { + return Some(complexes); + } + + let mut unified_base: Option> = None; + + for complex in &complexes { + let base = complex.last()?; + + if let ComplexSelectorComponent::Compound(base) = base { + if let Some(mut some_unified_base) = unified_base.clone() { + for simple in base.components.clone() { + some_unified_base = simple.unify(some_unified_base.clone())?; + } + unified_base = Some(some_unified_base); + } else { + unified_base = Some(base.components.clone()); + } + } else { + return None; + } + } + + let mut complexes_without_bases: Vec> = complexes + .into_iter() + .map(|mut complex| { + complex.pop(); + complex + }) + .collect(); + + complexes_without_bases + .last_mut() + .unwrap() + .push(ComplexSelectorComponent::Compound(CompoundSelector { + components: unified_base?, + })); + + Some(weave(complexes_without_bases)) +} + +/// Expands "parenthesized selectors" in `complexes`. +/// +/// That is, if we have `.A .B {@extend .C}` and `.D .C {...}`, this +/// conceptually expands into `.D .C, .D (.A .B)`, and this function translates +/// `.D (.A .B)` into `.D .A .B, .A .D .B`. For thoroughness, `.A.D .B` would +/// also be required, but including merged selectors results in exponential +/// output for very little gain. +/// +/// The selector `.D (.A .B)` is represented as the list `[[.D], [.A, .B]]`. +fn weave(complexes: Vec>) -> Vec> { + let mut prefixes: Vec> = vec![complexes.first().unwrap().clone()]; + + for complex in complexes.into_iter().skip(1) { + if complex.is_empty() { + continue; + } + + let target = complex.last().unwrap().clone(); + + if complex.len() == 1 { + for prefix in prefixes.iter_mut() { + prefix.push(target.clone()); + } + continue; + } + + let complex_len = complex.len(); + + let parents: Vec = + complex.into_iter().take(complex_len - 1).collect(); + let mut new_prefixes: Vec> = Vec::new(); + + for prefix in prefixes { + let parent_prefixes = weave_parents(prefix, parents.clone()); + + if let Some(parent_prefixes) = parent_prefixes { + for mut parent_prefix in parent_prefixes { + parent_prefix.push(target.clone()); + new_prefixes.push(parent_prefix); + } + } else { + continue; + } + } + prefixes = new_prefixes; + } + + prefixes +} + +/// Interweaves `parents1` and `parents2` as parents of the same target selector. +/// +/// Returns all possible orderings of the selectors in the inputs (including +/// using unification) that maintain the relative ordering of the input. For +/// example, given `.foo .bar` and `.baz .bang`, this would return `.foo .bar +/// .baz .bang`, `.foo .bar.baz .bang`, `.foo .baz .bar .bang`, `.foo .baz +/// .bar.bang`, `.foo .baz .bang .bar`, and so on until `.baz .bang .foo .bar`. +/// +/// Semantically, for selectors A and B, this returns all selectors `AB_i` +/// such that the union over all i of elements matched by `AB_i X` is +/// identical to the intersection of all elements matched by `A X` and all +/// elements matched by `B X`. Some `AB_i` are elided to reduce the size of +/// the output. +fn weave_parents( + parents1: Vec, + parents2: Vec, +) -> Option>> { + let mut queue1 = VecDeque::from(parents1); + let mut queue2 = VecDeque::from(parents2); + + let initial_combinators = merge_initial_combinators(&mut queue1, &mut queue2)?; + + let mut final_combinators = merge_final_combinators(&mut queue1, &mut queue2, None)?; + + match (first_if_root(&mut queue1), first_if_root(&mut queue2)) { + (Some(root1), Some(root2)) => { + let root = ComplexSelectorComponent::Compound(root1.unify(root2)?); + queue1.push_front(root.clone()); + queue2.push_front(root); + } + (Some(root1), None) => { + queue2.push_front(ComplexSelectorComponent::Compound(root1)); + } + (None, Some(root2)) => { + queue1.push_front(ComplexSelectorComponent::Compound(root2)); + } + (None, None) => {} + } + + let mut groups1 = group_selectors(Vec::from(queue1)); + let mut groups2 = group_selectors(Vec::from(queue2)); + + let lcs = longest_common_subsequence( + Vec::from(groups2.clone()), + Vec::from(groups1.clone()), + Some(&|group1, group2| { + if group1 == group2 { + return Some(group1); + } + + if let ComplexSelectorComponent::Combinator(..) = group1.first()? { + return None; + } + if let ComplexSelectorComponent::Combinator(..) = group2.first()? { + return None; + } + + if complex_is_parent_superselector(group1.clone(), group2.clone()) { + return Some(group2); + } + if complex_is_parent_superselector(group2.clone(), group1.clone()) { + return Some(group1); + } + + if !must_unify(group1.clone(), group2.clone()) { + return None; + } + + let unified = unify_complex(vec![group1, group2])?; + if unified.len() > 1 { + return None; + } + + unified.first().cloned() + }), + ); + + let mut choices = vec![vec![initial_combinators + .into_iter() + .map(ComplexSelectorComponent::Combinator) + .collect::>()]]; + + for group in lcs { + choices.push( + chunks(&mut groups1, &mut groups2, |sequence| { + complex_is_parent_superselector(sequence.get(0).unwrap().clone(), group.clone()) + }) + .into_iter() + .map(|chunk| chunk.into_iter().flatten().collect()) + .collect(), + ); + choices.push(vec![group]); + groups1.pop_front(); + groups2.pop_front(); + } + + choices.push( + chunks(&mut groups1, &mut groups2, |sequence| sequence.is_empty()) + .into_iter() + .map(|chunk| chunk.into_iter().flatten().collect()) + .collect(), + ); + + choices.append(&mut final_combinators); + + Some( + paths( + choices + .into_iter() + .filter(|choice| !choice.is_empty()) + .collect(), + ) + .into_iter() + .map(|chunk| chunk.into_iter().flatten().collect()) + .collect(), + ) +} + +/// Extracts leading `Combinator`s from `components1` and `components2` and +/// merges them together into a single list of combinators. +/// +/// If there are no combinators to be merged, returns an empty list. If the +/// combinators can't be merged, returns `None`. +fn merge_initial_combinators( + components1: &mut VecDeque, + components2: &mut VecDeque, +) -> Option> { + let mut combinators1: Vec = Vec::new(); + + while let Some(ComplexSelectorComponent::Combinator(c)) = components1.get(0) { + combinators1.push(*c); + components1.pop_front(); + } + + let mut combinators2 = Vec::new(); + + while let Some(ComplexSelectorComponent::Combinator(c)) = components2.get(0) { + combinators2.push(*c); + components2.pop_front(); + } + + let lcs = longest_common_subsequence(combinators1.clone(), combinators2.clone(), None); + + if lcs == combinators1 { + Some(combinators2) + } else if lcs == combinators2 { + Some(combinators1) + } else { + // If neither sequence of combinators is a subsequence of the other, they + // cannot be merged successfully. + None + } +} + +/// Returns the longest common subsequence between `list1` and `list2`. +/// +/// If there are more than one equally long common subsequence, returns the one +/// which starts first in `list1`. +/// +/// If `select` is passed, it's used to check equality between elements in each +/// list. If it returns `None`, the elements are considered unequal; otherwise, +/// it should return the element to include in the return value. +fn longest_common_subsequence( + list1: Vec, + list2: Vec, + select: Option<&dyn Fn(T, T) -> Option>, +) -> Vec { + let select = select.unwrap_or(&|element1, element2| { + if element1 == element2 { + Some(element1) + } else { + None + } + }); + + let mut lengths = vec![vec![0; list2.len() + 1]; list1.len() + 1]; + + let mut selections: Vec>> = vec![vec![None; list2.len()]; list1.len()]; + + for i in 0..list1.len() { + for j in 0..list2.len() { + let selection = select(list1.get(i).unwrap().clone(), list2.get(j).unwrap().clone()); + selections[i][j] = selection.clone(); + lengths[i + 1][j + 1] = if selection.is_none() { + std::cmp::max(lengths[i + 1][j], lengths[i][j + 1]) + } else { + lengths[i][j] + 1 + }; + } + } + + fn backtrack( + i: isize, + j: isize, + lengths: Vec>, + selections: &mut Vec>>, + ) -> Vec { + if i == -1 || j == -1 { + return Vec::new(); + } + let selection = selections.get(i as usize).cloned().unwrap_or_else(Vec::new); + + if let Some(Some(selection)) = selection.get(j as usize) { + let mut tmp = backtrack(i - 1, j - 1, lengths, selections); + tmp.push(selection.clone()); + return tmp; + } + + if lengths[(i + 1) as usize][j as usize] > lengths[i as usize][(j + 1) as usize] { + backtrack(i, j - 1, lengths, selections) + } else { + backtrack(i - 1, j, lengths, selections) + } + } + backtrack( + list1.len() as isize - 1, + list2.len() as isize - 1, + lengths, + &mut selections, + ) +} + +/// Extracts trailing `Combinator`s, and the selectors to which they apply, from +/// `components1` and `components2` and merges them together into a single list. +/// +/// If there are no combinators to be merged, returns an empty list. If the +/// sequences can't be merged, returns `None`. +fn merge_final_combinators( + components1: &mut VecDeque, + components2: &mut VecDeque, + result: Option>>>, +) -> Option>>> { + let mut result = result.unwrap_or(VecDeque::new()); + + if (components1.is_empty() + || !components1 + .get(components1.len() - 1) + .unwrap() + .is_combinator()) + && (components2.is_empty() + || !components2 + .get(components2.len() - 1) + .unwrap() + .is_combinator()) + { + return Some(Vec::from(result)); + } + + let mut combinators1 = Vec::new(); + + while let Some(ComplexSelectorComponent::Combinator(combinator)) = + components1.get(components1.len().checked_sub(1).unwrap_or(0)) + { + combinators1.push(*combinator); + components1.pop_back(); + } + + let mut combinators2 = Vec::new(); + + while let Some(ComplexSelectorComponent::Combinator(combinator)) = + components2.get(components2.len().checked_sub(1).unwrap_or(0)) + { + combinators2.push(*combinator); + components2.pop_back(); + } + + if combinators1.len() > 1 || combinators2.len() > 1 { + // If there are multiple combinators, something hacky's going on. If one + // is a supersequence of the other, use that, otherwise give up. + let lcs = longest_common_subsequence(combinators1.clone(), combinators2.clone(), None); + if lcs == combinators1 { + result.push_front(vec![combinators2 + .into_iter() + .map(ComplexSelectorComponent::Combinator) + .rev() + .collect()]); + } else if lcs == combinators2 { + result.push_front(vec![combinators1 + .into_iter() + .map(ComplexSelectorComponent::Combinator) + .rev() + .collect()]); + } else { + return None; + } + + return Some(Vec::from(result)); + } + + let combinator1 = if combinators1.is_empty() { + None + } else { + combinators1.first() + }; + + let combinator2 = if combinators2.is_empty() { + None + } else { + combinators2.first() + }; + + // This code looks complicated, but it's actually just a bunch of special + // cases for interactions between different combinators. + match (combinator1, combinator2) { + (Some(combinator1), Some(combinator2)) => { + let compound1 = match components1.pop_back() { + Some(ComplexSelectorComponent::Compound(c)) => c, + Some(..) | None => unreachable!(), + }; + let compound2 = match components2.pop_back() { + Some(ComplexSelectorComponent::Compound(c)) => c, + Some(..) | None => unreachable!(), + }; + + match (combinator1, combinator2) { + (Combinator::FollowingSibling, Combinator::FollowingSibling) => { + if compound1.is_super_selector(&compound2, None) { + result.push_front(vec![vec![ + ComplexSelectorComponent::Compound(compound2), + ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), + ]]) + } else if compound2.is_super_selector(&compound1, None) { + result.push_front(vec![vec![ + ComplexSelectorComponent::Compound(compound1), + ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), + ]]) + } else { + let mut choices = vec![ + vec![ + ComplexSelectorComponent::Compound(compound1.clone()), + ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), + ComplexSelectorComponent::Compound(compound2.clone()), + ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), + ], + vec![ + ComplexSelectorComponent::Compound(compound2.clone()), + ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), + ComplexSelectorComponent::Compound(compound1.clone()), + ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), + ], + ]; + + if let Some(unified) = compound1.unify(compound2) { + choices.push(vec![ + ComplexSelectorComponent::Compound(unified), + ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), + ]) + } + + result.push_front(choices); + } + } + (Combinator::FollowingSibling, Combinator::NextSibling) + | (Combinator::NextSibling, Combinator::FollowingSibling) => { + let following_sibling_selector = if combinator1 == &Combinator::FollowingSibling + { + compound1.clone() + } else { + compound2.clone() + }; + + let next_sibling_selector = if combinator1 == &Combinator::FollowingSibling { + compound2.clone() + } else { + compound1.clone() + }; + + if following_sibling_selector.is_super_selector(&next_sibling_selector, None) { + result.push_front(vec![vec![ + ComplexSelectorComponent::Compound(next_sibling_selector), + ComplexSelectorComponent::Combinator(Combinator::NextSibling), + ]]); + } else { + let mut v = vec![vec![ + ComplexSelectorComponent::Compound(following_sibling_selector), + ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), + ComplexSelectorComponent::Compound(next_sibling_selector), + ComplexSelectorComponent::Combinator(Combinator::NextSibling), + ]]; + + if let Some(unified) = compound1.unify(compound2) { + v.push(vec![ + ComplexSelectorComponent::Compound(unified), + ComplexSelectorComponent::Combinator(Combinator::NextSibling), + ]); + } + result.push_front(v); + } + } + (Combinator::Child, Combinator::NextSibling) + | (Combinator::Child, Combinator::FollowingSibling) => { + result.push_front(vec![vec![ + ComplexSelectorComponent::Compound(compound2), + ComplexSelectorComponent::Combinator(*combinator2), + ]]); + components1.push_back(ComplexSelectorComponent::Compound(compound1)); + components1.push_back(ComplexSelectorComponent::Combinator(Combinator::Child)); + } + (Combinator::NextSibling, Combinator::Child) + | (Combinator::FollowingSibling, Combinator::Child) => { + result.push_front(vec![vec![ + ComplexSelectorComponent::Compound(compound1), + ComplexSelectorComponent::Combinator(*combinator1), + ]]); + components2.push_back(ComplexSelectorComponent::Compound(compound2)); + components2.push_back(ComplexSelectorComponent::Combinator(Combinator::Child)); + } + (..) => { + if combinator1 != combinator2 { + return None; + } + + let unified = compound1.unify(compound2)?; + + result.push_front(vec![vec![ + ComplexSelectorComponent::Compound(unified), + ComplexSelectorComponent::Combinator(*combinator1), + ]]); + } + } + + merge_final_combinators(components1, components2, Some(result)) + } + (Some(combinator1), None) => { + if *combinator1 == Combinator::Child && !components2.is_empty() { + if let Some(ComplexSelectorComponent::Compound(c1)) = + components1.get(components1.len() - 1) + { + if let Some(ComplexSelectorComponent::Compound(c2)) = + components2.get(components2.len() - 1) + { + if c2.is_super_selector(c1, None) { + components2.pop_back(); + } + } + } + } + + result.push_front(vec![vec![ + components1.pop_back().unwrap(), + ComplexSelectorComponent::Combinator(*combinator1), + ]]); + + merge_final_combinators(components1, components2, Some(result)) + } + (None, Some(combinator2)) => { + if *combinator2 == Combinator::Child && !components1.is_empty() { + if let Some(ComplexSelectorComponent::Compound(c1)) = + components1.get(components1.len() - 1) + { + if let Some(ComplexSelectorComponent::Compound(c2)) = + components2.get(components2.len() - 1) + { + if c1.is_super_selector(c2, None) { + components1.pop_back(); + } + } + } + } + + result.push_front(vec![vec![ + components2.pop_back().unwrap(), + ComplexSelectorComponent::Combinator(*combinator2), + ]]); + merge_final_combinators(components1, components2, Some(result)) + } + (None, None) => todo!("the above, but we dont have access to combinator2"), + } +} + +/// If the first element of `queue` has a `::root` selector, removes and returns +/// that element. +fn first_if_root(queue: &mut VecDeque) -> Option { + if queue.is_empty() { + return None; + } + if let Some(ComplexSelectorComponent::Compound(c)) = queue.get(0) { + if !has_root(c) { + return None; + } + let compound = c.clone(); + queue.pop_front(); + return Some(compound); + } else { + None + } +} + +/// Returns whether or not `compound` contains a `::root` selector. +fn has_root(compound: &CompoundSelector) -> bool { + compound.components.iter().any(|simple| { + if let SimpleSelector::Pseudo(pseudo) = simple { + pseudo.is_class && pseudo.normalized_name == "root" + } else { + false + } + }) +} + +/// Returns `complex`, grouped into sub-lists such that no sub-list contains two +/// adjacent `ComplexSelector`s. +/// +/// For example, `(A B > C D + E ~ > G)` is grouped into +/// `[(A) (B > C) (D + E ~ > G)]`. +fn group_selectors( + complex: Vec, +) -> VecDeque> { + let mut groups = VecDeque::new(); + + let mut iter = complex.into_iter(); + + let mut group = if let Some(c) = iter.next() { + vec![c] + } else { + return groups; + }; + + groups.push_back(group.clone()); + + while let Some(c) = iter.next() { + if group.last().map_or(false, |g| g.is_combinator()) || c.is_combinator() { + group.push(c); + } else { + group = vec![c]; + groups.push_back(group.clone()); + } + } + + groups +} + +/// Returns all orderings of initial subseqeuences of `queue1` and `queue2`. +/// +/// The `done` callback is used to determine the extent of the initial +/// subsequences. It's called with each queue until it returns `true`. +/// +/// This destructively removes the initial subsequences of `queue1` and +/// `queue2`. +/// +/// For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|` denoting +/// the boundary of the initial subsequence), this would return `[(A B C 1 2), +/// (1 2 A B C)]`. The queues would then contain `(D E)` and `(3 4 5)`. +fn chunks( + queue1: &mut VecDeque, + queue2: &mut VecDeque, + done: impl Fn(&VecDeque) -> bool, +) -> Vec> { + let mut chunk1 = Vec::new(); + while !done(&queue1) { + chunk1.push(queue1.pop_front().unwrap()); + } + + let mut chunk2 = Vec::new(); + while !done(&queue2) { + chunk2.push(queue2.pop_front().unwrap()); + } + + match (chunk1.is_empty(), chunk2.is_empty()) { + (true, true) => Vec::new(), + (true, false) => vec![chunk2], + (false, true) => vec![chunk1], + (false, false) => { + let mut l1 = chunk1.clone(); + l1.append(&mut chunk2.clone()); + + let mut l2 = chunk2.clone(); + l2.append(&mut chunk1); + + vec![l1, l2] + } + } +} + +/// Like `complex_is_superselector`, but compares `complex1` and `complex2` as +/// though they shared an implicit base `SimpleSelector`. +/// +/// For example, `B` is not normally a superselector of `B A`, since it doesn't +/// match elements that match `A`. However, it *is* a parent superselector, +/// since `B X` is a superselector of `B A X`. +fn complex_is_parent_superselector( + mut complex1: Vec, + mut complex2: Vec, +) -> bool { + if let Some(ComplexSelectorComponent::Combinator(..)) = complex1.first() { + return false; + } + if let Some(ComplexSelectorComponent::Combinator(..)) = complex2.first() { + return false; + } + if complex1.len() > complex2.len() { + return false; + } + let base = CompoundSelector { + components: vec![SimpleSelector::Placeholder(String::new())], + }; + complex1.push(ComplexSelectorComponent::Compound(base.clone())); + complex2.push(ComplexSelectorComponent::Compound(base)); + + ComplexSelector { + components: complex1, + line_break: false, + } + .is_super_selector(&ComplexSelector { + components: complex2, + line_break: false, + }) +} + +/// Returns a list of all possible paths through the given lists. +/// +/// For example, given `[[1, 2], [3, 4], [5]]`, this returns: +/// +/// ```norun +/// [[1, 3, 5], +/// [2, 3, 5], +/// [1, 4, 5], +/// [2, 4, 5]] +/// ``` +fn paths(choices: Vec>) -> Vec> { + choices.into_iter().fold(vec![vec![]], |paths, choice| { + choice + .into_iter() + .flat_map(move |option| { + paths.clone().into_iter().map(move |mut path| { + path.push(option.clone()); + path + }) + }) + .collect() + }) +} + +/// Returns whether `complex1` and `complex2` need to be unified to produce a +/// valid combined selector. +/// +/// This is necessary when both selectors contain the same unique simple +/// selector, such as an ID. +fn must_unify( + complex1: Vec, + complex2: Vec, +) -> bool { + let mut unique_selectors = Vec::new(); + for component in complex1 { + if let ComplexSelectorComponent::Compound(c) = component { + unique_selectors.extend(c.components.into_iter().filter(|f| is_unique(f))); + } + } + + if unique_selectors.is_empty() { + return false; + } + + complex2.iter().any(|component| { + if let ComplexSelectorComponent::Compound(compound) = component { + compound + .components + .iter() + .any(|simple| is_unique(simple) && unique_selectors.contains(simple)) + } else { + false + } + }) +} + +/// Returns whether a `CompoundSelector` may contain only one simple selector of +/// the same type as `simple`. +fn is_unique(simple: &SimpleSelector) -> bool { + matches!(simple, SimpleSelector::Id(..) | SimpleSelector::Pseudo(Pseudo { is_class: false, .. })) +} diff --git a/src/selector/list.rs b/src/selector/list.rs new file mode 100644 index 0000000..6af1d95 --- /dev/null +++ b/src/selector/list.rs @@ -0,0 +1,261 @@ +use std::collections::VecDeque; +use std::{ + fmt::{self, Write}, + mem, +}; + +use super::{unify_complex, ComplexSelector, ComplexSelectorComponent}; + +use crate::common::{Brackets, ListSeparator, QuoteKind}; +use crate::value::Value; + +/// A selector list. +/// +/// A selector list is composed of `ComplexSelector`s. It matches an element +/// that matches any of the component selectors. +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct SelectorList { + /// The components of this selector. + /// + /// This is never empty. + pub components: Vec, +} + +impl fmt::Display for SelectorList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let complexes = self.components.iter().filter(|c| !c.is_invisible()); + + let mut first = true; + + for complex in complexes { + if first { + first = false; + } else { + f.write_char(',')?; + if complex.line_break { + f.write_char('\n')?; + } else { + // todo: not emitted in compressed + f.write_char(' ')?; + } + } + write!(f, "{}", complex)?; + } + Ok(()) + } +} + +impl SelectorList { + pub fn is_invisible(&self) -> bool { + self.components.iter().all(|c| c.is_invisible()) + } + + pub fn contains_parent_selector(&self) -> bool { + self.components.iter().any(|c| c.contains_parent_selector()) + } + + pub fn new() -> Self { + Self { + components: Vec::new(), + } + } + + pub fn is_empty(&self) -> bool { + self.components.is_empty() + } + + /// Returns a SassScript list that represents this selector. + /// + /// This has the same format as a list returned by `selector-parse()`. + pub fn to_sass_list(self) -> Value { + Value::List( + self.components + .into_iter() + .map(|complex| { + Value::List( + complex + .components + .into_iter() + .map(|complex_component| { + Value::String(complex_component.to_string(), QuoteKind::None) + }) + .collect(), + ListSeparator::Space, + Brackets::None, + ) + }) + .collect(), + ListSeparator::Comma, + Brackets::None, + ) + } + + /// Returns a `SelectorList` that matches only elements that are matched by + /// both this and `other`. + /// + /// If no such list can be produced, returns `None`. + pub fn unify(self, other: Self) -> Option { + let contents: Vec = self + .components + .into_iter() + .flat_map(|c1| { + other.clone().components.into_iter().flat_map(move |c2| { + let unified: Option>> = + unify_complex(vec![c1.components.clone(), c2.components]); + if let Some(u) = unified { + u.into_iter() + .map(|c| ComplexSelector { + components: c, + line_break: false, + }) + .collect() + } else { + Vec::new() + } + }) + }) + .collect(); + + if contents.is_empty() { + return None; + } + + Some(Self { + components: contents, + }) + } + + /// Returns a new list with all `SimpleSelector::Parent`s replaced with `parent`. + /// + /// If `implicit_parent` is true, this treats `ComplexSelector`s that don't + /// contain an explicit `SimpleSelector::Parent` as though they began with one. + /// + /// The given `parent` may be `None`, indicating that this has no parents. If + /// so, this list is returned as-is if it doesn't contain any explicit + /// `SimpleSelector::Parent`s. If it does, this returns a `SassError`. + // todo: return SassResult (the issue is figuring out the span) + pub fn resolve_parent_selectors(self, parent: Option, implicit_parent: bool) -> Self { + let parent = match parent { + Some(p) => p, + None => { + if !self.contains_parent_selector() { + return self; + } + todo!("Top-level selectors may not contain the parent selector \"&\".") + } + }; + + Self { + components: flatten_vertically( + self.components + .into_iter() + .map(|complex| { + if !complex.contains_parent_selector() { + if !implicit_parent { + return vec![complex]; + } + return parent + .clone() + .components + .into_iter() + .map(move |parent_complex| { + let mut components = parent_complex.components; + components.append(&mut complex.components.clone()); + ComplexSelector { + components, + line_break: complex.line_break || parent_complex.line_break, + } + }) + .collect(); + } + + let mut new_complexes: Vec> = + vec![Vec::new()]; + let mut line_breaks = vec![false]; + + for component in complex.components { + if component.is_compound() { + let resolved = match component + .clone() + .resolve_parent_selectors(parent.clone()) + { + Some(r) => r, + None => { + for new_complex in new_complexes.iter_mut() { + new_complex.push(component.clone()); + } + continue; + } + }; + + let previous_complexes = mem::take(&mut new_complexes); + let previous_line_breaks = mem::take(&mut line_breaks); + + let mut i = 0; + for new_complex in previous_complexes { + let line_break = previous_line_breaks[i]; + i += 1; + for mut resolved_complex in resolved.clone() { + let mut new_this_complex = new_complex.clone(); + new_this_complex.append(&mut resolved_complex.components); + new_complexes.push(mem::take(&mut new_this_complex)); + line_breaks.push(line_break || resolved_complex.line_break); + } + } + } else { + for new_complex in new_complexes.iter_mut() { + new_complex.push(component.clone()); + } + } + } + + let mut i = 0; + return new_complexes + .into_iter() + .map(|new_complex| { + i += 1; + ComplexSelector { + components: new_complex, + line_break: line_breaks[i - 1], + } + }) + .collect(); + }) + .collect(), + ), + } + } + + pub fn is_superselector(&self, other: &Self) -> bool { + other.components.iter().all(|complex1| { + self.components + .iter() + .any(|complex2| complex2.is_super_selector(complex1)) + }) + } +} + +fn flatten_vertically(iterable: Vec>) -> Vec { + let mut queues: Vec> = iterable + .into_iter() + .map(|inner| VecDeque::from(inner)) + .collect(); + + let mut result = Vec::new(); + + while !queues.is_empty() { + for queue in queues.iter_mut() { + if queue.is_empty() { + continue; + } + result.push(queue.pop_front().unwrap()); + } + + queues = queues + .into_iter() + .filter(|queue| !queue.is_empty()) + .collect(); + } + + result +} diff --git a/src/selector/mod.rs b/src/selector/mod.rs index 06461b3..9beae81 100644 --- a/src/selector/mod.rs +++ b/src/selector/mod.rs @@ -1,234 +1,57 @@ -use std::fmt::{self, Display, Write}; +#![allow(dead_code, unused_variables, unused_mut)] -use codemap::Span; +use std::fmt::{self, Display}; use peekmore::{PeekMore, PeekMoreIterator}; -use crate::common::{Brackets, ListSeparator, QuoteKind}; use crate::error::SassResult; use crate::scope::Scope; -use crate::utils::{ - devour_whitespace, eat_comment, eat_ident_no_interpolation, parse_interpolation, - read_until_closing_paren, read_until_newline, IsWhitespace, -}; +use crate::utils::{devour_whitespace, eat_comment, parse_interpolation, read_until_newline}; use crate::value::Value; use crate::Token; -use attribute::Attribute; +pub(crate) use attribute::Attribute; +pub(crate) use common::*; +pub(crate) use complex::*; +pub(crate) use compound::*; +pub(crate) use extend::*; +pub(crate) use list::*; +pub(crate) use parse::*; +pub(crate) use simple::*; mod attribute; +mod common; +mod complex; +mod compound; +mod extend; +mod list; +mod parse; +mod simple; #[derive(Clone, Debug, Eq, PartialEq)] -pub(crate) struct Selector(Vec); - -#[derive(Clone, Debug, Eq, PartialEq)] -struct SelectorPart { - pub inner: Vec, - pub is_invisible: bool, - pub has_newline: bool, - pub contains_super_selector: bool, -} - -impl SelectorPart { - pub fn into_value(&self) -> Value { - let mut kinds = Vec::new(); - let mut this_kind = Vec::new(); - for kind in &self.inner { - match kind { - SelectorKind::Whitespace => { - if !this_kind.is_empty() { - kinds.push(SelectorPart { - inner: std::mem::take(&mut this_kind), - is_invisible: false, - has_newline: false, - contains_super_selector: false, - }); - } - } - v => this_kind.push(v.clone()), - } - } - if !this_kind.is_empty() { - kinds.push(SelectorPart { - inner: std::mem::take(&mut this_kind), - is_invisible: false, - has_newline: false, - contains_super_selector: false, - }); - } - Value::List( - kinds - .iter() - .map(|s| Value::String(s.to_string(), QuoteKind::None)) - .collect(), - ListSeparator::Space, - Brackets::None, - ) - } -} - -impl Display for SelectorPart { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut iter = self.inner.iter().peekmore(); - devour_whitespace(&mut iter); - while let Some(s) = iter.next() { - write!(f, "{}", s)?; - if devour_whitespace(&mut iter) { - match iter.peek() { - Some(SelectorKind::Universal) - | Some(SelectorKind::Following) - | Some(SelectorKind::ImmediateChild) - | Some(SelectorKind::Preceding) => { - f.write_char(' ')?; - write!(f, "{}", iter.next().unwrap())?; - devour_whitespace(&mut iter); - if iter.peek().is_some() { - f.write_char(' ')?; - } - } - Some(..) => { - f.write_char(' ')?; - } - None => break, - } - } - } - Ok(()) - } -} +pub(crate) struct Selector(pub SelectorList); impl Display for Selector { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for (idx, part) in self.0.iter().enumerate() { - write!(f, "{}", part)?; - if idx + 1 < self.0.len() { - f.write_char(',')?; - if part.has_newline { - f.write_char('\n')?; - } else { - f.write_char(' ')?; - } - } - } - Ok(()) + write!(f, "{}", self.0) } } -#[derive(Clone, Debug, Eq, PartialEq)] -pub(crate) enum SelectorKind { - /// Any string - /// - /// `button` - Element(String), - - /// An id selector - /// - /// `#` - Id(String), - - /// A class selector - /// - /// `.` - Class(String), - - /// A universal selector - /// - /// `*` - Universal, - - /// Select all immediate children - /// - /// `>` - ImmediateChild, - - /// Select all elements immediately following - /// - /// `+` - Following, - - /// Select elements preceeded by - /// - /// `~` - Preceding, - - /// Select elements with attribute - /// - /// `[lang|=en]` - Attribute(Attribute), - - Super, - - /// Pseudo selector - /// - /// `:hover` - Pseudo(String), - - /// Pseudo element selector - /// - /// `::before` - PseudoElement(String), - - /// Pseudo selector with additional parens - /// - /// `:any(h1, h2, h3, h4, h5, h6)` - PseudoParen(String, Selector), - - /// Placeholder selector - /// - /// `%` - Placeholder(String), - - /// Denotes whitespace between two selectors - Whitespace, -} - -impl IsWhitespace for &SelectorKind { - fn is_whitespace(&self) -> bool { - self == &&SelectorKind::Whitespace - } -} - -impl Display for SelectorKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - SelectorKind::Element(s) => write!(f, "{}", s), - SelectorKind::Id(s) => write!(f, "#{}", s), - SelectorKind::Class(s) => write!(f, ".{}", s), - SelectorKind::Universal => write!(f, "*"), - SelectorKind::ImmediateChild => write!(f, ">"), - SelectorKind::Following => write!(f, "+"), - SelectorKind::Preceding => write!(f, "~"), - SelectorKind::Attribute(attr) => write!(f, "{}", attr), - SelectorKind::Pseudo(s) => write!(f, ":{}", s), - SelectorKind::PseudoElement(s) => write!(f, "::{}", s), - SelectorKind::PseudoParen(s, val) => write!(f, ":{}({})", s, val), - SelectorKind::Placeholder(s) => write!(f, "%{}", s), - SelectorKind::Super => unreachable!("& selector should not be emitted"), - SelectorKind::Whitespace => f.write_char(' '), - } - } -} - -fn is_selector_name_char(c: char) -> bool { - c.is_ascii_alphanumeric() - || c == '_' - || c == '\\' - || (!c.is_ascii() && !c.is_control()) - || c == '-' -} - impl Selector { pub fn from_tokens>( toks: &mut PeekMoreIterator, scope: &Scope, super_selector: &Selector, + allows_parent: bool, ) -> SassResult { let mut string = String::new(); + let mut span = if let Some(tok) = toks.peek() { tok.pos() } else { return Ok(Selector::new()); }; + while let Some(tok) = toks.next() { span = span.merge(tok.pos()); match tok.kind { @@ -279,344 +102,66 @@ impl Selector { break; } - let mut inner = Vec::new(); - let mut is_invisible = false; - let mut has_newline = false; - let mut contains_super_selector = false; - let mut parts = Vec::new(); - - let mut sel_toks = Vec::new(); - - sel_toks.extend(string.chars().map(|x| Token::new(span, x))); + let sel_toks: Vec = string.chars().map(|x| Token::new(span, x)).collect(); let mut iter = sel_toks.into_iter().peekmore(); - while let Some(tok) = iter.peek() { - let Token { kind, pos } = *tok; - inner.push(match kind { - _ if is_selector_name_char(kind) => { - inner.push(SelectorKind::Element( - eat_ident_no_interpolation(&mut iter, false, pos)?.node, - )); - continue; - } - '&' => { - contains_super_selector = true; - SelectorKind::Super - } - '.' => { - iter.next(); - inner.push(SelectorKind::Class( - eat_ident_no_interpolation(&mut iter, false, pos)?.node, - )); - continue; - } - '#' => { - iter.next(); - inner.push(SelectorKind::Id( - eat_ident_no_interpolation(&mut iter, false, pos)?.node, - )); - continue; - } - '%' => { - iter.next(); - is_invisible = true; - inner.push(SelectorKind::Placeholder( - eat_ident_no_interpolation(&mut iter, false, pos)?.node, - )); - continue; - } - '>' => SelectorKind::ImmediateChild, - '+' => SelectorKind::Following, - '~' => SelectorKind::Preceding, - '*' => SelectorKind::Universal, - ',' => { - iter.next(); - if iter.peek().is_some() && iter.peek().unwrap().kind == '\n' { - has_newline = true; - } - if !inner.is_empty() { - parts.push(SelectorPart { - inner: inner.clone(), - is_invisible, - has_newline, - contains_super_selector, - }); - inner.clear(); - } - is_invisible = false; - has_newline = false; - contains_super_selector = false; - devour_whitespace(&mut iter); - continue; - } - '[' => { - let span = iter.next().unwrap().pos(); - inner.push(Attribute::from_tokens( - &mut iter, - scope, - super_selector, - span, - )?); - continue; - } - ':' => { - iter.next(); - let sel = Self::consume_pseudo_selector(&mut iter, scope, super_selector, pos)?; - match &sel { - SelectorKind::PseudoParen(_, s) => { - if s.contains_super_selector() { - contains_super_selector = true; - } - } - _ => {} - } - inner.push(sel); - continue; - } - c if c.is_whitespace() => { - if devour_whitespace(&mut iter) { - inner.push(SelectorKind::Whitespace); - } - continue; - } - _ => return Err(("expected selector.", tok.pos()).into()), - }); - iter.next(); - } - - if !inner.is_empty() { - parts.push(SelectorPart { - inner, - is_invisible, - has_newline, - contains_super_selector, - }); - } - - Ok(Selector(parts)) - } - - fn consume_pseudo_selector>( - toks: &mut PeekMoreIterator, - scope: &Scope, - super_selector: &Selector, - span_before: Span, - ) -> SassResult { - let is_pseudo_element = if toks - .peek() - .ok_or(("Expected identifier.", span_before))? - .kind - == ':' - { - toks.next(); - true - } else { - false - }; - let t = if let Some(tok) = toks.peek() { - *tok - } else { - todo!() - }; - - if !is_selector_name_char(t.kind) { - return Err(("Expected identifier.", t.pos).into()); - } - - let name = eat_ident_no_interpolation(toks, false, t.pos)?.node; - Ok( - if toks.peek().is_some() && toks.peek().unwrap().kind == '(' { - toks.next(); - let mut inner_toks = read_until_closing_paren(toks)?; - inner_toks.pop(); - let inner = Selector::from_tokens( - &mut inner_toks.into_iter().peekmore(), - scope, - super_selector, - )?; - SelectorKind::PseudoParen(name, inner) - } else if is_pseudo_element { - SelectorKind::PseudoElement(name) - } else { - SelectorKind::Pseudo(name) - }, - ) + Ok(Selector( + SelectorParser::new(&mut iter, scope, super_selector, allows_parent, true, span) + .parse()?, + )) } pub fn replace(super_selector: &Selector, this: Selector) -> Selector { - if super_selector.0.is_empty() || this.0.is_empty() { - return this; - } - let mut parts = Vec::with_capacity(super_selector.0.len()); - for (idx, part) in super_selector.clone().0.into_iter().enumerate() { - let mut found_inner = false; - for part2 in this.clone().0 { - if !part2.contains_super_selector { - if idx == 0 { - parts.push(part2); - } - continue; - } - let mut kinds = Vec::new(); - for kind in part2.clone().inner { - match kind { - SelectorKind::Super => kinds.extend(part.inner.clone()), - SelectorKind::PseudoParen(name, inner) => { - if inner.contains_super_selector() { - found_inner = true; - kinds.push(SelectorKind::PseudoParen( - name, - Selector::replace(super_selector, inner), - )) - } else { - kinds.push(SelectorKind::PseudoParen(name, inner)); - } - } - _ => kinds.push(kind), - } - } - parts.push(SelectorPart { - inner: kinds, - is_invisible: part2.is_invisible, - has_newline: part2.has_newline, - contains_super_selector: false, - }); - } - if found_inner { - break; - } - } - Selector(parts) + todo!() } - pub fn zip(&self, other: &Selector) -> Selector { - if self.0.is_empty() { - return Selector(other.0.clone()); - } else if other.0.is_empty() { - return self.clone(); - } - let mut rules = Vec::with_capacity(self.0.len()); - for sel1 in self.clone().0 { - let mut found_inner = false; - for sel2 in other.clone().0 { - let mut this_selector: Vec = Vec::with_capacity(other.0.len()); - let mut found_super = false; + /// Small wrapper around `SelectorList`'s method that turns an empty parent selector + /// into `None`. This is a hack and in the future should be replaced. + // todo: take Option for parent + pub fn resolve_parent_selectors(&self, parent: &Self, implicit_parent: bool) -> Self { + Self(self.0.clone().resolve_parent_selectors( + if parent.is_empty() { + None + } else { + Some(parent.0.clone()) + }, + implicit_parent, + )) + } - for sel in sel2.inner { - match sel { - SelectorKind::Super => { - this_selector.extend(sel1.inner.clone()); - found_super = true; - } - SelectorKind::PseudoParen(s, inner_selector) => { - if inner_selector.contains_super_selector() { - found_super = true; - found_inner = true; - this_selector.push(SelectorKind::PseudoParen( - s, - Selector::replace(self, inner_selector), - )) - } else { - this_selector.push(SelectorKind::PseudoParen(s, inner_selector)); - } - } - _ => this_selector.push(sel), - } - } + pub fn is_super_selector(&self, other: &Self) -> bool { + self.0.is_superselector(&other.0) + } - if !found_super { - let mut x = std::mem::take(&mut this_selector); - let mut y = sel1.clone().inner; - y.push(SelectorKind::Whitespace); - y.append(&mut x); - this_selector = y; - } - rules.push(SelectorPart { - inner: this_selector, - is_invisible: sel1.is_invisible || sel2.is_invisible, - has_newline: (sel1.has_newline || sel2.has_newline) && !found_inner, - contains_super_selector: false, - }); - } - if found_inner { - break; - } - } - Selector(rules) + pub fn contains_parent_selector(&self) -> bool { + self.0.contains_parent_selector() } pub fn remove_placeholders(self) -> Selector { - Selector( - self.0 + Self(SelectorList { + components: self + .0 + .components .into_iter() - .filter_map(|s| { - if s.is_invisible { - None - } else { - let mut inner = Vec::new(); - let mut last_was_whitespace = false; - let len = s.inner.len(); - for kind in s.inner { - match kind { - SelectorKind::PseudoParen(name, inner_selector) => { - let inner_empty = inner_selector.is_empty(); - let removed_placeholders = inner_selector.remove_placeholders(); - if removed_placeholders.is_empty() && !inner_empty { - if name.to_ascii_lowercase().as_str() == "not" { - if last_was_whitespace || len == 1 { - inner.push(SelectorKind::Universal); - } else { - continue; - } - } else { - return None; - } - } else { - inner.push(SelectorKind::PseudoParen( - name, - removed_placeholders, - )); - } - } - SelectorKind::Whitespace => { - last_was_whitespace = true; - inner.push(kind); - continue; - } - _ => inner.push(kind), - } - last_was_whitespace = false; - } - Some(SelectorPart { - inner, - is_invisible: false, - has_newline: s.has_newline, - contains_super_selector: s.contains_super_selector, - }) - } - }) + .filter(|c| !c.is_invisible()) .collect(), - ) + }) } pub fn is_empty(&self) -> bool { self.0.is_empty() } - pub const fn new() -> Selector { - Selector(Vec::new()) + pub fn new() -> Selector { + Selector(SelectorList::new()) } - pub fn contains_super_selector(&self) -> bool { - self.0.iter().any(|s| s.contains_super_selector) + pub fn into_value(self) -> Value { + self.0.to_sass_list() } - pub fn into_value(&self) -> Value { - Value::List( - self.0.iter().map(SelectorPart::into_value).collect(), - ListSeparator::Comma, - Brackets::None, - ) + pub fn unify(self, other: Self) -> Option { + Some(Selector(self.0.unify(other.0)?)) } } diff --git a/src/selector/parse.rs b/src/selector/parse.rs new file mode 100644 index 0000000..d4cd16e --- /dev/null +++ b/src/selector/parse.rs @@ -0,0 +1,583 @@ +use peekmore::PeekMoreIterator; + +use codemap::Span; + +use super::{ + Attribute, Combinator, ComplexSelector, ComplexSelectorComponent, CompoundSelector, Namespace, + Pseudo, QualifiedName, SelectorList, SimpleSelector, +}; +use crate::error::SassResult; +use crate::scope::Scope; +use crate::selector::Selector; +use crate::utils::{ + devour_whitespace, eat_ident, is_name, is_name_start, read_until_closing_paren, +}; +use crate::Token; + +#[derive(PartialEq)] +enum DevouredWhitespace { + /// Some whitespace was found + Whitespace, + /// A newline and potentially other whitespace was found + Newline, + /// No whitespace was found + None, +} + +impl DevouredWhitespace { + fn found_whitespace(&mut self) { + if self == &Self::None { + *self = Self::Whitespace; + } + } + + fn found_newline(&mut self) { + *self = Self::Newline; + } +} + +/// Pseudo-class selectors that take unadorned selectors as arguments. +const SELECTOR_PSEUDO_CLASSES: [&'static str; 7] = [ + "not", + "matches", + "current", + "any", + "has", + "host", + "host-context", +]; + +/// Pseudo-element selectors that take unadorned selectors as arguments. +const SELECTOR_PSEUDO_ELEMENTS: [&'static str; 1] = ["slotted"]; + +pub(crate) struct SelectorParser<'a, I: Iterator> { + /// Whether this parser allows the parent selector `&`. + allows_parent: bool, + + /// Whether this parser allows placeholder selectors beginning with `%`. + allows_placeholder: bool, + + toks: &'a mut PeekMoreIterator, + + scope: &'a Scope, + + super_selector: &'a Selector, + + span: Span, +} + +impl<'a, I: Iterator> SelectorParser<'a, I> { + pub fn new( + toks: &'a mut PeekMoreIterator, + scope: &'a Scope, + super_selector: &'a Selector, + allows_parent: bool, + allows_placeholder: bool, + span: Span, + ) -> Self { + Self { + toks, + scope, + super_selector, + allows_parent, + allows_placeholder, + span, + } + } + + pub fn parse(mut self) -> SassResult { + let tmp = self.parse_selector_list()?; + if self.toks.peek().is_some() { + return Err(("expected selector.", self.span).into()); + } + Ok(tmp) + } + + fn parse_selector_list(&mut self) -> SassResult { + let mut components = vec![self.parse_complex_selector(false)?]; + + devour_whitespace(self.toks); + + let mut line_break = false; + + while let Some(Token { kind: ',', .. }) = self.toks.peek() { + self.toks.next(); + line_break = self.eat_whitespace() == DevouredWhitespace::Newline || line_break; + match self.toks.peek() { + Some(Token { kind: ',', .. }) => continue, + Some(..) => {} + None => break, + } + components.push(self.parse_complex_selector(line_break)?); + + line_break = false; + } + + return Ok(SelectorList { components }); + } + + fn eat_whitespace(&mut self) -> DevouredWhitespace { + let mut whitespace_devoured = DevouredWhitespace::None; + while let Some(tok) = self.toks.peek() { + match tok.kind { + ' ' | '\t' => whitespace_devoured.found_whitespace(), + '\n' => whitespace_devoured.found_newline(), + _ => break, + } + self.toks.next(); + } + + whitespace_devoured + } + + /// Consumes a complex selector. + /// + /// If `line_break` is `true`, that indicates that there was a line break + /// before this selector. + fn parse_complex_selector(&mut self, line_break: bool) -> SassResult { + let mut components = Vec::new(); + + // todo: or patterns + loop { + devour_whitespace(self.toks); + + // todo: can we do while let Some(..) = self.toks.peek() ? + match self.toks.peek() { + Some(Token { kind: '+', .. }) => { + self.toks.next(); + components.push(ComplexSelectorComponent::Combinator( + Combinator::NextSibling, + )); + } + Some(Token { kind: '>', .. }) => { + self.toks.next(); + components.push(ComplexSelectorComponent::Combinator(Combinator::Child)) + } + Some(Token { kind: '~', .. }) => { + self.toks.next(); + components.push(ComplexSelectorComponent::Combinator( + Combinator::FollowingSibling, + )); + } + Some(Token { kind: '[', .. }) + | Some(Token { kind: '.', .. }) + | Some(Token { kind: '#', .. }) + | Some(Token { kind: '%', .. }) + | Some(Token { kind: ':', .. }) + // todo: ampersand? + | Some(Token { kind: '&', .. }) + | Some(Token { kind: '*', .. }) + | Some(Token { kind: '|', .. }) => { + components.push(ComplexSelectorComponent::Compound( + self.parse_compound_selector()?, + )); + if let Some(Token { kind: '&', .. }) = self.toks.peek() { + return Err(("\"&\" may only used at the beginning of a compound selector.", self.span).into()); + } + } + Some(..) => { + if !self.looking_at_identifier() { + break; + } + components.push(ComplexSelectorComponent::Compound( + self.parse_compound_selector()?, + )); + if let Some(Token { kind: '&', .. }) = self.toks.peek() { + return Err(("\"&\" may only used at the beginning of a compound selector.", self.span).into()); + } + } + None => break, + } + } + + if components.is_empty() { + return Err(("expected selector.", self.span).into()); + } + + Ok(ComplexSelector { + components, + line_break, + }) + } + + fn parse_compound_selector(&mut self) -> SassResult { + let mut components = vec![self.parse_simple_selector(true)?]; + + while let Some(Token { kind, .. }) = self.toks.peek() { + if !is_simple_selector_start(*kind) { + break; + } + + components.push(self.parse_simple_selector(false)?); + } + + Ok(CompoundSelector { components }) + } + + /// Returns whether the scanner is immediately before a plain CSS identifier. + /// + // todo: foward arg + /// If `forward` is passed, this looks that many characters forward instead. + /// + /// This is based on [the CSS algorithm][], but it assumes all backslashes + /// start escapes. + /// + /// [the CSS algorithm]: https://drafts.csswg.org/css-syntax-3/#would-start-an-identifier + fn looking_at_identifier(&mut self) -> bool { + match self.toks.peek() { + Some(Token { kind, .. }) if is_name_start(*kind) || kind == &'\\' => return true, + Some(Token { kind: '-', .. }) => {} + Some(..) | None => return false, + } + + match self.toks.peek_forward(1) { + Some(Token { kind, .. }) if is_name_start(*kind) || kind == &'-' || kind == &'\\' => { + self.toks.reset_view(); + true + } + Some(..) | None => { + self.toks.reset_view(); + false + } + } + } + + fn looking_at_identifier_body(&mut self) -> bool { + matches!(self.toks.peek(), Some(t) if is_name(t.kind) || t.kind == '\\') + } + + /// Consumes a simple selector. + fn parse_simple_selector(&mut self, allow_parent: bool) -> SassResult { + match self.toks.peek() { + Some(Token { kind: '[', .. }) => self.parse_attribute_selector(), + Some(Token { kind: '.', .. }) => self.parse_class_selector(), + Some(Token { kind: '#', .. }) => self.parse_id_selector(), + Some(Token { kind: '%', .. }) => { + if !self.allows_placeholder { + return Err(("Placeholder selectors aren't allowed here.", self.span).into()); + } + self.parse_placeholder_selector() + } + Some(Token { kind: ':', .. }) => self.parse_pseudo_selector(), + Some(Token { kind: '&', .. }) => { + if !allow_parent && !self.allows_parent { + return Err(("Parent selectors aren't allowed here.", self.span).into()); + } + self.parse_parent_selector() + } + _ => self.parse_type_or_universal_selector(), + } + } + + fn parse_attribute_selector(&mut self) -> SassResult { + let start = self.toks.next().unwrap().pos; + Ok(SimpleSelector::Attribute(Attribute::from_tokens( + self.toks, + self.scope, + self.super_selector, + start, + )?)) + } + + fn parse_class_selector(&mut self) -> SassResult { + let span_before = self.toks.next().unwrap().pos; + Ok(SimpleSelector::Class( + eat_ident(self.toks, self.scope, self.super_selector, span_before)?.node, + )) + } + + fn parse_id_selector(&mut self) -> SassResult { + let span_before = self.toks.next().unwrap().pos; + Ok(SimpleSelector::Id( + eat_ident(self.toks, self.scope, self.super_selector, span_before)?.node, + )) + } + + fn parse_pseudo_selector(&mut self) -> SassResult { + let span_before = self.toks.next().unwrap().pos; + let element = match self.toks.peek() { + Some(Token { kind: ':', .. }) => { + self.toks.next(); + true + } + _ => false, + }; + + let name = eat_ident(self.toks, self.scope, self.super_selector, span_before)?; + + match self.toks.peek() { + Some(Token { kind: '(', .. }) => self.toks.next(), + _ => { + return Ok(SimpleSelector::Pseudo(Pseudo { + // todo: we can store the reference to this + normalized_name: unvendor(&name.node).to_string(), + is_class: !element && !is_fake_pseudo_element(&name), + name: name.node, + selector: None, + is_syntactic_class: !element, + argument: None, + })); + } + }; + + devour_whitespace(self.toks); + + let unvendored = unvendor(&name); + + let mut argument: Option = None; + let mut selector: Option = None; + + if element { + // todo: lowercase? + if SELECTOR_PSEUDO_ELEMENTS.contains(&unvendored) { + selector = Some(self.parse_selector_list()?); + devour_whitespace(self.toks); + assert!(matches!(self.toks.next(), Some(Token { kind: ')', .. }))); + } else { + argument = Some(self.declaration_value(true)?); + } + } else if SELECTOR_PSEUDO_CLASSES.contains(&unvendored) { + selector = Some(self.parse_selector_list()?); + devour_whitespace(self.toks); + assert!(matches!(self.toks.next(), Some(Token { kind: ')', .. }))); + } else if unvendored == "nth-child" || unvendored == "nth-last-child" { + let mut this_arg = self.parse_a_n_plus_b()?; + let found_whitespace = devour_whitespace(self.toks); + match (found_whitespace, self.toks.peek()) { + (_, Some(Token { kind: ')', .. })) => {} + (true, _) => { + expect_identifier("of")?; + this_arg.push_str("of"); + devour_whitespace(self.toks); + selector = Some(self.parse_selector_list()?); + } + _ => {} + } + argument = Some(this_arg); + } else { + argument = Some(self.declaration_value(true)?.trim_end().to_string()); + } + + // todo: closing paren is consumed + // if let Some(Token { kind: ')', .. }) = dbg!(self.toks.next()) { + // } else { + // todo!("expected \")\"."); + // } + + Ok(SimpleSelector::Pseudo(Pseudo { + normalized_name: unvendor(&name.node).to_string(), + is_class: !element && !is_fake_pseudo_element(&name), + name: name.node, + selector, + // todo: we can store the reference to this + is_syntactic_class: !element, + argument, + })) + } + + fn parse_parent_selector(&mut self) -> SassResult { + let span_before = self.toks.next().unwrap().pos; + let suffix = if self.looking_at_identifier_body() { + Some(eat_ident(self.toks, self.scope, self.super_selector, span_before)?.node) + } else { + None + }; + Ok(SimpleSelector::Parent(suffix)) + } + + fn parse_placeholder_selector(&mut self) -> SassResult { + let span_before = self.toks.next().unwrap().pos; + Ok(SimpleSelector::Placeholder( + eat_ident(self.toks, self.scope, self.super_selector, span_before)?.node, + )) + } + + /// Consumes a type selector or a universal selector. + /// + /// These are combined because either one could start with `*`. + fn parse_type_or_universal_selector(&mut self) -> SassResult { + let span_before = self.toks.peek().unwrap().pos; + + match self.toks.peek().cloned() { + Some(Token { kind: '*', pos }) => { + self.toks.next(); + if let Some(Token { kind: '|', .. }) = self.toks.peek() { + self.toks.next(); + if let Some(Token { kind: '*', .. }) = self.toks.peek() { + self.toks.next(); + return Ok(SimpleSelector::Universal(Namespace::Asterisk)); + } else { + return Ok(SimpleSelector::Type(QualifiedName { + ident: eat_ident(self.toks, self.scope, self.super_selector, pos)?.node, + namespace: Namespace::Asterisk, + })); + } + } else { + return Ok(SimpleSelector::Universal(Namespace::None)); + } + } + Some(Token { kind: '|', pos }) => { + self.toks.next(); + match self.toks.peek() { + Some(Token { kind: '*', .. }) => { + self.toks.next(); + return Ok(SimpleSelector::Universal(Namespace::Empty)); + } + _ => { + return Ok(SimpleSelector::Type(QualifiedName { + ident: eat_ident(self.toks, self.scope, self.super_selector, pos)?.node, + namespace: Namespace::Empty, + })); + } + } + } + _ => {} + } + + let name_or_namespace = + eat_ident(self.toks, self.scope, self.super_selector, span_before)?.node; + + Ok(match self.toks.peek() { + Some(Token { kind: '|', .. }) => { + self.toks.next(); + if let Some(Token { kind: '*', .. }) = self.toks.peek() { + self.toks.next(); + SimpleSelector::Universal(Namespace::Other(name_or_namespace)) + } else { + SimpleSelector::Type(QualifiedName { + ident: eat_ident(self.toks, self.scope, self.super_selector, span_before)? + .node, + namespace: Namespace::Other(name_or_namespace), + }) + } + } + Some(..) | None => SimpleSelector::Type(QualifiedName { + ident: name_or_namespace, + namespace: Namespace::None, + }), + }) + } + + /// Consumes an [`An+B` production][An+B] and returns its text. + /// + /// [An+B]: https://drafts.csswg.org/css-syntax-3/#anb-microsyntax + fn parse_a_n_plus_b(&mut self) -> SassResult { + let mut buf = String::new(); + + match self.toks.peek() { + Some(Token { kind: 'e', .. }) | Some(Token { kind: 'E', .. }) => todo!("even"), + Some(Token { kind: 'o', .. }) | Some(Token { kind: 'O', .. }) => todo!("odd"), + Some(t @ Token { kind: '+', .. }) | Some(t @ Token { kind: '-', .. }) => { + buf.push(t.kind); + self.toks.next(); + } + _ => {} + } + + match self.toks.peek() { + Some(t) if t.kind.is_ascii_digit() => { + while let Some(t) = self.toks.peek() { + if !t.kind.is_ascii_digit() { + break; + } + devour_whitespace(self.toks); + if let Some(t) = self.toks.next() { + if t.kind != 'n' && t.kind != 'N' { + return Ok(buf); + } + } + } + } + Some(t) => { + if t.kind == 'n' || t.kind == 'N' { + self.toks.next(); + } else { + todo!() + } + } + None => todo!(), + } + + buf.push('n'); + + devour_whitespace(self.toks); + + if let Some(Token { kind: '+', .. }) | Some(Token { kind: '-', .. }) = self.toks.peek() { + buf.push(self.toks.next().unwrap().kind); + devour_whitespace(self.toks); + match self.toks.peek() { + Some(t) if !t.kind.is_ascii_digit() => { + return Err(("Expected a number.", self.span).into()) + } + None => return Err(("Expected a number.", self.span).into()), + _ => {} + } + + while let Some(t) = self.toks.peek() { + if !t.kind.is_ascii_digit() { + break; + } + buf.push(self.toks.next().unwrap().kind); + } + } + Ok(buf) + } + + fn declaration_value(&mut self, allow_empty: bool) -> SassResult { + // todo: this consumes the closing paren + let mut tmp = read_until_closing_paren(self.toks)?; + if let Some(Token { kind: ')', .. }) = tmp.pop() { + } else { + return Err(("expected \")\".", self.span).into()); + } + Ok(tmp.into_iter().map(|t| t.kind).collect::()) + } +} + +/// Returns whether `c` can start a simple selector other than a type +/// selector. +fn is_simple_selector_start(c: char) -> bool { + matches!(c, '*' | '[' | '.' | '#' | '%' | ':') +} + +/// Returns `name` without a vendor prefix. +/// +/// If `name` has no vendor prefix, it's returned as-is. +fn unvendor(name: &str) -> &str { + let bytes = name.as_bytes(); + + if bytes.len() < 2 { + return name; + } + + if bytes[0usize] != b'-' || bytes[1usize] == b'-' { + return name; + } + + for i in 2..bytes.len() { + if bytes.get(i) == Some(&b'-') { + return &name[i + 1..]; + } + } + + name +} + +/// Returns whether `name` is the name of a pseudo-element that can be written +/// with pseudo-class syntax (`:before`, `:after`, `:first-line`, or +/// `:first-letter`) +fn is_fake_pseudo_element(name: &str) -> bool { + match name.as_bytes()[0] { + b'a' | b'A' => name.to_ascii_lowercase() == "after", + b'b' | b'B' => name.to_ascii_lowercase() == "before", + b'f' | b'F' => match name.to_ascii_lowercase().as_str() { + "first-line" | "first-letter" => true, + _ => false, + }, + _ => false, + } +} + +fn expect_identifier(s: &str) -> SassResult<()> { + todo!() +} diff --git a/src/selector/simple.rs b/src/selector/simple.rs new file mode 100644 index 0000000..7adf05c --- /dev/null +++ b/src/selector/simple.rs @@ -0,0 +1,582 @@ +use std::fmt::{self, Write}; + +use super::{ + Attribute, ComplexSelector, ComplexSelectorComponent, CompoundSelector, Namespace, + QualifiedName, SelectorList, +}; + +const SUBSELECTOR_PSEUDOS: [&'static str; 4] = ["matches", "any", "nth-child", "nth-last-child"]; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) enum SimpleSelector { + /// * + Universal(Namespace), + + /// A pseudo-class or pseudo-element selector. + /// + /// The semantics of a specific pseudo selector depends on its name. Some + /// selectors take arguments, including other selectors. Sass manually encodes + /// logic for each pseudo selector that takes a selector as an argument, to + /// ensure that extension and other selector operations work properly. + Pseudo(Pseudo), + + /// A type selector. + /// + /// This selects elements whose name equals the given name. + Type(QualifiedName), + + /// A placeholder selector. + /// + /// This doesn't match any elements. It's intended to be extended using + /// `@extend`. It's not a plain CSS selector—it should be removed before + /// emitting a CSS document. + Placeholder(String), + + /// A selector that matches the parent in the Sass stylesheet. + /// `&` + /// + /// This is not a plain CSS selector—it should be removed before emitting a CSS + /// document. + /// + /// The parameter is the suffix that will be added to the parent selector after + /// it's been resolved. + /// + /// This is assumed to be a valid identifier suffix. It may be `None`, + /// indicating that the parent selector will not be modified. + Parent(Option), + + Id(String), + + /// A class selector. + /// + /// This selects elements whose `class` attribute contains an identifier with + /// the given name. + Class(String), + + Attribute(Attribute), +} + +impl fmt::Display for SimpleSelector { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Id(name) => write!(f, "#{}", name), + Self::Class(name) => write!(f, ".{}", name), + Self::Placeholder(name) => write!(f, "%{}", name), + Self::Universal(namespace) => write!(f, "{}*", namespace), + Self::Pseudo(pseudo) => write!(f, "{}", pseudo), + Self::Type(name) => write!(f, "{}", name), + Self::Attribute(attr) => write!(f, "{}", attr), + Self::Parent(..) => todo!(), + } + } +} + +impl SimpleSelector { + /// The minimum possible specificity that this selector can have. + /// + /// Pseudo selectors that contain selectors, like `:not()` and `:matches()`, + /// can have a range of possible specificities. + /// + /// Specifity is represented in base 1000. The spec says this should be + /// "sufficiently high"; it's extremely unlikely that any single selector + /// sequence will contain 1000 simple selectors. + pub fn min_specificity(&self) -> u32 { + match self { + Self::Universal(..) => 0, + Self::Type(..) => 1, + Self::Pseudo { .. } => todo!(), + Self::Id(..) => 1000u32.pow(2u32), + _ => 1000, + } + } + + /// The maximum possible specificity that this selector can have. + /// + /// Pseudo selectors that contain selectors, like `:not()` and `:matches()`, + /// can have a range of possible specificities. + pub fn max_specificity(&self) -> u32 { + match self { + Self::Universal(..) => 0, + _ => self.min_specificity(), + } + } + + pub fn is_invisible(&self) -> bool { + match self { + Self::Universal(..) + | Self::Type(..) + | Self::Id(..) + | Self::Class(..) + | Self::Attribute(..) => false, + Self::Pseudo(Pseudo { name, selector, .. }) => { + name != "not" && selector.as_ref().map_or(false, |s| s.is_invisible()) + } + Self::Placeholder(..) => true, + Self::Parent(..) => todo!(), + } + } + + pub fn add_suffix(&mut self, suffix: &str) { + match self { + Self::Type(name) => name.ident.push_str(suffix), + Self::Pseudo(Pseudo { + name, + argument: None, + selector: None, + .. + }) => name.push_str(suffix), + Self::Placeholder(name) | Self::Id(name) | Self::Class(name) => name.push_str(suffix), + Self::Universal(..) | _ => todo!(), + } + } + + pub fn is_universal(&self) -> bool { + matches!(self, Self::Universal(..)) + } + + pub fn is_pseudo(&self) -> bool { + matches!(self, Self::Pseudo { .. }) + } + + pub fn is_parent(&self) -> bool { + matches!(self, Self::Parent(..)) + } + + pub fn is_id(&self) -> bool { + matches!(self, Self::Id(..)) + } + + pub fn is_type(&self) -> bool { + matches!(self, Self::Type(..)) + } + + pub fn unify(self, compound: Vec) -> Option> { + match self { + Self::Type(..) => self.unify_type(compound), + Self::Universal(..) => self.unify_universal(compound), + Self::Pseudo { .. } => self.unify_pseudo(compound), + Self::Id(..) => { + if compound + .iter() + .any(|simple| simple.is_id() && simple != &self) + { + return None; + } + + self.unify_default(compound) + } + _ => self.unify_default(compound), + } + } + + /// Returns the compoments of a `CompoundSelector` that matches only elements + /// matched by both this and `compound`. + /// + /// By default, this just returns a copy of `compound` with this selector + /// added to the end, or returns the original array if this selector already + /// exists in it. + /// + /// Returns `None` if unification is impossible—for example, if there are + /// multiple ID selectors. + fn unify_default(self, mut compound: Vec) -> Option> { + if compound.len() == 1 && compound[0].is_universal() { + return compound.swap_remove(0).unify(vec![self]); + } + if compound.contains(&self) { + return Some(compound); + } + let mut result: Vec = Vec::new(); + let mut added_this = false; + for simple in compound { + if !added_this && simple.is_pseudo() { + result.push(self.clone()); + added_this = true; + } + result.push(simple); + } + + if !added_this { + result.push(self.clone()); + } + + Some(result) + } + + fn unify_universal(self, mut compound: Vec) -> Option> { + if let Self::Universal(..) | Self::Type(..) = compound[0] { + let mut unified = vec![self.unify_universal_and_element(&compound[0])?]; + unified.extend(compound.into_iter().skip(1)); + return Some(unified); + } + + if self != Self::Universal(Namespace::Asterisk) && self != Self::Universal(Namespace::None) + { + let mut v = vec![self]; + v.append(&mut compound); + return Some(v); + } + + if !compound.is_empty() { + return Some(compound); + } + + Some(vec![self]) + } + + /// Returns a `SimpleSelector` that matches only elements that are matched by + /// both `selector1` and `selector2`, which must both be either + /// `SimpleSelector::Universal`s or `SimpleSelector::Type`s. + /// + /// If no such selector can be produced, returns `None`. + fn unify_universal_and_element(&self, other: &Self) -> Option { + let namespace1; + let name1; + if let SimpleSelector::Type(name) = self.clone() { + namespace1 = name.namespace; + name1 = name.ident; + } else if let SimpleSelector::Universal(namespace) = self.clone() { + namespace1 = namespace; + name1 = String::new(); + } else { + todo!("ArgumentError.value(selector1, 'selector1', 'must be a UniversalSelector or a TypeSelector')") + } + + let namespace2; + let mut name2 = String::new(); + + if let SimpleSelector::Universal(namespace) = other { + namespace2 = namespace.clone(); + } else if let SimpleSelector::Type(name) = other { + namespace2 = name.namespace.clone(); + name2 = name.ident.clone(); + } else { + todo!("ArgumentError.value(selector2, 'selector2', 'must be a UniversalSelector or a TypeSelector');") + } + + let namespace = if namespace1 == namespace2 || namespace2 == Namespace::Asterisk { + namespace1 + } else if namespace1 == Namespace::Asterisk { + namespace2 + } else { + return None; + }; + + let name = if name1 == name2 || name2.is_empty() { + name1 + } else if name1.is_empty() || name1 == "*" { + name2 + } else { + return None; + }; + + Some(if name.is_empty() { + SimpleSelector::Universal(namespace) + } else { + SimpleSelector::Type(QualifiedName { + namespace, + ident: name, + }) + }) + } + + fn unify_type(self, mut compound: Vec) -> Option> { + if let Self::Universal(..) | Self::Type(..) = compound[0] { + let mut unified = vec![self.unify_universal_and_element(&compound[0])?]; + unified.extend(compound.into_iter().skip(1)); + Some(unified) + } else { + let mut unified = vec![self]; + unified.append(&mut compound); + Some(unified) + } + } + + fn unify_pseudo(self, mut compound: Vec) -> Option> { + if compound.len() == 1 && compound[0].is_universal() { + return compound.remove(0).unify(vec![self]); + } + if compound.contains(&self) { + return Some(compound); + } + + let mut result = Vec::new(); + + let mut added_self = false; + + for simple in compound { + if let Self::Pseudo(Pseudo { + is_class: false, .. + }) = simple + { + // A given compound selector may only contain one pseudo element. If + // [compound] has a different one than [this], unification fails. + if let Self::Pseudo(Pseudo { + is_class: false, .. + }) = self + { + return None; + } + + // Otherwise, this is a pseudo selector and should come before pseduo + // elements. + result.push(self.clone()); + added_self = true; + } + result.push(simple); + } + + if !added_self { + result.push(self); + } + + Some(result) + } + + pub fn is_super_selector_of_compound(&self, compound: &CompoundSelector) -> bool { + compound.components.iter().any(|their_simple| { + if self == their_simple { + return true; + } + if let SimpleSelector::Pseudo(Pseudo { + selector: Some(sel), + normalized_name, + .. + }) = their_simple + { + if SUBSELECTOR_PSEUDOS.contains(&normalized_name.as_str()) { + sel.components.iter().all(|complex| { + if complex.components.len() != 1 { + return false; + }; + return complex + .components + .get(0) + .unwrap() + .as_compound() + .components + .contains(self); + }); + } + false + } else { + false + } + }) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct Pseudo { + /// The name of this selector. + pub name: String, + + /// Like `name`, but without any vendor prefixes. + pub normalized_name: String, + + /// Whether this is a pseudo-class selector. + /// + /// If this is false, this is a pseudo-element selector + pub is_class: bool, + + /// Whether this is syntactically a pseudo-class selector. + /// + /// This is the same as `is_class` unless this selector is a pseudo-element + /// that was written syntactically as a pseudo-class (`:before`, `:after`, + /// `:first-line`, or `:first-letter`). + /// + /// If this is false, it is syntactically a psuedo-element + pub is_syntactic_class: bool, + + /// The non-selector argument passed to this selector. + /// + /// This is `None` if there's no argument. If `argument` and `selector` are + /// both non-`None`, the selector follows the argument. + pub argument: Option, + + /// The selector argument passed to this selector. + /// + /// This is `None` if there's no selector. If `argument` and `selector` are + /// both non-`None`, the selector follows the argument. + pub selector: Option, +} + +impl fmt::Display for Pseudo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(sel) = &self.selector { + if self.name == "not" && sel.is_invisible() { + return Ok(()); + } + } + + f.write_char(':')?; + + if !self.is_syntactic_class { + f.write_char(':')?; + } + + f.write_str(&self.name)?; + + if self.argument.is_none() && self.selector.is_none() { + return Ok(()); + } + + f.write_char('(')?; + if let Some(arg) = &self.argument { + f.write_str(arg)?; + if self.selector.is_some() { + f.write_char(' ')?; + } + } + + if let Some(sel) = &self.selector { + write!(f, "{}", sel)?; + } + + f.write_char(')') + } +} + +impl Pseudo { + /// Returns whether `pseudo1` is a superselector of `compound2`. + /// + /// That is, whether `pseudo1` matches every element that `compound2` matches, as well + /// as possibly additional elements. + /// + /// This assumes that `pseudo1`'s `selector` argument is not `None`. + /// + /// If `parents is passed, it represents the parents of `compound`. This is + /// relevant for pseudo selectors with selector arguments, where we may need to + /// know if the parent selectors in the selector argument match `parents`. + pub fn is_super_selector( + &self, + compound: &CompoundSelector, + parents: Option>, + ) -> bool { + match self.normalized_name.as_str() { + "matches" | "any" => { + let pseudos = selector_pseudos_named(compound.clone(), &self.name, true); + pseudos.iter().any(move |pseudo2| { + self.selector + .as_ref() + .unwrap() + .is_superselector(&pseudo2.selector.clone().unwrap()) + }) || self + .selector + .as_ref() + .unwrap() + .components + .iter() + .any(move |complex1| { + let mut components = parents.clone().unwrap_or_default(); + components.push(ComplexSelectorComponent::Compound(compound.clone())); + complex1.is_super_selector(&ComplexSelector { + components, + line_break: false, + }) + }) + } + "has" | "host" | "host-context" => { + selector_pseudos_named(compound.clone(), &self.name, true) + .iter() + .any(|pseudo2| { + self.selector + .as_ref() + .unwrap() + .is_superselector(&pseudo2.selector.clone().unwrap()) + }) + } + "slotted" => selector_pseudos_named(compound.clone(), &self.name, false) + .iter() + .any(|pseudo2| { + self.selector + .as_ref() + .unwrap() + .is_superselector(pseudo2.selector.as_ref().unwrap()) + }), + "not" => self + .selector + .as_ref() + .unwrap() + .components + .iter() + .all(|complex| { + compound.components.iter().any(|simple2| { + if let SimpleSelector::Type(..) = simple2 { + let compound1 = complex.components.last(); + if let Some(ComplexSelectorComponent::Compound(c)) = compound1 { + c.components + .iter() + .any(|simple1| simple1.is_type() && simple1 != simple2) + } else { + false + } + } else if let SimpleSelector::Id(..) = simple2 { + let compound1 = complex.components.last(); + if let Some(ComplexSelectorComponent::Compound(c)) = compound1 { + c.components + .iter() + .any(|simple1| simple1.is_id() && simple1 != simple2) + } else { + false + } + } else if let SimpleSelector::Pseudo(Pseudo { + selector: Some(sel), + name, + .. + }) = simple2 + { + if name != &self.name { + return false; + } + sel.is_superselector(&SelectorList { + components: vec![complex.clone()], + }) + } else { + false + } + }) + }), + "current" => selector_pseudos_named(compound.clone(), &self.name, false) + .iter() + .any(|pseudo2| self.selector == pseudo2.selector), + "nth-child" | "nth-last-child" => compound.components.iter().any(|pseudo2| { + if let SimpleSelector::Pseudo( + pseudo + @ Pseudo { + selector: Some(..), .. + }, + ) = pseudo2 + { + pseudo.name == self.name + && pseudo.argument == self.argument + && self + .selector + .as_ref() + .unwrap() + .is_superselector(pseudo.selector.as_ref().unwrap()) + } else { + false + } + }), + _ => unreachable!(), + } + } +} + +/// Returns all pseudo selectors in `compound` that have a selector argument, +/// and that have the given `name`. +// todo: return `impl Iterator` +fn selector_pseudos_named(compound: CompoundSelector, name: &str, is_class: bool) -> Vec { + compound + .components + .into_iter() + .filter_map(|c| { + if let SimpleSelector::Pseudo(p) = c { + Some(p) + } else { + None + } + }) + .filter(|p| p.is_class == is_class && p.selector.is_some() && p.name == name) + .collect() +} diff --git a/src/stylesheet.rs b/src/stylesheet.rs index f852910..6c234cc 100644 --- a/src/stylesheet.rs +++ b/src/stylesheet.rs @@ -420,7 +420,8 @@ impl<'a> StyleSheetParser<'a> { } Expr::Selector(s) => { self.nesting += 1; - let rules = self.eat_rules(&super_selector.zip(&s), scope)?; + let rules = + self.eat_rules(&s.resolve_parent_selectors(&super_selector, true), scope)?; stmts.push(Spanned { node: Stmt::RuleSet(RuleSet { super_selector: super_selector.clone(), diff --git a/src/value/mod.rs b/src/value/mod.rs index c45f3a4..cefcc02 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -1,13 +1,17 @@ use std::iter::Iterator; +use peekmore::PeekMore; + use codemap::{Span, Spanned}; use crate::color::Color; use crate::common::{Brackets, ListSeparator, Op, QuoteKind}; use crate::error::SassResult; +use crate::scope::Scope; +use crate::selector::Selector; use crate::unit::Unit; use crate::utils::hex_char_for; -use crate::Cow; +use crate::{Cow, Token}; use css_function::is_special_function; pub(crate) use map::SassMap; @@ -309,4 +313,77 @@ impl Value { v => v.to_css_string(span)?, }) } + + pub fn as_list(self) -> Vec { + match self { + Value::List(v, ..) => v, + Value::Map(m) => m.entries(), + v => vec![v], + } + } + + /// Parses `self` as a selector list, in the same manner as the + /// `selector-parse()` function. + /// + /// Returns a `SassError` if `self` isn't a type that can be parsed as a + /// selector, or if parsing fails. If `allow_parent` is `true`, this allows + /// parent selectors. Otherwise, they're considered parse errors. + /// + /// `name` is the argument name. It's used for error reporting. + pub fn to_selector( + self, + span: Span, + scope: &Scope, + super_selector: &Selector, + name: &str, + allows_parent: bool, + ) -> SassResult { + let string = match self.clone().selector_string(span)? { + Some(v) => v, + None => return Err((format!("${}: {} is not a valid selector: it must be a string, a list of strings, or a list of lists of strings.", name, self.inspect(span)?), span).into()), + }; + Selector::from_tokens( + &mut string.chars().map(|c| Token::new(span, c)).peekmore(), + scope, + super_selector, + allows_parent, + ) + } + + fn selector_string(self, span: Span) -> SassResult> { + Ok(Some(match self.eval(span)?.node { + Self::String(text, ..) => text, + Self::List(list, sep, ..) if !list.is_empty() => { + let mut result = Vec::new(); + match sep { + ListSeparator::Comma => { + for complex in list { + if let Value::String(text, ..) = complex { + result.push(text); + } else if let Value::List(_, ListSeparator::Space, ..) = complex { + result.push(match complex.selector_string(span)? { + Some(v) => v, + None => return Ok(None), + }); + } else { + return Ok(None); + } + } + } + ListSeparator::Space => { + for compound in list { + if let Value::String(text, ..) = compound { + result.push(text); + } else { + return Ok(None); + } + } + } + } + + result.join(sep.as_str()) + } + _ => return Ok(None), + })) + } } diff --git a/src/value/parse.rs b/src/value/parse.rs index 7c49e53..0e9190f 100644 --- a/src/value/parse.rs +++ b/src/value/parse.rs @@ -804,7 +804,7 @@ impl Value { } '&' => { let span = toks.next().unwrap().pos(); - IntermediateValue::Value(super_selector.into_value()).span(span) + IntermediateValue::Value(super_selector.clone().into_value()).span(span) } '#' => { if let Some(Token { kind: '{', pos }) = toks.peek_forward(1) { diff --git a/tests/at-root.rs b/tests/at-root.rs index e16d4e5..7e122ac 100644 --- a/tests/at-root.rs +++ b/tests/at-root.rs @@ -1,65 +1,65 @@ -#![cfg(test)] +// #![cfg(test)] -#[macro_use] -mod macros; +// #[macro_use] +// mod macros; -test!( - simple_nested, - ".foo {\n @at-root {\n .bar {a: b}\n }\n}\n", - ".bar {\n a: b;\n}\n" -); -test!( - with_selector, - ".foo {\n @at-root .bar {a: b}\n}\n", - ".bar {\n a: b;\n}\n" -); -test!( - with_selector_in_mixin, - "@mixin bar {\n @at-root .bar {a: b}\n}\n\n.foo {\n @include bar;\n}\n", - ".bar {\n a: b;\n}\n" -); -test!( - with_super_selector, - ".foo {\n @at-root & {\n a: b;\n }\n}\n", - ".foo {\n a: b;\n}\n" -); -test!( - nested_with_super_selector, - ".foo {\n @at-root & {\n .bar {\n @at-root & {\n a: b;\n }\n }\n }\n}\n", - ".foo .bar {\n a: b;\n}\n" -); -test!( - deeply_nested_with_rulesets_and_styles, - ".foo {\n @at-root .bar {\n a: b;\n c {\n d: e;\n foo {\n bar: baz;\n }\n h: j;\n }\n f: g;\n }\n}\n", - ".bar {\n a: b;\n f: g;\n}\n.bar c {\n d: e;\n h: j;\n}\n.bar c foo {\n bar: baz;\n}\n" -); -test!( - super_selector_inside_with_nothing, - "foo {\n @at-root {\n & {\n color: bar;\n }\n }\n}\n", - "foo {\n color: bar;\n}\n" -); -test!( - interpolated_super_selector_with_nothing, - "test {\n @at-root {\n #{&}post {\n foo {\n bar: baz;\n }\n }\n }\n}\n", - "testpost foo {\n bar: baz;\n}\n" -); -test!( - with_ampersand_single, - "test {\n @at-root {\n #{&}post {\n foo {\n bar: baz;\n }\n }\n }\n}\n", - "testpost foo {\n bar: baz;\n}\n" -); -test!( - root_interpolated_ampersand, - "@at-root {\n #{&}post {\n foo {\n bar: baz;\n }\n }\n}\n", - "post foo {\n bar: baz;\n}\n" -); -test!( - nested_prefix_interpolated_ampersand, - "test {\n @at-root {\n pre#{&} {\n foo {\n bar: baz;\n }\n }\n }\n}\n", - "pretest foo {\n bar: baz;\n}\n" -); -test!( - nested_alone_interpolated_ampersand, - "test {\n @at-root {\n #{&} {\n foo {\n bar: baz;\n }\n }\n }\n}\n", - "test foo {\n bar: baz;\n}\n" -); +// test!( +// simple_nested, +// ".foo {\n @at-root {\n .bar {a: b}\n }\n}\n", +// ".bar {\n a: b;\n}\n" +// ); +// test!( +// with_selector, +// ".foo {\n @at-root .bar {a: b}\n}\n", +// ".bar {\n a: b;\n}\n" +// ); +// test!( +// with_selector_in_mixin, +// "@mixin bar {\n @at-root .bar {a: b}\n}\n\n.foo {\n @include bar;\n}\n", +// ".bar {\n a: b;\n}\n" +// ); +// test!( +// with_super_selector, +// ".foo {\n @at-root & {\n a: b;\n }\n}\n", +// ".foo {\n a: b;\n}\n" +// ); +// test!( +// nested_with_super_selector, +// ".foo {\n @at-root & {\n .bar {\n @at-root & {\n a: b;\n }\n }\n }\n}\n", +// ".foo .bar {\n a: b;\n}\n" +// ); +// test!( +// deeply_nested_with_rulesets_and_styles, +// ".foo {\n @at-root .bar {\n a: b;\n c {\n d: e;\n foo {\n bar: baz;\n }\n h: j;\n }\n f: g;\n }\n}\n", +// ".bar {\n a: b;\n f: g;\n}\n.bar c {\n d: e;\n h: j;\n}\n.bar c foo {\n bar: baz;\n}\n" +// ); +// test!( +// super_selector_inside_with_nothing, +// "foo {\n @at-root {\n & {\n color: bar;\n }\n }\n}\n", +// "foo {\n color: bar;\n}\n" +// ); +// test!( +// interpolated_super_selector_with_nothing, +// "test {\n @at-root {\n #{&}post {\n foo {\n bar: baz;\n }\n }\n }\n}\n", +// "testpost foo {\n bar: baz;\n}\n" +// ); +// test!( +// with_ampersand_single, +// "test {\n @at-root {\n #{&}post {\n foo {\n bar: baz;\n }\n }\n }\n}\n", +// "testpost foo {\n bar: baz;\n}\n" +// ); +// test!( +// root_interpolated_ampersand, +// "@at-root {\n #{&}post {\n foo {\n bar: baz;\n }\n }\n}\n", +// "post foo {\n bar: baz;\n}\n" +// ); +// test!( +// nested_prefix_interpolated_ampersand, +// "test {\n @at-root {\n pre#{&} {\n foo {\n bar: baz;\n }\n }\n }\n}\n", +// "pretest foo {\n bar: baz;\n}\n" +// ); +// test!( +// nested_alone_interpolated_ampersand, +// "test {\n @at-root {\n #{&} {\n foo {\n bar: baz;\n }\n }\n }\n}\n", +// "test foo {\n bar: baz;\n}\n" +// ); diff --git a/tests/error.rs b/tests/error.rs index 708372c..6f37719 100644 --- a/tests/error.rs +++ b/tests/error.rs @@ -187,6 +187,7 @@ error!(toplevel_caret_alone, "^", "Error: expected \"{\"."); test!(toplevel_gt_as_selector, "> {}", ""); test!(toplevel_tilde_as_selector, "~ {}", ""); error!(toplevel_lt_as_selector, "< {}", "Error: expected selector."); +error!(toplevel_pipe, "| {}", "Error: Expected identifier."); error!( toplevel_question_as_selector, "? {}", "Error: expected selector." diff --git a/tests/is-superselector.rs b/tests/is-superselector.rs new file mode 100644 index 0000000..e1a32b4 --- /dev/null +++ b/tests/is-superselector.rs @@ -0,0 +1,75 @@ +#![cfg(test)] + +#[macro_use] +mod macros; + +test!( + more_specific_class_compound, + "a {\n color: is-superselector(\".foo\", \".foo.bar\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + less_specific_class_compound, + "a {\n color: is-superselector(\".foo.bar\", \".foo\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + more_specific_class_complex, + "a {\n color: is-superselector(\".bar\", \".foo .bar\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + less_specific_class_complex, + "a {\n color: is-superselector(\".foo .bar\", \".bar\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + two_in_sub, + "a {\n color: is-superselector(\"c\", \"c, d\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + two_in_super, + "a {\n color: is-superselector(\"c, d\", \"c\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + two_in_both_equal, + "a {\n color: is-superselector(\"c, d\", \"c, d\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + two_in_both_subset, + "a {\n color: is-superselector(\"c, d\", \"c.e, d.f\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + two_in_both_superset, + "a {\n color: is-superselector(\"c.e, d.f\", \"c, d\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + two_in_sub_satisfied_by_one_super, + "a {\n color: is-superselector(\".c\", \"d.c, e.c\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + three_in_super_match_one, + "a {\n color: is-superselector(\"c, d, e\", \"d\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + three_in_super_match_two, + "a {\n color: is-superselector(\"c, d, e\", \"e, c\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + three_in_super_match_three, + "a {\n color: is-superselector(\"c, d, e\", \"d, c, e\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + three_in_super_miss_one, + "a {\n color: is-superselector(\"c, d, e\", \"c, f\");\n}\n", + "a {\n color: false;\n}\n" +); diff --git a/tests/selector-append.rs b/tests/selector-append.rs new file mode 100644 index 0000000..28feda7 --- /dev/null +++ b/tests/selector-append.rs @@ -0,0 +1,78 @@ +#![cfg(test)] + +#[macro_use] +mod macros; + +test!( + classes_single, + "a {\n color: selector-append(\".c\", \".d\");\n}\n", + "a {\n color: .c.d;\n}\n" +); +test!( + classes_multiple, + "a {\n color: selector-append(\".c, .d\", \".e, .f\");\n}\n", + "a {\n color: .c.e, .c.f, .d.e, .d.f;\n}\n" +); +test!( + suffix_single, + "a {\n color: selector-append(\".c\", \"d\");\n}\n", + "a {\n color: .cd;\n}\n" +); +test!( + suffix_multiple, + "a {\n color: selector-append(\".c, .d\", \"e, f\");\n}\n", + "a {\n color: .ce, .cf, .de, .df;\n}\n" +); +test!( + suffix_descendant, + "a {\n color: selector-append(\"c d\", \"e f\");\n}\n", + "a {\n color: c de f;\n}\n" +); +test!( + one_arg, + "a {\n color: selector-append(\".c.d\");\n}\n", + "a {\n color: .c.d;\n}\n" +); +test!( + many_args, + "a {\n color: selector-append(\".c\", \".d\", \".e\");\n}\n", + "a {\n color: .c.d.e;\n}\n" +); +test!( + paren_first_arg, + "a {\n color: selector-append((c, d e), f);\n}\n", + "a {\n color: cf, d ef;\n}\n" +); +test!( + paren_second_arg, + "a {\n color: selector-append(c, (d, e f));\n}\n", + "a {\n color: cd, ce f;\n}\n" +); +test!( + output_structure, + "a {\n color: selector-append(\"c d, e f\", \"g\") == (\"c\" \"dg\", \"e\" \"fg\");\n}\n", + "a {\n color: true;\n}\n" +); +error!( + universal_in_second_arg, + "a {\n color: selector-append(\".c\", \"*\");\n}\n", "Error: Can't append * to .c." +); +error!( + parent_in_second_arg, + "a {\n color: selector-append(\"c\", \"&\");\n}\n", + "Error: Parent selectors aren't allowed here." +); +error!( + malformed_selector_in_first_arg, + "a {\n color: selector-append(\"[c\", \".d\");\n}\n", "Error: expected more input." +); +error!( + invalid_type_in_first_arg, + "a {\n color: selector-append(\"c\", 1);\n}\n", + "Error: $selectors: 1 is not a valid selector: it must be a string, a list of strings, or a list of lists of strings." +); +error!( + no_args, + "a {\n color: selector-append();\n}\n", + "Error: $selectors: At least one selector must be passed." +); diff --git a/tests/selector-nest.rs b/tests/selector-nest.rs new file mode 100644 index 0000000..d7dde1c --- /dev/null +++ b/tests/selector-nest.rs @@ -0,0 +1,158 @@ +#![cfg(test)] + +#[macro_use] +mod macros; + +test!( + nest_one_arg, + "a {\n color: selector-nest(\"c\");\n}\n", + "a {\n color: c;\n}\n" +); +test!( + nest_many_args, + "a {\n color: selector-nest(\"c\", \"d\", \"e\", \"f\", \"g\");\n}\n", + "a {\n color: c d e f g;\n}\n" +); +test!( + nest_parent_alone, + "a {\n color: selector-nest(\"c\", \"&\");\n}\n", + "a {\n color: c;\n}\n" +); +test!( + nest_parent_compound, + "a {\n color: selector-nest(\"c\", \"&.d\");\n}\n", + "a {\n color: c.d;\n}\n" +); +test!( + nest_parent_with_suffix, + "a {\n color: selector-nest(\"c\", \"&d\");\n}\n", + "a {\n color: cd;\n}\n" +); +test!( + nest_complex_parent_compound, + "a {\n color: selector-nest(\"c\", \"d &.e\");\n}\n", + "a {\n color: d c.e;\n}\n" +); +test!( + nest_complex_super_parent_compound, + "a {\n color: selector-nest(\"c d\", \"e &.f\");\n}\n", + "a {\n color: e c d.f;\n}\n" +); +test!( + nest_parent_in_special_pseudo, + "a {\n color: selector-nest(\"c\", \":matches(&)\");\n}\n", + "a {\n color: :matches(c);\n}\n" +); +test!( + nest_complex_super_parent_in_special_pseudo, + "a {\n color: selector-nest(\"c d\", \":matches(&)\");\n}\n", + "a {\n color: :matches(c d);\n}\n" +); +test!( + nest_multiple_parent, + "a {\n color: selector-nest(\"c\", \"&.d &.e\");\n}\n", + "a {\n color: c.d c.e;\n}\n" +); +test!( + nest_compound_parent_in_list, + "a {\n color: selector-nest(\"c\", \"&.d, e\");\n}\n", + "a {\n color: c.d, c e;\n}\n" +); +test!( + nest_list_super, + "a {\n color: selector-nest(\"c, d\", \"e\");\n}\n", + "a {\n color: c e, d e;\n}\n" +); +test!( + nest_list_sub, + "a {\n color: selector-nest(\"c\", \"d, e\");\n}\n", + "a {\n color: c d, c e;\n}\n" +); +test!( + nest_three_args_list, + "a {\n color: selector-nest(\"c, d\", \"e, f\", \"g, h\");\n}\n", + "a {\n color: c e g, c e h, c f g, c f h, d e g, d e h, d f g, d f h;\n}\n" +); +test!( + nest_super_list_parent_alone, + "a {\n color: selector-nest(\"c, d\", \"&\");\n}\n", + "a {\n color: c, d;\n}\n" +); +test!( + nest_super_list_parent_compound, + "a {\n color: selector-nest(\"c, d\", \"&.e\");\n}\n", + "a {\n color: c.e, d.e;\n}\n" +); +test!( + nest_super_list_parent_suffix, + "a {\n color: selector-nest(\"c, d\", \"&e\");\n}\n", + "a {\n color: ce, de;\n}\n" +); +test!( + nest_super_list_parent_complex, + "a {\n color: selector-nest(\"c, d\", \"e &.f\");\n}\n", + "a {\n color: e c.f, e d.f;\n}\n" +); +test!( + nest_super_list_parent_inside_pseudo, + "a {\n color: selector-nest(\"c, d\", \":matches(&)\");\n}\n", + "a {\n color: :matches(c, d);\n}\n" +); +test!( + nest_super_list_multiple_parent, + "a {\n color: selector-nest(\"c, d\", \"&.e &.f\");\n}\n", + "a {\n color: c.e c.f, c.e d.f, d.e c.f, d.e d.f;\n}\n" +); +test!( + nest_super_list_sub_list_contains_parent, + "a {\n color: selector-nest(\"c, d\", \"&.e, f\");\n}\n", + "a {\n color: c.e, c f, d.e, d f;\n}\n" +); +test!( + nest_comma_separated_list_as_super, + "a {\n color: selector-nest((c, d e), \"f\");\n}\n", + "a {\n color: c f, d e f;\n}\n" +); +test!( + nest_comma_separated_list_as_sub, + "a {\n color: selector-nest(\"c\", (d, e f));\n}\n", + "a {\n color: c d, c e f;\n}\n" +); +error!( + #[ignore = "https://github.com/sass/dart-sass/issues/966"] + disallows_parent_selector_as_first_arg, + "a {\n color: selector-nest(\"&\");\n}\n", "Error: Parent selectors aren't allowed here." +); +error!( + disallows_parent_not_at_start_of_compound_selector_attribute, + "a {\n color: selector-nest(\"[d]&\");\n}\n", + "Error: \"&\" may only used at the beginning of a compound selector." +); +error!( + disallows_parent_not_at_start_of_compound_selector_type, + "a {\n color: selector-nest(\"d&\");\n}\n", + "Error: \"&\" may only used at the beginning of a compound selector." +); +error!( + improperly_terminated_attribute_selector_first_arg, + "a {\n color: selector-nest(\"[d\");\n}\n", "Error: expected more input." +); +error!( + improperly_terminated_attribute_selector_second_arg, + "a {\n color: selector-nest(\"c\", \"[d\");\n}\n", "Error: expected more input." +); +error!( + unquoted_integer_first_arg, + "a {\n color: selector-nest(1);\n}\n", + "Error: $selectors: 1 is not a valid selector: it must be a string, a list of strings, or a list of lists of strings." +); +error!( + unquoted_integer_second_arg, + "a {\n color: selector-nest(\"c\", 1);\n}\n", + "Error: $selectors: 1 is not a valid selector: it must be a string, a list of strings, or a list of lists of strings." +); +error!( + empty_args, + "a {\n color: selector-nest();\n}\n", + "Error: $selectors: At least one selector must be passed." +); diff --git a/tests/selector-parse.rs b/tests/selector-parse.rs new file mode 100644 index 0000000..12a0568 --- /dev/null +++ b/tests/selector-parse.rs @@ -0,0 +1,100 @@ +#![cfg(test)] + +#[macro_use] +mod macros; + +test!( + named_args, + "a {\n color: selector-parse($selector: \"c\");\n}\n", + "a {\n color: c;\n}\n" +); +test!( + simple_class, + "a {\n color: selector-parse(\".c\");\n}\n", + "a {\n color: .c;\n}\n" +); +test!( + simple_id, + "a {\n color: selector-parse(\"#c\");\n}\n", + "a {\n color: #c;\n}\n" +); +test!( + simple_placeholder, + "a {\n color: selector-parse(\"%c\");\n}\n", + "a {\n color: %c;\n}\n" +); +test!( + simple_attribute, + "a {\n color: selector-parse(\"[c^=d]\");\n}\n", + "a {\n color: [c^=d];\n}\n" +); +test!( + simple_universal, + "a {\n color: selector-parse(\"*\");\n}\n", + "a {\n color: *;\n}\n" +); +test!( + simple_pseudo, + "a {\n color: selector-parse(\":c\");\n}\n", + "a {\n color: :c;\n}\n" +); +test!( + pseudo_weird_args, + "a {\n color: selector-parse(\":c(@#$)\");\n}\n", + "a {\n color: :c(@#$);\n}\n" +); +test!( + pseudo_matches_with_list_args, + "a {\n color: selector-parse(\":matches(b, c)\");\n}\n", + "a {\n color: :matches(b, c);\n}\n" +); +test!( + pseudo_element, + "a {\n color: selector-parse(\"::c\");\n}\n", + "a {\n color: ::c;\n}\n" +); +test!( + pseudo_element_args, + "a {\n color: selector-parse(\"::c(@#$)\");\n}\n", + "a {\n color: ::c(@#$);\n}\n" +); +test!( + pseudo_element_slotted_list_args_output, + "a {\n color: selector-parse(\"::slotted(b, c)\");\n}\n", + "a {\n color: ::slotted(b, c);\n}\n" +); +test!( + pseudo_element_slotted_list_args_structure, + "a {\n color: selector-parse(\"::slotted(b, c)\") == (append((), \"::slotted(b, c)\"),);\n}\n", + "a {\n color: true;\n}\n" +); +test!( + multiple_compound, + "a {\n color: selector-parse(\"b.c:d\");\n}\n", + "a {\n color: b.c:d;\n}\n" +); +test!( + multiple_complex, + "a {\n color: selector-parse(\"b c d\");\n}\n", + "a {\n color: b c d;\n}\n" +); +test!( + sibling_combinator, + "a {\n color: selector-parse(\"b ~ c ~ d\");\n}\n", + "a {\n color: b ~ c ~ d;\n}\n" +); +test!( + adjacent_combinator, + "a {\n color: selector-parse(\"b + c + d\");\n}\n", + "a {\n color: b + c + d;\n}\n" +); +test!( + child_combinator, + "a {\n color: selector-parse(\"b > c > d\");\n}\n", + "a {\n color: b > c > d;\n}\n" +); +test!( + comma_and_space_list, + "a {\n color: selector-parse(\"b c, d e, f g\");\n}\n", + "a {\n color: b c, d e, f g;\n}\n" +); diff --git a/tests/selector-unify.rs b/tests/selector-unify.rs new file mode 100644 index 0000000..c4fdcc9 --- /dev/null +++ b/tests/selector-unify.rs @@ -0,0 +1,602 @@ +#![cfg(test)] + +#[macro_use] +mod macros; + +test!( + no_overlap, + "a {\n color: selector-unify(\".c.d\", \".e.f\");\n}\n", + "a {\n color: .c.d.e.f;\n}\n" +); +test!( + partial_overlap, + "a {\n color: selector-unify(\".c.d\", \".d.e\");\n}\n", + "a {\n color: .c.d.e;\n}\n" +); +test!( + full_overlap, + "a {\n color: selector-unify(\".c.d\", \".c.d\");\n}\n", + "a {\n color: .c.d;\n}\n" +); +test!( + order_element_at_start, + "a {\n color: selector-unify(\".c\", \"d\");\n}\n", + "a {\n color: d.c;\n}\n" +); +test!( + order_pseudo_element_at_end, + "a {\n color: selector-unify(\"::c\", \".d\");\n}\n", + "a {\n color: .d::c;\n}\n" +); +test!( + order_pseudo_class_at_end, + "a {\n color: selector-unify(\":c\", \".d\");\n}\n", + "a {\n color: .d:c;\n}\n" +); +test!( + order_pseudo_element_after_pseudo_class, + "a {\n color: selector-unify(\"::c\", \":d\");\n}\n", + "a {\n color: :d::c;\n}\n" +); +test!( + attribute_identical, + "a {\n color: selector-unify(\"[a]\", \"[a]\");\n}\n", + "a {\n color: [a];\n}\n" +); +test!( + attribute_distinct, + "a {\n color: selector-unify(\"[a]\", \"[b]\");\n}\n", + "a {\n color: [a][b];\n}\n" +); +test!( + class_identical, + "a {\n color: selector-unify(\".a\", \".a\");\n}\n", + "a {\n color: .a;\n}\n" +); +test!( + class_distinct, + "a {\n color: selector-unify(\".a\", \".b\");\n}\n", + "a {\n color: .a.b;\n}\n" +); +test!( + element_id, + "a {\n color: selector-unify(\"a\", \"#b\");\n}\n", + "a {\n color: a#b;\n}\n" +); +test!( + id_identical, + "a {\n color: selector-unify(\"#a\", \"#a\");\n}\n", + "a {\n color: #a;\n}\n" +); +test!( + id_distinct, + "a {\n color: selector-unify(\"#a\", \"#b\");\n}\n", + "" +); +test!( + placeholder_identical, + "a {\n color: selector-unify(\"%a\", \"%a\");\n}\n", + "a {\n color: %a;\n}\n" +); +test!( + placeholder_distinct, + "a {\n color: selector-unify(\"%a\", \"%b\");\n}\n", + "a {\n color: %a%b;\n}\n" +); +test!( + universal_and_namespace, + "a {\n color: selector-unify(\"*\", \"a|b\");\n}\n", + "" +); +test!( + universal_and_empty_namespace, + "a {\n color: selector-unify(\"*\", \"|b\");\n}\n", + "" +); +test!( + universal_and_type, + "a {\n color: selector-unify(\"*\", \"a\");\n}\n", + "a {\n color: a;\n}\n" +); +test!( + universal_and_asterisk_namespace, + "a {\n color: selector-unify(\"*\", \"*|a\");\n}\n", + "a {\n color: a;\n}\n" +); +test!( + universal_with_namespace_and_same_namespace, + "a {\n color: selector-unify(\"a|*\", \"a|b\");\n}\n", + "a {\n color: a|b;\n}\n" +); +test!( + universal_with_namespace_and_different_namespace, + "a {\n color: selector-unify(\"a|*\", \"c|b\");\n}\n", + "" +); +test!( + universal_with_namespace_and_no_namespace, + "a {\n color: selector-unify(\"a|*\", \"|b\");\n}\n", + "" +); +test!( + universal_with_namespace_and_asterisk_namespace, + "a {\n color: selector-unify(\"a|*\", \"*|b\");\n}\n", + "a {\n color: a|b;\n}\n" +); +test!( + universal_with_empty_namespace_and_empty_namespace, + "a {\n color: selector-unify(\"|*\", \"|b\");\n}\n", + "a {\n color: |b;\n}\n" +); +test!( + universal_with_empty_namespace_and_no_namespace, + "a {\n color: selector-unify(\"|*\", \"b\");\n}\n", + "" +); +test!( + universal_with_empty_namespace_and_asterisk_namespace, + "a {\n color: selector-unify(\"|*\", \"*|b\");\n}\n", + "a {\n color: |b;\n}\n" +); +test!( + universal_with_asterisk_namespace_and_namespace, + "a {\n color: selector-unify(\"*|*\", \"a|b\");\n}\n", + "a {\n color: a|b;\n}\n" +); +test!( + universal_with_asterisk_namespace_and_empty_namespace, + "a {\n color: selector-unify(\"*|*\", \"|b\");\n}\n", + "a {\n color: |b;\n}\n" +); +test!( + universal_with_asterisk_namespace_and_no_namespace, + "a {\n color: selector-unify(\"*|*\", \"b\");\n}\n", + "a {\n color: b;\n}\n" +); +test!( + universal_with_asterisk_namespace_and_asterisk_namespace, + "a {\n color: selector-unify(\"*|*\", \"*|b\");\n}\n", + "a {\n color: *|b;\n}\n" +); +test!( + universal_with_no_namespace_and_namespace_on_universal, + "a {\n color: selector-unify(\"*\", \"a|*\");\n}\n", + "" +); +test!( + universal_with_no_namespace_and_empty_namespace_on_universal, + "a {\n color: selector-unify(\"*\", \"|*\");\n}\n", + "" +); +test!( + universal_with_no_namespace_and_universal_with_no_namespace, + "a {\n color: selector-unify(\"*\", \"*\");\n}\n", + "a {\n color: *;\n}\n" +); +test!( + universal_with_no_namespace_and_universal_with_asterisk_namespace, + "a {\n color: selector-unify(\"*\", \"*|*\");\n}\n", + "a {\n color: *;\n}\n" +); +test!( + universal_with_namespace_and_universal_with_namespace, + "a {\n color: selector-unify(\"c|*\", \"c|*\");\n}\n", + "a {\n color: c|*;\n}\n" +); +test!( + universal_with_namespace_and_universal_with_empty_namespace, + "a {\n color: selector-unify(\"c|*\", \"|*\");\n}\n", + "" +); +test!( + universal_with_namespace_and_universal_with_no_namespace, + "a {\n color: selector-unify(\"c|*\", \"*\");\n}\n", + "" +); +test!( + universal_with_namespace_and_universal_with_asterisk_namespace, + "a {\n color: selector-unify(\"c|*\", \"*|*\");\n}\n", + "a {\n color: c|*;\n}\n" +); +test!( + universal_with_empty_namespace_and_universal_with_namespace, + "a {\n color: selector-unify(\"|*\", \"b|*\");\n}\n", + "" +); +test!( + universal_with_empty_namespace_and_universal_with_empty_namespace, + "a {\n color: selector-unify(\"|*\", \"|*\");\n}\n", + "a {\n color: |*;\n}\n" +); +test!( + universal_with_empty_namespace_and_universal_with_no_namespace, + "a {\n color: selector-unify(\"|*\", \"*\");\n}\n", + "" +); +test!( + universal_with_empty_namespace_and_universal_with_asterisk_namespace, + "a {\n color: selector-unify(\"|*\", \"*|*\");\n}\n", + "a {\n color: |*;\n}\n" +); +test!( + universal_with_asterisk_namespace_and_universal_with_namespace, + "a {\n color: selector-unify(\"*|*\", \"a|*\");\n}\n", + "a {\n color: a|*;\n}\n" +); +test!( + universal_with_asterisk_namespace_and_universal_with_empty_namespace, + "a {\n color: selector-unify(\"*|*\", \"|*\");\n}\n", + "a {\n color: |*;\n}\n" +); +test!( + universal_with_asterisk_namespace_and_universal_with_asterisk_namespace, + "a {\n color: selector-unify(\"*|*\", \"*|*\");\n}\n", + "a {\n color: *|*;\n}\n" +); +test!( + universal_with_asterisk_namespace_and_universal_with_no_namespace, + "a {\n color: selector-unify(\"*|*\", \"*\");\n}\n", + "a {\n color: *;\n}\n" +); +test!( + complex_two_levels_same_first, + "a {\n color: selector-unify(\".c .s1\", \".c .s2\");\n}\n", + "a {\n color: .c .s1.s2;\n}\n" +); +test!( + complex_three_levels_same_first, + "a {\n color: selector-unify(\".c .s1-1 .s1-2\", \".c .s2-1 .s2-2\");\n}\n", + "a {\n color: .c .s1-1 .s2-1 .s1-2.s2-2, .c .s2-1 .s1-1 .s1-2.s2-2;\n}\n" +); +test!( + complex_three_levels_same_second, + "a {\n color: selector-unify(\".s1-1 .d .s1-2\", \".s2-1 .d .s2-2\");\n}\n", + "a {\n color: .s1-1 .s2-1 .d .s1-2.s2-2, .s2-1 .s1-1 .d .s1-2.s2-2;\n}\n" +); +test!( + second_is_super_selector, + "a {\n color: selector-unify(\"c\", \"d c.e\");\n}\n", + "a {\n color: d c.e;\n}\n" +); +test!( + first_is_super_selector, + "a {\n color: selector-unify(\"d c.e\", \"c\");\n}\n", + "a {\n color: d c.e;\n}\n" +); +test!( + second_parent_is_super_selector, + "a {\n color: selector-unify(\"c d\", \"c.e .f\");\n}\n", + "a {\n color: c.e d.f;\n}\n" +); +test!( + first_parent_is_super_selector, + "a {\n color: selector-unify(\"c.e .f\", \"c d\");\n}\n", + "a {\n color: c.e d.f;\n}\n" +); +test!( + two_level_distinct, + "a {\n color: selector-unify(\".c .d\", \".e .f\");\n}\n", + "a {\n color: .c .e .d.f, .e .c .d.f;\n}\n" +); +test!( + three_level_distinct, + "a {\n color: selector-unify(\".c .d .e\", \".f .g .h\");\n}\n", + "a {\n color: .c .d .f .g .e.h, .f .g .c .d .e.h;\n}\n" +); +test!( + two_level_super_selector, + "a {\n color: selector-unify(\".c.s1-1 .s1-2\", \".c .s2\");\n}\n", + "a {\n color: .c.s1-1 .s1-2.s2;\n}\n" +); +test!( + three_level_outer_super_selector, + "a {\n color: selector-unify(\".c.s1-1 .s1-2 .s1-3\", \".c .s2-1 .s2-2\");\n}\n", + "a {\n color: .c.s1-1 .s1-2 .s2-1 .s1-3.s2-2, .c.s1-1 .s2-1 .s1-2 .s1-3.s2-2;\n}\n" +); +test!( + three_level_inner_super_selector, + "a {\n color: selector-unify(\".s1-1 .c.s1-2 .s1-3\", \".s2-1 .c .s2-2\");\n}\n", + "a {\n color: .s1-1 .s2-1 .c.s1-2 .s1-3.s2-2, .s2-1 .s1-1 .c.s1-2 .s1-3.s2-2;\n}\n" +); +test!( + combinator_child_and_descendant_distinct, + "a {\n color: selector-unify(\".c > .d\", \".e .f\");\n}\n", + "a {\n color: .e .c > .d.f;\n}\n" +); +test!( + combinator_child_and_descendant_same, + "a {\n color: selector-unify(\".c > .s1\", \".c .s2\");\n}\n", + "a {\n color: .c > .s1.s2;\n}\n" +); +test!( + combinator_child_and_descendant_super_selector, + "a {\n color: selector-unify(\".c.s1-1 > .s1-2\", \".c .s2\");\n}\n", + "a {\n color: .c.s1-1 > .s1-2.s2;\n}\n" +); +test!( + combinator_child_and_descendant_overlap, + "a {\n color: selector-unify(\".c.s1-1 > .s1-2\", \".c.s2-1 .s2-2\");\n}\n", + "a {\n color: .c.s2-1 .c.s1-1 > .s1-2.s2-2;\n}\n" +); +test!( + combinator_child_and_child_distinct, + "a {\n color: selector-unify(\".c > .d\", \".e > .f\");\n}\n", + "a {\n color: .e.c > .d.f;\n}\n" +); +test!( + combinator_child_and_child_super_selector, + "a {\n color: selector-unify(\".c.s1-1 > .s1-2\", \".c > .s2\");\n}\n", + "a {\n color: .c.s1-1 > .s1-2.s2;\n}\n" +); +test!( + combinator_child_and_child_overlap, + "a {\n color: selector-unify(\".c.s1-1 > .s1-2\", \".c.s2-1 > .s2-2\");\n}\n", + "a {\n color: .c.s2-1.s1-1 > .s1-2.s2-2;\n}\n" +); +test!( + combinator_child_and_child_conflict, + "a {\n color: selector-unify(\"#s1-1 > .s1-2\", \"#s2-1 > .s2-2\");\n}\n", + "" +); +test!( + combinator_child_and_sibling, + "a {\n color: selector-unify(\".c > .s1\", \".c ~ .s2\");\n}\n", + "a {\n color: .c > .c ~ .s1.s2;\n}\n" +); +test!( + combinator_child_and_next_sibling, + "a {\n color: selector-unify(\".c > .s1\", \".c + .s2\");\n}\n", + "a {\n color: .c > .c + .s1.s2;\n}\n" +); +test!( + combinator_sibling_and_descendant, + "a {\n color: selector-unify(\".c ~ .s1\", \".c .s2\");\n}\n", + "a {\n color: .c .c ~ .s1.s2;\n}\n" +); +test!( + combinator_sibling_and_child, + "a {\n color: selector-unify(\".c ~ .s1\", \".c > .s2\");\n}\n", + "a {\n color: .c > .c ~ .s1.s2;\n}\n" +); +test!( + combinator_sibling_and_sibling_distinct, + "a {\n color: selector-unify(\".c ~ .d\", \".e ~ .f\");\n}\n", + "a {\n color: .c ~ .e ~ .d.f, .e ~ .c ~ .d.f, .e.c ~ .d.f;\n}\n" +); +test!( + combinator_sibling_and_sibling_same, + "a {\n color: selector-unify(\".c ~ .s1\", \".c ~ .s2\");\n}\n", + "a {\n color: .c ~ .s1.s2;\n}\n" +); +test!( + combinator_sibling_and_sibling_super_selector, + "a {\n color: selector-unify(\".c.s1-1 ~ .s1-2\", \".c ~ .s2\");\n}\n", + "a {\n color: .c.s1-1 ~ .s1-2.s2;\n}\n" +); +test!( + combinator_sibling_and_sibling_overlap, + "a {\n color: selector-unify(\".c.s1-1 ~ .s1-2\", \".c.s2-1 ~ .s2-2\");\n}\n", + "a {\n color: .c.s1-1 ~ .c.s2-1 ~ .s1-2.s2-2, .c.s2-1 ~ .c.s1-1 ~ .s1-2.s2-2, .c.s2-1.s1-1 ~ .s1-2.s2-2;\n}\n" +); +test!( + combinator_sibling_and_sibling_conflict, + "a {\n color: selector-unify(\"#s1-1 ~ .s1-2\", \"#s2-1 ~ .s2-2\");\n}\n", + "a {\n color: #s1-1 ~ #s2-1 ~ .s1-2.s2-2, #s2-1 ~ #s1-1 ~ .s1-2.s2-2;\n}\n" +); +test!( + combinator_sibling_and_next_sibling_distinct, + "a {\n color: selector-unify(\".c ~ .d\", \".e + .f\");\n}\n", + "a {\n color: .c ~ .e + .d.f, .e.c + .d.f;\n}\n" +); +test!( + combinator_sibling_and_next_sibling_identical, + "a {\n color: selector-unify(\".c ~ .s1\", \".c + .s2\");\n}\n", + "a {\n color: .c + .s1.s2;\n}\n" +); +test!( + combinator_sibling_and_next_sibling_super_selector, + "a {\n color: selector-unify(\".c.s1-1 ~ .s1-2\", \".c + .s2\");\n}\n", + "a {\n color: .c.s1-1 ~ .c + .s1-2.s2, .c.s1-1 + .s1-2.s2;\n}\n" +); +test!( + combinator_sibling_and_next_sibling_overlap, + "a {\n color: selector-unify(\".c.s1-1 ~ .s1-2\", \".c.s2-1 + .s2-2\");\n}\n", + "a {\n color: .c.s1-1 ~ .c.s2-1 + .s1-2.s2-2, .c.s2-1.s1-1 + .s1-2.s2-2;\n}\n" +); +test!( + combinator_sibling_and_next_sibling_conflict, + "a {\n color: selector-unify(\"#s1-1 ~ .s1-2\", \"#s2-1 + .s2-2\");\n}\n", + "a {\n color: #s1-1 ~ #s2-1 + .s1-2.s2-2;\n}\n" +); +test!( + combinator_next_sibling_and_descendant, + "a {\n color: selector-unify(\".c + .s1\", \".c .s2\");\n}\n", + "a {\n color: .c .c + .s1.s2;\n}\n" +); +test!( + combinator_next_sibling_and_sibling_distinct, + "a {\n color: selector-unify(\".c + .d\", \".e ~ .f\");\n}\n", + "a {\n color: .e ~ .c + .d.f, .e.c + .d.f;\n}\n" +); +test!( + combinator_next_sibling_and_sibling_identical, + "a {\n color: selector-unify(\".c + .s1\", \".c ~ .s2\");\n}\n", + "a {\n color: .c + .s1.s2;\n}\n" +); +test!( + combinator_next_sibling_and_sibling_super_selector, + "a {\n color: selector-unify(\".c.s1-1 + .s1-2\", \".c ~ .s2\");\n}\n", + "a {\n color: .c.s1-1 + .s1-2.s2;\n}\n" +); +test!( + combinator_next_sibling_and_sibling_overlap, + "a {\n color: selector-unify(\".c.s1-1 + .s1-2\", \".c.s2-1 ~ .s2-2\");\n}\n", + "a {\n color: .c.s2-1 ~ .c.s1-1 + .s1-2.s2-2, .c.s2-1.s1-1 + .s1-2.s2-2;\n}\n" +); +test!( + combinator_next_sibling_and_sibling_conflict, + "a {\n color: selector-unify(\"#s1-1 + .s1-2\", \"#s2-1 ~ .s2-2\");\n}\n", + "a {\n color: #s2-1 ~ #s1-1 + .s1-2.s2-2;\n}\n" +); +test!( + combinator_next_sibling_and_next_sibling_distinct, + "a {\n color: selector-unify(\".c + .d\", \".e + .f\");\n}\n", + "a {\n color: .e.c + .d.f;\n}\n" +); +test!( + combinator_next_sibling_and_next_sibling_super_selector, + "a {\n color: selector-unify(\".c.s1-1 + .s1-2\", \".c + .s2\");\n}\n", + "a {\n color: .c.s1-1 + .s1-2.s2;\n}\n" +); +test!( + combinator_next_sibling_and_next_sibling_overlap, + "a {\n color: selector-unify(\".c.s1-1 + .s1-2\", \".c.s2-1 + .s2-2\");\n}\n", + "a {\n color: .c.s2-1.s1-1 + .s1-2.s2-2;\n}\n" +); +test!( + combinator_next_sibling_and_next_sibling_conflict, + "a {\n color: selector-unify(\"#s1-1 + .s1-2\", \"#s2-1 + .s2-2\");\n}\n", + "" +); +test!( + combinator_at_start_first, + "a {\n color: selector-unify(\"> .c\", \".d\");\n}\n", + "a {\n color: > .c.d;\n}\n" +); +test!( + combinator_at_start_second, + "a {\n color: selector-unify(\".c\", \"~ .d\");\n}\n", + "a {\n color: ~ .c.d;\n}\n" +); +test!( + combinator_at_start_both_identical, + "a {\n color: selector-unify(\"+ .c\", \"+ .d\");\n}\n", + "a {\n color: + .c.d;\n}\n" +); +test!( + combinator_at_start_contiguous_super_sequence, + "a {\n color: selector-unify(\"+ ~ > .c\", \"> + ~ > > .d\");\n}\n", + "a {\n color: > + ~ > > .c.d;\n}\n" +); +test!( + combinator_at_start_non_contiguous_super_sequence, + "a {\n color: selector-unify(\"+ ~ > .c\", \"+ > ~ ~ > .d\");\n}\n", + "a {\n color: + > ~ ~ > .c.d;\n}\n" +); +test!( + combinator_at_start_distinct, + "a {\n color: selector-unify(\"+ ~ > .c\", \"+ > ~ ~ .d\");\n}\n", + "" +); +test!( + combinator_multiple, + "a {\n color: selector-unify(\".c > .d + .e\", \".f .g ~ .h\");\n}\n", + "a {\n color: .f .c > .g ~ .d + .e.h, .f .c > .g.d + .e.h;\n}\n" +); +test!( + combinator_multiple_in_a_row_same, + "a {\n color: selector-unify(\".c + ~ > .d\", \".e + ~ > .f\");\n}\n", + "a {\n color: .c .e + ~ > .d.f, .e .c + ~ > .d.f;\n}\n" +); +test!( + combinator_multiple_in_a_row_contiguous_super_sequence, + "a {\n color: selector-unify(\".c + ~ > .d\", \".e > + ~ > > .f\");\n}\n", + "a {\n color: .c .e > + ~ > > .d.f, .e .c > + ~ > > .d.f;\n}\n" +); +test!( + combinator_multiple_in_a_row_non_contiguous_super_sequence, + "a {\n color: selector-unify(\".c + ~ > .d\", \".e + > ~ ~ > .f\");\n}\n", + "a {\n color: .c .e + > ~ ~ > .d.f, .e .c + > ~ ~ > .d.f;\n}\n" +); +test!( + combinator_multiple_in_a_row_distinct, + "a {\n color: selector-unify(\".c + ~ > .d\", \".e + > ~ ~ .f\");\n}\n", + "" +); +test!( + lcs_two_vs_one, + "a {\n color: selector-unify(\".c .d .e .s1\", \".e .c .d .s2\");\n}\n", + "a {\n color: .e .c .d .e .s1.s2;\n}\n" +); +test!( + lcs_three_vs_two, + "a {\n color: selector-unify(\".c .d .e .f .g .s1\", \".f .g .c .d .e .s2\");\n}\n", + "a {\n color: .f .g .c .d .e .f .g .s1.s2;\n}\n" +); +test!( + lcs_non_contiguous_same_position, + "a {\n color: selector-unify(\".s1-1 .c .d .s1-2 .e .s1-3\", \".s2-1 .c .d .s2-2 .e .s2-3\");\n}\n", + "a {\n color: .s1-1 .s2-1 .c .d .s1-2 .s2-2 .e .s1-3.s2-3, .s2-1 .s1-1 .c .d .s1-2 .s2-2 .e .s1-3.s2-3, .s1-1 .s2-1 .c .d .s2-2 .s1-2 .e .s1-3.s2-3, .s2-1 .s1-1 .c .d .s2-2 .s1-2 .e .s1-3.s2-3;\n}\n" +); +test!( + lcs_non_contiguous_different_positions, + "a {\n color: selector-unify(\".s1-1 .c .d .s1-2 .e .s1-3\", \".c .s2-1 .d .e .s2-2 .s2-3\");\n}\n", + "a {\n color: .s1-1 .c .s2-1 .d .s1-2 .e .s2-2 .s1-3.s2-3;\n}\n" +); +test!( + root_in_first_two_layers, + "a {\n color: selector-unify(\":root .c\", \".d .e\");\n}\n", + "a {\n color: :root .d .c.e;\n}\n" +); +test!( + #[ignore = "https://github.com/sass/dart-sass/issues/969"] + root_in_first_three_layers, + "a {\n color: selector-unify(\":root .c .d\", \".e .f\");\n}\n", + "a {\n color: :root .c .e .d.f, :root .e .c .d.f;\n}\n" +); +test!( + root_in_second_two_layers, + "a {\n color: selector-unify(\".c .d\", \":root .e\");\n}\n", + "a {\n color: :root .c .d.e;\n}\n" +); +test!( + #[ignore = "https://github.com/sass/dart-sass/issues/969"] + root_in_second_three_layers, + "a {\n color: selector-unify(\".c .d\", \":root .e .f\");\n}\n", + "a {\n color: :root .c .e .d.f, :root .e .c .d.f;\n}\n" +); +test!( + root_in_both_cant_unify, + "a {\n color: selector-unify(\"c:root .d\", \"e:root .f\");\n}\n", + "" +); +test!( + root_in_both_super_selector, + "a {\n color: selector-unify(\"c:root .d\", \":root .e\");\n}\n", + "a {\n color: c:root .d.e;\n}\n" +); +test!( + root_in_both_can_unify, + "a {\n color: selector-unify(\".c:root .d\", \".e:root .f\");\n}\n", + "a {\n color: .e.c:root .d.f;\n}\n" +); +error!( + parent_in_first_arg, + "a {\n color: selector-unify(\"&\", \"c\");\n}\n", + "Error: $selector1: Parent selectors aren't allowed here." +); +error!( + parent_in_second_arg, + "a {\n color: selector-unify(\"c\", \"&\");\n}\n", + "Error: $selector2: Parent selectors aren't allowed here." +); +error!( + #[ignore = "we don't include the name of the arg in the error message"] + malformed_selector_in_first_arg, + "a {\n color: selector-unify(\"[c\", \"c\");\n}\n", "Error: $selector1: expected more input." +); +error!( + #[ignore = "we don't include the name of the arg in the error message"] + malformed_selector_in_second_arg, + "a {\n color: selector-unify(\"c\", \"[c\");\n}\n", "Error: $selector2: expected more input." +); +error!( + invalid_type_in_first_arg, + "a {\n color: selector-unify(1, \"c\");\n}\n", + "Error: $selector1: 1 is not a valid selector: it must be a string, a list of strings, or a list of lists of strings." +); +error!( + invalid_type_in_second_arg, + "a {\n color: selector-unify(\"c\", 1);\n}\n", + "Error: $selector2: 1 is not a valid selector: it must be a string, a list of strings, or a list of lists of strings." +); diff --git a/tests/selectors.rs b/tests/selectors.rs index 5f8b666..dbf1027 100644 --- a/tests/selectors.rs +++ b/tests/selectors.rs @@ -530,17 +530,26 @@ test!( "+ {\n color: &;\n}\n", "+ {\n color: +;\n}\n" ); -error!( - #[ignore = "namespaces are not yet parsed correctly"] - empty_namespace, - "| {}", "Error: Expected identifier." +test!( + invalid_chars_in_pseudo_parens, + ":c(@#$) {\n color: &;\n}\n", + ":c(@#$) {\n color: :c(@#$);\n}\n" ); test!( - #[ignore = "namespaces are not yet parsed correctly"] - simple_namespace, + empty_namespace_element, "|f {\n color: &;\n}\n", "|f {\n color: |f;\n}\n" ); +test!( + universal_with_namespace, + "a|* {\n color: &;\n}\n", + "a|* {\n color: a|*;\n}\n" +); +test!( + psuedo_element_slotted_args, + "::slotted(b, c) {\n color: &;\n}\n", + "::slotted(b, c) {\n color: ::slotted(b, c);\n}\n" +); error!(nothing_after_period, ". {}", "Error: Expected identifier."); error!(nothing_after_hash, "# {}", "Error: Expected identifier."); error!(nothing_after_percent, "% {}", "Error: Expected identifier."); From d5844e3536e5ec9a31733b77ea00d3e92c0022e7 Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 31 May 2020 08:01:32 -0400 Subject: [PATCH 03/15] more tests for is-superselector --- src/selector/compound.rs | 2 +- tests/is-superselector.rs | 153 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 1 deletion(-) diff --git a/src/selector/compound.rs b/src/selector/compound.rs index 61fcba2..a014cb4 100644 --- a/src/selector/compound.rs +++ b/src/selector/compound.rs @@ -84,7 +84,7 @@ impl CompoundSelector { .. }) = simple2 { - if simple2.is_super_selector_of_compound(&self) { + if !simple2.is_super_selector_of_compound(&self) { return false; } } diff --git a/tests/is-superselector.rs b/tests/is-superselector.rs index e1a32b4..86f3723 100644 --- a/tests/is-superselector.rs +++ b/tests/is-superselector.rs @@ -73,3 +73,156 @@ test!( "a {\n color: is-superselector(\"c, d, e\", \"c, f\");\n}\n", "a {\n color: false;\n}\n" ); +test!( + simple_attribute_equal, + "a {\n color: is-superselector(\"[c=d]\", \"[c=d]\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + simple_attribute_different_attr, + "a {\n color: is-superselector(\"[c=d]\", \"[e=d]\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_attribute_different_val, + "a {\n color: is-superselector(\"[c=d]\", \"[c=e]\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_attribute_different_operator, + "a {\n color: is-superselector(\"[c=d]\", \"[c^=e]\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_class_equal, + "a {\n color: is-superselector(\".c\", \".c\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + simple_class_not_equal, + "a {\n color: is-superselector(\".c\", \".d\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_id_equal, + "a {\n color: is-superselector(\"#c\", \"#c\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + simple_id_not_equal, + "a {\n color: is-superselector(\"#c\", \"#d\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_placeholder_equal, + "a {\n color: is-superselector(\"%c\", \"%c\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + simple_placeholder_not_equal, + "a {\n color: is-superselector(\"%c\", \"%d\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_type_equal, + "a {\n color: is-superselector(\"c\", \"c\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + simple_type_not_equal, + "a {\n color: is-superselector(\"c\", \"d\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_type_and_universal, + "a {\n color: is-superselector(\"c\", \"*\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_type_explicit_namespace_equal, + "a {\n color: is-superselector(\"c|d\", \"c|d\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + simple_type_different_explicit_namespace, + "a {\n color: is-superselector(\"c|d\", \"e|d\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_type_explicit_namespace_and_implicit_namespace, + "a {\n color: is-superselector(\"c|d\", \"d\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_type_explicit_namespace_and_empty_namespace, + "a {\n color: is-superselector(\"c|d\", \"|d\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_type_explicit_namespace_and_universal_namespace, + "a {\n color: is-superselector(\"c|d\", \"*|d\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_type_empty_namespace_and_explicit_namespace, + "a {\n color: is-superselector(\"|c\", \"d|c\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_type_empty_namespace_and_empty_namespace, + "a {\n color: is-superselector(\"|c\", \"|c\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + #[ignore = "https://github.com/sass/dart-sass/issues/789"] + simple_type_universal_namespace_and_explicit_namespace, + "a {\n color: is-superselector(\"*|c\", \"d|c\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + #[ignore = "https://github.com/sass/dart-sass/issues/789"] + simple_type_universal_namespace_and_implicit_namespace, + "a {\n color: is-superselector(\"*|c\", \"c\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + #[ignore = "https://github.com/sass/dart-sass/issues/789"] + simple_type_universal_namespace_and_empty_namespace, + "a {\n color: is-superselector(\"*|c\", \"|c\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + simple_type_universal_namespace_and_universal_namespace, + "a {\n color: is-superselector(\"*|c\", \"*|c\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + simple_pseudo_no_args_equal, + "a {\n color: is-superselector(\":c\", \":c\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + simple_pseudo_no_args_different, + "a {\n color: is-superselector(\":c\", \":d\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_pseudo_no_args_class_and_element, + "a {\n color: is-superselector(\":c\", \"::c\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_pseudo_no_args_element_and_element_equal, + "a {\n color: is-superselector(\"::c\", \"::c\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + simple_pseudo_no_args_element_and_element_different, + "a {\n color: is-superselector(\"::c\", \"::d\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_pseudo_no_args_element_and_class, + "a {\n color: is-superselector(\"::c\", \":c\");\n}\n", + "a {\n color: false;\n}\n" +); From ccc0c84eeede00127e359ba24e69e5af3c71d3d1 Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 31 May 2020 15:16:25 -0400 Subject: [PATCH 04/15] remove all unwraps and todo! from selector parsing --- src/selector/parse.rs | 105 +++++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 42 deletions(-) diff --git a/src/selector/parse.rs b/src/selector/parse.rs index d4cd16e..5fd682d 100644 --- a/src/selector/parse.rs +++ b/src/selector/parse.rs @@ -10,7 +10,8 @@ use crate::error::SassResult; use crate::scope::Scope; use crate::selector::Selector; use crate::utils::{ - devour_whitespace, eat_ident, is_name, is_name_start, read_until_closing_paren, + devour_whitespace, eat_ident, eat_ident_no_interpolation, is_name, is_name_start, + read_until_closing_paren, }; use crate::Token; @@ -270,31 +271,31 @@ impl<'a, I: Iterator> SelectorParser<'a, I> { } fn parse_attribute_selector(&mut self) -> SassResult { - let start = self.toks.next().unwrap().pos; + self.toks.next(); Ok(SimpleSelector::Attribute(Attribute::from_tokens( self.toks, self.scope, self.super_selector, - start, + self.span, )?)) } fn parse_class_selector(&mut self) -> SassResult { - let span_before = self.toks.next().unwrap().pos; + self.toks.next(); Ok(SimpleSelector::Class( - eat_ident(self.toks, self.scope, self.super_selector, span_before)?.node, + eat_ident(self.toks, self.scope, self.super_selector, self.span)?.node, )) } fn parse_id_selector(&mut self) -> SassResult { - let span_before = self.toks.next().unwrap().pos; + self.toks.next(); Ok(SimpleSelector::Id( - eat_ident(self.toks, self.scope, self.super_selector, span_before)?.node, + eat_ident(self.toks, self.scope, self.super_selector, self.span)?.node, )) } fn parse_pseudo_selector(&mut self) -> SassResult { - let span_before = self.toks.next().unwrap().pos; + self.toks.next(); let element = match self.toks.peek() { Some(Token { kind: ':', .. }) => { self.toks.next(); @@ -303,7 +304,7 @@ impl<'a, I: Iterator> SelectorParser<'a, I> { _ => false, }; - let name = eat_ident(self.toks, self.scope, self.super_selector, span_before)?; + let name = eat_ident(self.toks, self.scope, self.super_selector, self.span)?; match self.toks.peek() { Some(Token { kind: '(', .. }) => self.toks.next(), @@ -332,38 +333,33 @@ impl<'a, I: Iterator> SelectorParser<'a, I> { if SELECTOR_PSEUDO_ELEMENTS.contains(&unvendored) { selector = Some(self.parse_selector_list()?); devour_whitespace(self.toks); - assert!(matches!(self.toks.next(), Some(Token { kind: ')', .. }))); + self.expect_closing_paren()?; } else { argument = Some(self.declaration_value(true)?); } } else if SELECTOR_PSEUDO_CLASSES.contains(&unvendored) { selector = Some(self.parse_selector_list()?); devour_whitespace(self.toks); - assert!(matches!(self.toks.next(), Some(Token { kind: ')', .. }))); + self.expect_closing_paren()?; } else if unvendored == "nth-child" || unvendored == "nth-last-child" { let mut this_arg = self.parse_a_n_plus_b()?; let found_whitespace = devour_whitespace(self.toks); match (found_whitespace, self.toks.peek()) { (_, Some(Token { kind: ')', .. })) => {} (true, _) => { - expect_identifier("of")?; - this_arg.push_str("of"); + self.expect_identifier("of")?; + this_arg.push_str(" of"); devour_whitespace(self.toks); selector = Some(self.parse_selector_list()?); } _ => {} } + self.expect_closing_paren()?; argument = Some(this_arg); } else { argument = Some(self.declaration_value(true)?.trim_end().to_string()); } - // todo: closing paren is consumed - // if let Some(Token { kind: ')', .. }) = dbg!(self.toks.next()) { - // } else { - // todo!("expected \")\"."); - // } - Ok(SimpleSelector::Pseudo(Pseudo { normalized_name: unvendor(&name.node).to_string(), is_class: !element && !is_fake_pseudo_element(&name), @@ -376,9 +372,9 @@ impl<'a, I: Iterator> SelectorParser<'a, I> { } fn parse_parent_selector(&mut self) -> SassResult { - let span_before = self.toks.next().unwrap().pos; + self.toks.next(); let suffix = if self.looking_at_identifier_body() { - Some(eat_ident(self.toks, self.scope, self.super_selector, span_before)?.node) + Some(eat_ident(self.toks, self.scope, self.super_selector, self.span)?.node) } else { None }; @@ -386,9 +382,9 @@ impl<'a, I: Iterator> SelectorParser<'a, I> { } fn parse_placeholder_selector(&mut self) -> SassResult { - let span_before = self.toks.next().unwrap().pos; + self.toks.next(); Ok(SimpleSelector::Placeholder( - eat_ident(self.toks, self.scope, self.super_selector, span_before)?.node, + eat_ident(self.toks, self.scope, self.super_selector, self.span)?.node, )) } @@ -396,7 +392,7 @@ impl<'a, I: Iterator> SelectorParser<'a, I> { /// /// These are combined because either one could start with `*`. fn parse_type_or_universal_selector(&mut self) -> SassResult { - let span_before = self.toks.peek().unwrap().pos; + self.toks.peek(); match self.toks.peek().cloned() { Some(Token { kind: '*', pos }) => { @@ -435,7 +431,7 @@ impl<'a, I: Iterator> SelectorParser<'a, I> { } let name_or_namespace = - eat_ident(self.toks, self.scope, self.super_selector, span_before)?.node; + eat_ident(self.toks, self.scope, self.super_selector, self.span)?.node; Ok(match self.toks.peek() { Some(Token { kind: '|', .. }) => { @@ -445,7 +441,7 @@ impl<'a, I: Iterator> SelectorParser<'a, I> { SimpleSelector::Universal(Namespace::Other(name_or_namespace)) } else { SimpleSelector::Type(QualifiedName { - ident: eat_ident(self.toks, self.scope, self.super_selector, span_before)? + ident: eat_ident(self.toks, self.scope, self.super_selector, self.span)? .node, namespace: Namespace::Other(name_or_namespace), }) @@ -465,8 +461,14 @@ impl<'a, I: Iterator> SelectorParser<'a, I> { let mut buf = String::new(); match self.toks.peek() { - Some(Token { kind: 'e', .. }) | Some(Token { kind: 'E', .. }) => todo!("even"), - Some(Token { kind: 'o', .. }) | Some(Token { kind: 'O', .. }) => todo!("odd"), + Some(Token { kind: 'e', .. }) | Some(Token { kind: 'E', .. }) => { + self.expect_identifier("even")?; + return Ok("even".to_string()); + } + Some(Token { kind: 'o', .. }) | Some(Token { kind: 'O', .. }) => { + self.expect_identifier("odd")?; + return Ok("odd".to_string()); + } Some(t @ Token { kind: '+', .. }) | Some(t @ Token { kind: '-', .. }) => { buf.push(t.kind); self.toks.next(); @@ -480,30 +482,34 @@ impl<'a, I: Iterator> SelectorParser<'a, I> { if !t.kind.is_ascii_digit() { break; } - devour_whitespace(self.toks); - if let Some(t) = self.toks.next() { - if t.kind != 'n' && t.kind != 'N' { - return Ok(buf); - } + buf.push(t.kind); + self.toks.next(); + } + devour_whitespace(self.toks); + if let Some(t) = self.toks.peek() { + if t.kind != 'n' && t.kind != 'N' { + return Ok(buf); } + self.toks.next(); } } Some(t) => { if t.kind == 'n' || t.kind == 'N' { self.toks.next(); } else { - todo!() + return Err(("Expected \"n\".", self.span).into()); } } - None => todo!(), + None => return Err(("expected more input.", self.span).into()), } buf.push('n'); devour_whitespace(self.toks); - if let Some(Token { kind: '+', .. }) | Some(Token { kind: '-', .. }) = self.toks.peek() { - buf.push(self.toks.next().unwrap().kind); + if let Some(t @ Token { kind: '+', .. }) | Some(t @ Token { kind: '-', .. }) = self.toks.peek() { + buf.push(t.kind); + self.toks.next(); devour_whitespace(self.toks); match self.toks.peek() { Some(t) if !t.kind.is_ascii_digit() => { @@ -517,7 +523,8 @@ impl<'a, I: Iterator> SelectorParser<'a, I> { if !t.kind.is_ascii_digit() { break; } - buf.push(self.toks.next().unwrap().kind); + buf.push(t.kind); + self.toks.next(); } } Ok(buf) @@ -532,6 +539,24 @@ impl<'a, I: Iterator> SelectorParser<'a, I> { } Ok(tmp.into_iter().map(|t| t.kind).collect::()) } + + fn expect_identifier(&mut self, s: &str) -> SassResult<()> { + let mut ident = eat_ident_no_interpolation(self.toks, false, self.span)?.node; + ident.make_ascii_lowercase(); + if ident == s { + Ok(()) + } else { + Err((format!("Expected \"{}\".", s), self.span).into()) + } + } + + fn expect_closing_paren(&mut self) -> SassResult<()> { + if let Some(Token { kind: ')', .. }) = self.toks.next() { + Ok(()) + } else { + Err(("expected \")\".", self.span).into()) + } + } } /// Returns whether `c` can start a simple selector other than a type @@ -577,7 +602,3 @@ fn is_fake_pseudo_element(name: &str) -> bool { _ => false, } } - -fn expect_identifier(s: &str) -> SassResult<()> { - todo!() -} From b4ef2a6fb4acd6c8092c573aa4974b08d03eb562 Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 31 May 2020 15:16:38 -0400 Subject: [PATCH 05/15] add tests for an+b --- tests/selectors.rs | 120 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/tests/selectors.rs b/tests/selectors.rs index dbf1027..eefe45d 100644 --- a/tests/selectors.rs +++ b/tests/selectors.rs @@ -550,6 +550,126 @@ test!( "::slotted(b, c) {\n color: &;\n}\n", "::slotted(b, c) {\n color: ::slotted(b, c);\n}\n" ); +test!( + a_n_plus_b, + ":nth-child(2n+0) {\n color: &;\n}\n", + ":nth-child(2n+0) {\n color: :nth-child(2n+0);\n}\n" +); +test!( + a_n_plus_b_leading_negative, + ":nth-child(-1n+6) {\n color: &;\n}\n", + ":nth-child(-1n+6) {\n color: :nth-child(-1n+6);\n}\n" +); +test!( + a_n_plus_b_leading_plus, + ":nth-child(+3n-2) {\n color: &;\n}\n", + ":nth-child(+3n-2) {\n color: :nth-child(+3n-2);\n}\n" +); +test!( + a_n_plus_b_n_alone, + ":nth-child(n) {\n color: &;\n}\n", + ":nth-child(n) {\n color: :nth-child(n);\n}\n" +); +test!( + a_n_plus_b_capital_n, + ":nth-child(N) {\n color: &;\n}\n", + ":nth-child(n) {\n color: :nth-child(n);\n}\n" +); +test!( + a_n_plus_b_n_with_leading_number, + ":nth-child(2n) {\n color: &;\n}\n", + ":nth-child(2n) {\n color: :nth-child(2n);\n}\n" +); +test!( + a_n_plus_b_n_whitespace_on_both_sides, + ":nth-child(3n + 1) {\n color: &;\n}\n", + ":nth-child(3n+1) {\n color: :nth-child(3n+1);\n}\n" +); +test!( + a_n_plus_b_n_of, + ":nth-child(2n+1 of b, c) {\n color: &;\n}\n", + ":nth-child(2n+1 of b, c) {\n color: :nth-child(2n+1 of b, c);\n}\n" +); +test!( + a_n_plus_b_n_number_alone, + ":nth-child(5) {\n color: &;\n}\n", + ":nth-child(5) {\n color: :nth-child(5);\n}\n" +); +test!( + a_n_plus_b_n_number_leading_negative, + ":nth-child(-5) {\n color: &;\n}\n", + ":nth-child(-5) {\n color: :nth-child(-5);\n}\n" +); +test!( + a_n_plus_b_n_number_leading_plus, + ":nth-child(+5) {\n color: &;\n}\n", + ":nth-child(+5) {\n color: :nth-child(+5);\n}\n" +); +test!( + a_n_plus_b_n_leading_negative_no_leading_number, + ":nth-child(-n+ 6) {\n color: &;\n}\n", + ":nth-child(-n+6) {\n color: :nth-child(-n+6);\n}\n" +); +test!( + a_n_plus_b_n_even_all_lowercase, + ":nth-child(even) {\n color: &;\n}\n", + ":nth-child(even) {\n color: :nth-child(even);\n}\n" +); +test!( + a_n_plus_b_n_even_mixed_case, + ":nth-child(eVeN) {\n color: &;\n}\n", + ":nth-child(even) {\n color: :nth-child(even);\n}\n" +); +test!( + a_n_plus_b_n_even_uppercase, + ":nth-child(EVEN) {\n color: &;\n}\n", + ":nth-child(even) {\n color: :nth-child(even);\n}\n" +); +test!( + a_n_plus_b_n_even_whitespace, + ":nth-child( even ) {\n color: &;\n}\n", + ":nth-child(even) {\n color: :nth-child(even);\n}\n" +); +error!( + a_n_plus_b_n_value_after_even, + ":nth-child(even 1) {\n color: &;\n}\n", "Error: Expected \"of\"." +); +error!( + a_n_plus_b_n_invalid_even, + ":nth-child(efven) {\n color: &;\n}\n", "Error: Expected \"even\"." +); +test!( + a_n_plus_b_n_odd_all_lowercase, + ":nth-child(odd) {\n color: &;\n}\n", + ":nth-child(odd) {\n color: :nth-child(odd);\n}\n" +); +test!( + a_n_plus_b_n_odd_mixed_case, + ":nth-child(oDd) {\n color: &;\n}\n", + ":nth-child(odd) {\n color: :nth-child(odd);\n}\n" +); +test!( + a_n_plus_b_n_odd_uppercase, + ":nth-child(ODD) {\n color: &;\n}\n", + ":nth-child(odd) {\n color: :nth-child(odd);\n}\n" +); +error!( + a_n_plus_b_n_invalid_odd, + ":nth-child(ofdd) {\n color: &;\n}\n", "Error: Expected \"odd\"." +); +error!( + a_n_plus_b_n_invalid_starting_char, + ":nth-child(f) {\n color: &;\n}\n", "Error: Expected \"n\"." +); +error!( + #[ignore = "we read until closing paren, giving a different error message"] + a_n_plus_b_n_nothing_after_open_paren, + ":nth-child({\n color: &;\n}\n", "Error: expected more input." +); +error!( + a_n_plus_b_n_invalid_char_after_even, + ":nth-child(even#) {\n color: &;\n}\n", "Error: expected \")\"." +); error!(nothing_after_period, ". {}", "Error: Expected identifier."); error!(nothing_after_hash, "# {}", "Error: Expected identifier."); error!(nothing_after_percent, "% {}", "Error: Expected identifier."); From 8390fd83549271008366e51a874f1e69c7f247c3 Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 31 May 2020 15:48:11 -0400 Subject: [PATCH 06/15] resolve clippy lints --- src/atrule/mixin.rs | 2 +- src/atrule/parse.rs | 4 +- src/builtin/selector.rs | 8 +- src/color/mod.rs | 2 +- src/error.rs | 2 +- src/lib.rs | 1 + src/selector/complex.rs | 6 +- src/selector/compound.rs | 18 +- src/selector/extend/mod.rs | 395 +++++++++++++++++++------------------ src/selector/list.rs | 32 ++- src/selector/mod.rs | 7 +- src/selector/parse.rs | 15 +- src/selector/simple.rs | 20 +- src/stylesheet.rs | 2 +- 14 files changed, 263 insertions(+), 251 deletions(-) diff --git a/src/atrule/mixin.rs b/src/atrule/mixin.rs index a31cbbe..d8df725 100644 --- a/src/atrule/mixin.rs +++ b/src/atrule/mixin.rs @@ -156,7 +156,7 @@ impl Mixin { } Expr::Selector(selector) => { let rules = self.eval( - &selector.resolve_parent_selectors(&super_selector, true), + &selector.resolve_parent_selectors(super_selector, true), content, )?; stmts.push(Spanned { diff --git a/src/atrule/parse.rs b/src/atrule/parse.rs index 33f058c..b1bbf13 100644 --- a/src/atrule/parse.rs +++ b/src/atrule/parse.rs @@ -33,7 +33,7 @@ pub(crate) fn eat_stmts>( let rules = eat_stmts( toks, scope, - &selector.resolve_parent_selectors(&super_selector, true), + &selector.resolve_parent_selectors(super_selector, true), at_root, content, )?; @@ -82,7 +82,7 @@ pub(crate) fn eat_stmts_at_root>( Expr::MixinDecl(..) | Expr::FunctionDecl(..) => todo!(), Expr::Selector(mut selector) => { if nesting > 1 || is_some { - selector = selector.resolve_parent_selectors(&super_selector, true); + selector = selector.resolve_parent_selectors(super_selector, true); } else { selector = Selector::replace(super_selector, selector); } diff --git a/src/builtin/selector.rs b/src/builtin/selector.rs index f3c56d3..11e6054 100644 --- a/src/builtin/selector.rs +++ b/src/builtin/selector.rs @@ -93,7 +93,7 @@ fn selector_append( .node .to_selector(span, scope, super_selector, "selectors", false)?; if tmp.contains_parent_selector() { - return Err(("Parent selectors aren't allowed here.", span).into()); + Err(("Parent selectors aren't allowed here.", span).into()) } else { Ok(tmp) } @@ -128,9 +128,7 @@ fn selector_append( line_break: false, }) } else { - return Err( - (format!("Can't append {} to {}.", complex, parent), span).into() - ); + Err((format!("Can't append {} to {}.", complex, parent), span).into()) } }) .collect::>>()?, @@ -196,7 +194,7 @@ fn selector_unify( .into()); } - Ok(match selector1.unify(selector2) { + Ok(match selector1.unify(&selector2) { Some(sel) => sel.into_value(), None => Value::Null, }) diff --git a/src/color/mod.rs b/src/color/mod.rs index 2162383..81b8806 100644 --- a/src/color/mod.rs +++ b/src/color/mod.rs @@ -47,7 +47,7 @@ impl Color { } } - fn new_hsla( + const fn new_hsla( red: Number, green: Number, blue: Number, diff --git a/src/error.rs b/src/error.rs index bc6db85..f1b9908 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,7 +20,7 @@ impl SassError { } } - pub(crate) fn from_loc(message: String, loc: SpanLoc) -> Self { + pub(crate) const fn from_loc(message: String, loc: SpanLoc) -> Self { SassError { kind: SassErrorKind::ParseError { message, loc }, } diff --git a/src/lib.rs b/src/lib.rs index cc32ed9..82105d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,6 +67,7 @@ grass input.scss clippy::todo, clippy::too_many_lines, clippy::panic, + clippy::unwrap_used, clippy::option_unwrap_used, clippy::result_unwrap_used, clippy::cast_possible_truncation, diff --git a/src/selector/complex.rs b/src/selector/complex.rs index 2904e2f..feaecae 100644 --- a/src/selector/complex.rs +++ b/src/selector/complex.rs @@ -62,7 +62,7 @@ impl ComplexSelector { pub fn is_invisible(&self) -> bool { self.components .iter() - .any(|component| component.is_invisible()) + .any(ComplexSelectorComponent::is_invisible) } /// Returns whether `self` is a superselector of `other`. @@ -108,7 +108,7 @@ impl ComplexSelector { .collect(); return compound1.is_super_selector( other.components.last().unwrap().as_compound(), - Some(parents), + &Some(parents), ); } @@ -119,7 +119,7 @@ impl ComplexSelector { { if compound1.is_super_selector( compound2, - Some( + &Some( other .components .iter() diff --git a/src/selector/compound.rs b/src/selector/compound.rs index a014cb4..64c81f2 100644 --- a/src/selector/compound.rs +++ b/src/selector/compound.rs @@ -16,14 +16,14 @@ impl fmt::Display for CompoundSelector { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut did_write = false; for simple in &self.components { - if !did_write { + if did_write { + write!(f, "{}", simple)?; + } else { let s = simple.to_string(); if !s.is_empty() { did_write = true; } write!(f, "{}", s)?; - } else { - write!(f, "{}", simple)?; } } @@ -43,7 +43,7 @@ impl CompoundSelector { pub fn specificity(&self) -> Specificity { let mut min = 0; let mut max = 0; - for simple in self.components.iter() { + for simple in &self.components { todo!() // min += simple.min_specificity; // max += simple.max_specificity; @@ -52,15 +52,13 @@ impl CompoundSelector { } pub fn is_invisible(&self) -> bool { - self.components - .iter() - .any(|component| component.is_invisible()) + self.components.iter().any(SimpleSelector::is_invisible) } pub fn is_super_selector( &self, other: &Self, - parents: Option>, + parents: &Option>, ) -> bool { for simple1 in &self.components { if let SimpleSelector::Pseudo( @@ -84,13 +82,13 @@ impl CompoundSelector { .. }) = simple2 { - if !simple2.is_super_selector_of_compound(&self) { + if !simple2.is_super_selector_of_compound(self) { return false; } } } - return true; + true } /// Returns a new `CompoundSelector` based on `compound` with all diff --git a/src/selector/extend/mod.rs b/src/selector/extend/mod.rs index d5ddf2c..d237677 100644 --- a/src/selector/extend/mod.rs +++ b/src/selector/extend/mod.rs @@ -1,3 +1,5 @@ +#![allow(clippy::similar_names)] + use std::collections::VecDeque; use super::{ @@ -5,7 +7,7 @@ use super::{ }; /// Returns the contents of a `SelectorList` that matches only elements that are -/// matched by both `complex1` and `complex2`. +/// matched by both `complex_one` and `complex_two`. /// /// If no such list can be produced, returns `None`. pub(crate) fn unify_complex( @@ -74,7 +76,7 @@ fn weave(complexes: Vec>) -> Vec>) -> Vec>) -> Vec>) -> Vec, - parents2: Vec, + parents_one: Vec, + parents_two: Vec, ) -> Option>> { - let mut queue1 = VecDeque::from(parents1); - let mut queue2 = VecDeque::from(parents2); + let mut queue_one = VecDeque::from(parents_one); + let mut queue_two = VecDeque::from(parents_two); - let initial_combinators = merge_initial_combinators(&mut queue1, &mut queue2)?; + let initial_combinators = merge_initial_combinators(&mut queue_one, &mut queue_two)?; - let mut final_combinators = merge_final_combinators(&mut queue1, &mut queue2, None)?; + let mut final_combinators = merge_final_combinators(&mut queue_one, &mut queue_two, None)?; - match (first_if_root(&mut queue1), first_if_root(&mut queue2)) { - (Some(root1), Some(root2)) => { - let root = ComplexSelectorComponent::Compound(root1.unify(root2)?); - queue1.push_front(root.clone()); - queue2.push_front(root); + match (first_if_root(&mut queue_one), first_if_root(&mut queue_two)) { + (Some(root_one), Some(root_two)) => { + let root = ComplexSelectorComponent::Compound(root_one.unify(root_two)?); + queue_one.push_front(root.clone()); + queue_two.push_front(root); } - (Some(root1), None) => { - queue2.push_front(ComplexSelectorComponent::Compound(root1)); + (Some(root_one), None) => { + queue_two.push_front(ComplexSelectorComponent::Compound(root_one)); } - (None, Some(root2)) => { - queue1.push_front(ComplexSelectorComponent::Compound(root2)); + (None, Some(root_two)) => { + queue_one.push_front(ComplexSelectorComponent::Compound(root_two)); } (None, None) => {} } - let mut groups1 = group_selectors(Vec::from(queue1)); - let mut groups2 = group_selectors(Vec::from(queue2)); + let mut groups_one = group_selectors(Vec::from(queue_one)); + let mut groups_two = group_selectors(Vec::from(queue_two)); let lcs = longest_common_subsequence( - Vec::from(groups2.clone()), - Vec::from(groups1.clone()), - Some(&|group1, group2| { - if group1 == group2 { - return Some(group1); + groups_two.as_slices().0, + groups_one.as_slices().0, + Some(&|group_one, group_two| { + if group_one == group_two { + return Some(group_one); } - if let ComplexSelectorComponent::Combinator(..) = group1.first()? { + if let ComplexSelectorComponent::Combinator(..) = group_one.first()? { return None; } - if let ComplexSelectorComponent::Combinator(..) = group2.first()? { + if let ComplexSelectorComponent::Combinator(..) = group_two.first()? { return None; } - if complex_is_parent_superselector(group1.clone(), group2.clone()) { - return Some(group2); + if complex_is_parent_superselector(group_one.clone(), group_two.clone()) { + return Some(group_two); } - if complex_is_parent_superselector(group2.clone(), group1.clone()) { - return Some(group1); + if complex_is_parent_superselector(group_two.clone(), group_one.clone()) { + return Some(group_one); } - if !must_unify(group1.clone(), group2.clone()) { + if !must_unify(&group_one, &group_two) { return None; } - let unified = unify_complex(vec![group1, group2])?; + let unified = unify_complex(vec![group_one, group_two])?; if unified.len() > 1 { return None; } @@ -188,7 +188,7 @@ fn weave_parents( for group in lcs { choices.push( - chunks(&mut groups1, &mut groups2, |sequence| { + chunks(&mut groups_one, &mut groups_two, |sequence| { complex_is_parent_superselector(sequence.get(0).unwrap().clone(), group.clone()) }) .into_iter() @@ -196,12 +196,12 @@ fn weave_parents( .collect(), ); choices.push(vec![group]); - groups1.pop_front(); - groups2.pop_front(); + groups_one.pop_front(); + groups_two.pop_front(); } choices.push( - chunks(&mut groups1, &mut groups2, |sequence| sequence.is_empty()) + chunks(&mut groups_one, &mut groups_two, VecDeque::is_empty) .into_iter() .map(|chunk| chunk.into_iter().flatten().collect()) .collect(), @@ -222,35 +222,35 @@ fn weave_parents( ) } -/// Extracts leading `Combinator`s from `components1` and `components2` and +/// Extracts leading `Combinator`s from `components_one` and `components_two` and /// merges them together into a single list of combinators. /// /// If there are no combinators to be merged, returns an empty list. If the /// combinators can't be merged, returns `None`. fn merge_initial_combinators( - components1: &mut VecDeque, - components2: &mut VecDeque, + components_one: &mut VecDeque, + components_two: &mut VecDeque, ) -> Option> { - let mut combinators1: Vec = Vec::new(); + let mut combinators_one: Vec = Vec::new(); - while let Some(ComplexSelectorComponent::Combinator(c)) = components1.get(0) { - combinators1.push(*c); - components1.pop_front(); + while let Some(ComplexSelectorComponent::Combinator(c)) = components_one.get(0) { + combinators_one.push(*c); + components_one.pop_front(); } - let mut combinators2 = Vec::new(); + let mut combinators_two = Vec::new(); - while let Some(ComplexSelectorComponent::Combinator(c)) = components2.get(0) { - combinators2.push(*c); - components2.pop_front(); + while let Some(ComplexSelectorComponent::Combinator(c)) = components_two.get(0) { + combinators_two.push(*c); + components_two.pop_front(); } - let lcs = longest_common_subsequence(combinators1.clone(), combinators2.clone(), None); + let lcs = longest_common_subsequence(&combinators_one, &combinators_two, None); - if lcs == combinators1 { - Some(combinators2) - } else if lcs == combinators2 { - Some(combinators1) + if lcs == combinators_one { + Some(combinators_two) + } else if lcs == combinators_two { + Some(combinators_one) } else { // If neither sequence of combinators is a subsequence of the other, they // cannot be merged successfully. @@ -258,34 +258,38 @@ fn merge_initial_combinators( } } -/// Returns the longest common subsequence between `list1` and `list2`. +/// Returns the longest common subsequence between `list_one` and `list_two`. /// /// If there are more than one equally long common subsequence, returns the one -/// which starts first in `list1`. +/// which starts first in `list_one`. /// /// If `select` is passed, it's used to check equality between elements in each /// list. If it returns `None`, the elements are considered unequal; otherwise, /// it should return the element to include in the return value. +#[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)] fn longest_common_subsequence( - list1: Vec, - list2: Vec, + list_one: &[T], + list_two: &[T], select: Option<&dyn Fn(T, T) -> Option>, ) -> Vec { - let select = select.unwrap_or(&|element1, element2| { - if element1 == element2 { - Some(element1) + let select = select.unwrap_or(&|element_one, element_two| { + if element_one == element_two { + Some(element_one) } else { None } }); - let mut lengths = vec![vec![0; list2.len() + 1]; list1.len() + 1]; + let mut lengths = vec![vec![0; list_two.len() + 1]; list_one.len() + 1]; - let mut selections: Vec>> = vec![vec![None; list2.len()]; list1.len()]; + let mut selections: Vec>> = vec![vec![None; list_two.len()]; list_one.len()]; - for i in 0..list1.len() { - for j in 0..list2.len() { - let selection = select(list1.get(i).unwrap().clone(), list2.get(j).unwrap().clone()); + for i in 0..list_one.len() { + for j in 0..list_two.len() { + let selection = select( + list_one.get(i).unwrap().clone(), + list_two.get(j).unwrap().clone(), + ); selections[i][j] = selection.clone(); lengths[i + 1][j + 1] = if selection.is_none() { std::cmp::max(lengths[i + 1][j], lengths[i][j + 1]) @@ -319,69 +323,70 @@ fn longest_common_subsequence( } } backtrack( - list1.len() as isize - 1, - list2.len() as isize - 1, + (list_one.len() as isize).saturating_sub(1), + (list_two.len() as isize).saturating_sub(1), lengths, &mut selections, ) } /// Extracts trailing `Combinator`s, and the selectors to which they apply, from -/// `components1` and `components2` and merges them together into a single list. +/// `components_one` and `components_two` and merges them together into a single list. /// /// If there are no combinators to be merged, returns an empty list. If the /// sequences can't be merged, returns `None`. +#[allow(clippy::cognitive_complexity)] fn merge_final_combinators( - components1: &mut VecDeque, - components2: &mut VecDeque, + components_one: &mut VecDeque, + components_two: &mut VecDeque, result: Option>>>, ) -> Option>>> { - let mut result = result.unwrap_or(VecDeque::new()); + let mut result = result.unwrap_or_default(); - if (components1.is_empty() - || !components1 - .get(components1.len() - 1) + if (components_one.is_empty() + || !components_one + .get(components_one.len() - 1) .unwrap() .is_combinator()) - && (components2.is_empty() - || !components2 - .get(components2.len() - 1) + && (components_two.is_empty() + || !components_two + .get(components_two.len() - 1) .unwrap() .is_combinator()) { return Some(Vec::from(result)); } - let mut combinators1 = Vec::new(); + let mut combinators_one = Vec::new(); while let Some(ComplexSelectorComponent::Combinator(combinator)) = - components1.get(components1.len().checked_sub(1).unwrap_or(0)) + components_one.get(components_one.len().saturating_sub(1)) { - combinators1.push(*combinator); - components1.pop_back(); + combinators_one.push(*combinator); + components_one.pop_back(); } - let mut combinators2 = Vec::new(); + let mut combinators_two = Vec::new(); while let Some(ComplexSelectorComponent::Combinator(combinator)) = - components2.get(components2.len().checked_sub(1).unwrap_or(0)) + components_two.get(components_two.len().saturating_sub(1)) { - combinators2.push(*combinator); - components2.pop_back(); + combinators_two.push(*combinator); + components_two.pop_back(); } - if combinators1.len() > 1 || combinators2.len() > 1 { + if combinators_one.len() > 1 || combinators_two.len() > 1 { // If there are multiple combinators, something hacky's going on. If one // is a supersequence of the other, use that, otherwise give up. - let lcs = longest_common_subsequence(combinators1.clone(), combinators2.clone(), None); - if lcs == combinators1 { - result.push_front(vec![combinators2 + let lcs = longest_common_subsequence(&combinators_one, &combinators_two, None); + if lcs == combinators_one { + result.push_front(vec![combinators_two .into_iter() .map(ComplexSelectorComponent::Combinator) .rev() .collect()]); - } else if lcs == combinators2 { - result.push_front(vec![combinators1 + } else if lcs == combinators_two { + result.push_front(vec![combinators_one .into_iter() .map(ComplexSelectorComponent::Combinator) .rev() @@ -393,60 +398,60 @@ fn merge_final_combinators( return Some(Vec::from(result)); } - let combinator1 = if combinators1.is_empty() { + let combinator_one = if combinators_one.is_empty() { None } else { - combinators1.first() + combinators_one.first() }; - let combinator2 = if combinators2.is_empty() { + let combinator_two = if combinators_two.is_empty() { None } else { - combinators2.first() + combinators_two.first() }; // This code looks complicated, but it's actually just a bunch of special // cases for interactions between different combinators. - match (combinator1, combinator2) { - (Some(combinator1), Some(combinator2)) => { - let compound1 = match components1.pop_back() { + match (combinator_one, combinator_two) { + (Some(combinator_one), Some(combinator_two)) => { + let compound_one = match components_one.pop_back() { Some(ComplexSelectorComponent::Compound(c)) => c, Some(..) | None => unreachable!(), }; - let compound2 = match components2.pop_back() { + let compound_two = match components_two.pop_back() { Some(ComplexSelectorComponent::Compound(c)) => c, Some(..) | None => unreachable!(), }; - match (combinator1, combinator2) { + match (combinator_one, combinator_two) { (Combinator::FollowingSibling, Combinator::FollowingSibling) => { - if compound1.is_super_selector(&compound2, None) { + if compound_one.is_super_selector(&compound_two, &None) { result.push_front(vec![vec![ - ComplexSelectorComponent::Compound(compound2), + ComplexSelectorComponent::Compound(compound_two), ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), ]]) - } else if compound2.is_super_selector(&compound1, None) { + } else if compound_two.is_super_selector(&compound_one, &None) { result.push_front(vec![vec![ - ComplexSelectorComponent::Compound(compound1), + ComplexSelectorComponent::Compound(compound_one), ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), ]]) } else { let mut choices = vec![ vec![ - ComplexSelectorComponent::Compound(compound1.clone()), + ComplexSelectorComponent::Compound(compound_one.clone()), ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), - ComplexSelectorComponent::Compound(compound2.clone()), + ComplexSelectorComponent::Compound(compound_two.clone()), ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), ], vec![ - ComplexSelectorComponent::Compound(compound2.clone()), + ComplexSelectorComponent::Compound(compound_two.clone()), ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), - ComplexSelectorComponent::Compound(compound1.clone()), + ComplexSelectorComponent::Compound(compound_one.clone()), ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), ], ]; - if let Some(unified) = compound1.unify(compound2) { + if let Some(unified) = compound_one.unify(compound_two) { choices.push(vec![ ComplexSelectorComponent::Compound(unified), ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), @@ -458,20 +463,20 @@ fn merge_final_combinators( } (Combinator::FollowingSibling, Combinator::NextSibling) | (Combinator::NextSibling, Combinator::FollowingSibling) => { - let following_sibling_selector = if combinator1 == &Combinator::FollowingSibling - { - compound1.clone() + let following_sibling_selector = + if combinator_one == &Combinator::FollowingSibling { + compound_one.clone() + } else { + compound_two.clone() + }; + + let next_sibling_selector = if combinator_one == &Combinator::FollowingSibling { + compound_two.clone() } else { - compound2.clone() + compound_one.clone() }; - let next_sibling_selector = if combinator1 == &Combinator::FollowingSibling { - compound2.clone() - } else { - compound1.clone() - }; - - if following_sibling_selector.is_super_selector(&next_sibling_selector, None) { + if following_sibling_selector.is_super_selector(&next_sibling_selector, &None) { result.push_front(vec![vec![ ComplexSelectorComponent::Compound(next_sibling_selector), ComplexSelectorComponent::Combinator(Combinator::NextSibling), @@ -484,7 +489,7 @@ fn merge_final_combinators( ComplexSelectorComponent::Combinator(Combinator::NextSibling), ]]; - if let Some(unified) = compound1.unify(compound2) { + if let Some(unified) = compound_one.unify(compound_two) { v.push(vec![ ComplexSelectorComponent::Compound(unified), ComplexSelectorComponent::Combinator(Combinator::NextSibling), @@ -496,81 +501,83 @@ fn merge_final_combinators( (Combinator::Child, Combinator::NextSibling) | (Combinator::Child, Combinator::FollowingSibling) => { result.push_front(vec![vec![ - ComplexSelectorComponent::Compound(compound2), - ComplexSelectorComponent::Combinator(*combinator2), + ComplexSelectorComponent::Compound(compound_two), + ComplexSelectorComponent::Combinator(*combinator_two), ]]); - components1.push_back(ComplexSelectorComponent::Compound(compound1)); - components1.push_back(ComplexSelectorComponent::Combinator(Combinator::Child)); + components_one.push_back(ComplexSelectorComponent::Compound(compound_one)); + components_one + .push_back(ComplexSelectorComponent::Combinator(Combinator::Child)); } (Combinator::NextSibling, Combinator::Child) | (Combinator::FollowingSibling, Combinator::Child) => { result.push_front(vec![vec![ - ComplexSelectorComponent::Compound(compound1), - ComplexSelectorComponent::Combinator(*combinator1), + ComplexSelectorComponent::Compound(compound_one), + ComplexSelectorComponent::Combinator(*combinator_one), ]]); - components2.push_back(ComplexSelectorComponent::Compound(compound2)); - components2.push_back(ComplexSelectorComponent::Combinator(Combinator::Child)); + components_two.push_back(ComplexSelectorComponent::Compound(compound_two)); + components_two + .push_back(ComplexSelectorComponent::Combinator(Combinator::Child)); } (..) => { - if combinator1 != combinator2 { + if combinator_one != combinator_two { return None; } - let unified = compound1.unify(compound2)?; + let unified = compound_one.unify(compound_two)?; result.push_front(vec![vec![ ComplexSelectorComponent::Compound(unified), - ComplexSelectorComponent::Combinator(*combinator1), + ComplexSelectorComponent::Combinator(*combinator_one), ]]); } } - merge_final_combinators(components1, components2, Some(result)) + merge_final_combinators(components_one, components_two, Some(result)) } - (Some(combinator1), None) => { - if *combinator1 == Combinator::Child && !components2.is_empty() { + (Some(combinator_one), None) => { + if *combinator_one == Combinator::Child && !components_two.is_empty() { if let Some(ComplexSelectorComponent::Compound(c1)) = - components1.get(components1.len() - 1) + components_one.get(components_one.len() - 1) { if let Some(ComplexSelectorComponent::Compound(c2)) = - components2.get(components2.len() - 1) + components_two.get(components_two.len() - 1) { - if c2.is_super_selector(c1, None) { - components2.pop_back(); + if c2.is_super_selector(c1, &None) { + components_two.pop_back(); } } } } result.push_front(vec![vec![ - components1.pop_back().unwrap(), - ComplexSelectorComponent::Combinator(*combinator1), + components_one.pop_back().unwrap(), + ComplexSelectorComponent::Combinator(*combinator_one), ]]); - merge_final_combinators(components1, components2, Some(result)) + merge_final_combinators(components_one, components_two, Some(result)) } - (None, Some(combinator2)) => { - if *combinator2 == Combinator::Child && !components1.is_empty() { + (None, Some(combinator_two)) => { + if *combinator_two == Combinator::Child && !components_one.is_empty() { if let Some(ComplexSelectorComponent::Compound(c1)) = - components1.get(components1.len() - 1) + components_one.get(components_one.len() - 1) { if let Some(ComplexSelectorComponent::Compound(c2)) = - components2.get(components2.len() - 1) + components_two.get(components_two.len() - 1) { - if c1.is_super_selector(c2, None) { - components1.pop_back(); + if c1.is_super_selector(c2, &None) { + components_one.pop_back(); } } } } result.push_front(vec![vec![ - components2.pop_back().unwrap(), - ComplexSelectorComponent::Combinator(*combinator2), + components_two.pop_back().unwrap(), + ComplexSelectorComponent::Combinator(*combinator_two), ]]); - merge_final_combinators(components1, components2, Some(result)) + merge_final_combinators(components_one, components_two, Some(result)) } - (None, None) => todo!("the above, but we dont have access to combinator2"), + (None, None) => todo!("the above, but we dont have access to combinator_two"), } } @@ -586,7 +593,7 @@ fn first_if_root(queue: &mut VecDeque) -> Option( - queue1: &mut VecDeque, - queue2: &mut VecDeque, + queue_one: &mut VecDeque, + queue_two: &mut VecDeque, done: impl Fn(&VecDeque) -> bool, ) -> Vec> { - let mut chunk1 = Vec::new(); - while !done(&queue1) { - chunk1.push(queue1.pop_front().unwrap()); + let mut chunk_one = Vec::new(); + while !done(queue_one) { + chunk_one.push(queue_one.pop_front().unwrap()); } - let mut chunk2 = Vec::new(); - while !done(&queue2) { - chunk2.push(queue2.pop_front().unwrap()); + let mut chunk_two = Vec::new(); + while !done(queue_two) { + chunk_two.push(queue_two.pop_front().unwrap()); } - match (chunk1.is_empty(), chunk2.is_empty()) { + match (chunk_one.is_empty(), chunk_two.is_empty()) { (true, true) => Vec::new(), - (true, false) => vec![chunk2], - (false, true) => vec![chunk1], + (true, false) => vec![chunk_two], + (false, true) => vec![chunk_one], (false, false) => { - let mut l1 = chunk1.clone(); - l1.append(&mut chunk2.clone()); + let mut l1 = chunk_one.clone(); + l1.append(&mut chunk_two.clone()); - let mut l2 = chunk2.clone(); - l2.append(&mut chunk1); + let mut l2 = chunk_two; + l2.append(&mut chunk_one); vec![l1, l2] } } } -/// Like `complex_is_superselector`, but compares `complex1` and `complex2` as +/// Like `complex_is_superselector`, but compares `complex_one` and `complex_two` as /// though they shared an implicit base `SimpleSelector`. /// /// For example, `B` is not normally a superselector of `B A`, since it doesn't /// match elements that match `A`. However, it *is* a parent superselector, /// since `B X` is a superselector of `B A X`. fn complex_is_parent_superselector( - mut complex1: Vec, - mut complex2: Vec, + mut complex_one: Vec, + mut complex_two: Vec, ) -> bool { - if let Some(ComplexSelectorComponent::Combinator(..)) = complex1.first() { + if let Some(ComplexSelectorComponent::Combinator(..)) = complex_one.first() { return false; } - if let Some(ComplexSelectorComponent::Combinator(..)) = complex2.first() { + if let Some(ComplexSelectorComponent::Combinator(..)) = complex_two.first() { return false; } - if complex1.len() > complex2.len() { + if complex_one.len() > complex_two.len() { return false; } let base = CompoundSelector { components: vec![SimpleSelector::Placeholder(String::new())], }; - complex1.push(ComplexSelectorComponent::Compound(base.clone())); - complex2.push(ComplexSelectorComponent::Compound(base)); + complex_one.push(ComplexSelectorComponent::Compound(base.clone())); + complex_two.push(ComplexSelectorComponent::Compound(base)); ComplexSelector { - components: complex1, + components: complex_one, line_break: false, } .is_super_selector(&ComplexSelector { - components: complex2, + components: complex_two, line_break: false, }) } @@ -736,19 +747,19 @@ fn paths(choices: Vec>) -> Vec> { }) } -/// Returns whether `complex1` and `complex2` need to be unified to produce a +/// Returns whether `complex_one` and `complex_two` need to be unified to produce a /// valid combined selector. /// /// This is necessary when both selectors contain the same unique simple /// selector, such as an ID. fn must_unify( - complex1: Vec, - complex2: Vec, + complex_one: &[ComplexSelectorComponent], + complex_two: &[ComplexSelectorComponent], ) -> bool { let mut unique_selectors = Vec::new(); - for component in complex1 { + for component in complex_one { if let ComplexSelectorComponent::Compound(c) = component { - unique_selectors.extend(c.components.into_iter().filter(|f| is_unique(f))); + unique_selectors.extend(c.components.iter().filter(|f| is_unique(f))); } } @@ -756,12 +767,12 @@ fn must_unify( return false; } - complex2.iter().any(|component| { + complex_two.iter().any(|component| { if let ComplexSelectorComponent::Compound(compound) = component { compound .components .iter() - .any(|simple| is_unique(simple) && unique_selectors.contains(simple)) + .any(|simple| is_unique(simple) && unique_selectors.contains(&simple)) } else { false } diff --git a/src/selector/list.rs b/src/selector/list.rs index 6af1d95..f4187b6 100644 --- a/src/selector/list.rs +++ b/src/selector/list.rs @@ -47,14 +47,16 @@ impl fmt::Display for SelectorList { impl SelectorList { pub fn is_invisible(&self) -> bool { - self.components.iter().all(|c| c.is_invisible()) + self.components.iter().all(ComplexSelector::is_invisible) } pub fn contains_parent_selector(&self) -> bool { - self.components.iter().any(|c| c.contains_parent_selector()) + self.components + .iter() + .any(ComplexSelector::contains_parent_selector) } - pub fn new() -> Self { + pub const fn new() -> Self { Self { components: Vec::new(), } @@ -64,7 +66,7 @@ impl SelectorList { self.components.is_empty() } - /// Returns a SassScript list that represents this selector. + /// Returns a `SassScript` list that represents this selector. /// /// This has the same format as a list returned by `selector-parse()`. pub fn to_sass_list(self) -> Value { @@ -94,7 +96,7 @@ impl SelectorList { /// both this and `other`. /// /// If no such list can be produced, returns `None`. - pub fn unify(self, other: Self) -> Option { + pub fn unify(self, other: &Self) -> Option { let contents: Vec = self .components .into_iter() @@ -181,7 +183,7 @@ impl SelectorList { { Some(r) => r, None => { - for new_complex in new_complexes.iter_mut() { + for new_complex in &mut new_complexes { new_complex.push(component.clone()); } continue; @@ -191,10 +193,9 @@ impl SelectorList { let previous_complexes = mem::take(&mut new_complexes); let previous_line_breaks = mem::take(&mut line_breaks); - let mut i = 0; - for new_complex in previous_complexes { + for (i, new_complex) in previous_complexes.into_iter().enumerate() { + // todo: use .get(i) let line_break = previous_line_breaks[i]; - i += 1; for mut resolved_complex in resolved.clone() { let mut new_this_complex = new_complex.clone(); new_this_complex.append(&mut resolved_complex.components); @@ -203,14 +204,14 @@ impl SelectorList { } } } else { - for new_complex in new_complexes.iter_mut() { + for new_complex in &mut new_complexes { new_complex.push(component.clone()); } } } let mut i = 0; - return new_complexes + new_complexes .into_iter() .map(|new_complex| { i += 1; @@ -219,7 +220,7 @@ impl SelectorList { line_break: line_breaks[i - 1], } }) - .collect(); + .collect() }) .collect(), ), @@ -236,15 +237,12 @@ impl SelectorList { } fn flatten_vertically(iterable: Vec>) -> Vec { - let mut queues: Vec> = iterable - .into_iter() - .map(|inner| VecDeque::from(inner)) - .collect(); + let mut queues: Vec> = iterable.into_iter().map(VecDeque::from).collect(); let mut result = Vec::new(); while !queues.is_empty() { - for queue in queues.iter_mut() { + for queue in &mut queues { if queue.is_empty() { continue; } diff --git a/src/selector/mod.rs b/src/selector/mod.rs index 9beae81..171dfdc 100644 --- a/src/selector/mod.rs +++ b/src/selector/mod.rs @@ -112,6 +112,7 @@ impl Selector { )) } + #[allow(clippy::needless_pass_by_value)] pub fn replace(super_selector: &Selector, this: Selector) -> Selector { todo!() } @@ -153,7 +154,7 @@ impl Selector { self.0.is_empty() } - pub fn new() -> Selector { + pub const fn new() -> Selector { Selector(SelectorList::new()) } @@ -161,7 +162,7 @@ impl Selector { self.0.to_sass_list() } - pub fn unify(self, other: Self) -> Option { - Some(Selector(self.0.unify(other.0)?)) + pub fn unify(self, other: &Self) -> Option { + Some(Selector(self.0.unify(&other.0)?)) } } diff --git a/src/selector/parse.rs b/src/selector/parse.rs index 5fd682d..e2ff283 100644 --- a/src/selector/parse.rs +++ b/src/selector/parse.rs @@ -38,7 +38,7 @@ impl DevouredWhitespace { } /// Pseudo-class selectors that take unadorned selectors as arguments. -const SELECTOR_PSEUDO_CLASSES: [&'static str; 7] = [ +const SELECTOR_PSEUDO_CLASSES: [&str; 7] = [ "not", "matches", "current", @@ -49,7 +49,7 @@ const SELECTOR_PSEUDO_CLASSES: [&'static str; 7] = [ ]; /// Pseudo-element selectors that take unadorned selectors as arguments. -const SELECTOR_PSEUDO_ELEMENTS: [&'static str; 1] = ["slotted"]; +const SELECTOR_PSEUDO_ELEMENTS: [&str; 1] = ["slotted"]; pub(crate) struct SelectorParser<'a, I: Iterator> { /// Whether this parser allows the parent selector `&`. @@ -114,7 +114,7 @@ impl<'a, I: Iterator> SelectorParser<'a, I> { line_break = false; } - return Ok(SelectorList { components }); + Ok(SelectorList { components }) } fn eat_whitespace(&mut self) -> DevouredWhitespace { @@ -344,6 +344,7 @@ impl<'a, I: Iterator> SelectorParser<'a, I> { } else if unvendored == "nth-child" || unvendored == "nth-last-child" { let mut this_arg = self.parse_a_n_plus_b()?; let found_whitespace = devour_whitespace(self.toks); + #[allow(clippy::match_same_arms)] match (found_whitespace, self.toks.peek()) { (_, Some(Token { kind: ')', .. })) => {} (true, _) => { @@ -507,7 +508,9 @@ impl<'a, I: Iterator> SelectorParser<'a, I> { devour_whitespace(self.toks); - if let Some(t @ Token { kind: '+', .. }) | Some(t @ Token { kind: '-', .. }) = self.toks.peek() { + if let Some(t @ Token { kind: '+', .. }) | Some(t @ Token { kind: '-', .. }) = + self.toks.peek() + { buf.push(t.kind); self.toks.next(); devour_whitespace(self.toks); @@ -516,7 +519,7 @@ impl<'a, I: Iterator> SelectorParser<'a, I> { return Err(("Expected a number.", self.span).into()) } None => return Err(("Expected a number.", self.span).into()), - _ => {} + Some(..) => {} } while let Some(t) = self.toks.peek() { @@ -575,7 +578,7 @@ fn unvendor(name: &str) -> &str { return name; } - if bytes[0usize] != b'-' || bytes[1usize] == b'-' { + if bytes[0_usize] != b'-' || bytes[1_usize] == b'-' { return name; } diff --git a/src/selector/simple.rs b/src/selector/simple.rs index 7adf05c..2121db7 100644 --- a/src/selector/simple.rs +++ b/src/selector/simple.rs @@ -5,7 +5,7 @@ use super::{ QualifiedName, SelectorList, }; -const SUBSELECTOR_PSEUDOS: [&'static str; 4] = ["matches", "any", "nth-child", "nth-last-child"]; +const SUBSELECTOR_PSEUDOS: [&str; 4] = ["matches", "any", "nth-child", "nth-last-child"]; #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) enum SimpleSelector { @@ -85,7 +85,7 @@ impl SimpleSelector { Self::Universal(..) => 0, Self::Type(..) => 1, Self::Pseudo { .. } => todo!(), - Self::Id(..) => 1000u32.pow(2u32), + Self::Id(..) => 1000_u32.pow(2_u32), _ => 1000, } } @@ -109,7 +109,7 @@ impl SimpleSelector { | Self::Class(..) | Self::Attribute(..) => false, Self::Pseudo(Pseudo { name, selector, .. }) => { - name != "not" && selector.as_ref().map_or(false, |s| s.is_invisible()) + name != "not" && selector.as_ref().map_or(false, SelectorList::is_invisible) } Self::Placeholder(..) => true, Self::Parent(..) => todo!(), @@ -119,14 +119,16 @@ impl SimpleSelector { pub fn add_suffix(&mut self, suffix: &str) { match self { Self::Type(name) => name.ident.push_str(suffix), - Self::Pseudo(Pseudo { + Self::Placeholder(name) + | Self::Id(name) + | Self::Class(name) + | Self::Pseudo(Pseudo { name, argument: None, selector: None, .. }) => name.push_str(suffix), - Self::Placeholder(name) | Self::Id(name) | Self::Class(name) => name.push_str(suffix), - Self::Universal(..) | _ => todo!(), + _ => todo!(), } } @@ -196,7 +198,7 @@ impl SimpleSelector { } if !added_this { - result.push(self.clone()); + result.push(self); } Some(result) @@ -348,13 +350,13 @@ impl SimpleSelector { if complex.components.len() != 1 { return false; }; - return complex + complex .components .get(0) .unwrap() .as_compound() .components - .contains(self); + .contains(self) }); } false diff --git a/src/stylesheet.rs b/src/stylesheet.rs index 0133cbd..eeb0db0 100644 --- a/src/stylesheet.rs +++ b/src/stylesheet.rs @@ -422,7 +422,7 @@ impl<'a> StyleSheetParser<'a> { Expr::Selector(s) => { self.nesting += 1; let rules = - self.eat_rules(&s.resolve_parent_selectors(&super_selector, true), scope)?; + self.eat_rules(&s.resolve_parent_selectors(super_selector, true), scope)?; stmts.push(Spanned { node: Stmt::RuleSet(RuleSet { super_selector: super_selector.clone(), From d71e996e2b00882ea3dfacbb3009243e6cd3cb5d Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 7 Jun 2020 03:06:08 -0400 Subject: [PATCH 07/15] initial implementation of selector-extend --- Cargo.toml | 1 + src/builtin/selector.rs | 28 +- src/selector/attribute.rs | 4 +- src/selector/common.rs | 21 +- src/selector/complex.rs | 37 +- src/selector/compound.rs | 15 +- src/selector/extend/extension.rs | 59 ++ src/selector/extend/functions.rs | 788 ++++++++++++++++ src/selector/extend/mod.rs | 1489 +++++++++++++++--------------- src/selector/list.rs | 2 +- src/selector/simple.rs | 16 +- tests/selector-extend.rs | 175 ++++ 12 files changed, 1868 insertions(+), 767 deletions(-) create mode 100644 src/selector/extend/extension.rs create mode 100644 src/selector/extend/functions.rs create mode 100644 tests/selector-extend.rs diff --git a/Cargo.toml b/Cargo.toml index 7d39116..5a5e8ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ beef = "0.4.4" # criterion is not a dev-dependency because it makes tests take too # long to compile, and you cannot make dev-dependencies optional criterion = { version = "0.3.2", optional = true } +indexmap = "1.4.0" [features] default = ["commandline", "random"] diff --git a/src/builtin/selector.rs b/src/builtin/selector.rs index 11e6054..88f327b 100644 --- a/src/builtin/selector.rs +++ b/src/builtin/selector.rs @@ -5,7 +5,9 @@ use super::{Builtin, GlobalFunctionMap}; use crate::args::CallArgs; use crate::error::SassResult; use crate::scope::Scope; -use crate::selector::{ComplexSelector, ComplexSelectorComponent, Selector, SelectorList}; +use crate::selector::{ + ComplexSelector, ComplexSelectorComponent, Extender, Selector, SelectorList, +}; use crate::value::Value; fn is_superselector( @@ -144,7 +146,29 @@ fn selector_extend( super_selector: &Selector, ) -> SassResult { args.max_args(3)?; - todo!("built-in fn selector-extend") + let selector = arg!(args, scope, super_selector, 0, "selector").to_selector( + args.span(), + scope, + super_selector, + "selector", + false, + )?; + let target = arg!(args, scope, super_selector, 1, "extendee").to_selector( + args.span(), + scope, + super_selector, + "extendee", + false, + )?; + let source = arg!(args, scope, super_selector, 2, "extender").to_selector( + args.span(), + scope, + super_selector, + "extender", + false, + )?; + + Ok(Extender::extend(selector.0, source.0, target.0).to_sass_list()) } fn selector_replace( diff --git a/src/selector/attribute.rs b/src/selector/attribute.rs index 7de0a70..7abf0b5 100644 --- a/src/selector/attribute.rs +++ b/src/selector/attribute.rs @@ -12,7 +12,7 @@ use crate::utils::{devour_whitespace, eat_ident, is_ident, parse_quoted_string}; use crate::value::Value; use crate::Token; -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Hash)] pub(crate) struct Attribute { attr: QualifiedName, value: String, @@ -201,7 +201,7 @@ impl Display for Attribute { } } -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] enum AttributeOp { /// \[attr\] /// diff --git a/src/selector/common.rs b/src/selector/common.rs index a8fc201..908ef42 100644 --- a/src/selector/common.rs +++ b/src/selector/common.rs @@ -6,7 +6,7 @@ use std::fmt::{self, Display}; /// it's `Empty`, this matches all elements that aren't in any /// namespace. If it's `Asterisk`, this matches all elements in any namespace. /// Otherwise, it matches all elements in the given namespace. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Hash)] pub(crate) enum Namespace { Empty, Asterisk, @@ -25,7 +25,7 @@ impl Display for Namespace { } } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Hash)] pub(crate) struct QualifiedName { pub ident: String, pub namespace: Namespace, @@ -38,18 +38,13 @@ impl Display for QualifiedName { } } -pub(crate) struct Specificity(u32, u32); +pub(crate) struct Specificity { + pub min: i32, + pub max: i32, +} impl Specificity { - pub const fn new(min: u32, max: u32) -> Self { - Specificity(min, max) - } - - pub const fn min(&self) -> u32 { - self.0 - } - - pub const fn max(&self) -> u32 { - self.1 + pub const fn new(min: i32, max: i32) -> Self { + Specificity { min, max } } } diff --git a/src/selector/complex.rs b/src/selector/complex.rs index feaecae..4655918 100644 --- a/src/selector/complex.rs +++ b/src/selector/complex.rs @@ -1,11 +1,11 @@ -use super::{CompoundSelector, Pseudo, SelectorList, SimpleSelector}; +use super::{CompoundSelector, Pseudo, SelectorList, SimpleSelector, Specificity}; use std::fmt::{self, Display, Write}; /// A complex selector. /// /// A complex selector is composed of `CompoundSelector`s separated by /// `Combinator`s. It selects elements based on their parent selectors. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Hash)] pub(crate) struct ComplexSelector { /// The components of this selector. /// @@ -48,16 +48,25 @@ fn omit_spaces_around(component: &ComplexSelectorComponent) -> bool { } impl ComplexSelector { - // pub fn specificity(&self) -> Specificity { - // let mut min = 0; - // let mut max = 0; - // for component in self.components.iter() { - // todo!() - // // min += simple.min_specificity(); - // // max += simple.max_specificity(); - // } - // Specificity::new(min, max) - // } + pub fn max_specificity(&self) -> i32 { + self.specificity().min + } + + pub fn min_specificity(&self) -> i32 { + self.specificity().max + } + + pub fn specificity(&self) -> Specificity { + let mut min = 0; + let mut max = 0; + for component in &self.components { + if let ComplexSelectorComponent::Compound(compound) = component { + min += compound.min_specificity(); + max += compound.max_specificity(); + } + } + Specificity::new(min, max) + } pub fn is_invisible(&self) -> bool { self.components @@ -201,7 +210,7 @@ impl ComplexSelector { } } -#[derive(Clone, Debug, Eq, PartialEq, Copy)] +#[derive(Clone, Debug, Eq, PartialEq, Copy, Hash)] pub(crate) enum Combinator { /// Matches the right-hand selector if it's immediately adjacent to the /// left-hand selector in the DOM tree. @@ -232,7 +241,7 @@ impl Display for Combinator { } } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Hash)] pub(crate) enum ComplexSelectorComponent { Combinator(Combinator), Compound(CompoundSelector), diff --git a/src/selector/compound.rs b/src/selector/compound.rs index 64c81f2..179513c 100644 --- a/src/selector/compound.rs +++ b/src/selector/compound.rs @@ -7,7 +7,7 @@ use super::{ /// A compound selector is composed of several /// simple selectors -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Hash)] pub(crate) struct CompoundSelector { pub components: Vec, } @@ -39,14 +39,21 @@ impl fmt::Display for CompoundSelector { } impl CompoundSelector { + pub fn max_specificity(&self) -> i32 { + self.specificity().min + } + + pub fn min_specificity(&self) -> i32 { + self.specificity().max + } + /// Returns tuple of (min, max) specificity pub fn specificity(&self) -> Specificity { let mut min = 0; let mut max = 0; for simple in &self.components { - todo!() - // min += simple.min_specificity; - // max += simple.max_specificity; + min += simple.min_specificity(); + max += simple.max_specificity(); } Specificity::new(min, max) } diff --git a/src/selector/extend/extension.rs b/src/selector/extend/extension.rs new file mode 100644 index 0000000..095a145 --- /dev/null +++ b/src/selector/extend/extension.rs @@ -0,0 +1,59 @@ +use codemap::Span; + +use super::{ComplexSelector, CssMediaQuery, SimpleSelector}; + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub(super) struct Extension { + /// The selector in which the `@extend` appeared. + pub extender: ComplexSelector, + + /// The selector that's being extended. + /// + /// `None` for one-off extensions. + pub target: Option, + + /// The minimum specificity required for any selector generated from this + /// extender. + pub specificity: i32, + + /// Whether this extension is optional. + pub is_optional: bool, + + /// Whether this is a one-off extender representing a selector that was + /// originally in the document, rather than one defined with `@extend`. + pub is_original: bool, + + /// The media query context to which this extend is restricted, or `None` if + /// it can apply within any context. + // todo: Option + pub media_context: Vec, + + /// The span in which `extender` was defined. + pub span: Option, +} + +impl Extension { + pub fn one_off(extender: ComplexSelector, specificity: Option, is_original: bool) -> Self { + Self { + specificity: specificity.unwrap_or(extender.max_specificity()), + extender, + target: None, + span: None, + is_optional: true, + is_original, + media_context: Vec::new(), + } + } + + /// Asserts that the `media_context` for a selector is compatible with the + /// query context for this extender. + pub fn assert_compatible_media_context(&self, media_context: &Option>) { + if let Some(media_context) = media_context { + if &self.media_context == media_context { + return; + } + } + + // todo!("throw SassException(\"You may not @extend selectors across media queries.\", span);") + } +} diff --git a/src/selector/extend/functions.rs b/src/selector/extend/functions.rs new file mode 100644 index 0000000..64a5d3c --- /dev/null +++ b/src/selector/extend/functions.rs @@ -0,0 +1,788 @@ +#![allow(clippy::similar_names)] + +use std::collections::VecDeque; + +use super::super::{ + Combinator, ComplexSelector, ComplexSelectorComponent, CompoundSelector, Pseudo, SimpleSelector, +}; + +/// Returns the contents of a `SelectorList` that matches only elements that are +/// matched by both `complex_one` and `complex_two`. +/// +/// If no such list can be produced, returns `None`. +pub(crate) fn unify_complex( + complexes: Vec>, +) -> Option>> { + debug_assert!(!complexes.is_empty()); + + if complexes.len() == 1 { + return Some(complexes); + } + + let mut unified_base: Option> = None; + + for complex in &complexes { + let base = complex.last()?; + + if let ComplexSelectorComponent::Compound(base) = base { + if let Some(mut some_unified_base) = unified_base.clone() { + for simple in base.components.clone() { + some_unified_base = simple.unify(some_unified_base.clone())?; + } + unified_base = Some(some_unified_base); + } else { + unified_base = Some(base.components.clone()); + } + } else { + return None; + } + } + + let mut complexes_without_bases: Vec> = complexes + .into_iter() + .map(|mut complex| { + complex.pop(); + complex + }) + .collect(); + + complexes_without_bases + .last_mut() + .unwrap() + .push(ComplexSelectorComponent::Compound(CompoundSelector { + components: unified_base?, + })); + + Some(weave(complexes_without_bases)) +} + +/// Expands "parenthesized selectors" in `complexes`. +/// +/// That is, if we have `.A .B {@extend .C}` and `.D .C {...}`, this +/// conceptually expands into `.D .C, .D (.A .B)`, and this function translates +/// `.D (.A .B)` into `.D .A .B, .A .D .B`. For thoroughness, `.A.D .B` would +/// also be required, but including merged selectors results in exponential +/// output for very little gain. +/// +/// The selector `.D (.A .B)` is represented as the list `[[.D], [.A, .B]]`. +pub(crate) fn weave( + complexes: Vec>, +) -> Vec> { + let mut prefixes: Vec> = vec![complexes.first().unwrap().clone()]; + + for complex in complexes.into_iter().skip(1) { + if complex.is_empty() { + continue; + } + + let target = complex.last().unwrap().clone(); + + if complex.len() == 1 { + for prefix in &mut prefixes { + prefix.push(target.clone()); + } + continue; + } + + let complex_len = complex.len(); + + let parents: Vec = + complex.into_iter().take(complex_len - 1).collect(); + let mut new_prefixes: Vec> = Vec::new(); + + for prefix in prefixes { + let parent_prefixes = weave_parents(prefix, parents.clone()); + + if let Some(parent_prefixes) = parent_prefixes { + for mut parent_prefix in parent_prefixes { + parent_prefix.push(target.clone()); + new_prefixes.push(parent_prefix); + } + } + } + prefixes = new_prefixes; + } + + prefixes +} + +/// Interweaves `parents_one` and `parents_two` as parents of the same target selector. +/// +/// Returns all possible orderings of the selectors in the inputs (including +/// using unification) that maintain the relative ordering of the input. For +/// example, given `.foo .bar` and `.baz .bang`, this would return `.foo .bar +/// .baz .bang`, `.foo .bar.baz .bang`, `.foo .baz .bar .bang`, `.foo .baz +/// .bar.bang`, `.foo .baz .bang .bar`, and so on until `.baz .bang .foo .bar`. +/// +/// Semantically, for selectors A and B, this returns all selectors `AB_i` +/// such that the union over all i of elements matched by `AB_i X` is +/// identical to the intersection of all elements matched by `A X` and all +/// elements matched by `B X`. Some `AB_i` are elided to reduce the size of +/// the output. +fn weave_parents( + parents_one: Vec, + parents_two: Vec, +) -> Option>> { + let mut queue_one = VecDeque::from(parents_one); + let mut queue_two = VecDeque::from(parents_two); + + let initial_combinators = merge_initial_combinators(&mut queue_one, &mut queue_two)?; + + let mut final_combinators = merge_final_combinators(&mut queue_one, &mut queue_two, None)?; + + match (first_if_root(&mut queue_one), first_if_root(&mut queue_two)) { + (Some(root_one), Some(root_two)) => { + let root = ComplexSelectorComponent::Compound(root_one.unify(root_two)?); + queue_one.push_front(root.clone()); + queue_two.push_front(root); + } + (Some(root_one), None) => { + queue_two.push_front(ComplexSelectorComponent::Compound(root_one)); + } + (None, Some(root_two)) => { + queue_one.push_front(ComplexSelectorComponent::Compound(root_two)); + } + (None, None) => {} + } + + let mut groups_one = group_selectors(Vec::from(queue_one)); + let mut groups_two = group_selectors(Vec::from(queue_two)); + + let lcs = longest_common_subsequence( + groups_two.as_slices().0, + groups_one.as_slices().0, + Some(&|group_one, group_two| { + if group_one == group_two { + return Some(group_one); + } + + if let ComplexSelectorComponent::Combinator(..) = group_one.first()? { + return None; + } + if let ComplexSelectorComponent::Combinator(..) = group_two.first()? { + return None; + } + + if complex_is_parent_superselector(group_one.clone(), group_two.clone()) { + return Some(group_two); + } + if complex_is_parent_superselector(group_two.clone(), group_one.clone()) { + return Some(group_one); + } + + if !must_unify(&group_one, &group_two) { + return None; + } + + let unified = unify_complex(vec![group_one, group_two])?; + if unified.len() > 1 { + return None; + } + + unified.first().cloned() + }), + ); + + let mut choices = vec![vec![initial_combinators + .into_iter() + .map(ComplexSelectorComponent::Combinator) + .collect::>()]]; + + for group in lcs { + choices.push( + chunks(&mut groups_one, &mut groups_two, |sequence| { + complex_is_parent_superselector(sequence.get(0).unwrap().clone(), group.clone()) + }) + .into_iter() + .map(|chunk| chunk.into_iter().flatten().collect()) + .collect(), + ); + choices.push(vec![group]); + groups_one.pop_front(); + groups_two.pop_front(); + } + + choices.push( + chunks(&mut groups_one, &mut groups_two, VecDeque::is_empty) + .into_iter() + .map(|chunk| chunk.into_iter().flatten().collect()) + .collect(), + ); + + choices.append(&mut final_combinators); + + Some( + paths( + choices + .into_iter() + .filter(|choice| !choice.is_empty()) + .collect(), + ) + .into_iter() + .map(|chunk| chunk.into_iter().flatten().collect()) + .collect(), + ) +} + +/// Extracts leading `Combinator`s from `components_one` and `components_two` and +/// merges them together into a single list of combinators. +/// +/// If there are no combinators to be merged, returns an empty list. If the +/// combinators can't be merged, returns `None`. +fn merge_initial_combinators( + components_one: &mut VecDeque, + components_two: &mut VecDeque, +) -> Option> { + let mut combinators_one: Vec = Vec::new(); + + while let Some(ComplexSelectorComponent::Combinator(c)) = components_one.get(0) { + combinators_one.push(*c); + components_one.pop_front(); + } + + let mut combinators_two = Vec::new(); + + while let Some(ComplexSelectorComponent::Combinator(c)) = components_two.get(0) { + combinators_two.push(*c); + components_two.pop_front(); + } + + let lcs = longest_common_subsequence(&combinators_one, &combinators_two, None); + + if lcs == combinators_one { + Some(combinators_two) + } else if lcs == combinators_two { + Some(combinators_one) + } else { + // If neither sequence of combinators is a subsequence of the other, they + // cannot be merged successfully. + None + } +} + +/// Returns the longest common subsequence between `list_one` and `list_two`. +/// +/// If there are more than one equally long common subsequence, returns the one +/// which starts first in `list_one`. +/// +/// If `select` is passed, it's used to check equality between elements in each +/// list. If it returns `None`, the elements are considered unequal; otherwise, +/// it should return the element to include in the return value. +#[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)] +fn longest_common_subsequence( + list_one: &[T], + list_two: &[T], + select: Option<&dyn Fn(T, T) -> Option>, +) -> Vec { + let select = select.unwrap_or(&|element_one, element_two| { + if element_one == element_two { + Some(element_one) + } else { + None + } + }); + + let mut lengths = vec![vec![0; list_two.len() + 1]; list_one.len() + 1]; + + let mut selections: Vec>> = vec![vec![None; list_two.len()]; list_one.len()]; + + for i in 0..list_one.len() { + for j in 0..list_two.len() { + let selection = select( + list_one.get(i).unwrap().clone(), + list_two.get(j).unwrap().clone(), + ); + selections[i][j] = selection.clone(); + lengths[i + 1][j + 1] = if selection.is_none() { + std::cmp::max(lengths[i + 1][j], lengths[i][j + 1]) + } else { + lengths[i][j] + 1 + }; + } + } + + fn backtrack( + i: isize, + j: isize, + lengths: Vec>, + selections: &mut Vec>>, + ) -> Vec { + if i == -1 || j == -1 { + return Vec::new(); + } + let selection = selections.get(i as usize).cloned().unwrap_or_else(Vec::new); + + if let Some(Some(selection)) = selection.get(j as usize) { + let mut tmp = backtrack(i - 1, j - 1, lengths, selections); + tmp.push(selection.clone()); + return tmp; + } + + if lengths[(i + 1) as usize][j as usize] > lengths[i as usize][(j + 1) as usize] { + backtrack(i, j - 1, lengths, selections) + } else { + backtrack(i - 1, j, lengths, selections) + } + } + backtrack( + (list_one.len() as isize).saturating_sub(1), + (list_two.len() as isize).saturating_sub(1), + lengths, + &mut selections, + ) +} + +/// Extracts trailing `Combinator`s, and the selectors to which they apply, from +/// `components_one` and `components_two` and merges them together into a single list. +/// +/// If there are no combinators to be merged, returns an empty list. If the +/// sequences can't be merged, returns `None`. +#[allow(clippy::cognitive_complexity)] +fn merge_final_combinators( + components_one: &mut VecDeque, + components_two: &mut VecDeque, + result: Option>>>, +) -> Option>>> { + let mut result = result.unwrap_or_default(); + + if (components_one.is_empty() + || !components_one + .get(components_one.len() - 1) + .unwrap() + .is_combinator()) + && (components_two.is_empty() + || !components_two + .get(components_two.len() - 1) + .unwrap() + .is_combinator()) + { + return Some(Vec::from(result)); + } + + let mut combinators_one = Vec::new(); + + while let Some(ComplexSelectorComponent::Combinator(combinator)) = + components_one.get(components_one.len().saturating_sub(1)) + { + combinators_one.push(*combinator); + components_one.pop_back(); + } + + let mut combinators_two = Vec::new(); + + while let Some(ComplexSelectorComponent::Combinator(combinator)) = + components_two.get(components_two.len().saturating_sub(1)) + { + combinators_two.push(*combinator); + components_two.pop_back(); + } + + if combinators_one.len() > 1 || combinators_two.len() > 1 { + // If there are multiple combinators, something hacky's going on. If one + // is a supersequence of the other, use that, otherwise give up. + let lcs = longest_common_subsequence(&combinators_one, &combinators_two, None); + if lcs == combinators_one { + result.push_front(vec![combinators_two + .into_iter() + .map(ComplexSelectorComponent::Combinator) + .rev() + .collect()]); + } else if lcs == combinators_two { + result.push_front(vec![combinators_one + .into_iter() + .map(ComplexSelectorComponent::Combinator) + .rev() + .collect()]); + } else { + return None; + } + + return Some(Vec::from(result)); + } + + let combinator_one = if combinators_one.is_empty() { + None + } else { + combinators_one.first() + }; + + let combinator_two = if combinators_two.is_empty() { + None + } else { + combinators_two.first() + }; + + // This code looks complicated, but it's actually just a bunch of special + // cases for interactions between different combinators. + match (combinator_one, combinator_two) { + (Some(combinator_one), Some(combinator_two)) => { + let compound_one = match components_one.pop_back() { + Some(ComplexSelectorComponent::Compound(c)) => c, + Some(..) | None => unreachable!(), + }; + let compound_two = match components_two.pop_back() { + Some(ComplexSelectorComponent::Compound(c)) => c, + Some(..) | None => unreachable!(), + }; + + match (combinator_one, combinator_two) { + (Combinator::FollowingSibling, Combinator::FollowingSibling) => { + if compound_one.is_super_selector(&compound_two, &None) { + result.push_front(vec![vec![ + ComplexSelectorComponent::Compound(compound_two), + ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), + ]]) + } else if compound_two.is_super_selector(&compound_one, &None) { + result.push_front(vec![vec![ + ComplexSelectorComponent::Compound(compound_one), + ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), + ]]) + } else { + let mut choices = vec![ + vec![ + ComplexSelectorComponent::Compound(compound_one.clone()), + ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), + ComplexSelectorComponent::Compound(compound_two.clone()), + ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), + ], + vec![ + ComplexSelectorComponent::Compound(compound_two.clone()), + ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), + ComplexSelectorComponent::Compound(compound_one.clone()), + ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), + ], + ]; + + if let Some(unified) = compound_one.unify(compound_two) { + choices.push(vec![ + ComplexSelectorComponent::Compound(unified), + ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), + ]) + } + + result.push_front(choices); + } + } + (Combinator::FollowingSibling, Combinator::NextSibling) + | (Combinator::NextSibling, Combinator::FollowingSibling) => { + let following_sibling_selector = + if combinator_one == &Combinator::FollowingSibling { + compound_one.clone() + } else { + compound_two.clone() + }; + + let next_sibling_selector = if combinator_one == &Combinator::FollowingSibling { + compound_two.clone() + } else { + compound_one.clone() + }; + + if following_sibling_selector.is_super_selector(&next_sibling_selector, &None) { + result.push_front(vec![vec![ + ComplexSelectorComponent::Compound(next_sibling_selector), + ComplexSelectorComponent::Combinator(Combinator::NextSibling), + ]]); + } else { + let mut v = vec![vec![ + ComplexSelectorComponent::Compound(following_sibling_selector), + ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), + ComplexSelectorComponent::Compound(next_sibling_selector), + ComplexSelectorComponent::Combinator(Combinator::NextSibling), + ]]; + + if let Some(unified) = compound_one.unify(compound_two) { + v.push(vec![ + ComplexSelectorComponent::Compound(unified), + ComplexSelectorComponent::Combinator(Combinator::NextSibling), + ]); + } + result.push_front(v); + } + } + (Combinator::Child, Combinator::NextSibling) + | (Combinator::Child, Combinator::FollowingSibling) => { + result.push_front(vec![vec![ + ComplexSelectorComponent::Compound(compound_two), + ComplexSelectorComponent::Combinator(*combinator_two), + ]]); + components_one.push_back(ComplexSelectorComponent::Compound(compound_one)); + components_one + .push_back(ComplexSelectorComponent::Combinator(Combinator::Child)); + } + (Combinator::NextSibling, Combinator::Child) + | (Combinator::FollowingSibling, Combinator::Child) => { + result.push_front(vec![vec![ + ComplexSelectorComponent::Compound(compound_one), + ComplexSelectorComponent::Combinator(*combinator_one), + ]]); + components_two.push_back(ComplexSelectorComponent::Compound(compound_two)); + components_two + .push_back(ComplexSelectorComponent::Combinator(Combinator::Child)); + } + (..) => { + if combinator_one != combinator_two { + return None; + } + + let unified = compound_one.unify(compound_two)?; + + result.push_front(vec![vec![ + ComplexSelectorComponent::Compound(unified), + ComplexSelectorComponent::Combinator(*combinator_one), + ]]); + } + } + + merge_final_combinators(components_one, components_two, Some(result)) + } + (Some(combinator_one), None) => { + if *combinator_one == Combinator::Child && !components_two.is_empty() { + if let Some(ComplexSelectorComponent::Compound(c1)) = + components_one.get(components_one.len() - 1) + { + if let Some(ComplexSelectorComponent::Compound(c2)) = + components_two.get(components_two.len() - 1) + { + if c2.is_super_selector(c1, &None) { + components_two.pop_back(); + } + } + } + } + + result.push_front(vec![vec![ + components_one.pop_back().unwrap(), + ComplexSelectorComponent::Combinator(*combinator_one), + ]]); + + merge_final_combinators(components_one, components_two, Some(result)) + } + (None, Some(combinator_two)) => { + if *combinator_two == Combinator::Child && !components_one.is_empty() { + if let Some(ComplexSelectorComponent::Compound(c1)) = + components_one.get(components_one.len() - 1) + { + if let Some(ComplexSelectorComponent::Compound(c2)) = + components_two.get(components_two.len() - 1) + { + if c1.is_super_selector(c2, &None) { + components_one.pop_back(); + } + } + } + } + + result.push_front(vec![vec![ + components_two.pop_back().unwrap(), + ComplexSelectorComponent::Combinator(*combinator_two), + ]]); + merge_final_combinators(components_one, components_two, Some(result)) + } + (None, None) => todo!("the above, but we dont have access to combinator_two"), + } +} + +/// If the first element of `queue` has a `::root` selector, removes and returns +/// that element. +fn first_if_root(queue: &mut VecDeque) -> Option { + if queue.is_empty() { + return None; + } + if let Some(ComplexSelectorComponent::Compound(c)) = queue.get(0) { + if !has_root(c) { + return None; + } + let compound = c.clone(); + queue.pop_front(); + Some(compound) + } else { + None + } +} + +/// Returns whether or not `compound` contains a `::root` selector. +fn has_root(compound: &CompoundSelector) -> bool { + compound.components.iter().any(|simple| { + if let SimpleSelector::Pseudo(pseudo) = simple { + pseudo.is_class && pseudo.normalized_name == "root" + } else { + false + } + }) +} + +/// Returns `complex`, grouped into sub-lists such that no sub-list contains two +/// adjacent `ComplexSelector`s. +/// +/// For example, `(A B > C D + E ~ > G)` is grouped into +/// `[(A) (B > C) (D + E ~ > G)]`. +fn group_selectors( + complex: Vec, +) -> VecDeque> { + let mut groups = VecDeque::new(); + + let mut iter = complex.into_iter(); + + let mut group = if let Some(c) = iter.next() { + vec![c] + } else { + return groups; + }; + + groups.push_back(group.clone()); + + for c in iter { + if group + .last() + .map_or(false, ComplexSelectorComponent::is_combinator) + || c.is_combinator() + { + group.push(c); + } else { + group = vec![c]; + groups.push_back(group.clone()); + } + } + + groups +} + +/// Returns all orderings of initial subseqeuences of `queue_one` and `queue_two`. +/// +/// The `done` callback is used to determine the extent of the initial +/// subsequences. It's called with each queue until it returns `true`. +/// +/// This destructively removes the initial subsequences of `queue_one` and +/// `queue_two`. +/// +/// For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|` denoting +/// the boundary of the initial subsequence), this would return `[(A B C 1 2), +/// (1 2 A B C)]`. The queues would then contain `(D E)` and `(3 4 5)`. +fn chunks( + queue_one: &mut VecDeque, + queue_two: &mut VecDeque, + done: impl Fn(&VecDeque) -> bool, +) -> Vec> { + let mut chunk_one = Vec::new(); + while !done(queue_one) { + chunk_one.push(queue_one.pop_front().unwrap()); + } + + let mut chunk_two = Vec::new(); + while !done(queue_two) { + chunk_two.push(queue_two.pop_front().unwrap()); + } + + match (chunk_one.is_empty(), chunk_two.is_empty()) { + (true, true) => Vec::new(), + (true, false) => vec![chunk_two], + (false, true) => vec![chunk_one], + (false, false) => { + let mut l1 = chunk_one.clone(); + l1.append(&mut chunk_two.clone()); + + let mut l2 = chunk_two; + l2.append(&mut chunk_one); + + vec![l1, l2] + } + } +} + +/// Like `complex_is_superselector`, but compares `complex_one` and `complex_two` as +/// though they shared an implicit base `SimpleSelector`. +/// +/// For example, `B` is not normally a superselector of `B A`, since it doesn't +/// match elements that match `A`. However, it *is* a parent superselector, +/// since `B X` is a superselector of `B A X`. +fn complex_is_parent_superselector( + mut complex_one: Vec, + mut complex_two: Vec, +) -> bool { + if let Some(ComplexSelectorComponent::Combinator(..)) = complex_one.first() { + return false; + } + if let Some(ComplexSelectorComponent::Combinator(..)) = complex_two.first() { + return false; + } + if complex_one.len() > complex_two.len() { + return false; + } + let base = CompoundSelector { + components: vec![SimpleSelector::Placeholder(String::new())], + }; + complex_one.push(ComplexSelectorComponent::Compound(base.clone())); + complex_two.push(ComplexSelectorComponent::Compound(base)); + + ComplexSelector { + components: complex_one, + line_break: false, + } + .is_super_selector(&ComplexSelector { + components: complex_two, + line_break: false, + }) +} + +/// Returns a list of all possible paths through the given lists. +/// +/// For example, given `[[1, 2], [3, 4], [5]]`, this returns: +/// +/// ```norun +/// [[1, 3, 5], +/// [2, 3, 5], +/// [1, 4, 5], +/// [2, 4, 5]] +/// ``` +pub(crate) fn paths(choices: Vec>) -> Vec> { + choices.into_iter().fold(vec![vec![]], |paths, choice| { + choice + .into_iter() + .flat_map(move |option| { + paths.clone().into_iter().map(move |mut path| { + path.push(option.clone()); + path + }) + }) + .collect() + }) +} + +/// Returns whether `complex_one` and `complex_two` need to be unified to produce a +/// valid combined selector. +/// +/// This is necessary when both selectors contain the same unique simple +/// selector, such as an ID. +fn must_unify( + complex_one: &[ComplexSelectorComponent], + complex_two: &[ComplexSelectorComponent], +) -> bool { + let mut unique_selectors = Vec::new(); + for component in complex_one { + if let ComplexSelectorComponent::Compound(c) = component { + unique_selectors.extend(c.components.iter().filter(|f| is_unique(f))); + } + } + + if unique_selectors.is_empty() { + return false; + } + + complex_two.iter().any(|component| { + if let ComplexSelectorComponent::Compound(compound) = component { + compound + .components + .iter() + .any(|simple| is_unique(simple) && unique_selectors.contains(&simple)) + } else { + false + } + }) +} + +/// Returns whether a `CompoundSelector` may contain only one simple selector of +/// the same type as `simple`. +fn is_unique(simple: &SimpleSelector) -> bool { + matches!(simple, SimpleSelector::Id(..) | SimpleSelector::Pseudo(Pseudo { is_class: false, .. })) +} diff --git a/src/selector/extend/mod.rs b/src/selector/extend/mod.rs index d237677..4fe65d4 100644 --- a/src/selector/extend/mod.rs +++ b/src/selector/extend/mod.rs @@ -1,786 +1,825 @@ -#![allow(clippy::similar_names)] +use std::collections::{HashMap, HashSet, VecDeque}; -use std::collections::VecDeque; +use indexmap::IndexMap; use super::{ - Combinator, ComplexSelector, ComplexSelectorComponent, CompoundSelector, Pseudo, SimpleSelector, + ComplexSelector, ComplexSelectorComponent, CompoundSelector, Pseudo, SelectorList, + SimpleSelector, }; -/// Returns the contents of a `SelectorList` that matches only elements that are -/// matched by both `complex_one` and `complex_two`. -/// -/// If no such list can be produced, returns `None`. -pub(crate) fn unify_complex( - complexes: Vec>, -) -> Option>> { - debug_assert!(!complexes.is_empty()); +use extension::Extension; +pub(crate) use functions::unify_complex; +use functions::{paths, weave}; - if complexes.len() == 1 { - return Some(complexes); - } +mod extension; +mod functions; - let mut unified_base: Option> = None; +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub(crate) struct CssMediaQuery; - for complex in &complexes { - let base = complex.last()?; +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +/// Different modes in which extension can run. +enum ExtendMode { + /// Normal mode, used with the `@extend` rule. + /// + /// This preserves existing selectors and extends each target individually. + Normal, - if let ComplexSelectorComponent::Compound(base) = base { - if let Some(mut some_unified_base) = unified_base.clone() { - for simple in base.components.clone() { - some_unified_base = simple.unify(some_unified_base.clone())?; - } - unified_base = Some(some_unified_base); - } else { - unified_base = Some(base.components.clone()); - } - } else { - return None; - } - } + /// Replace mode, used by the `selector-replace()` function. + /// + /// This replaces existing selectors and requires every target to match to + /// extend a given compound selector. + Replace, - let mut complexes_without_bases: Vec> = complexes - .into_iter() - .map(|mut complex| { - complex.pop(); - complex - }) - .collect(); - - complexes_without_bases - .last_mut() - .unwrap() - .push(ComplexSelectorComponent::Compound(CompoundSelector { - components: unified_base?, - })); - - Some(weave(complexes_without_bases)) + /// All-targets mode, used by the `selector-extend()` function. + /// + /// This preserves existing selectors but requires every target to match to + /// extend a given compound selector. + AllTargets, } -/// Expands "parenthesized selectors" in `complexes`. -/// -/// That is, if we have `.A .B {@extend .C}` and `.D .C {...}`, this -/// conceptually expands into `.D .C, .D (.A .B)`, and this function translates -/// `.D (.A .B)` into `.D .A .B, .A .D .B`. For thoroughness, `.A.D .B` would -/// also be required, but including merged selectors results in exponential -/// output for very little gain. -/// -/// The selector `.D (.A .B)` is represented as the list `[[.D], [.A, .B]]`. -fn weave(complexes: Vec>) -> Vec> { - let mut prefixes: Vec> = vec![complexes.first().unwrap().clone()]; +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct Extender { + /// A map from all simple selectors in the stylesheet to the selector lists + /// that contain them. + /// + /// This is used to find which selectors an `@extend` applies to and adjust + /// them. + selectors: HashMap>, - for complex in complexes.into_iter().skip(1) { - if complex.is_empty() { - continue; - } + /// A map from all extended simple selectors to the sources of those + /// extensions. + extensions: HashMap>, - let target = complex.last().unwrap().clone(); + /// A map from all simple selectors in extenders to the extensions that those + /// extenders define. + extensions_by_extender: HashMap>, - if complex.len() == 1 { - for prefix in &mut prefixes { - prefix.push(target.clone()); - } - continue; - } + /// A map from CSS selectors to the media query contexts they're defined in. + /// + /// This tracks the contexts in which each selector's style rule is defined. + /// If a rule is defined at the top level, it doesn't have an entry. + media_contexts: HashMap>, - let complex_len = complex.len(); + /// A map from `SimpleSelector`s to the specificity of their source + /// selectors. + /// + /// This tracks the maximum specificity of the `ComplexSelector` that + /// originally contained each `SimpleSelector`. This allows us to ensure that + /// we don't trim any selectors that need to exist to satisfy the [second law + /// of extend][]. + /// + /// [second law of extend]: https://github.com/sass/sass/issues/324#issuecomment-4607184 + source_specificity: HashMap, - let parents: Vec = - complex.into_iter().take(complex_len - 1).collect(); - let mut new_prefixes: Vec> = Vec::new(); + /// A set of `ComplexSelector`s that were originally part of + /// their component `SelectorList`s, as opposed to being added by `@extend`. + /// + /// This allows us to ensure that we don't trim any selectors that need to + /// exist to satisfy the [first law of extend][]. + /// + /// [first law of extend]: https://github.com/sass/sass/issues/324#issuecomment-4607184 + originals: HashSet, - for prefix in prefixes { - let parent_prefixes = weave_parents(prefix, parents.clone()); - - if let Some(parent_prefixes) = parent_prefixes { - for mut parent_prefix in parent_prefixes { - parent_prefix.push(target.clone()); - new_prefixes.push(parent_prefix); - } - } - } - prefixes = new_prefixes; - } - - prefixes + /// The mode that controls this extender's behavior. + mode: ExtendMode, } -/// Interweaves `parents_one` and `parents_two` as parents of the same target selector. -/// -/// Returns all possible orderings of the selectors in the inputs (including -/// using unification) that maintain the relative ordering of the input. For -/// example, given `.foo .bar` and `.baz .bang`, this would return `.foo .bar -/// .baz .bang`, `.foo .bar.baz .bang`, `.foo .baz .bar .bang`, `.foo .baz -/// .bar.bang`, `.foo .baz .bang .bar`, and so on until `.baz .bang .foo .bar`. -/// -/// Semantically, for selectors A and B, this returns all selectors `AB_i` -/// such that the union over all i of elements matched by `AB_i X` is -/// identical to the intersection of all elements matched by `A X` and all -/// elements matched by `B X`. Some `AB_i` are elided to reduce the size of -/// the output. -fn weave_parents( - parents_one: Vec, - parents_two: Vec, -) -> Option>> { - let mut queue_one = VecDeque::from(parents_one); - let mut queue_two = VecDeque::from(parents_two); +impl Extender { + /// An `Extender` that contains no extensions and can have no extensions added. + // TODO: empty extender + const EMPTY: () = (); - let initial_combinators = merge_initial_combinators(&mut queue_one, &mut queue_two)?; - - let mut final_combinators = merge_final_combinators(&mut queue_one, &mut queue_two, None)?; - - match (first_if_root(&mut queue_one), first_if_root(&mut queue_two)) { - (Some(root_one), Some(root_two)) => { - let root = ComplexSelectorComponent::Compound(root_one.unify(root_two)?); - queue_one.push_front(root.clone()); - queue_two.push_front(root); - } - (Some(root_one), None) => { - queue_two.push_front(ComplexSelectorComponent::Compound(root_one)); - } - (None, Some(root_two)) => { - queue_one.push_front(ComplexSelectorComponent::Compound(root_two)); - } - (None, None) => {} + pub fn extend( + selector: SelectorList, + source: SelectorList, + targets: SelectorList, + ) -> SelectorList { + Self::extend_or_replace(selector, source, targets, ExtendMode::AllTargets) } - let mut groups_one = group_selectors(Vec::from(queue_one)); - let mut groups_two = group_selectors(Vec::from(queue_two)); + pub fn replace( + selector: SelectorList, + source: SelectorList, + targets: SelectorList, + ) -> SelectorList { + Self::extend_or_replace(selector, source, targets, ExtendMode::Replace) + } - let lcs = longest_common_subsequence( - groups_two.as_slices().0, - groups_one.as_slices().0, - Some(&|group_one, group_two| { - if group_one == group_two { - return Some(group_one); - } - - if let ComplexSelectorComponent::Combinator(..) = group_one.first()? { - return None; - } - if let ComplexSelectorComponent::Combinator(..) = group_two.first()? { - return None; - } - - if complex_is_parent_superselector(group_one.clone(), group_two.clone()) { - return Some(group_two); - } - if complex_is_parent_superselector(group_two.clone(), group_one.clone()) { - return Some(group_one); - } - - if !must_unify(&group_one, &group_two) { - return None; - } - - let unified = unify_complex(vec![group_one, group_two])?; - if unified.len() > 1 { - return None; - } - - unified.first().cloned() - }), - ); - - let mut choices = vec![vec![initial_combinators - .into_iter() - .map(ComplexSelectorComponent::Combinator) - .collect::>()]]; - - for group in lcs { - choices.push( - chunks(&mut groups_one, &mut groups_two, |sequence| { - complex_is_parent_superselector(sequence.get(0).unwrap().clone(), group.clone()) - }) + fn extend_or_replace( + mut selector: SelectorList, + source: SelectorList, + targets: SelectorList, + mode: ExtendMode, + ) -> SelectorList { + let mut extenders: IndexMap = source + .components + .clone() .into_iter() - .map(|chunk| chunk.into_iter().flatten().collect()) - .collect(), - ); - choices.push(vec![group]); - groups_one.pop_front(); - groups_two.pop_front(); - } + .zip( + source + .components + .into_iter() + .map(|complex| Extension::one_off(complex, None, false)), + ) + .collect(); - choices.push( - chunks(&mut groups_one, &mut groups_two, VecDeque::is_empty) - .into_iter() - .map(|chunk| chunk.into_iter().flatten().collect()) - .collect(), - ); + for complex in targets.components { + if complex.components.len() != 1 { + todo!("throw SassScriptException(\"Can't extend complex selector $complex.\");") + } - choices.append(&mut final_combinators); - - Some( - paths( - choices - .into_iter() - .filter(|choice| !choice.is_empty()) - .collect(), - ) - .into_iter() - .map(|chunk| chunk.into_iter().flatten().collect()) - .collect(), - ) -} - -/// Extracts leading `Combinator`s from `components_one` and `components_two` and -/// merges them together into a single list of combinators. -/// -/// If there are no combinators to be merged, returns an empty list. If the -/// combinators can't be merged, returns `None`. -fn merge_initial_combinators( - components_one: &mut VecDeque, - components_two: &mut VecDeque, -) -> Option> { - let mut combinators_one: Vec = Vec::new(); - - while let Some(ComplexSelectorComponent::Combinator(c)) = components_one.get(0) { - combinators_one.push(*c); - components_one.pop_front(); - } - - let mut combinators_two = Vec::new(); - - while let Some(ComplexSelectorComponent::Combinator(c)) = components_two.get(0) { - combinators_two.push(*c); - components_two.pop_front(); - } - - let lcs = longest_common_subsequence(&combinators_one, &combinators_two, None); - - if lcs == combinators_one { - Some(combinators_two) - } else if lcs == combinators_two { - Some(combinators_one) - } else { - // If neither sequence of combinators is a subsequence of the other, they - // cannot be merged successfully. - None - } -} - -/// Returns the longest common subsequence between `list_one` and `list_two`. -/// -/// If there are more than one equally long common subsequence, returns the one -/// which starts first in `list_one`. -/// -/// If `select` is passed, it's used to check equality between elements in each -/// list. If it returns `None`, the elements are considered unequal; otherwise, -/// it should return the element to include in the return value. -#[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)] -fn longest_common_subsequence( - list_one: &[T], - list_two: &[T], - select: Option<&dyn Fn(T, T) -> Option>, -) -> Vec { - let select = select.unwrap_or(&|element_one, element_two| { - if element_one == element_two { - Some(element_one) - } else { - None - } - }); - - let mut lengths = vec![vec![0; list_two.len() + 1]; list_one.len() + 1]; - - let mut selections: Vec>> = vec![vec![None; list_two.len()]; list_one.len()]; - - for i in 0..list_one.len() { - for j in 0..list_two.len() { - let selection = select( - list_one.get(i).unwrap().clone(), - list_two.get(j).unwrap().clone(), - ); - selections[i][j] = selection.clone(); - lengths[i + 1][j + 1] = if selection.is_none() { - std::cmp::max(lengths[i + 1][j], lengths[i][j + 1]) - } else { - lengths[i][j] + 1 - }; - } - } - - fn backtrack( - i: isize, - j: isize, - lengths: Vec>, - selections: &mut Vec>>, - ) -> Vec { - if i == -1 || j == -1 { - return Vec::new(); - } - let selection = selections.get(i as usize).cloned().unwrap_or_else(Vec::new); - - if let Some(Some(selection)) = selection.get(j as usize) { - let mut tmp = backtrack(i - 1, j - 1, lengths, selections); - tmp.push(selection.clone()); - return tmp; - } - - if lengths[(i + 1) as usize][j as usize] > lengths[i as usize][(j + 1) as usize] { - backtrack(i, j - 1, lengths, selections) - } else { - backtrack(i - 1, j, lengths, selections) - } - } - backtrack( - (list_one.len() as isize).saturating_sub(1), - (list_two.len() as isize).saturating_sub(1), - lengths, - &mut selections, - ) -} - -/// Extracts trailing `Combinator`s, and the selectors to which they apply, from -/// `components_one` and `components_two` and merges them together into a single list. -/// -/// If there are no combinators to be merged, returns an empty list. If the -/// sequences can't be merged, returns `None`. -#[allow(clippy::cognitive_complexity)] -fn merge_final_combinators( - components_one: &mut VecDeque, - components_two: &mut VecDeque, - result: Option>>>, -) -> Option>>> { - let mut result = result.unwrap_or_default(); - - if (components_one.is_empty() - || !components_one - .get(components_one.len() - 1) - .unwrap() - .is_combinator()) - && (components_two.is_empty() - || !components_two - .get(components_two.len() - 1) - .unwrap() - .is_combinator()) - { - return Some(Vec::from(result)); - } - - let mut combinators_one = Vec::new(); - - while let Some(ComplexSelectorComponent::Combinator(combinator)) = - components_one.get(components_one.len().saturating_sub(1)) - { - combinators_one.push(*combinator); - components_one.pop_back(); - } - - let mut combinators_two = Vec::new(); - - while let Some(ComplexSelectorComponent::Combinator(combinator)) = - components_two.get(components_two.len().saturating_sub(1)) - { - combinators_two.push(*combinator); - components_two.pop_back(); - } - - if combinators_one.len() > 1 || combinators_two.len() > 1 { - // If there are multiple combinators, something hacky's going on. If one - // is a supersequence of the other, use that, otherwise give up. - let lcs = longest_common_subsequence(&combinators_one, &combinators_two, None); - if lcs == combinators_one { - result.push_front(vec![combinators_two - .into_iter() - .map(ComplexSelectorComponent::Combinator) - .rev() - .collect()]); - } else if lcs == combinators_two { - result.push_front(vec![combinators_one - .into_iter() - .map(ComplexSelectorComponent::Combinator) - .rev() - .collect()]); - } else { - return None; - } - - return Some(Vec::from(result)); - } - - let combinator_one = if combinators_one.is_empty() { - None - } else { - combinators_one.first() - }; - - let combinator_two = if combinators_two.is_empty() { - None - } else { - combinators_two.first() - }; - - // This code looks complicated, but it's actually just a bunch of special - // cases for interactions between different combinators. - match (combinator_one, combinator_two) { - (Some(combinator_one), Some(combinator_two)) => { - let compound_one = match components_one.pop_back() { + let compound = match complex.components.first() { Some(ComplexSelectorComponent::Compound(c)) => c, - Some(..) | None => unreachable!(), - }; - let compound_two = match components_two.pop_back() { - Some(ComplexSelectorComponent::Compound(c)) => c, - Some(..) | None => unreachable!(), + Some(..) | None => todo!(), }; - match (combinator_one, combinator_two) { - (Combinator::FollowingSibling, Combinator::FollowingSibling) => { - if compound_one.is_super_selector(&compound_two, &None) { - result.push_front(vec![vec![ - ComplexSelectorComponent::Compound(compound_two), - ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), - ]]) - } else if compound_two.is_super_selector(&compound_one, &None) { - result.push_front(vec![vec![ - ComplexSelectorComponent::Compound(compound_one), - ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), - ]]) - } else { - let mut choices = vec![ - vec![ - ComplexSelectorComponent::Compound(compound_one.clone()), - ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), - ComplexSelectorComponent::Compound(compound_two.clone()), - ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), - ], - vec![ - ComplexSelectorComponent::Compound(compound_two.clone()), - ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), - ComplexSelectorComponent::Compound(compound_one.clone()), - ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), - ], - ]; + let extensions: HashMap> = + compound + .components + .clone() + .into_iter() + .map(|simple| (simple, extenders.clone())) + .collect(); - if let Some(unified) = compound_one.unify(compound_two) { - choices.push(vec![ - ComplexSelectorComponent::Compound(unified), - ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), - ]) - } + let mut extender = Extender::with_mode(mode); + if !selector.is_invisible() { + extender + .originals + .extend(selector.components.clone().into_iter()); + } - result.push_front(choices); + selector = extender.extend_list(selector, extensions, None); + } + + selector + } + + fn with_mode(mode: ExtendMode) -> Self { + Self { + mode, + selectors: Default::default(), + extensions: Default::default(), + extensions_by_extender: Default::default(), + media_contexts: Default::default(), + source_specificity: Default::default(), + originals: Default::default(), + } + } + + /// Extends `list` using `extensions`. + fn extend_list( + &mut self, + list: SelectorList, + extensions: HashMap>, + media_query_context: Option>, + ) -> SelectorList { + // This could be written more simply using Vec>, but we want to avoid + // any allocations in the common case where no extends apply. + let mut extended: Vec = Vec::new(); + for i in 0..list.components.len() { + let complex = list.components.get(i).unwrap().clone(); + + if let Some(result) = self.extend_complex( + complex.clone(), + extensions.clone(), + media_query_context.clone(), + ) { + if extended.is_empty() { + if i != 0 { + extended = list.components[0..i].to_vec(); } + + extended.extend(result.into_iter()); } - (Combinator::FollowingSibling, Combinator::NextSibling) - | (Combinator::NextSibling, Combinator::FollowingSibling) => { - let following_sibling_selector = - if combinator_one == &Combinator::FollowingSibling { - compound_one.clone() - } else { - compound_two.clone() + } else { + if !extended.is_empty() { + extended.push(complex); + } + } + } + + if extended.is_empty() { + return list; + } + + SelectorList { + components: self.trim(extended, |complex| self.originals.contains(&complex)), + } + } + + /// Extends `complex` using `extensions`, and returns the contents of a + /// `SelectorList`. + fn extend_complex( + &mut self, + complex: ComplexSelector, + extensions: HashMap>, + media_query_context: Option>, + ) -> Option> { + // The complex selectors that each compound selector in `complex.components` + // can expand to. + // + // For example, given + // + // .a .b {...} + // .x .y {@extend .b} + // + // this will contain + // + // [ + // [.a], + // [.b, .x .y] + // ] + // + // This could be written more simply using `Vec::into_iter::map`, but we want to avoid + // any allocations in the common case where no extends apply. + let mut extended_not_expanded: Vec> = Vec::new(); + + let complex_has_line_break = complex.line_break; + + let is_original = self.originals.contains(&complex); + + for i in 0..complex.components.len() { + if let Some(ComplexSelectorComponent::Compound(component)) = complex.components.get(i) { + if let Some(extended) = self.extend_compound( + component.clone(), + extensions.clone(), + media_query_context.clone(), + is_original, + ) { + if extended_not_expanded.is_empty() { + extended_not_expanded = complex + .components + .clone() + .into_iter() + .take(i) + .map(|component| { + vec![ComplexSelector { + components: vec![component], + line_break: complex.line_break, + }] + }) + .collect(); + } + extended_not_expanded.push(extended); + } else { + extended_not_expanded.push(vec![ComplexSelector { + components: vec![ComplexSelectorComponent::Compound(component.clone())], + line_break: false, + }]) + } + } + } + + if extended_not_expanded.is_empty() { + return None; + } + + // dbg!(&extended_not_expanded); + + let mut first = true; + + let mut originals: Vec = Vec::new(); + + Some( + paths(extended_not_expanded) + .into_iter() + .flat_map(move |path| { + weave( + path.clone() + .into_iter() + .map(move |complex| complex.components) + .collect(), + ) + .into_iter() + .map(|components| { + let output_complex = ComplexSelector { + components, + line_break: complex_has_line_break + || path.iter().any(|input_complex| input_complex.line_break), }; - let next_sibling_selector = if combinator_one == &Combinator::FollowingSibling { - compound_two.clone() - } else { - compound_one.clone() - }; - - if following_sibling_selector.is_super_selector(&next_sibling_selector, &None) { - result.push_front(vec![vec![ - ComplexSelectorComponent::Compound(next_sibling_selector), - ComplexSelectorComponent::Combinator(Combinator::NextSibling), - ]]); - } else { - let mut v = vec![vec![ - ComplexSelectorComponent::Compound(following_sibling_selector), - ComplexSelectorComponent::Combinator(Combinator::FollowingSibling), - ComplexSelectorComponent::Compound(next_sibling_selector), - ComplexSelectorComponent::Combinator(Combinator::NextSibling), - ]]; - - if let Some(unified) = compound_one.unify(compound_two) { - v.push(vec![ - ComplexSelectorComponent::Compound(unified), - ComplexSelectorComponent::Combinator(Combinator::NextSibling), - ]); + if first && originals.contains(&complex.clone()) { + originals.push(output_complex.clone()); } - result.push_front(v); - } - } - (Combinator::Child, Combinator::NextSibling) - | (Combinator::Child, Combinator::FollowingSibling) => { - result.push_front(vec![vec![ - ComplexSelectorComponent::Compound(compound_two), - ComplexSelectorComponent::Combinator(*combinator_two), - ]]); - components_one.push_back(ComplexSelectorComponent::Compound(compound_one)); - components_one - .push_back(ComplexSelectorComponent::Combinator(Combinator::Child)); - } - (Combinator::NextSibling, Combinator::Child) - | (Combinator::FollowingSibling, Combinator::Child) => { - result.push_front(vec![vec![ - ComplexSelectorComponent::Compound(compound_one), - ComplexSelectorComponent::Combinator(*combinator_one), - ]]); - components_two.push_back(ComplexSelectorComponent::Compound(compound_two)); - components_two - .push_back(ComplexSelectorComponent::Combinator(Combinator::Child)); - } - (..) => { - if combinator_one != combinator_two { - return None; - } + first = false; - let unified = compound_one.unify(compound_two)?; - - result.push_front(vec![vec![ - ComplexSelectorComponent::Compound(unified), - ComplexSelectorComponent::Combinator(*combinator_one), - ]]); - } - } - - merge_final_combinators(components_one, components_two, Some(result)) - } - (Some(combinator_one), None) => { - if *combinator_one == Combinator::Child && !components_two.is_empty() { - if let Some(ComplexSelectorComponent::Compound(c1)) = - components_one.get(components_one.len() - 1) - { - if let Some(ComplexSelectorComponent::Compound(c2)) = - components_two.get(components_two.len() - 1) - { - if c2.is_super_selector(c1, &None) { - components_two.pop_back(); - } - } - } - } - - result.push_front(vec![vec![ - components_one.pop_back().unwrap(), - ComplexSelectorComponent::Combinator(*combinator_one), - ]]); - - merge_final_combinators(components_one, components_two, Some(result)) - } - (None, Some(combinator_two)) => { - if *combinator_two == Combinator::Child && !components_one.is_empty() { - if let Some(ComplexSelectorComponent::Compound(c1)) = - components_one.get(components_one.len() - 1) - { - if let Some(ComplexSelectorComponent::Compound(c2)) = - components_two.get(components_two.len() - 1) - { - if c1.is_super_selector(c2, &None) { - components_one.pop_back(); - } - } - } - } - - result.push_front(vec![vec![ - components_two.pop_back().unwrap(), - ComplexSelectorComponent::Combinator(*combinator_two), - ]]); - merge_final_combinators(components_one, components_two, Some(result)) - } - (None, None) => todo!("the above, but we dont have access to combinator_two"), + output_complex + }) + .collect::>() + }) + .collect(), + ) } -} -/// If the first element of `queue` has a `::root` selector, removes and returns -/// that element. -fn first_if_root(queue: &mut VecDeque) -> Option { - if queue.is_empty() { - return None; - } - if let Some(ComplexSelectorComponent::Compound(c)) = queue.get(0) { - if !has_root(c) { + /// Extends `compound` using `extensions`, and returns the contents of a + /// `SelectorList`. + /// + /// The `in_original` parameter indicates whether this is in an original + /// complex selector, meaning that `compound` should not be trimmed out. + fn extend_compound( + &mut self, + compound: CompoundSelector, + extensions: HashMap>, + media_query_context: Option>, + in_original: bool, + ) -> Option> { + // If there's more than one target and they all need to match, we track + // which targets are actually extended. + let mut targets_used: HashSet = HashSet::new(); + + let mut options: Vec> = Vec::new(); + + for i in 0..compound.components.len() { + let simple = compound.components.get(i).cloned().unwrap(); + + if let Some(extended) = self.extend_simple( + simple.clone(), + extensions.clone(), + media_query_context.clone(), + &mut targets_used, + ) { + if options.is_empty() { + if i != 0 { + options.push(vec![self.extension_for_compound( + compound.components.clone().into_iter().take(i).collect(), + )]); + } + } + + options.extend(extended.into_iter()); + } else { + options.push(vec![self.extension_for_simple(simple)]); + } + } + + if options.is_empty() { return None; } - let compound = c.clone(); - queue.pop_front(); - Some(compound) - } else { - None - } -} -/// Returns whether or not `compound` contains a `::root` selector. -fn has_root(compound: &CompoundSelector) -> bool { - compound.components.iter().any(|simple| { - if let SimpleSelector::Pseudo(pseudo) = simple { - pseudo.is_class && pseudo.normalized_name == "root" - } else { - false + // If `self.mode` isn't `ExtendMode::Normal` and we didn't use all the targets in + // `extensions`, extension fails for `compound`. + if targets_used.len() != extensions.len() { + return None; } - }) -} -/// Returns `complex`, grouped into sub-lists such that no sub-list contains two -/// adjacent `ComplexSelector`s. -/// -/// For example, `(A B > C D + E ~ > G)` is grouped into -/// `[(A) (B > C) (D + E ~ > G)]`. -fn group_selectors( - complex: Vec, -) -> VecDeque> { - let mut groups = VecDeque::new(); - - let mut iter = complex.into_iter(); - - let mut group = if let Some(c) = iter.next() { - vec![c] - } else { - return groups; - }; - - groups.push_back(group.clone()); - - for c in iter { - if group - .last() - .map_or(false, ComplexSelectorComponent::is_combinator) - || c.is_combinator() - { - group.push(c); - } else { - group = vec![c]; - groups.push_back(group.clone()); + // Optimize for the simple case of a single simple selector that doesn't + // need any unification. + if options.len() == 1 { + return Some( + options + .first()? + .clone() + .into_iter() + .map(|state| { + state.assert_compatible_media_context(&media_query_context); + state.extender + }) + .collect(), + ); } - } - groups -} + // Find all paths through `options`. In this case, each path represents a + // different unification of the base selector. For example, if we have: + // + // .a.b {...} + // .w .x {@extend .a} + // .y .z {@extend .b} + // + // then `options` is `[[.a, .w .x], [.b, .y .z]]` and `paths(options)` is + // + // [ + // [.a, .b], + // [.a, .y .z], + // [.w .x, .b], + // [.w .x, .y .z] + // ] + // + // We then unify each path to get a list of complex selectors: + // + // [ + // [.a.b], + // [.y .a.z], + // [.w .x.b], + // [.w .y .x.z, .y .w .x.z] + // ] + let mut first = self.mode != ExtendMode::Replace; -/// Returns all orderings of initial subseqeuences of `queue_one` and `queue_two`. -/// -/// The `done` callback is used to determine the extent of the initial -/// subsequences. It's called with each queue until it returns `true`. -/// -/// This destructively removes the initial subsequences of `queue_one` and -/// `queue_two`. -/// -/// For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|` denoting -/// the boundary of the initial subsequence), this would return `[(A B C 1 2), -/// (1 2 A B C)]`. The queues would then contain `(D E)` and `(3 4 5)`. -fn chunks( - queue_one: &mut VecDeque, - queue_two: &mut VecDeque, - done: impl Fn(&VecDeque) -> bool, -) -> Vec> { - let mut chunk_one = Vec::new(); - while !done(queue_one) { - chunk_one.push(queue_one.pop_front().unwrap()); - } - - let mut chunk_two = Vec::new(); - while !done(queue_two) { - chunk_two.push(queue_two.pop_front().unwrap()); - } - - match (chunk_one.is_empty(), chunk_two.is_empty()) { - (true, true) => Vec::new(), - (true, false) => vec![chunk_two], - (false, true) => vec![chunk_one], - (false, false) => { - let mut l1 = chunk_one.clone(); - l1.append(&mut chunk_two.clone()); - - let mut l2 = chunk_two; - l2.append(&mut chunk_one); - - vec![l1, l2] - } - } -} - -/// Like `complex_is_superselector`, but compares `complex_one` and `complex_two` as -/// though they shared an implicit base `SimpleSelector`. -/// -/// For example, `B` is not normally a superselector of `B A`, since it doesn't -/// match elements that match `A`. However, it *is* a parent superselector, -/// since `B X` is a superselector of `B A X`. -fn complex_is_parent_superselector( - mut complex_one: Vec, - mut complex_two: Vec, -) -> bool { - if let Some(ComplexSelectorComponent::Combinator(..)) = complex_one.first() { - return false; - } - if let Some(ComplexSelectorComponent::Combinator(..)) = complex_two.first() { - return false; - } - if complex_one.len() > complex_two.len() { - return false; - } - let base = CompoundSelector { - components: vec![SimpleSelector::Placeholder(String::new())], - }; - complex_one.push(ComplexSelectorComponent::Compound(base.clone())); - complex_two.push(ComplexSelectorComponent::Compound(base)); - - ComplexSelector { - components: complex_one, - line_break: false, - } - .is_super_selector(&ComplexSelector { - components: complex_two, - line_break: false, - }) -} - -/// Returns a list of all possible paths through the given lists. -/// -/// For example, given `[[1, 2], [3, 4], [5]]`, this returns: -/// -/// ```norun -/// [[1, 3, 5], -/// [2, 3, 5], -/// [1, 4, 5], -/// [2, 4, 5]] -/// ``` -fn paths(choices: Vec>) -> Vec> { - choices.into_iter().fold(vec![vec![]], |paths, choice| { - choice + let unified_paths: Vec>> = paths(options) .into_iter() - .flat_map(move |option| { - paths.clone().into_iter().map(move |mut path| { - path.push(option.clone()); - path - }) + .map(|path| { + let complexes: Vec>; + + if first { + // The first path is always the original selector. We can't just + // return `compound` directly because pseudo selectors may be + // modified, but we don't have to do any unification. + first = false; + + complexes = vec![vec![ComplexSelectorComponent::Compound(CompoundSelector { + components: path + .clone() + .into_iter() + .flat_map(|state| { + assert!(state.extender.components.len() == 1); + match state.extender.components.last().cloned() { + Some(ComplexSelectorComponent::Compound(c)) => c.components, + Some(..) | None => unreachable!(), + } + }) + .collect(), + })]]; + } else { + let mut to_unify: VecDeque> = VecDeque::new(); + let mut originals: Vec = Vec::new(); + + for state in path.clone() { + if state.is_original { + originals.extend(match state.extender.components.last().cloned() { + Some(ComplexSelectorComponent::Compound(c)) => c.components, + Some(..) | None => unreachable!(), + }); + } else { + to_unify.push_back(state.extender.components.clone()); + } + } + if originals.is_empty() { + to_unify.push_front(vec![ComplexSelectorComponent::Compound( + CompoundSelector { + components: originals, + }, + )]); + } + + complexes = unify_complex(Vec::from(to_unify))?; + } + + let mut line_break = false; + + for state in path { + state.assert_compatible_media_context(&media_query_context); + line_break = line_break || state.extender.line_break; + } + + Some( + complexes + .into_iter() + .map(|components| ComplexSelector { + components, + line_break, + }) + .collect(), + ) }) - .collect() - }) -} + .collect(); -/// Returns whether `complex_one` and `complex_two` need to be unified to produce a -/// valid combined selector. -/// -/// This is necessary when both selectors contain the same unique simple -/// selector, such as an ID. -fn must_unify( - complex_one: &[ComplexSelectorComponent], - complex_two: &[ComplexSelectorComponent], -) -> bool { - let mut unique_selectors = Vec::new(); - for component in complex_one { - if let ComplexSelectorComponent::Compound(c) = component { - unique_selectors.extend(c.components.iter().filter(|f| is_unique(f))); + dbg!(&unified_paths); + + Some( + unified_paths + .into_iter() + .filter_map(|complexes| complexes) + .flatten() + .collect(), + ) + } + + fn extend_simple( + &mut self, + simple: SimpleSelector, + extensions: HashMap>, + media_query_context: Option>, + targets_used: &mut HashSet, + ) -> Option>> { + if let SimpleSelector::Pseudo( + simple @ Pseudo { + selector: Some(..), .. + }, + ) = simple.clone() + { + if let Some(extended) = + self.extend_pseudo(simple, extensions.clone(), media_query_context) + { + return Some( + extended + .into_iter() + .map(move |pseudo| { + self.without_pseudo( + SimpleSelector::Pseudo(pseudo.clone()), + extensions.clone(), + targets_used, + self.mode, + ) + .unwrap_or_else(|| { + vec![self.extension_for_simple(SimpleSelector::Pseudo(pseudo))] + }) + }) + .collect(), + ); + } } + + self.without_pseudo(simple, extensions, targets_used, self.mode) + .map(|v| vec![v]) } - if unique_selectors.is_empty() { - return false; - } + /// Extends `pseudo` using `extensions`, and returns a list of resulting + /// pseudo selectors. + fn extend_pseudo( + &mut self, + pseudo: Pseudo, + extensions: HashMap>, + media_query_context: Option>, + ) -> Option> { + let extended = self.extend_list( + pseudo + .selector + .clone() + .unwrap_or_else(|| SelectorList::new()), + extensions, + media_query_context, + ); + /*todo: identical(extended, pseudo.selector)*/ + if Some(&extended) == pseudo.selector.as_ref() { + return None; + } - complex_two.iter().any(|component| { - if let ComplexSelectorComponent::Compound(compound) = component { - compound + // For `:not()`, we usually want to get rid of any complex selectors because + // that will cause the selector to fail to parse on all browsers at time of + // writing. We can keep them if either the original selector had a complex + // selector, or the result of extending has only complex selectors, because + // either way we aren't breaking anything that isn't already broken. + let mut complexes = extended.components.clone(); + if pseudo.normalized_name == "not" + && !pseudo + .selector + .clone() + .unwrap() .components .iter() - .any(|simple| is_unique(simple) && unique_selectors.contains(&simple)) - } else { - false + .any(|complex| complex.components.len() > 1) + && extended + .components + .iter() + .any(|complex| complex.components.len() == 1) + { + complexes = extended + .components + .clone() + .into_iter() + .filter(|complex| complex.components.len() <= 1) + .collect(); } - }) + + complexes = complexes + .into_iter() + .flat_map(|complex| { + if complex.components.len() != 1 { + return vec![complex]; + } + let compound = match complex.components.first() { + Some(ComplexSelectorComponent::Compound(c)) => c, + Some(..) | None => return vec![complex], + }; + if compound.components.len() != 1 { + return vec![complex]; + } + if !compound.components.first().unwrap().is_pseudo() { + return vec![complex]; + } + let inner_pseudo = match compound.components.first() { + Some(SimpleSelector::Pseudo(pseudo)) => pseudo, + Some(..) | None => return vec![complex], + }; + if inner_pseudo.selector.is_none() { + return vec![complex]; + } + + match pseudo.normalized_name.as_str() { + "not" => { + // In theory, if there's a `:not` nested within another `:not`, the + // inner `:not`'s contents should be unified with the return value. + // For example, if `:not(.foo)` extends `.bar`, `:not(.bar)` should + // become `.foo:not(.bar)`. However, this is a narrow edge case and + // supporting it properly would make this code and the code calling it + // a lot more complicated, so it's not supported for now. + if inner_pseudo.normalized_name != "matches" { + Vec::new() + } else { + inner_pseudo.selector.clone().unwrap().components + } + } + "matches" | "any" | "current" | "nth-child" | "nth-last-child" => { + // As above, we could theoretically support :not within :matches, but + // doing so would require this method and its callers to handle much + // more complex cases that likely aren't worth the pain. + if inner_pseudo.name != pseudo.name + || inner_pseudo.argument != pseudo.argument + { + Vec::new() + } else { + inner_pseudo.selector.clone().unwrap().components + } + } + "has" | "host" | "host-context" | "slotted" => { + // We can't expand nested selectors here, because each layer adds an + // additional layer of semantics. For example, `:has(:has(img))` + // doesn't match `
` but `:has(img)` does. + vec![complex] + } + _ => Vec::new(), + } + }) + .collect(); + // Older browsers support `:not`, but only with a single complex selector. + // In order to support those browsers, we break up the contents of a `:not` + // unless it originally contained a selector list. + if pseudo.normalized_name == "not" && pseudo.selector.clone().unwrap().components.len() == 1 + { + let result = complexes + .into_iter() + .map(|complex| { + pseudo.clone().with_selector(Some(SelectorList { + components: vec![complex], + })) + }) + .collect::>(); + if result.is_empty() { + None + } else { + Some(result) + } + } else { + Some(vec![pseudo.with_selector(Some(SelectorList { + components: complexes, + }))]) + } + } + + // Extends `simple` without extending the contents of any selector pseudos + // it contains. + fn without_pseudo( + &self, + simple: SimpleSelector, + extensions: HashMap>, + targets_used: &mut HashSet, + mode: ExtendMode, + ) -> Option> { + let extenders = extensions.get(&simple)?; + + targets_used.insert(simple.clone()); + + if mode == ExtendMode::Replace { + return Some(extenders.values().cloned().collect()); + } + + let mut tmp = vec![self.extension_for_simple(simple)]; + tmp.extend(extenders.values().cloned()); + + Some(tmp) + } + + /// Returns a one-off `Extension` whose extender is composed solely of + /// `simple`. + fn extension_for_simple(&self, simple: SimpleSelector) -> Extension { + let specificity = Some(*self.source_specificity.get(&simple).unwrap_or(&0_i32)); + Extension::one_off( + ComplexSelector { + components: vec![ComplexSelectorComponent::Compound(CompoundSelector { + components: vec![simple], + })], + line_break: false, + }, + specificity, + true, + ) + } + + /// Returns a one-off `Extension` whose extender is composed solely of a + /// compound selector containing `simples`. + fn extension_for_compound(&self, simples: Vec) -> Extension { + let compound = CompoundSelector { + components: simples, + }; + let specificity = Some(self.source_specificity_for(&compound)); + Extension::one_off( + ComplexSelector { + components: vec![ComplexSelectorComponent::Compound(compound)], + line_break: false, + }, + specificity, + true, + ) + } + + /// Returns the maximum specificity for sources that went into producing + /// `compound`. + fn source_specificity_for(&self, compound: &CompoundSelector) -> i32 { + let mut specificity = 0; + for simple in &compound.components { + specificity = specificity.max(*self.source_specificity.get(simple).unwrap_or(&0)); + } + specificity + } + + // Removes elements from `selectors` if they're subselectors of other + // elements. + // + // The `is_original` callback indicates which selectors are original to the + // document, and thus should never be trimmed. + fn trim( + &self, + selectors: Vec, + is_original: impl Fn(ComplexSelector) -> bool, + ) -> Vec { + // Avoid truly horrific quadratic behavior. + // + // TODO(nweiz): I think there may be a way to get perfect trimming without + // going quadratic by building some sort of trie-like data structure that + // can be used to look up superselectors. + if selectors.len() > 100 { + return selectors; + } + + // This is n² on the sequences, but only comparing between separate + // sequences should limit the quadratic behavior. We iterate from last to + // first and reverse the result so that, if two selectors are identical, we + // keep the first one. + let mut result: VecDeque = VecDeque::new(); + let mut num_originals = 0; + + // :outer + loop { + let mut should_break_to_outer = false; + for i in (0..=(selectors.len().saturating_sub(1))).rev() { + let complex1 = selectors.get(i).unwrap(); + if is_original(complex1.clone()) { + // Make sure we don't include duplicate originals, which could happen if + // a style rule extends a component of its own selector. + for j in 0..num_originals { + if result.get(j).unwrap() == complex1 { + rotate_slice(&mut result, 0, j + 1); + should_break_to_outer = true; + break; + } + } + if should_break_to_outer { + break; + } + num_originals += 1; + result.push_front(complex1.clone()); + continue; + } + + // The maximum specificity of the sources that caused `complex1` to be + // generated. In order for `complex1` to be removed, there must be another + // selector that's a superselector of it *and* that has specificity + // greater or equal to this. + let mut max_specificity = 0; + for component in &complex1.components { + if let ComplexSelectorComponent::Compound(compound) = component { + max_specificity = max_specificity.max(self.source_specificity_for(compound)) + } + } + + // Look in `result` rather than `selectors` for selectors after `i`. This + // ensures that we aren't comparing against a selector that's already been + // trimmed, and thus that if there are two identical selectors only one is + // trimmed. + if result.iter().any(|complex2| { + complex2.min_specificity() >= max_specificity + && complex2.is_super_selector(complex1) + }) { + continue; + } + + if selectors.iter().take(i).any(|complex2| { + complex2.min_specificity() >= max_specificity + && complex2.is_super_selector(complex1) + }) { + continue; + } + + result.push_front(complex1.clone()); + } + if should_break_to_outer { + continue; + } else { + break; + } + } + + return Vec::from(result); + } } -/// Returns whether a `CompoundSelector` may contain only one simple selector of -/// the same type as `simple`. -fn is_unique(simple: &SimpleSelector) -> bool { - matches!(simple, SimpleSelector::Id(..) | SimpleSelector::Pseudo(Pseudo { is_class: false, .. })) +/// Rotates the element in list from `start` (inclusive) to `end` (exclusive) +/// one index higher, looping the final element back to `start`. +fn rotate_slice(list: &mut VecDeque, start: usize, end: usize) { + let mut element = list.get(end - 1).unwrap().clone(); + for i in start..end { + let next = list.get(i).unwrap().clone(); + list[i] = element; + element = next; + } } diff --git a/src/selector/list.rs b/src/selector/list.rs index f4187b6..7710ac3 100644 --- a/src/selector/list.rs +++ b/src/selector/list.rs @@ -13,7 +13,7 @@ use crate::value::Value; /// /// A selector list is composed of `ComplexSelector`s. It matches an element /// that matches any of the component selectors. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Hash)] pub(crate) struct SelectorList { /// The components of this selector. /// diff --git a/src/selector/simple.rs b/src/selector/simple.rs index 2121db7..9ecd3f3 100644 --- a/src/selector/simple.rs +++ b/src/selector/simple.rs @@ -7,7 +7,7 @@ use super::{ const SUBSELECTOR_PSEUDOS: [&str; 4] = ["matches", "any", "nth-child", "nth-last-child"]; -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Hash)] pub(crate) enum SimpleSelector { /// * Universal(Namespace), @@ -80,12 +80,12 @@ impl SimpleSelector { /// Specifity is represented in base 1000. The spec says this should be /// "sufficiently high"; it's extremely unlikely that any single selector /// sequence will contain 1000 simple selectors. - pub fn min_specificity(&self) -> u32 { + pub fn min_specificity(&self) -> i32 { match self { Self::Universal(..) => 0, Self::Type(..) => 1, Self::Pseudo { .. } => todo!(), - Self::Id(..) => 1000_u32.pow(2_u32), + Self::Id(..) => 1000_i32.pow(2_u32), _ => 1000, } } @@ -94,7 +94,7 @@ impl SimpleSelector { /// /// Pseudo selectors that contain selectors, like `:not()` and `:matches()`, /// can have a range of possible specificities. - pub fn max_specificity(&self) -> u32 { + pub fn max_specificity(&self) -> i32 { match self { Self::Universal(..) => 0, _ => self.min_specificity(), @@ -128,7 +128,7 @@ impl SimpleSelector { selector: None, .. }) => name.push_str(suffix), - _ => todo!(), + _ => todo!("Invalid parent selector"), //return Err((format!("Invalid parent selector \"{}\"", self), SPAN)), } } @@ -367,7 +367,7 @@ impl SimpleSelector { } } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Hash)] pub(crate) struct Pseudo { /// The name of this selector. pub name: String, @@ -563,6 +563,10 @@ impl Pseudo { _ => unreachable!(), } } + + pub fn with_selector(self, selector: Option) -> Self { + Self { selector, ..self } + } } /// Returns all pseudo selectors in `compound` that have a selector argument, diff --git a/tests/selector-extend.rs b/tests/selector-extend.rs new file mode 100644 index 0000000..aebd99c --- /dev/null +++ b/tests/selector-extend.rs @@ -0,0 +1,175 @@ +#![cfg(test)] + +#[macro_use] +mod macros; + +test!( + simple_attribute_equal, + "a {\n color: selector-extend(\"[c=d]\", \"[c=d]\", \"e\");\n}\n", + "a {\n color: [c=d], e;\n}\n" +); +test!( + simple_attribute_unequal_name, + "a {\n color: selector-extend(\"[c=d]\", \"[e=d]\", \"f\");\n}\n", + "a {\n color: [c=d];\n}\n" +); +test!( + simple_attribute_unequal_value, + "a {\n color: selector-extend(\"[c=d]\", \"[c=e]\", \"f\");\n}\n", + "a {\n color: [c=d];\n}\n" +); +test!( + simple_attribute_unequal_operator, + "a {\n color: selector-extend(\"[c=d]\", \"[c^=e]\", \"f\");\n}\n", + "a {\n color: [c=d];\n}\n" +); +test!( + simple_class_equal, + "a {\n color: selector-extend(\".c\", \".c\", \"e\");\n}\n", + "a {\n color: .c, e;\n}\n" +); +test!( + simple_class_unequal, + "a {\n color: selector-extend(\".c\", \".d\", \"e\");\n}\n", + "a {\n color: .c;\n}\n" +); +test!( + simple_id_equal, + "a {\n color: selector-extend(\"#c\", \"#c\", \"e\");\n}\n", + "a {\n color: #c, e;\n}\n" +); +test!( + simple_id_unequal, + "a {\n color: selector-extend(\"#c\", \"#d\", \"e\");\n}\n", + "a {\n color: #c;\n}\n" +); +test!( + simple_placeholder_equal, + "a {\n color: selector-extend(\"%c\", \"%c\", \"e\");\n}\n", + "a {\n color: %c, e;\n}\n" +); +test!( + simple_placeholder_unequal, + "a {\n color: selector-extend(\"%c\", \"%d\", \"e\");\n}\n", + "a {\n color: %c;\n}\n" +); +test!( + simple_type_equal, + "a {\n color: selector-extend(\"c\", \"c\", \"e\");\n}\n", + "a {\n color: c, e;\n}\n" +); +test!( + simple_type_unequal, + "a {\n color: selector-extend(\"c\", \"d\", \"e\");\n}\n", + "a {\n color: c;\n}\n" +); +test!( + simple_type_and_universal, + "a {\n color: selector-extend(\"c\", \"*\", \"d\");\n}\n", + "a {\n color: c;\n}\n" +); +test!( + simple_type_explicit_namespace_and_type_explicit_namespace_equal, + "a {\n color: selector-extend(\"c|d\", \"c|d\", \"e\");\n}\n", + "a {\n color: c|d, e;\n}\n" +); +test!( + simple_type_explicit_namespace_and_type_implicit_namespace, + "a {\n color: selector-extend(\"c|d\", \"d\", \"e\");\n}\n", + "a {\n color: c|d;\n}\n" +); +test!( + simple_type_explicit_namespace_and_type_empty_namespace, + "a {\n color: selector-extend(\"c|d\", \"|d\", \"e\");\n}\n", + "a {\n color: c|d;\n}\n" +); +test!( + simple_type_explicit_namespace_and_type_universal_namespace, + "a {\n color: selector-extend(\"c|d\", \"*|d\", \"e\");\n}\n", + "a {\n color: c|d;\n}\n" +); +test!( + simple_type_empty_namespace_and_type_explicit_namespace_equal, + "a {\n color: selector-extend(\"|c\", \"d|c\", \"e\");\n}\n", + "a {\n color: |c;\n}\n" +); +test!( + simple_type_empty_namespace_and_type_implicit_namespace, + "a {\n color: selector-extend(\"|c\", \"c\", \"d\");\n}\n", + "a {\n color: |c;\n}\n" +); +test!( + simple_type_empty_namespace_and_type_empty_namespace, + "a {\n color: selector-extend(\"|c\", \"|c\", \"d\");\n}\n", + "a {\n color: |c, d;\n}\n" +); +test!( + simple_type_empty_namespace_and_type_universal_namespace, + "a {\n color: selector-extend(\"|c\", \"*|c\", \"d\");\n}\n", + "a {\n color: |c;\n}\n" +); +test!( + simple_type_universal_namespace_and_type_explicit_namespace_equal, + "a {\n color: selector-extend(\"*|c\", \"d|c\", \"d\");\n}\n", + "a {\n color: *|c;\n}\n" +); +test!( + simple_type_universal_namespace_and_type_implicit_namespace, + "a {\n color: selector-extend(\"*|c\", \"c\", \"d\");\n}\n", + "a {\n color: *|c;\n}\n" +); +test!( + simple_type_universal_namespace_and_type_empty_namespace, + "a {\n color: selector-extend(\"*|c\", \"|c\", \"d\");\n}\n", + "a {\n color: *|c;\n}\n" +); +test!( + simple_type_universal_namespace_and_type_universal_namespace, + "a {\n color: selector-extend(\"*|c\", \"*|c\", \"d\");\n}\n", + "a {\n color: *|c, d;\n}\n" +); +test!( + complex_parent_without_grandparents_simple, + "a {\n color: selector-extend(\".c .d\", \".c\", \".e\");\n}\n", + "a {\n color: .c .d, .e .d;\n}\n" +); +test!( + complex_parent_without_grandparents_complex, + "a {\n color: selector-extend(\".c .d\", \".c\", \".e .f\");\n}\n", + "a {\n color: .c .d, .e .f .d;\n}\n" +); +test!( + complex_parent_without_grandparents_list, + "a {\n color: selector-extend(\".c .d\", \".c\", \".e, .f\");\n}\n", + "a {\n color: .c .d, .e .d, .f .d;\n}\n" +); +test!( + complex_parent_with_grandparents_simple, + "a {\n color: selector-extend(\".c .d .e\", \".d\", \".f\");\n}\n", + "a {\n color: .c .d .e, .c .f .e;\n}\n" +); +test!( + complex_parent_with_grandparents_complex, + "a {\n color: selector-extend(\".c .d .e\", \".d\", \".f .g\");\n}\n", + "a {\n color: .c .d .e, .c .f .g .e, .f .c .g .e;\n}\n" +); +test!( + complex_parent_with_grandparents_list, + "a {\n color: selector-extend(\".c .d .e\", \".d\", \".f, .g\");\n}\n", + "a {\n color: .c .d .e, .c .f .e, .c .g .e;\n}\n" +); +test!( + complex_trailing_combinator_child, + "a {\n color: selector-extend(\".c .d\", \".c\", \".e >\");\n}\n", + "a {\n color: .c .d, .e > .d;\n}\n" +); +test!( + complex_trailing_combinator_sibling, + "a {\n color: selector-extend(\".c .d\", \".c\", \".e ~\");\n}\n", + "a {\n color: .c .d, .e ~ .d;\n}\n" +); +test!( + complex_trailing_combinator_next_sibling, + "a {\n color: selector-extend(\".c .d\", \".c\", \".e +\");\n}\n", + "a {\n color: .c .d, .e + .d;\n}\n" +); From e0ab71f73e2043a89f2a9db5bcac97bee72f963e Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 7 Jun 2020 13:27:54 -0400 Subject: [PATCH 08/15] simple pseudo selector-extend tests --- src/selector/compound.rs | 4 +-- src/selector/simple.rs | 54 ++++++++++++++++++++++++++++++++--- tests/selector-extend.rs | 61 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 6 deletions(-) diff --git a/src/selector/compound.rs b/src/selector/compound.rs index 179513c..9b398d5 100644 --- a/src/selector/compound.rs +++ b/src/selector/compound.rs @@ -40,11 +40,11 @@ impl fmt::Display for CompoundSelector { impl CompoundSelector { pub fn max_specificity(&self) -> i32 { - self.specificity().min + self.specificity().max } pub fn min_specificity(&self) -> i32 { - self.specificity().max + self.specificity().min } /// Returns tuple of (min, max) specificity diff --git a/src/selector/simple.rs b/src/selector/simple.rs index 9ecd3f3..fe7ea0c 100644 --- a/src/selector/simple.rs +++ b/src/selector/simple.rs @@ -2,11 +2,13 @@ use std::fmt::{self, Write}; use super::{ Attribute, ComplexSelector, ComplexSelectorComponent, CompoundSelector, Namespace, - QualifiedName, SelectorList, + QualifiedName, SelectorList, Specificity, }; const SUBSELECTOR_PSEUDOS: [&str; 4] = ["matches", "any", "nth-child", "nth-last-child"]; +const BASE_SPECIFICITY: i32 = 1000; + #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub(crate) enum SimpleSelector { /// * @@ -84,9 +86,9 @@ impl SimpleSelector { match self { Self::Universal(..) => 0, Self::Type(..) => 1, - Self::Pseudo { .. } => todo!(), - Self::Id(..) => 1000_i32.pow(2_u32), - _ => 1000, + Self::Pseudo(pseudo) => pseudo.min_specificity(), + Self::Id(..) => BASE_SPECIFICITY.pow(2_u32), + _ => BASE_SPECIFICITY, } } @@ -97,6 +99,7 @@ impl SimpleSelector { pub fn max_specificity(&self) -> i32 { match self { Self::Universal(..) => 0, + Self::Pseudo(pseudo) => pseudo.max_specificity(), _ => self.min_specificity(), } } @@ -567,6 +570,49 @@ impl Pseudo { pub fn with_selector(self, selector: Option) -> Self { Self { selector, ..self } } + + pub fn max_specificity(&self) -> i32 { + self.specificity().max + } + + pub fn min_specificity(&self) -> i32 { + self.specificity().min + } + + pub fn specificity(&self) -> Specificity { + if !self.is_class { + return Specificity { min: 1, max: 1 }; + } + + let selector = match &self.selector { + Some(sel) => sel, + None => { + return Specificity { + min: BASE_SPECIFICITY, + max: BASE_SPECIFICITY, + } + } + }; + + if self.name == "not" { + let mut min = 0; + let mut max = 0; + for complex in &selector.components { + min = min.max(complex.min_specificity()); + max = max.max(complex.max_specificity()); + } + return Specificity { min, max }; + } else { + // This is higher than any selector's specificity can actually be. + let mut min = BASE_SPECIFICITY.pow(3_u32); + let mut max = 0; + for complex in &selector.components { + min = min.min(complex.min_specificity()); + max = max.max(complex.max_specificity()); + } + return Specificity { min, max }; + } + } } /// Returns all pseudo selectors in `compound` that have a selector argument, diff --git a/tests/selector-extend.rs b/tests/selector-extend.rs index aebd99c..8e795a9 100644 --- a/tests/selector-extend.rs +++ b/tests/selector-extend.rs @@ -128,6 +128,66 @@ test!( "a {\n color: selector-extend(\"*|c\", \"*|c\", \"d\");\n}\n", "a {\n color: *|c, d;\n}\n" ); +test!( + simple_pseudo_class_no_arg_equal, + "a {\n color: selector-extend(\":c\", \":c\", \"e\");\n}\n", + "a {\n color: :c, e;\n}\n" +); +test!( + simple_pseudo_class_no_arg_unequal, + "a {\n color: selector-extend(\":c\", \":d\", \"e\");\n}\n", + "a {\n color: :c;\n}\n" +); +test!( + simple_pseudo_class_no_arg_and_element, + "a {\n color: selector-extend(\":c\", \"::c\", \"e\");\n}\n", + "a {\n color: :c;\n}\n" +); +test!( + simple_pseudo_element_no_arg_and_element_equal, + "a {\n color: selector-extend(\"::c\", \"::c\", \"e\");\n}\n", + "a {\n color: ::c, e;\n}\n" +); +test!( + simple_pseudo_element_no_arg_and_class, + "a {\n color: selector-extend(\"::c\", \":c\", \"e\");\n}\n", + "a {\n color: ::c;\n}\n" +); +test!( + simple_pseudo_class_arg_equal, + "a {\n color: selector-extend(\":c(@#$)\", \":c(@#$)\", \"e\");\n}\n", + "a {\n color: :c(@#$), e;\n}\n" +); +test!( + simple_pseudo_class_arg_unequal_name, + "a {\n color: selector-extend(\":c(@#$)\", \":d(@#$)\", \"e\");\n}\n", + "a {\n color: :c(@#$);\n}\n" +); +test!( + simple_pseudo_class_arg_unequal_arg, + "a {\n color: selector-extend(\":c(@#$)\", \":c(*&^)\", \"e\");\n}\n", + "a {\n color: :c(@#$);\n}\n" +); +test!( + simple_pseudo_class_arg_unequal_no_arg, + "a {\n color: selector-extend(\":c(@#$)\", \":c\", \"e\");\n}\n", + "a {\n color: :c(@#$);\n}\n" +); +test!( + simple_pseudo_class_arg_and_element, + "a {\n color: selector-extend(\":c(@#$)\", \"::c(@#$)\", \"e\");\n}\n", + "a {\n color: :c(@#$);\n}\n" +); +test!( + simple_pseudo_element_arg_and_element_equal, + "a {\n color: selector-extend(\"::c(@#$)\", \"::c(@#$)\", \"e\");\n}\n", + "a {\n color: ::c(@#$), e;\n}\n" +); +test!( + simple_pseudo_element_arg_and_class, + "a {\n color: selector-extend(\"::c(@#$)\", \":c(@#$)\", \"e\");\n}\n", + "a {\n color: ::c(@#$);\n}\n" +); test!( complex_parent_without_grandparents_simple, "a {\n color: selector-extend(\".c .d\", \".c\", \".e\");\n}\n", @@ -173,3 +233,4 @@ test!( "a {\n color: selector-extend(\".c .d\", \".c\", \".e +\");\n}\n", "a {\n color: .c .d, .e + .d;\n}\n" ); +// todo: https://github.com/sass/sass-spec/tree/master/spec/core_functions/selector/extend/simple/pseudo/selector/ From e76903cb47d66b23408fdd860d8921facc7a6617 Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 7 Jun 2020 14:23:15 -0400 Subject: [PATCH 09/15] initial implementation of selector-replace --- src/builtin/selector.rs | 23 +++++++++++++++++++++- src/selector/extend/mod.rs | 12 ++++-------- tests/selector-replace.rs | 40 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 tests/selector-replace.rs diff --git a/src/builtin/selector.rs b/src/builtin/selector.rs index 88f327b..a437dbd 100644 --- a/src/builtin/selector.rs +++ b/src/builtin/selector.rs @@ -177,7 +177,28 @@ fn selector_replace( super_selector: &Selector, ) -> SassResult { args.max_args(3)?; - todo!("built-in fn selector-replace") + let selector = arg!(args, scope, super_selector, 0, "selector").to_selector( + args.span(), + scope, + super_selector, + "selector", + false, + )?; + let target = arg!(args, scope, super_selector, 1, "original").to_selector( + args.span(), + scope, + super_selector, + "original", + false, + )?; + let source = arg!(args, scope, super_selector, 2, "replacement").to_selector( + args.span(), + scope, + super_selector, + "replacement", + false, + )?; + Ok(Extender::replace(selector.0, source.0, target.0).to_sass_list()) } fn selector_unify( diff --git a/src/selector/extend/mod.rs b/src/selector/extend/mod.rs index 4fe65d4..70f7e90 100644 --- a/src/selector/extend/mod.rs +++ b/src/selector/extend/mod.rs @@ -189,9 +189,8 @@ impl Extender { if i != 0 { extended = list.components[0..i].to_vec(); } - - extended.extend(result.into_iter()); } + extended.extend(result.into_iter()); } else { if !extended.is_empty() { extended.push(complex); @@ -275,8 +274,6 @@ impl Extender { return None; } - // dbg!(&extended_not_expanded); - let mut first = true; let mut originals: Vec = Vec::new(); @@ -359,7 +356,7 @@ impl Extender { // If `self.mode` isn't `ExtendMode::Normal` and we didn't use all the targets in // `extensions`, extension fails for `compound`. - if targets_used.len() != extensions.len() { + if targets_used.len() > 0 && targets_used.len() != extensions.len() { return None; } @@ -443,7 +440,7 @@ impl Extender { to_unify.push_back(state.extender.components.clone()); } } - if originals.is_empty() { + if !originals.is_empty() { to_unify.push_front(vec![ComplexSelectorComponent::Compound( CompoundSelector { components: originals, @@ -473,8 +470,6 @@ impl Extender { }) .collect(); - dbg!(&unified_paths); - Some( unified_paths .into_iter() @@ -631,6 +626,7 @@ impl Extender { } }) .collect(); + // Older browsers support `:not`, but only with a single complex selector. // In order to support those browsers, we break up the contents of a `:not` // unless it originally contained a selector list. diff --git a/tests/selector-replace.rs b/tests/selector-replace.rs new file mode 100644 index 0000000..853f2bd --- /dev/null +++ b/tests/selector-replace.rs @@ -0,0 +1,40 @@ +#![cfg(test)] + +#[macro_use] +mod macros; + +test!( + simple, + "a {\n color: selector-replace(\"c\", \"c\", \"d\");\n}\n", + "a {\n color: d;\n}\n" +); +test!( + compound, + "a {\n color: selector-replace(\"c.d\", \"c\", \"e\");\n}\n", + "a {\n color: e.d;\n}\n" +); +test!( + complex, + "a {\n color: selector-replace(\"c d\", \"d\", \"e f\");\n}\n", + "a {\n color: c e f, e c f;\n}\n" +); +test!( + psuedo_matches, + "a {\n color: selector-replace(\":matches(c)\", \"c\", \"d\");\n}\n", + "a {\n color: :matches(d);\n}\n" +); +test!( + psuedo_not, + "a {\n color: selector-replace(\":not(c)\", \"c\", \"d\");\n}\n", + "a {\n color: :not(d);\n}\n" +); +test!( + no_op, + "a {\n color: selector-replace(\"c\", \"d\", \"e\");\n}\n", + "a {\n color: c;\n}\n" +); +test!( + partial_no_op, + "a {\n color: selector-replace(\"c, d\", \"d\", \"e\");\n}\n", + "a {\n color: c, e;\n}\n" +); From 113e0116525d2284665fbe2469f36b691c5de52c Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 7 Jun 2020 17:40:24 -0400 Subject: [PATCH 10/15] implement simple-selectors --- src/builtin/selector.rs | 32 +++++++++++++++++++++++++++++++- tests/simple-selectors.rs | 15 +++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 tests/simple-selectors.rs diff --git a/src/builtin/selector.rs b/src/builtin/selector.rs index a437dbd..923a56b 100644 --- a/src/builtin/selector.rs +++ b/src/builtin/selector.rs @@ -3,6 +3,7 @@ use super::{Builtin, GlobalFunctionMap}; use crate::args::CallArgs; +use crate::common::{Brackets, ListSeparator, QuoteKind}; use crate::error::SassResult; use crate::scope::Scope; use crate::selector::{ @@ -42,7 +43,36 @@ fn simple_selectors( super_selector: &Selector, ) -> SassResult { args.max_args(1)?; - todo!("built-in fn simple-selectors") + // todo: Value::to_compound_selector + let selector = arg!(args, scope, super_selector, 0, "selector").to_selector( + args.span(), + scope, + super_selector, + "selector", + false, + )?; + + if selector.0.components.len() != 1 { + return Err(("$selector: expected selector.", args.span()).into()); + } + + let compound = if let Some(ComplexSelectorComponent::Compound(compound)) = + selector.0.components[0].components.get(0).cloned() + { + compound + } else { + todo!() + }; + + Ok(Value::List( + compound + .components + .into_iter() + .map(|simple| Value::String(simple.to_string(), QuoteKind::None)) + .collect(), + ListSeparator::Comma, + Brackets::None, + )) } fn selector_parse( diff --git a/tests/simple-selectors.rs b/tests/simple-selectors.rs new file mode 100644 index 0000000..0c987d9 --- /dev/null +++ b/tests/simple-selectors.rs @@ -0,0 +1,15 @@ +#![cfg(test)] + +#[macro_use] +mod macros; + +test!( + two_classes, + "a {\n color: simple-selectors(\".foo.bar\");\n}\n", + "a {\n color: .foo, .bar;\n}\n" +); +test!( + three_classes, + "a {\n color: simple-selectors(\".foo.bar.baz\");\n}\n", + "a {\n color: .foo, .bar, .baz;\n}\n" +); From 457eccc59bc08ff0af369322c7cbbfa204920f0f Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 7 Jun 2020 17:49:39 -0400 Subject: [PATCH 11/15] reimplement @ at-root --- src/atrule/mod.rs | 14 +++-- src/atrule/parse.rs | 7 +-- src/selector/mod.rs | 5 -- tests/at-root.rs | 126 ++++++++++++++++++++++---------------------- 4 files changed, 71 insertions(+), 81 deletions(-) diff --git a/src/atrule/mod.rs b/src/atrule/mod.rs index fcbbeea..88cb3df 100644 --- a/src/atrule/mod.rs +++ b/src/atrule/mod.rs @@ -153,15 +153,13 @@ impl AtRule { } } AtRuleKind::AtRoot => { - let mut selector = &Selector::replace( + let mut selector = &Selector::from_tokens( + &mut read_until_open_curly_brace(toks)?.into_iter().peekmore(), + scope, super_selector, - Selector::from_tokens( - &mut read_until_open_curly_brace(toks)?.into_iter().peekmore(), - scope, - super_selector, - true, - )?, - ); + true, + )? + .resolve_parent_selectors(super_selector, false); let mut is_some = true; if selector.is_empty() { is_some = false; diff --git a/src/atrule/parse.rs b/src/atrule/parse.rs index b1bbf13..af54c93 100644 --- a/src/atrule/parse.rs +++ b/src/atrule/parse.rs @@ -81,11 +81,8 @@ pub(crate) fn eat_stmts_at_root>( ), Expr::MixinDecl(..) | Expr::FunctionDecl(..) => todo!(), Expr::Selector(mut selector) => { - if nesting > 1 || is_some { - selector = selector.resolve_parent_selectors(super_selector, true); - } else { - selector = Selector::replace(super_selector, selector); - } + selector = + selector.resolve_parent_selectors(super_selector, nesting > 1 || is_some); nesting += 1; let rules = eat_stmts_at_root(toks, scope, &selector, nesting, true, content)?; nesting -= 1; diff --git a/src/selector/mod.rs b/src/selector/mod.rs index 171dfdc..53facb4 100644 --- a/src/selector/mod.rs +++ b/src/selector/mod.rs @@ -112,11 +112,6 @@ impl Selector { )) } - #[allow(clippy::needless_pass_by_value)] - pub fn replace(super_selector: &Selector, this: Selector) -> Selector { - todo!() - } - /// Small wrapper around `SelectorList`'s method that turns an empty parent selector /// into `None`. This is a hack and in the future should be replaced. // todo: take Option for parent diff --git a/tests/at-root.rs b/tests/at-root.rs index 7e122ac..e16d4e5 100644 --- a/tests/at-root.rs +++ b/tests/at-root.rs @@ -1,65 +1,65 @@ -// #![cfg(test)] +#![cfg(test)] -// #[macro_use] -// mod macros; +#[macro_use] +mod macros; -// test!( -// simple_nested, -// ".foo {\n @at-root {\n .bar {a: b}\n }\n}\n", -// ".bar {\n a: b;\n}\n" -// ); -// test!( -// with_selector, -// ".foo {\n @at-root .bar {a: b}\n}\n", -// ".bar {\n a: b;\n}\n" -// ); -// test!( -// with_selector_in_mixin, -// "@mixin bar {\n @at-root .bar {a: b}\n}\n\n.foo {\n @include bar;\n}\n", -// ".bar {\n a: b;\n}\n" -// ); -// test!( -// with_super_selector, -// ".foo {\n @at-root & {\n a: b;\n }\n}\n", -// ".foo {\n a: b;\n}\n" -// ); -// test!( -// nested_with_super_selector, -// ".foo {\n @at-root & {\n .bar {\n @at-root & {\n a: b;\n }\n }\n }\n}\n", -// ".foo .bar {\n a: b;\n}\n" -// ); -// test!( -// deeply_nested_with_rulesets_and_styles, -// ".foo {\n @at-root .bar {\n a: b;\n c {\n d: e;\n foo {\n bar: baz;\n }\n h: j;\n }\n f: g;\n }\n}\n", -// ".bar {\n a: b;\n f: g;\n}\n.bar c {\n d: e;\n h: j;\n}\n.bar c foo {\n bar: baz;\n}\n" -// ); -// test!( -// super_selector_inside_with_nothing, -// "foo {\n @at-root {\n & {\n color: bar;\n }\n }\n}\n", -// "foo {\n color: bar;\n}\n" -// ); -// test!( -// interpolated_super_selector_with_nothing, -// "test {\n @at-root {\n #{&}post {\n foo {\n bar: baz;\n }\n }\n }\n}\n", -// "testpost foo {\n bar: baz;\n}\n" -// ); -// test!( -// with_ampersand_single, -// "test {\n @at-root {\n #{&}post {\n foo {\n bar: baz;\n }\n }\n }\n}\n", -// "testpost foo {\n bar: baz;\n}\n" -// ); -// test!( -// root_interpolated_ampersand, -// "@at-root {\n #{&}post {\n foo {\n bar: baz;\n }\n }\n}\n", -// "post foo {\n bar: baz;\n}\n" -// ); -// test!( -// nested_prefix_interpolated_ampersand, -// "test {\n @at-root {\n pre#{&} {\n foo {\n bar: baz;\n }\n }\n }\n}\n", -// "pretest foo {\n bar: baz;\n}\n" -// ); -// test!( -// nested_alone_interpolated_ampersand, -// "test {\n @at-root {\n #{&} {\n foo {\n bar: baz;\n }\n }\n }\n}\n", -// "test foo {\n bar: baz;\n}\n" -// ); +test!( + simple_nested, + ".foo {\n @at-root {\n .bar {a: b}\n }\n}\n", + ".bar {\n a: b;\n}\n" +); +test!( + with_selector, + ".foo {\n @at-root .bar {a: b}\n}\n", + ".bar {\n a: b;\n}\n" +); +test!( + with_selector_in_mixin, + "@mixin bar {\n @at-root .bar {a: b}\n}\n\n.foo {\n @include bar;\n}\n", + ".bar {\n a: b;\n}\n" +); +test!( + with_super_selector, + ".foo {\n @at-root & {\n a: b;\n }\n}\n", + ".foo {\n a: b;\n}\n" +); +test!( + nested_with_super_selector, + ".foo {\n @at-root & {\n .bar {\n @at-root & {\n a: b;\n }\n }\n }\n}\n", + ".foo .bar {\n a: b;\n}\n" +); +test!( + deeply_nested_with_rulesets_and_styles, + ".foo {\n @at-root .bar {\n a: b;\n c {\n d: e;\n foo {\n bar: baz;\n }\n h: j;\n }\n f: g;\n }\n}\n", + ".bar {\n a: b;\n f: g;\n}\n.bar c {\n d: e;\n h: j;\n}\n.bar c foo {\n bar: baz;\n}\n" +); +test!( + super_selector_inside_with_nothing, + "foo {\n @at-root {\n & {\n color: bar;\n }\n }\n}\n", + "foo {\n color: bar;\n}\n" +); +test!( + interpolated_super_selector_with_nothing, + "test {\n @at-root {\n #{&}post {\n foo {\n bar: baz;\n }\n }\n }\n}\n", + "testpost foo {\n bar: baz;\n}\n" +); +test!( + with_ampersand_single, + "test {\n @at-root {\n #{&}post {\n foo {\n bar: baz;\n }\n }\n }\n}\n", + "testpost foo {\n bar: baz;\n}\n" +); +test!( + root_interpolated_ampersand, + "@at-root {\n #{&}post {\n foo {\n bar: baz;\n }\n }\n}\n", + "post foo {\n bar: baz;\n}\n" +); +test!( + nested_prefix_interpolated_ampersand, + "test {\n @at-root {\n pre#{&} {\n foo {\n bar: baz;\n }\n }\n }\n}\n", + "pretest foo {\n bar: baz;\n}\n" +); +test!( + nested_alone_interpolated_ampersand, + "test {\n @at-root {\n #{&} {\n foo {\n bar: baz;\n }\n }\n }\n}\n", + "test foo {\n bar: baz;\n}\n" +); From b609e7a6ab05bbdc05801cf6d19aec047cc3fcf3 Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 7 Jun 2020 18:20:51 -0400 Subject: [PATCH 12/15] return true for is_superselector of subselector pseudos --- src/selector/simple.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/selector/simple.rs b/src/selector/simple.rs index fe7ea0c..027d67d 100644 --- a/src/selector/simple.rs +++ b/src/selector/simple.rs @@ -349,7 +349,7 @@ impl SimpleSelector { }) = their_simple { if SUBSELECTOR_PSEUDOS.contains(&normalized_name.as_str()) { - sel.components.iter().all(|complex| { + return sel.components.iter().all(|complex| { if complex.components.len() != 1 { return false; }; From 5dceb279505eda652b185cb1d64d6d4f2f93e2de Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 7 Jun 2020 21:34:34 -0400 Subject: [PATCH 13/15] test special case :current in is-superselector --- src/selector/simple.rs | 2 +- tests/is-superselector.rs | 85 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/selector/simple.rs b/src/selector/simple.rs index 027d67d..2daac6e 100644 --- a/src/selector/simple.rs +++ b/src/selector/simple.rs @@ -541,7 +541,7 @@ impl Pseudo { } }) }), - "current" => selector_pseudos_named(compound.clone(), &self.name, false) + "current" => selector_pseudos_named(compound.clone(), &self.name, self.is_class) .iter() .any(|pseudo2| self.selector == pseudo2.selector), "nth-child" | "nth-last-child" => compound.components.iter().any(|pseudo2| { diff --git a/tests/is-superselector.rs b/tests/is-superselector.rs index 86f3723..195c719 100644 --- a/tests/is-superselector.rs +++ b/tests/is-superselector.rs @@ -226,3 +226,88 @@ test!( "a {\n color: is-superselector(\"::c\", \":c\");\n}\n", "a {\n color: false;\n}\n" ); +test!( + simple_pseudo_arg_class_equal, + "a {\n color: is-superselector(\":c(@#$)\", \":c(@#$)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + simple_pseudo_arg_class_different_name, + "a {\n color: is-superselector(\":c(@#$)\", \":d(@#$)\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_pseudo_arg_class_different_arg, + "a {\n color: is-superselector(\":c(@#$)\", \":d(*&^)\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_pseudo_arg_class_different_no_arg, + "a {\n color: is-superselector(\":c(@#$)\", \":c\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_pseudo_arg_class_and_element, + "a {\n color: is-superselector(\":c(@#$)\", \"::c(@#$)\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_pseudo_arg_element_and_element_equal, + "a {\n color: is-superselector(\"::c(@#$)\", \"::c(@#$)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + simple_pseudo_arg_element_and_element_different_name, + "a {\n color: is-superselector(\"::c(@#$)\", \"::d(@#$)\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_pseudo_arg_element_and_element_different_arg, + "a {\n color: is-superselector(\"::c(@#$)\", \"::c(*&^)\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_pseudo_arg_element_and_element_different_no_arg, + "a {\n color: is-superselector(\"::c(@#$)\", \"::c\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + simple_pseudo_arg_element_and_class, + "a {\n color: is-superselector(\"::c(@#$)\", \":c(@#$)\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + psuedo_current_superset, + "a {\n color: is-superselector(\":current(c d, e f, g h)\", \":current(c d.i, e j f)\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + psuedo_current_subset, + "a {\n color: is-superselector(\":current(c d.i, e j f)\", \":current(c d, e f, g h)\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + psuedo_current_equal, + "a {\n color: is-superselector(\":current(c d, e f)\", \":current(c d, e f)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + psuedo_current_bare_sub, + "a {\n color: is-superselector(\":current(c d, e f)\", \"c d, e f\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + psuedo_current_prefix_superset, + "a {\n color: is-superselector(\":-pfx-current(c d, e f, g h)\", \":-pfx-current(c d.i, e j f)\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + psuedo_current_prefix_subset, + "a {\n color: is-superselector(\":-pfx-current(c d.i, e j f)\", \":-pfx-current(c d, e f, g h)\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + psuedo_current_prefix_equal, + "a {\n color: is-superselector(\":-pfx-current(c d, e f)\", \":-pfx-current(c d, e f)\");\n}\n", + "a {\n color: true;\n}\n" +); From a48d2b97ce15084383e5ca36acaaeace95b5a635 Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 7 Jun 2020 22:08:23 -0400 Subject: [PATCH 14/15] more psuedo tests for is-superselector --- tests/is-superselector.rs | 163 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/tests/is-superselector.rs b/tests/is-superselector.rs index 195c719..9d03d37 100644 --- a/tests/is-superselector.rs +++ b/tests/is-superselector.rs @@ -276,6 +276,26 @@ test!( "a {\n color: is-superselector(\"::c(@#$)\", \":c(@#$)\");\n}\n", "a {\n color: false;\n}\n" ); +test!( + psuedo_any_superset, + "a {\n color: is-superselector(\":any(c d, e f, g h)\", \":any(c d.i, e j f)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + psuedo_any_subset, + "a {\n color: is-superselector(\":any(c d.i, e j f)\", \":any(c d, e f, g h)\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + psuedo_any_prefix_superset, + "a {\n color: is-superselector(\":-pfx-any(c d, e f, g h)\", \":-pfx-any(c d.i, e j f)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + psuedo_any_prefix_subset, + "a {\n color: is-superselector(\":-pfx-any(c d.i, e j f)\", \":-pfx-any(c d, e f, g h)\");\n}\n", + "a {\n color: false;\n}\n" +); test!( psuedo_current_superset, "a {\n color: is-superselector(\":current(c d, e f, g h)\", \":current(c d.i, e j f)\");\n}\n", @@ -311,3 +331,146 @@ test!( "a {\n color: is-superselector(\":-pfx-current(c d, e f)\", \":-pfx-current(c d, e f)\");\n}\n", "a {\n color: true;\n}\n" ); +test!( + psuedo_has_superset, + "a {\n color: is-superselector(\":has(c d, e f, g h)\", \":has(c d.i, e j f)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + psuedo_has_subset, + "a {\n color: is-superselector(\":has(c d.i, e j f)\", \":has(c d, e f, g h)\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + psuedo_has_equal, + "a {\n color: is-superselector(\":has(c d, e f)\", \":has(c d, e f)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + psuedo_has_bare_sub, + "a {\n color: is-superselector(\":has(c d, e f)\", \"c d, e f\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + psuedo_has_prefix_superset, + "a {\n color: is-superselector(\":-pfx-has(c d, e f, g h)\", \":-pfx-has(c d.i, e j f)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + psuedo_has_prefix_subset, + "a {\n color: is-superselector(\":-pfx-has(c d.i, e j f)\", \":-pfx-has(c d, e f, g h)\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + psuedo_has_prefix_equal, + "a {\n color: is-superselector(\":-pfx-has(c d, e f)\", \":-pfx-has(c d, e f)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + psuedo_host_superset, + "a {\n color: is-superselector(\":host(c d, e f, g h)\", \":host(c d.i, e j f)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + psuedo_host_subset, + "a {\n color: is-superselector(\":host(c d.i, e j f)\", \":host(c d, e f, g h)\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + psuedo_host_equal, + "a {\n color: is-superselector(\":host(c d, e f)\", \":host(c d, e f)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + psuedo_host_bare_sub, + "a {\n color: is-superselector(\":host(c d, e f)\", \"c d, e f\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + psuedo_host_prefix_superset, + "a {\n color: is-superselector(\":-pfx-host(c d, e f, g h)\", \":-pfx-host(c d.i, e j f)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + psuedo_host_prefix_subset, + "a {\n color: is-superselector(\":-pfx-host(c d.i, e j f)\", \":-pfx-host(c d, e f, g h)\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + psuedo_host_prefix_equal, + "a {\n color: is-superselector(\":-pfx-host(c d, e f)\", \":-pfx-host(c d, e f)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + psuedo_host_context_superset, + "a {\n color: is-superselector(\":host-context(c d, e f, g h)\", \":host-context(c d.i, e j f)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + psuedo_host_context_subset, + "a {\n color: is-superselector(\":host-context(c d.i, e j f)\", \":host-context(c d, e f, g h)\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + psuedo_host_context_equal, + "a {\n color: is-superselector(\":host-context(c d, e f)\", \":host-context(c d, e f)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + psuedo_host_context_bare_sub, + "a {\n color: is-superselector(\":host-context(c d, e f)\", \"c d, e f\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + psuedo_host_context_prefix_superset, + "a {\n color: is-superselector(\":-pfx-host-context(c d, e f, g h)\", \":-pfx-host-context(c d.i, e j f)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + psuedo_host_context_prefix_subset, + "a {\n color: is-superselector(\":-pfx-host-context(c d.i, e j f)\", \":-pfx-host-context(c d, e f, g h)\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + psuedo_host_context_prefix_equal, + "a {\n color: is-superselector(\":-pfx-host-context(c d, e f)\", \":-pfx-host-context(c d, e f)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + psuedo_slotted_superset, + "a {\n color: is-superselector(\"::slotted(c d, e f, g h)\", \"::slotted(c d.i, e j f)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + psuedo_slotted_subset, + "a {\n color: is-superselector(\"::slotted(c d.i, e j f)\", \"::slotted(c d, e f, g h)\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + psuedo_slotted_equal, + "a {\n color: is-superselector(\"::slotted(c d, e f)\", \"::slotted(c d, e f)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + psuedo_slotted_bare_sub, + "a {\n color: is-superselector(\"::slotted(c d, e f)\", \"c d, e f\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + psuedo_slotted_prefix_superset, + "a {\n color: is-superselector(\"::-pfx-slotted(c d, e f, g h)\", \"::-pfx-slotted(c d.i, e j f)\");\n}\n", + "a {\n color: true;\n}\n" +); +test!( + psuedo_slotted_prefix_subset, + "a {\n color: is-superselector(\"::-pfx-slotted(c d.i, e j f)\", \"::-pfx-slotted(c d, e f, g h)\");\n}\n", + "a {\n color: false;\n}\n" +); +test!( + psuedo_slotted_prefix_equal, + "a {\n color: is-superselector(\"::-pfx-slotted(c d, e f)\", \"::-pfx-slotted(c d, e f)\");\n}\n", + "a {\n color: true;\n}\n" +); + +// todo: /spec/core_functions/selector/is_superselector/simple/pseudo/selector_arg/ +// :not, :matches, :nth-child, :nth-last-child From b135b87a69a4b14fe01faf5017e78e9134325263 Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 7 Jun 2020 23:11:43 -0400 Subject: [PATCH 15/15] resolve clippy lints --- src/builtin/selector.rs | 8 +- src/lib.rs | 3 + src/selector/compound.rs | 2 +- src/selector/extend/extension.rs | 2 +- src/selector/extend/functions.rs | 4 +- src/selector/extend/mod.rs | 155 +++++++++++++------------------ src/selector/mod.rs | 2 - src/selector/parse.rs | 6 +- src/selector/simple.rs | 5 +- 9 files changed, 81 insertions(+), 106 deletions(-) diff --git a/src/builtin/selector.rs b/src/builtin/selector.rs index 923a56b..6f99801 100644 --- a/src/builtin/selector.rs +++ b/src/builtin/selector.rs @@ -1,5 +1,3 @@ -#![allow(unused_variables, unused_mut)] - use super::{Builtin, GlobalFunctionMap}; use crate::args::CallArgs; @@ -107,11 +105,7 @@ fn selector_nest(args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa .into_value()) } -fn selector_append( - mut args: CallArgs, - scope: &Scope, - super_selector: &Selector, -) -> SassResult { +fn selector_append(args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult { let span = args.span(); let selectors = args.get_variadic(scope, super_selector)?; if selectors.is_empty() { diff --git a/src/lib.rs b/src/lib.rs index 82105d2..92aa6c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,6 +59,9 @@ grass input.scss clippy::filter_map, clippy::else_if_without_else, clippy::new_ret_no_self, + renamed_and_removed_lints, + clippy::unknown_clippy_lints, + clippy::replace_consts, // temporarily allowed while under heavy development. // eventually these allows should be refactored away diff --git a/src/selector/compound.rs b/src/selector/compound.rs index 9b398d5..c3f3612 100644 --- a/src/selector/compound.rs +++ b/src/selector/compound.rs @@ -214,7 +214,7 @@ impl CompoundSelector { Self { components } } - simple => { + _ => { let mut components = vec![SimpleSelector::Parent(None)]; components.append(&mut self.components); Self { components } diff --git a/src/selector/extend/extension.rs b/src/selector/extend/extension.rs index 095a145..6560da3 100644 --- a/src/selector/extend/extension.rs +++ b/src/selector/extend/extension.rs @@ -35,7 +35,7 @@ pub(super) struct Extension { impl Extension { pub fn one_off(extender: ComplexSelector, specificity: Option, is_original: bool) -> Self { Self { - specificity: specificity.unwrap_or(extender.max_specificity()), + specificity: specificity.unwrap_or_else(|| extender.max_specificity()), extender, target: None, span: None, diff --git a/src/selector/extend/functions.rs b/src/selector/extend/functions.rs index 64a5d3c..5527dc5 100644 --- a/src/selector/extend/functions.rs +++ b/src/selector/extend/functions.rs @@ -729,11 +729,11 @@ fn complex_is_parent_superselector( /// /// For example, given `[[1, 2], [3, 4], [5]]`, this returns: /// -/// ```norun +/// ```no_run /// [[1, 3, 5], /// [2, 3, 5], /// [1, 4, 5], -/// [2, 4, 5]] +/// [2, 4, 5]]; /// ``` pub(crate) fn paths(choices: Vec>) -> Vec> { choices.into_iter().fold(vec![vec![]], |paths, choice| { diff --git a/src/selector/extend/mod.rs b/src/selector/extend/mod.rs index 70f7e90..b2afa00 100644 --- a/src/selector/extend/mod.rs +++ b/src/selector/extend/mod.rs @@ -38,7 +38,13 @@ enum ExtendMode { AllTargets, } -#[derive(Clone, Debug, Eq, PartialEq)] +impl Default for ExtendMode { + fn default() -> Self { + Self::Normal + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Default)] pub(crate) struct Extender { /// A map from all simple selectors in the stylesheet to the selector lists /// that contain them. @@ -88,6 +94,7 @@ pub(crate) struct Extender { impl Extender { /// An `Extender` that contains no extensions and can have no extensions added. // TODO: empty extender + #[allow(dead_code)] const EMPTY: () = (); pub fn extend( @@ -112,7 +119,7 @@ impl Extender { targets: SelectorList, mode: ExtendMode, ) -> SelectorList { - let mut extenders: IndexMap = source + let extenders: IndexMap = source .components .clone() .into_iter() @@ -149,7 +156,7 @@ impl Extender { .extend(selector.components.clone().into_iter()); } - selector = extender.extend_list(selector, extensions, None); + selector = extender.extend_list(selector, &extensions, &None); } selector @@ -158,12 +165,7 @@ impl Extender { fn with_mode(mode: ExtendMode) -> Self { Self { mode, - selectors: Default::default(), - extensions: Default::default(), - extensions_by_extender: Default::default(), - media_contexts: Default::default(), - source_specificity: Default::default(), - originals: Default::default(), + ..Extender::default() } } @@ -171,8 +173,8 @@ impl Extender { fn extend_list( &mut self, list: SelectorList, - extensions: HashMap>, - media_query_context: Option>, + extensions: &HashMap>, + media_query_context: &Option>, ) -> SelectorList { // This could be written more simply using Vec>, but we want to avoid // any allocations in the common case where no extends apply. @@ -180,21 +182,15 @@ impl Extender { for i in 0..list.components.len() { let complex = list.components.get(i).unwrap().clone(); - if let Some(result) = self.extend_complex( - complex.clone(), - extensions.clone(), - media_query_context.clone(), - ) { - if extended.is_empty() { - if i != 0 { - extended = list.components[0..i].to_vec(); - } + if let Some(result) = + self.extend_complex(complex.clone(), extensions, media_query_context) + { + if extended.is_empty() && i != 0 { + extended = list.components[0..i].to_vec(); } extended.extend(result.into_iter()); - } else { - if !extended.is_empty() { - extended.push(complex); - } + } else if !extended.is_empty() { + extended.push(complex); } } @@ -212,8 +208,8 @@ impl Extender { fn extend_complex( &mut self, complex: ComplexSelector, - extensions: HashMap>, - media_query_context: Option>, + extensions: &HashMap>, + media_query_context: &Option>, ) -> Option> { // The complex selectors that each compound selector in `complex.components` // can expand to. @@ -236,16 +232,11 @@ impl Extender { let complex_has_line_break = complex.line_break; - let is_original = self.originals.contains(&complex); - for i in 0..complex.components.len() { if let Some(ComplexSelectorComponent::Compound(component)) = complex.components.get(i) { - if let Some(extended) = self.extend_compound( - component.clone(), - extensions.clone(), - media_query_context.clone(), - is_original, - ) { + if let Some(extended) = + self.extend_compound(component, extensions, media_query_context) + { if extended_not_expanded.is_empty() { extended_not_expanded = complex .components @@ -311,15 +302,11 @@ impl Extender { /// Extends `compound` using `extensions`, and returns the contents of a /// `SelectorList`. - /// - /// The `in_original` parameter indicates whether this is in an original - /// complex selector, meaning that `compound` should not be trimmed out. fn extend_compound( &mut self, - compound: CompoundSelector, - extensions: HashMap>, - media_query_context: Option>, - in_original: bool, + compound: &CompoundSelector, + extensions: &HashMap>, + media_query_context: &Option>, ) -> Option> { // If there's more than one target and they all need to match, we track // which targets are actually extended. @@ -332,16 +319,14 @@ impl Extender { if let Some(extended) = self.extend_simple( simple.clone(), - extensions.clone(), - media_query_context.clone(), + extensions, + media_query_context, &mut targets_used, ) { - if options.is_empty() { - if i != 0 { - options.push(vec![self.extension_for_compound( - compound.components.clone().into_iter().take(i).collect(), - )]); - } + if options.is_empty() && i != 0 { + options.push(vec![self.extension_for_compound( + compound.components.clone().into_iter().take(i).collect(), + )]); } options.extend(extended.into_iter()); @@ -356,7 +341,7 @@ impl Extender { // If `self.mode` isn't `ExtendMode::Normal` and we didn't use all the targets in // `extensions`, extension fails for `compound`. - if targets_used.len() > 0 && targets_used.len() != extensions.len() { + if !targets_used.is_empty() && targets_used.len() != extensions.len() { return None; } @@ -369,7 +354,7 @@ impl Extender { .clone() .into_iter() .map(|state| { - state.assert_compatible_media_context(&media_query_context); + state.assert_compatible_media_context(media_query_context); state.extender }) .collect(), @@ -405,15 +390,13 @@ impl Extender { let unified_paths: Vec>> = paths(options) .into_iter() .map(|path| { - let complexes: Vec>; - - if first { + let complexes: Vec> = if first { // The first path is always the original selector. We can't just // return `compound` directly because pseudo selectors may be // modified, but we don't have to do any unification. first = false; - complexes = vec![vec![ComplexSelectorComponent::Compound(CompoundSelector { + vec![vec![ComplexSelectorComponent::Compound(CompoundSelector { components: path .clone() .into_iter() @@ -425,7 +408,7 @@ impl Extender { } }) .collect(), - })]]; + })]] } else { let mut to_unify: VecDeque> = VecDeque::new(); let mut originals: Vec = Vec::new(); @@ -448,13 +431,13 @@ impl Extender { )]); } - complexes = unify_complex(Vec::from(to_unify))?; - } + unify_complex(Vec::from(to_unify))? + }; let mut line_break = false; for state in path { - state.assert_compatible_media_context(&media_query_context); + state.assert_compatible_media_context(media_query_context); line_break = line_break || state.extender.line_break; } @@ -482,8 +465,8 @@ impl Extender { fn extend_simple( &mut self, simple: SimpleSelector, - extensions: HashMap>, - media_query_context: Option>, + extensions: &HashMap>, + media_query_context: &Option>, targets_used: &mut HashSet, ) -> Option>> { if let SimpleSelector::Pseudo( @@ -492,16 +475,14 @@ impl Extender { }, ) = simple.clone() { - if let Some(extended) = - self.extend_pseudo(simple, extensions.clone(), media_query_context) - { + if let Some(extended) = self.extend_pseudo(simple, extensions, media_query_context) { return Some( extended .into_iter() .map(move |pseudo| { self.without_pseudo( SimpleSelector::Pseudo(pseudo.clone()), - extensions.clone(), + extensions, targets_used, self.mode, ) @@ -523,14 +504,11 @@ impl Extender { fn extend_pseudo( &mut self, pseudo: Pseudo, - extensions: HashMap>, - media_query_context: Option>, + extensions: &HashMap>, + media_query_context: &Option>, ) -> Option> { let extended = self.extend_list( - pseudo - .selector - .clone() - .unwrap_or_else(|| SelectorList::new()), + pseudo.selector.clone().unwrap_or_else(SelectorList::new), extensions, media_query_context, ); @@ -544,8 +522,7 @@ impl Extender { // writing. We can keep them if either the original selector had a complex // selector, or the result of extending has only complex selectors, because // either way we aren't breaking anything that isn't already broken. - let mut complexes = extended.components.clone(); - if pseudo.normalized_name == "not" + let mut complexes = if pseudo.normalized_name == "not" && !pseudo .selector .clone() @@ -558,13 +535,14 @@ impl Extender { .iter() .any(|complex| complex.components.len() == 1) { - complexes = extended + extended .components - .clone() .into_iter() .filter(|complex| complex.components.len() <= 1) - .collect(); - } + .collect() + } else { + extended.components + }; complexes = complexes .into_iter() @@ -598,10 +576,10 @@ impl Extender { // become `.foo:not(.bar)`. However, this is a narrow edge case and // supporting it properly would make this code and the code calling it // a lot more complicated, so it's not supported for now. - if inner_pseudo.normalized_name != "matches" { - Vec::new() - } else { + if inner_pseudo.normalized_name == "matches" { inner_pseudo.selector.clone().unwrap().components + } else { + Vec::new() } } "matches" | "any" | "current" | "nth-child" | "nth-last-child" => { @@ -657,7 +635,7 @@ impl Extender { fn without_pseudo( &self, simple: SimpleSelector, - extensions: HashMap>, + extensions: &HashMap>, targets_used: &mut HashSet, mode: ExtendMode, ) -> Option> { @@ -782,17 +760,19 @@ impl Extender { // ensures that we aren't comparing against a selector that's already been // trimmed, and thus that if there are two identical selectors only one is // trimmed. - if result.iter().any(|complex2| { + let should_continue = result.iter().any(|complex2| { complex2.min_specificity() >= max_specificity && complex2.is_super_selector(complex1) - }) { + }); + if should_continue { continue; } - if selectors.iter().take(i).any(|complex2| { + let should_continue = selectors.iter().take(i).any(|complex2| { complex2.min_specificity() >= max_specificity && complex2.is_super_selector(complex1) - }) { + }); + if should_continue { continue; } @@ -800,12 +780,11 @@ impl Extender { } if should_break_to_outer { continue; - } else { - break; } + break; } - return Vec::from(result); + Vec::from(result) } } diff --git a/src/selector/mod.rs b/src/selector/mod.rs index 53facb4..94becaa 100644 --- a/src/selector/mod.rs +++ b/src/selector/mod.rs @@ -1,5 +1,3 @@ -#![allow(dead_code, unused_variables, unused_mut)] - use std::fmt::{self, Display}; use peekmore::{PeekMore, PeekMoreIterator}; diff --git a/src/selector/parse.rs b/src/selector/parse.rs index e2ff283..e0bd091 100644 --- a/src/selector/parse.rs +++ b/src/selector/parse.rs @@ -335,7 +335,7 @@ impl<'a, I: Iterator> SelectorParser<'a, I> { devour_whitespace(self.toks); self.expect_closing_paren()?; } else { - argument = Some(self.declaration_value(true)?); + argument = Some(self.declaration_value()?); } } else if SELECTOR_PSEUDO_CLASSES.contains(&unvendored) { selector = Some(self.parse_selector_list()?); @@ -358,7 +358,7 @@ impl<'a, I: Iterator> SelectorParser<'a, I> { self.expect_closing_paren()?; argument = Some(this_arg); } else { - argument = Some(self.declaration_value(true)?.trim_end().to_string()); + argument = Some(self.declaration_value()?.trim_end().to_string()); } Ok(SimpleSelector::Pseudo(Pseudo { @@ -533,7 +533,7 @@ impl<'a, I: Iterator> SelectorParser<'a, I> { Ok(buf) } - fn declaration_value(&mut self, allow_empty: bool) -> SassResult { + fn declaration_value(&mut self) -> SassResult { // todo: this consumes the closing paren let mut tmp = read_until_closing_paren(self.toks)?; if let Some(Token { kind: ')', .. }) = tmp.pop() { diff --git a/src/selector/simple.rs b/src/selector/simple.rs index 2daac6e..1430de2 100644 --- a/src/selector/simple.rs +++ b/src/selector/simple.rs @@ -567,6 +567,7 @@ impl Pseudo { } } + #[allow(clippy::missing_const_for_fn)] pub fn with_selector(self, selector: Option) -> Self { Self { selector, ..self } } @@ -601,7 +602,7 @@ impl Pseudo { min = min.max(complex.min_specificity()); max = max.max(complex.max_specificity()); } - return Specificity { min, max }; + Specificity { min, max } } else { // This is higher than any selector's specificity can actually be. let mut min = BASE_SPECIFICITY.pow(3_u32); @@ -610,7 +611,7 @@ impl Pseudo { min = min.min(complex.min_specificity()); max = max.max(complex.max_specificity()); } - return Specificity { min, max }; + Specificity { min, max } } } }