initial implementation of selector-* builtin fns
This commit is contained in:
parent
c2f4014a1a
commit
f3a58e0fa3
@ -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)]
|
||||
|
@ -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(),
|
||||
|
@ -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;
|
||||
|
@ -33,7 +33,7 @@ pub(crate) fn eat_stmts<I: Iterator<Item = Token>>(
|
||||
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<I: Iterator<Item = Token>>(
|
||||
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);
|
||||
}
|
||||
|
@ -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<Value> {
|
||||
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<Value> {
|
||||
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
|
||||
let lists = args
|
||||
.get_variadic(scope, super_selector)?
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
Ok(match x.node.eval(span)?.node {
|
||||
Value::List(v, ..) => v,
|
||||
Value::Map(m) => m.entries(),
|
||||
v => vec![v],
|
||||
})
|
||||
})
|
||||
.map(|x| Ok(x.node.eval(span)?.node.as_list()))
|
||||
.collect::<SassResult<Vec<Vec<Value>>>>()?;
|
||||
|
||||
let len = lists.iter().map(Vec::len).min().unwrap_or(0);
|
||||
|
@ -50,6 +50,7 @@ pub(crate) static GLOBAL_FUNCTIONS: Lazy<GlobalFunctionMap> = Lazy::new(|| {
|
||||
map::declare(&mut m);
|
||||
math::declare(&mut m);
|
||||
meta::declare(&mut m);
|
||||
selector::declare(&mut m);
|
||||
string::declare(&mut m);
|
||||
m
|
||||
});
|
||||
|
@ -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<Value> {
|
||||
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<Value> {
|
||||
args.max_args(1)?;
|
||||
todo!("built-in fn simple-selectors")
|
||||
}
|
||||
|
||||
fn selector_parse(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
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<Value> {
|
||||
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::<SassResult<Vec<Selector>>>()?
|
||||
.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<Value> {
|
||||
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::<SassResult<Vec<Selector>>>()?;
|
||||
|
||||
let first = parsed_selectors.remove(0);
|
||||
Ok(parsed_selectors
|
||||
.into_iter()
|
||||
.try_fold(first, |parent, child| -> SassResult<Selector> {
|
||||
Ok(Selector(SelectorList {
|
||||
components: child
|
||||
.0
|
||||
.components
|
||||
.into_iter()
|
||||
.map(|complex| -> SassResult<ComplexSelector> {
|
||||
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::<SassResult<Vec<ComplexSelector>>>()?,
|
||||
})
|
||||
.resolve_parent_selectors(&parent, false))
|
||||
})?
|
||||
.into_value())
|
||||
}
|
||||
|
||||
fn selector_extend(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
todo!("built-in fn selector-extend")
|
||||
}
|
||||
|
||||
fn selector_replace(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
todo!("built-in fn selector-replace")
|
||||
}
|
||||
|
||||
fn selector_unify(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
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));
|
||||
}
|
||||
|
@ -105,21 +105,6 @@ impl ListSeparator {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub(crate) struct QualifiedName {
|
||||
pub ident: String,
|
||||
pub namespace: Option<String>,
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
@ -278,6 +278,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
|
||||
&mut values.into_iter().peekmore(),
|
||||
scope,
|
||||
super_selector,
|
||||
true,
|
||||
)?),
|
||||
span,
|
||||
}));
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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<I: Iterator<Item = Token>>(
|
||||
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<I: Iterator<Item = Token>>(
|
||||
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<I: Iterator<Item = Token>>(
|
||||
}
|
||||
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<I: Iterator<Item = Token>>(
|
||||
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<SelectorKind> {
|
||||
) -> SassResult<Attribute> {
|
||||
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,
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
55
src/selector/common.rs
Normal file
55
src/selector/common.rs
Normal file
@ -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
|
||||
}
|
||||
}
|
279
src/selector/complex.rs
Normal file
279
src/selector/complex.rs
Normal file
@ -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<ComplexSelectorComponent>,
|
||||
|
||||
/// 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<Vec<ComplexSelector>> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
219
src/selector/compound.rs
Normal file
219
src/selector/compound.rs
Normal file
@ -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<SimpleSelector>,
|
||||
}
|
||||
|
||||
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<Vec<ComplexSelectorComponent>>,
|
||||
) -> 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<Vec<ComplexSelector>> {
|
||||
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<SimpleSelector> = 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<Self> {
|
||||
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<Self> {
|
||||
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 }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
775
src/selector/extend/mod.rs
Normal file
775
src/selector/extend/mod.rs
Normal file
@ -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<Vec<ComplexSelectorComponent>>,
|
||||
) -> Option<Vec<Vec<ComplexSelectorComponent>>> {
|
||||
debug_assert!(!complexes.is_empty());
|
||||
|
||||
if complexes.len() == 1 {
|
||||
return Some(complexes);
|
||||
}
|
||||
|
||||
let mut unified_base: Option<Vec<SimpleSelector>> = 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<Vec<ComplexSelectorComponent>> = 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<ComplexSelectorComponent>>) -> Vec<Vec<ComplexSelectorComponent>> {
|
||||
let mut prefixes: Vec<Vec<ComplexSelectorComponent>> = 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<ComplexSelectorComponent> =
|
||||
complex.into_iter().take(complex_len - 1).collect();
|
||||
let mut new_prefixes: Vec<Vec<ComplexSelectorComponent>> = 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<ComplexSelectorComponent>,
|
||||
parents2: Vec<ComplexSelectorComponent>,
|
||||
) -> Option<Vec<Vec<ComplexSelectorComponent>>> {
|
||||
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::<Vec<ComplexSelectorComponent>>()]];
|
||||
|
||||
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<ComplexSelectorComponent>,
|
||||
components2: &mut VecDeque<ComplexSelectorComponent>,
|
||||
) -> Option<Vec<Combinator>> {
|
||||
let mut combinators1: Vec<Combinator> = 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<T: PartialEq + Clone + std::fmt::Debug>(
|
||||
list1: Vec<T>,
|
||||
list2: Vec<T>,
|
||||
select: Option<&dyn Fn(T, T) -> Option<T>>,
|
||||
) -> Vec<T> {
|
||||
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<Option<T>>> = 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<T: Clone>(
|
||||
i: isize,
|
||||
j: isize,
|
||||
lengths: Vec<Vec<i32>>,
|
||||
selections: &mut Vec<Vec<Option<T>>>,
|
||||
) -> Vec<T> {
|
||||
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<ComplexSelectorComponent>,
|
||||
components2: &mut VecDeque<ComplexSelectorComponent>,
|
||||
result: Option<VecDeque<Vec<Vec<ComplexSelectorComponent>>>>,
|
||||
) -> Option<Vec<Vec<Vec<ComplexSelectorComponent>>>> {
|
||||
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<ComplexSelectorComponent>) -> Option<CompoundSelector> {
|
||||
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<ComplexSelectorComponent>,
|
||||
) -> VecDeque<Vec<ComplexSelectorComponent>> {
|
||||
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<T: Clone>(
|
||||
queue1: &mut VecDeque<T>,
|
||||
queue2: &mut VecDeque<T>,
|
||||
done: impl Fn(&VecDeque<T>) -> bool,
|
||||
) -> Vec<Vec<T>> {
|
||||
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<ComplexSelectorComponent>,
|
||||
mut complex2: Vec<ComplexSelectorComponent>,
|
||||
) -> 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<T: Clone + std::fmt::Debug>(choices: Vec<Vec<T>>) -> Vec<Vec<T>> {
|
||||
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<ComplexSelectorComponent>,
|
||||
complex2: Vec<ComplexSelectorComponent>,
|
||||
) -> 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, .. }))
|
||||
}
|
261
src/selector/list.rs
Normal file
261
src/selector/list.rs
Normal file
@ -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<ComplexSelector>,
|
||||
}
|
||||
|
||||
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<Self> {
|
||||
let contents: Vec<ComplexSelector> = self
|
||||
.components
|
||||
.into_iter()
|
||||
.flat_map(|c1| {
|
||||
other.clone().components.into_iter().flat_map(move |c2| {
|
||||
let unified: Option<Vec<Vec<ComplexSelectorComponent>>> =
|
||||
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<Self> (the issue is figuring out the span)
|
||||
pub fn resolve_parent_selectors(self, parent: Option<Self>, 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<ComplexSelectorComponent>> =
|
||||
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<A: std::fmt::Debug>(iterable: Vec<Vec<A>>) -> Vec<A> {
|
||||
let mut queues: Vec<VecDeque<A>> = 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
|
||||
}
|
@ -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<SelectorPart>);
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
struct SelectorPart {
|
||||
pub inner: Vec<SelectorKind>,
|
||||
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<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
allows_parent: bool,
|
||||
) -> SassResult<Selector> {
|
||||
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<Token> = 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<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
span_before: Span,
|
||||
) -> SassResult<SelectorKind> {
|
||||
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<SelectorKind> = 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<Self> 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<Self> {
|
||||
Some(Selector(self.0.unify(other.0)?))
|
||||
}
|
||||
}
|
||||
|
583
src/selector/parse.rs
Normal file
583
src/selector/parse.rs
Normal file
@ -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<Item = Token>> {
|
||||
/// 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<I>,
|
||||
|
||||
scope: &'a Scope,
|
||||
|
||||
super_selector: &'a Selector,
|
||||
|
||||
span: Span,
|
||||
}
|
||||
|
||||
impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
pub fn new(
|
||||
toks: &'a mut PeekMoreIterator<I>,
|
||||
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<SelectorList> {
|
||||
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<SelectorList> {
|
||||
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<ComplexSelector> {
|
||||
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<CompoundSelector> {
|
||||
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<SimpleSelector> {
|
||||
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<SimpleSelector> {
|
||||
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<SimpleSelector> {
|
||||
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<SimpleSelector> {
|
||||
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<SimpleSelector> {
|
||||
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<String> = None;
|
||||
let mut selector: Option<SelectorList> = 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<SimpleSelector> {
|
||||
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<SimpleSelector> {
|
||||
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<SimpleSelector> {
|
||||
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<String> {
|
||||
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<String> {
|
||||
// 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::<String>())
|
||||
}
|
||||
}
|
||||
|
||||
/// 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!()
|
||||
}
|
582
src/selector/simple.rs
Normal file
582
src/selector/simple.rs
Normal file
@ -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<String>),
|
||||
|
||||
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<Self>) -> Option<Vec<Self>> {
|
||||
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<Self>) -> Option<Vec<Self>> {
|
||||
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<SimpleSelector> = 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<Self>) -> Option<Vec<Self>> {
|
||||
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<Self> {
|
||||
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<Self>) -> Option<Vec<Self>> {
|
||||
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<Self>) -> Option<Vec<Self>> {
|
||||
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<String>,
|
||||
|
||||
/// 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<SelectorList>,
|
||||
}
|
||||
|
||||
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<Vec<ComplexSelectorComponent>>,
|
||||
) -> 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<Item = Pseudo>`
|
||||
fn selector_pseudos_named(compound: CompoundSelector, name: &str, is_class: bool) -> Vec<Pseudo> {
|
||||
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()
|
||||
}
|
@ -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(),
|
||||
|
@ -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<Value> {
|
||||
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<Selector> {
|
||||
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<Option<String>> {
|
||||
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),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
126
tests/at-root.rs
126
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"
|
||||
// );
|
||||
|
@ -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."
|
||||
|
75
tests/is-superselector.rs
Normal file
75
tests/is-superselector.rs
Normal file
@ -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"
|
||||
);
|
78
tests/selector-append.rs
Normal file
78
tests/selector-append.rs
Normal file
@ -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."
|
||||
);
|
158
tests/selector-nest.rs
Normal file
158
tests/selector-nest.rs
Normal file
@ -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."
|
||||
);
|
100
tests/selector-parse.rs
Normal file
100
tests/selector-parse.rs
Normal file
@ -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"
|
||||
);
|
602
tests/selector-unify.rs
Normal file
602
tests/selector-unify.rs
Normal file
@ -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."
|
||||
);
|
@ -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.");
|
||||
|
Loading…
x
Reference in New Issue
Block a user