initial implementation of selector-extend
This commit is contained in:
parent
8390fd8354
commit
d71e996e2b
@ -58,6 +58,7 @@ beef = "0.4.4"
|
|||||||
# criterion is not a dev-dependency because it makes tests take too
|
# criterion is not a dev-dependency because it makes tests take too
|
||||||
# long to compile, and you cannot make dev-dependencies optional
|
# long to compile, and you cannot make dev-dependencies optional
|
||||||
criterion = { version = "0.3.2", optional = true }
|
criterion = { version = "0.3.2", optional = true }
|
||||||
|
indexmap = "1.4.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["commandline", "random"]
|
default = ["commandline", "random"]
|
||||||
|
@ -5,7 +5,9 @@ use super::{Builtin, GlobalFunctionMap};
|
|||||||
use crate::args::CallArgs;
|
use crate::args::CallArgs;
|
||||||
use crate::error::SassResult;
|
use crate::error::SassResult;
|
||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
use crate::selector::{ComplexSelector, ComplexSelectorComponent, Selector, SelectorList};
|
use crate::selector::{
|
||||||
|
ComplexSelector, ComplexSelectorComponent, Extender, Selector, SelectorList,
|
||||||
|
};
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
|
|
||||||
fn is_superselector(
|
fn is_superselector(
|
||||||
@ -144,7 +146,29 @@ fn selector_extend(
|
|||||||
super_selector: &Selector,
|
super_selector: &Selector,
|
||||||
) -> SassResult<Value> {
|
) -> SassResult<Value> {
|
||||||
args.max_args(3)?;
|
args.max_args(3)?;
|
||||||
todo!("built-in fn selector-extend")
|
let selector = arg!(args, scope, super_selector, 0, "selector").to_selector(
|
||||||
|
args.span(),
|
||||||
|
scope,
|
||||||
|
super_selector,
|
||||||
|
"selector",
|
||||||
|
false,
|
||||||
|
)?;
|
||||||
|
let target = arg!(args, scope, super_selector, 1, "extendee").to_selector(
|
||||||
|
args.span(),
|
||||||
|
scope,
|
||||||
|
super_selector,
|
||||||
|
"extendee",
|
||||||
|
false,
|
||||||
|
)?;
|
||||||
|
let source = arg!(args, scope, super_selector, 2, "extender").to_selector(
|
||||||
|
args.span(),
|
||||||
|
scope,
|
||||||
|
super_selector,
|
||||||
|
"extender",
|
||||||
|
false,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Extender::extend(selector.0, source.0, target.0).to_sass_list())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selector_replace(
|
fn selector_replace(
|
||||||
|
@ -12,7 +12,7 @@ use crate::utils::{devour_whitespace, eat_ident, is_ident, parse_quoted_string};
|
|||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
use crate::Token;
|
use crate::Token;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub(crate) struct Attribute {
|
pub(crate) struct Attribute {
|
||||||
attr: QualifiedName,
|
attr: QualifiedName,
|
||||||
value: String,
|
value: String,
|
||||||
@ -201,7 +201,7 @@ impl Display for Attribute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
enum AttributeOp {
|
enum AttributeOp {
|
||||||
/// \[attr\]
|
/// \[attr\]
|
||||||
///
|
///
|
||||||
|
@ -6,7 +6,7 @@ use std::fmt::{self, Display};
|
|||||||
/// it's `Empty`, this matches all elements that aren't in any
|
/// it's `Empty`, this matches all elements that aren't in any
|
||||||
/// namespace. If it's `Asterisk`, this matches all elements in any namespace.
|
/// namespace. If it's `Asterisk`, this matches all elements in any namespace.
|
||||||
/// Otherwise, it matches all elements in the given namespace.
|
/// Otherwise, it matches all elements in the given namespace.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub(crate) enum Namespace {
|
pub(crate) enum Namespace {
|
||||||
Empty,
|
Empty,
|
||||||
Asterisk,
|
Asterisk,
|
||||||
@ -25,7 +25,7 @@ impl Display for Namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub(crate) struct QualifiedName {
|
pub(crate) struct QualifiedName {
|
||||||
pub ident: String,
|
pub ident: String,
|
||||||
pub namespace: Namespace,
|
pub namespace: Namespace,
|
||||||
@ -38,18 +38,13 @@ impl Display for QualifiedName {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Specificity(u32, u32);
|
pub(crate) struct Specificity {
|
||||||
|
pub min: i32,
|
||||||
|
pub max: i32,
|
||||||
|
}
|
||||||
|
|
||||||
impl Specificity {
|
impl Specificity {
|
||||||
pub const fn new(min: u32, max: u32) -> Self {
|
pub const fn new(min: i32, max: i32) -> Self {
|
||||||
Specificity(min, max)
|
Specificity { min, max }
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn min(&self) -> u32 {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn max(&self) -> u32 {
|
|
||||||
self.1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use super::{CompoundSelector, Pseudo, SelectorList, SimpleSelector};
|
use super::{CompoundSelector, Pseudo, SelectorList, SimpleSelector, Specificity};
|
||||||
use std::fmt::{self, Display, Write};
|
use std::fmt::{self, Display, Write};
|
||||||
|
|
||||||
/// A complex selector.
|
/// A complex selector.
|
||||||
///
|
///
|
||||||
/// A complex selector is composed of `CompoundSelector`s separated by
|
/// A complex selector is composed of `CompoundSelector`s separated by
|
||||||
/// `Combinator`s. It selects elements based on their parent selectors.
|
/// `Combinator`s. It selects elements based on their parent selectors.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub(crate) struct ComplexSelector {
|
pub(crate) struct ComplexSelector {
|
||||||
/// The components of this selector.
|
/// The components of this selector.
|
||||||
///
|
///
|
||||||
@ -48,16 +48,25 @@ fn omit_spaces_around(component: &ComplexSelectorComponent) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ComplexSelector {
|
impl ComplexSelector {
|
||||||
// pub fn specificity(&self) -> Specificity {
|
pub fn max_specificity(&self) -> i32 {
|
||||||
// let mut min = 0;
|
self.specificity().min
|
||||||
// let mut max = 0;
|
}
|
||||||
// for component in self.components.iter() {
|
|
||||||
// todo!()
|
pub fn min_specificity(&self) -> i32 {
|
||||||
// // min += simple.min_specificity();
|
self.specificity().max
|
||||||
// // max += simple.max_specificity();
|
}
|
||||||
// }
|
|
||||||
// Specificity::new(min, max)
|
pub fn specificity(&self) -> Specificity {
|
||||||
// }
|
let mut min = 0;
|
||||||
|
let mut max = 0;
|
||||||
|
for component in &self.components {
|
||||||
|
if let ComplexSelectorComponent::Compound(compound) = component {
|
||||||
|
min += compound.min_specificity();
|
||||||
|
max += compound.max_specificity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Specificity::new(min, max)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_invisible(&self) -> bool {
|
pub fn is_invisible(&self) -> bool {
|
||||||
self.components
|
self.components
|
||||||
@ -201,7 +210,7 @@ impl ComplexSelector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Copy)]
|
#[derive(Clone, Debug, Eq, PartialEq, Copy, Hash)]
|
||||||
pub(crate) enum Combinator {
|
pub(crate) enum Combinator {
|
||||||
/// Matches the right-hand selector if it's immediately adjacent to the
|
/// Matches the right-hand selector if it's immediately adjacent to the
|
||||||
/// left-hand selector in the DOM tree.
|
/// left-hand selector in the DOM tree.
|
||||||
@ -232,7 +241,7 @@ impl Display for Combinator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub(crate) enum ComplexSelectorComponent {
|
pub(crate) enum ComplexSelectorComponent {
|
||||||
Combinator(Combinator),
|
Combinator(Combinator),
|
||||||
Compound(CompoundSelector),
|
Compound(CompoundSelector),
|
||||||
|
@ -7,7 +7,7 @@ use super::{
|
|||||||
|
|
||||||
/// A compound selector is composed of several
|
/// A compound selector is composed of several
|
||||||
/// simple selectors
|
/// simple selectors
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub(crate) struct CompoundSelector {
|
pub(crate) struct CompoundSelector {
|
||||||
pub components: Vec<SimpleSelector>,
|
pub components: Vec<SimpleSelector>,
|
||||||
}
|
}
|
||||||
@ -39,14 +39,21 @@ impl fmt::Display for CompoundSelector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CompoundSelector {
|
impl CompoundSelector {
|
||||||
|
pub fn max_specificity(&self) -> i32 {
|
||||||
|
self.specificity().min
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn min_specificity(&self) -> i32 {
|
||||||
|
self.specificity().max
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns tuple of (min, max) specificity
|
/// Returns tuple of (min, max) specificity
|
||||||
pub fn specificity(&self) -> Specificity {
|
pub fn specificity(&self) -> Specificity {
|
||||||
let mut min = 0;
|
let mut min = 0;
|
||||||
let mut max = 0;
|
let mut max = 0;
|
||||||
for simple in &self.components {
|
for simple in &self.components {
|
||||||
todo!()
|
min += simple.min_specificity();
|
||||||
// min += simple.min_specificity;
|
max += simple.max_specificity();
|
||||||
// max += simple.max_specificity;
|
|
||||||
}
|
}
|
||||||
Specificity::new(min, max)
|
Specificity::new(min, max)
|
||||||
}
|
}
|
||||||
|
59
src/selector/extend/extension.rs
Normal file
59
src/selector/extend/extension.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
use codemap::Span;
|
||||||
|
|
||||||
|
use super::{ComplexSelector, CssMediaQuery, SimpleSelector};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
pub(super) struct Extension {
|
||||||
|
/// The selector in which the `@extend` appeared.
|
||||||
|
pub extender: ComplexSelector,
|
||||||
|
|
||||||
|
/// The selector that's being extended.
|
||||||
|
///
|
||||||
|
/// `None` for one-off extensions.
|
||||||
|
pub target: Option<SimpleSelector>,
|
||||||
|
|
||||||
|
/// The minimum specificity required for any selector generated from this
|
||||||
|
/// extender.
|
||||||
|
pub specificity: i32,
|
||||||
|
|
||||||
|
/// Whether this extension is optional.
|
||||||
|
pub is_optional: bool,
|
||||||
|
|
||||||
|
/// Whether this is a one-off extender representing a selector that was
|
||||||
|
/// originally in the document, rather than one defined with `@extend`.
|
||||||
|
pub is_original: bool,
|
||||||
|
|
||||||
|
/// The media query context to which this extend is restricted, or `None` if
|
||||||
|
/// it can apply within any context.
|
||||||
|
// todo: Option
|
||||||
|
pub media_context: Vec<CssMediaQuery>,
|
||||||
|
|
||||||
|
/// The span in which `extender` was defined.
|
||||||
|
pub span: Option<Span>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Extension {
|
||||||
|
pub fn one_off(extender: ComplexSelector, specificity: Option<i32>, is_original: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
specificity: specificity.unwrap_or(extender.max_specificity()),
|
||||||
|
extender,
|
||||||
|
target: None,
|
||||||
|
span: None,
|
||||||
|
is_optional: true,
|
||||||
|
is_original,
|
||||||
|
media_context: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that the `media_context` for a selector is compatible with the
|
||||||
|
/// query context for this extender.
|
||||||
|
pub fn assert_compatible_media_context(&self, media_context: &Option<Vec<CssMediaQuery>>) {
|
||||||
|
if let Some(media_context) = media_context {
|
||||||
|
if &self.media_context == media_context {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo!("throw SassException(\"You may not @extend selectors across media queries.\", span);")
|
||||||
|
}
|
||||||
|
}
|
788
src/selector/extend/functions.rs
Normal file
788
src/selector/extend/functions.rs
Normal file
@ -0,0 +1,788 @@
|
|||||||
|
#![allow(clippy::similar_names)]
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
use super::super::{
|
||||||
|
Combinator, ComplexSelector, ComplexSelectorComponent, CompoundSelector, Pseudo, SimpleSelector,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Returns the contents of a `SelectorList` that matches only elements that are
|
||||||
|
/// matched by both `complex_one` and `complex_two`.
|
||||||
|
///
|
||||||
|
/// If no such list can be produced, returns `None`.
|
||||||
|
pub(crate) fn unify_complex(
|
||||||
|
complexes: Vec<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]]`.
|
||||||
|
pub(crate) 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 &mut prefixes {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prefixes = new_prefixes;
|
||||||
|
}
|
||||||
|
|
||||||
|
prefixes
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interweaves `parents_one` and `parents_two` as parents of the same target selector.
|
||||||
|
///
|
||||||
|
/// Returns all possible orderings of the selectors in the inputs (including
|
||||||
|
/// using unification) that maintain the relative ordering of the input. For
|
||||||
|
/// example, given `.foo .bar` and `.baz .bang`, this would return `.foo .bar
|
||||||
|
/// .baz .bang`, `.foo .bar.baz .bang`, `.foo .baz .bar .bang`, `.foo .baz
|
||||||
|
/// .bar.bang`, `.foo .baz .bang .bar`, and so on until `.baz .bang .foo .bar`.
|
||||||
|
///
|
||||||
|
/// Semantically, for selectors A and B, this returns all selectors `AB_i`
|
||||||
|
/// such that the union over all i of elements matched by `AB_i X` is
|
||||||
|
/// identical to the intersection of all elements matched by `A X` and all
|
||||||
|
/// elements matched by `B X`. Some `AB_i` are elided to reduce the size of
|
||||||
|
/// the output.
|
||||||
|
fn weave_parents(
|
||||||
|
parents_one: Vec<ComplexSelectorComponent>,
|
||||||
|
parents_two: Vec<ComplexSelectorComponent>,
|
||||||
|
) -> Option<Vec<Vec<ComplexSelectorComponent>>> {
|
||||||
|
let mut queue_one = VecDeque::from(parents_one);
|
||||||
|
let mut queue_two = VecDeque::from(parents_two);
|
||||||
|
|
||||||
|
let initial_combinators = merge_initial_combinators(&mut queue_one, &mut queue_two)?;
|
||||||
|
|
||||||
|
let mut final_combinators = merge_final_combinators(&mut queue_one, &mut queue_two, None)?;
|
||||||
|
|
||||||
|
match (first_if_root(&mut queue_one), first_if_root(&mut queue_two)) {
|
||||||
|
(Some(root_one), Some(root_two)) => {
|
||||||
|
let root = ComplexSelectorComponent::Compound(root_one.unify(root_two)?);
|
||||||
|
queue_one.push_front(root.clone());
|
||||||
|
queue_two.push_front(root);
|
||||||
|
}
|
||||||
|
(Some(root_one), None) => {
|
||||||
|
queue_two.push_front(ComplexSelectorComponent::Compound(root_one));
|
||||||
|
}
|
||||||
|
(None, Some(root_two)) => {
|
||||||
|
queue_one.push_front(ComplexSelectorComponent::Compound(root_two));
|
||||||
|
}
|
||||||
|
(None, None) => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut groups_one = group_selectors(Vec::from(queue_one));
|
||||||
|
let mut groups_two = group_selectors(Vec::from(queue_two));
|
||||||
|
|
||||||
|
let lcs = longest_common_subsequence(
|
||||||
|
groups_two.as_slices().0,
|
||||||
|
groups_one.as_slices().0,
|
||||||
|
Some(&|group_one, group_two| {
|
||||||
|
if group_one == group_two {
|
||||||
|
return Some(group_one);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let ComplexSelectorComponent::Combinator(..) = group_one.first()? {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if let ComplexSelectorComponent::Combinator(..) = group_two.first()? {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if complex_is_parent_superselector(group_one.clone(), group_two.clone()) {
|
||||||
|
return Some(group_two);
|
||||||
|
}
|
||||||
|
if complex_is_parent_superselector(group_two.clone(), group_one.clone()) {
|
||||||
|
return Some(group_one);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !must_unify(&group_one, &group_two) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let unified = unify_complex(vec![group_one, group_two])?;
|
||||||
|
if unified.len() > 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
unified.first().cloned()
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut choices = vec![vec![initial_combinators
|
||||||
|
.into_iter()
|
||||||
|
.map(ComplexSelectorComponent::Combinator)
|
||||||
|
.collect::<Vec<ComplexSelectorComponent>>()]];
|
||||||
|
|
||||||
|
for group in lcs {
|
||||||
|
choices.push(
|
||||||
|
chunks(&mut groups_one, &mut groups_two, |sequence| {
|
||||||
|
complex_is_parent_superselector(sequence.get(0).unwrap().clone(), group.clone())
|
||||||
|
})
|
||||||
|
.into_iter()
|
||||||
|
.map(|chunk| chunk.into_iter().flatten().collect())
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
choices.push(vec![group]);
|
||||||
|
groups_one.pop_front();
|
||||||
|
groups_two.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
choices.push(
|
||||||
|
chunks(&mut groups_one, &mut groups_two, VecDeque::is_empty)
|
||||||
|
.into_iter()
|
||||||
|
.map(|chunk| chunk.into_iter().flatten().collect())
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
|
||||||
|
choices.append(&mut final_combinators);
|
||||||
|
|
||||||
|
Some(
|
||||||
|
paths(
|
||||||
|
choices
|
||||||
|
.into_iter()
|
||||||
|
.filter(|choice| !choice.is_empty())
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.into_iter()
|
||||||
|
.map(|chunk| chunk.into_iter().flatten().collect())
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts leading `Combinator`s from `components_one` and `components_two` and
|
||||||
|
/// merges them together into a single list of combinators.
|
||||||
|
///
|
||||||
|
/// If there are no combinators to be merged, returns an empty list. If the
|
||||||
|
/// combinators can't be merged, returns `None`.
|
||||||
|
fn merge_initial_combinators(
|
||||||
|
components_one: &mut VecDeque<ComplexSelectorComponent>,
|
||||||
|
components_two: &mut VecDeque<ComplexSelectorComponent>,
|
||||||
|
) -> Option<Vec<Combinator>> {
|
||||||
|
let mut combinators_one: Vec<Combinator> = Vec::new();
|
||||||
|
|
||||||
|
while let Some(ComplexSelectorComponent::Combinator(c)) = components_one.get(0) {
|
||||||
|
combinators_one.push(*c);
|
||||||
|
components_one.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut combinators_two = Vec::new();
|
||||||
|
|
||||||
|
while let Some(ComplexSelectorComponent::Combinator(c)) = components_two.get(0) {
|
||||||
|
combinators_two.push(*c);
|
||||||
|
components_two.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
let lcs = longest_common_subsequence(&combinators_one, &combinators_two, None);
|
||||||
|
|
||||||
|
if lcs == combinators_one {
|
||||||
|
Some(combinators_two)
|
||||||
|
} else if lcs == combinators_two {
|
||||||
|
Some(combinators_one)
|
||||||
|
} else {
|
||||||
|
// If neither sequence of combinators is a subsequence of the other, they
|
||||||
|
// cannot be merged successfully.
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the longest common subsequence between `list_one` and `list_two`.
|
||||||
|
///
|
||||||
|
/// If there are more than one equally long common subsequence, returns the one
|
||||||
|
/// which starts first in `list_one`.
|
||||||
|
///
|
||||||
|
/// If `select` is passed, it's used to check equality between elements in each
|
||||||
|
/// list. If it returns `None`, the elements are considered unequal; otherwise,
|
||||||
|
/// it should return the element to include in the return value.
|
||||||
|
#[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
|
||||||
|
fn longest_common_subsequence<T: PartialEq + Clone>(
|
||||||
|
list_one: &[T],
|
||||||
|
list_two: &[T],
|
||||||
|
select: Option<&dyn Fn(T, T) -> Option<T>>,
|
||||||
|
) -> Vec<T> {
|
||||||
|
let select = select.unwrap_or(&|element_one, element_two| {
|
||||||
|
if element_one == element_two {
|
||||||
|
Some(element_one)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut lengths = vec![vec![0; list_two.len() + 1]; list_one.len() + 1];
|
||||||
|
|
||||||
|
let mut selections: Vec<Vec<Option<T>>> = vec![vec![None; list_two.len()]; list_one.len()];
|
||||||
|
|
||||||
|
for i in 0..list_one.len() {
|
||||||
|
for j in 0..list_two.len() {
|
||||||
|
let selection = select(
|
||||||
|
list_one.get(i).unwrap().clone(),
|
||||||
|
list_two.get(j).unwrap().clone(),
|
||||||
|
);
|
||||||
|
selections[i][j] = selection.clone();
|
||||||
|
lengths[i + 1][j + 1] = if selection.is_none() {
|
||||||
|
std::cmp::max(lengths[i + 1][j], lengths[i][j + 1])
|
||||||
|
} else {
|
||||||
|
lengths[i][j] + 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn backtrack<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(
|
||||||
|
(list_one.len() as isize).saturating_sub(1),
|
||||||
|
(list_two.len() as isize).saturating_sub(1),
|
||||||
|
lengths,
|
||||||
|
&mut selections,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts trailing `Combinator`s, and the selectors to which they apply, from
|
||||||
|
/// `components_one` and `components_two` and merges them together into a single list.
|
||||||
|
///
|
||||||
|
/// If there are no combinators to be merged, returns an empty list. If the
|
||||||
|
/// sequences can't be merged, returns `None`.
|
||||||
|
#[allow(clippy::cognitive_complexity)]
|
||||||
|
fn merge_final_combinators(
|
||||||
|
components_one: &mut VecDeque<ComplexSelectorComponent>,
|
||||||
|
components_two: &mut VecDeque<ComplexSelectorComponent>,
|
||||||
|
result: Option<VecDeque<Vec<Vec<ComplexSelectorComponent>>>>,
|
||||||
|
) -> Option<Vec<Vec<Vec<ComplexSelectorComponent>>>> {
|
||||||
|
let mut result = result.unwrap_or_default();
|
||||||
|
|
||||||
|
if (components_one.is_empty()
|
||||||
|
|| !components_one
|
||||||
|
.get(components_one.len() - 1)
|
||||||
|
.unwrap()
|
||||||
|
.is_combinator())
|
||||||
|
&& (components_two.is_empty()
|
||||||
|
|| !components_two
|
||||||
|
.get(components_two.len() - 1)
|
||||||
|
.unwrap()
|
||||||
|
.is_combinator())
|
||||||
|
{
|
||||||
|
return Some(Vec::from(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut combinators_one = Vec::new();
|
||||||
|
|
||||||
|
while let Some(ComplexSelectorComponent::Combinator(combinator)) =
|
||||||
|
components_one.get(components_one.len().saturating_sub(1))
|
||||||
|
{
|
||||||
|
combinators_one.push(*combinator);
|
||||||
|
components_one.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut combinators_two = Vec::new();
|
||||||
|
|
||||||
|
while let Some(ComplexSelectorComponent::Combinator(combinator)) =
|
||||||
|
components_two.get(components_two.len().saturating_sub(1))
|
||||||
|
{
|
||||||
|
combinators_two.push(*combinator);
|
||||||
|
components_two.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
if combinators_one.len() > 1 || combinators_two.len() > 1 {
|
||||||
|
// If there are multiple combinators, something hacky's going on. If one
|
||||||
|
// is a supersequence of the other, use that, otherwise give up.
|
||||||
|
let lcs = longest_common_subsequence(&combinators_one, &combinators_two, None);
|
||||||
|
if lcs == combinators_one {
|
||||||
|
result.push_front(vec![combinators_two
|
||||||
|
.into_iter()
|
||||||
|
.map(ComplexSelectorComponent::Combinator)
|
||||||
|
.rev()
|
||||||
|
.collect()]);
|
||||||
|
} else if lcs == combinators_two {
|
||||||
|
result.push_front(vec![combinators_one
|
||||||
|
.into_iter()
|
||||||
|
.map(ComplexSelectorComponent::Combinator)
|
||||||
|
.rev()
|
||||||
|
.collect()]);
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(Vec::from(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
let combinator_one = if combinators_one.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
combinators_one.first()
|
||||||
|
};
|
||||||
|
|
||||||
|
let combinator_two = if combinators_two.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
combinators_two.first()
|
||||||
|
};
|
||||||
|
|
||||||
|
// This code looks complicated, but it's actually just a bunch of special
|
||||||
|
// cases for interactions between different combinators.
|
||||||
|
match (combinator_one, combinator_two) {
|
||||||
|
(Some(combinator_one), Some(combinator_two)) => {
|
||||||
|
let compound_one = match components_one.pop_back() {
|
||||||
|
Some(ComplexSelectorComponent::Compound(c)) => c,
|
||||||
|
Some(..) | None => unreachable!(),
|
||||||
|
};
|
||||||
|
let compound_two = match components_two.pop_back() {
|
||||||
|
Some(ComplexSelectorComponent::Compound(c)) => c,
|
||||||
|
Some(..) | None => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
match (combinator_one, combinator_two) {
|
||||||
|
(Combinator::FollowingSibling, Combinator::FollowingSibling) => {
|
||||||
|
if compound_one.is_super_selector(&compound_two, &None) {
|
||||||
|
result.push_front(vec![vec![
|
||||||
|
ComplexSelectorComponent::Compound(compound_two),
|
||||||
|
ComplexSelectorComponent::Combinator(Combinator::FollowingSibling),
|
||||||
|
]])
|
||||||
|
} else if compound_two.is_super_selector(&compound_one, &None) {
|
||||||
|
result.push_front(vec![vec![
|
||||||
|
ComplexSelectorComponent::Compound(compound_one),
|
||||||
|
ComplexSelectorComponent::Combinator(Combinator::FollowingSibling),
|
||||||
|
]])
|
||||||
|
} else {
|
||||||
|
let mut choices = vec![
|
||||||
|
vec![
|
||||||
|
ComplexSelectorComponent::Compound(compound_one.clone()),
|
||||||
|
ComplexSelectorComponent::Combinator(Combinator::FollowingSibling),
|
||||||
|
ComplexSelectorComponent::Compound(compound_two.clone()),
|
||||||
|
ComplexSelectorComponent::Combinator(Combinator::FollowingSibling),
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
ComplexSelectorComponent::Compound(compound_two.clone()),
|
||||||
|
ComplexSelectorComponent::Combinator(Combinator::FollowingSibling),
|
||||||
|
ComplexSelectorComponent::Compound(compound_one.clone()),
|
||||||
|
ComplexSelectorComponent::Combinator(Combinator::FollowingSibling),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
if let Some(unified) = compound_one.unify(compound_two) {
|
||||||
|
choices.push(vec![
|
||||||
|
ComplexSelectorComponent::Compound(unified),
|
||||||
|
ComplexSelectorComponent::Combinator(Combinator::FollowingSibling),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push_front(choices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Combinator::FollowingSibling, Combinator::NextSibling)
|
||||||
|
| (Combinator::NextSibling, Combinator::FollowingSibling) => {
|
||||||
|
let following_sibling_selector =
|
||||||
|
if combinator_one == &Combinator::FollowingSibling {
|
||||||
|
compound_one.clone()
|
||||||
|
} else {
|
||||||
|
compound_two.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let next_sibling_selector = if combinator_one == &Combinator::FollowingSibling {
|
||||||
|
compound_two.clone()
|
||||||
|
} else {
|
||||||
|
compound_one.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
if following_sibling_selector.is_super_selector(&next_sibling_selector, &None) {
|
||||||
|
result.push_front(vec![vec![
|
||||||
|
ComplexSelectorComponent::Compound(next_sibling_selector),
|
||||||
|
ComplexSelectorComponent::Combinator(Combinator::NextSibling),
|
||||||
|
]]);
|
||||||
|
} else {
|
||||||
|
let mut v = vec![vec![
|
||||||
|
ComplexSelectorComponent::Compound(following_sibling_selector),
|
||||||
|
ComplexSelectorComponent::Combinator(Combinator::FollowingSibling),
|
||||||
|
ComplexSelectorComponent::Compound(next_sibling_selector),
|
||||||
|
ComplexSelectorComponent::Combinator(Combinator::NextSibling),
|
||||||
|
]];
|
||||||
|
|
||||||
|
if let Some(unified) = compound_one.unify(compound_two) {
|
||||||
|
v.push(vec![
|
||||||
|
ComplexSelectorComponent::Compound(unified),
|
||||||
|
ComplexSelectorComponent::Combinator(Combinator::NextSibling),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
result.push_front(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Combinator::Child, Combinator::NextSibling)
|
||||||
|
| (Combinator::Child, Combinator::FollowingSibling) => {
|
||||||
|
result.push_front(vec![vec![
|
||||||
|
ComplexSelectorComponent::Compound(compound_two),
|
||||||
|
ComplexSelectorComponent::Combinator(*combinator_two),
|
||||||
|
]]);
|
||||||
|
components_one.push_back(ComplexSelectorComponent::Compound(compound_one));
|
||||||
|
components_one
|
||||||
|
.push_back(ComplexSelectorComponent::Combinator(Combinator::Child));
|
||||||
|
}
|
||||||
|
(Combinator::NextSibling, Combinator::Child)
|
||||||
|
| (Combinator::FollowingSibling, Combinator::Child) => {
|
||||||
|
result.push_front(vec![vec![
|
||||||
|
ComplexSelectorComponent::Compound(compound_one),
|
||||||
|
ComplexSelectorComponent::Combinator(*combinator_one),
|
||||||
|
]]);
|
||||||
|
components_two.push_back(ComplexSelectorComponent::Compound(compound_two));
|
||||||
|
components_two
|
||||||
|
.push_back(ComplexSelectorComponent::Combinator(Combinator::Child));
|
||||||
|
}
|
||||||
|
(..) => {
|
||||||
|
if combinator_one != combinator_two {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let unified = compound_one.unify(compound_two)?;
|
||||||
|
|
||||||
|
result.push_front(vec![vec![
|
||||||
|
ComplexSelectorComponent::Compound(unified),
|
||||||
|
ComplexSelectorComponent::Combinator(*combinator_one),
|
||||||
|
]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
merge_final_combinators(components_one, components_two, Some(result))
|
||||||
|
}
|
||||||
|
(Some(combinator_one), None) => {
|
||||||
|
if *combinator_one == Combinator::Child && !components_two.is_empty() {
|
||||||
|
if let Some(ComplexSelectorComponent::Compound(c1)) =
|
||||||
|
components_one.get(components_one.len() - 1)
|
||||||
|
{
|
||||||
|
if let Some(ComplexSelectorComponent::Compound(c2)) =
|
||||||
|
components_two.get(components_two.len() - 1)
|
||||||
|
{
|
||||||
|
if c2.is_super_selector(c1, &None) {
|
||||||
|
components_two.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push_front(vec![vec![
|
||||||
|
components_one.pop_back().unwrap(),
|
||||||
|
ComplexSelectorComponent::Combinator(*combinator_one),
|
||||||
|
]]);
|
||||||
|
|
||||||
|
merge_final_combinators(components_one, components_two, Some(result))
|
||||||
|
}
|
||||||
|
(None, Some(combinator_two)) => {
|
||||||
|
if *combinator_two == Combinator::Child && !components_one.is_empty() {
|
||||||
|
if let Some(ComplexSelectorComponent::Compound(c1)) =
|
||||||
|
components_one.get(components_one.len() - 1)
|
||||||
|
{
|
||||||
|
if let Some(ComplexSelectorComponent::Compound(c2)) =
|
||||||
|
components_two.get(components_two.len() - 1)
|
||||||
|
{
|
||||||
|
if c1.is_super_selector(c2, &None) {
|
||||||
|
components_one.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push_front(vec![vec![
|
||||||
|
components_two.pop_back().unwrap(),
|
||||||
|
ComplexSelectorComponent::Combinator(*combinator_two),
|
||||||
|
]]);
|
||||||
|
merge_final_combinators(components_one, components_two, Some(result))
|
||||||
|
}
|
||||||
|
(None, None) => todo!("the above, but we dont have access to combinator_two"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the first element of `queue` has a `::root` selector, removes and returns
|
||||||
|
/// that element.
|
||||||
|
fn first_if_root(queue: &mut VecDeque<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();
|
||||||
|
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());
|
||||||
|
|
||||||
|
for c in iter {
|
||||||
|
if group
|
||||||
|
.last()
|
||||||
|
.map_or(false, ComplexSelectorComponent::is_combinator)
|
||||||
|
|| c.is_combinator()
|
||||||
|
{
|
||||||
|
group.push(c);
|
||||||
|
} else {
|
||||||
|
group = vec![c];
|
||||||
|
groups.push_back(group.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
groups
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns all orderings of initial subseqeuences of `queue_one` and `queue_two`.
|
||||||
|
///
|
||||||
|
/// The `done` callback is used to determine the extent of the initial
|
||||||
|
/// subsequences. It's called with each queue until it returns `true`.
|
||||||
|
///
|
||||||
|
/// This destructively removes the initial subsequences of `queue_one` and
|
||||||
|
/// `queue_two`.
|
||||||
|
///
|
||||||
|
/// For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|` denoting
|
||||||
|
/// the boundary of the initial subsequence), this would return `[(A B C 1 2),
|
||||||
|
/// (1 2 A B C)]`. The queues would then contain `(D E)` and `(3 4 5)`.
|
||||||
|
fn chunks<T: Clone>(
|
||||||
|
queue_one: &mut VecDeque<T>,
|
||||||
|
queue_two: &mut VecDeque<T>,
|
||||||
|
done: impl Fn(&VecDeque<T>) -> bool,
|
||||||
|
) -> Vec<Vec<T>> {
|
||||||
|
let mut chunk_one = Vec::new();
|
||||||
|
while !done(queue_one) {
|
||||||
|
chunk_one.push(queue_one.pop_front().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut chunk_two = Vec::new();
|
||||||
|
while !done(queue_two) {
|
||||||
|
chunk_two.push(queue_two.pop_front().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
match (chunk_one.is_empty(), chunk_two.is_empty()) {
|
||||||
|
(true, true) => Vec::new(),
|
||||||
|
(true, false) => vec![chunk_two],
|
||||||
|
(false, true) => vec![chunk_one],
|
||||||
|
(false, false) => {
|
||||||
|
let mut l1 = chunk_one.clone();
|
||||||
|
l1.append(&mut chunk_two.clone());
|
||||||
|
|
||||||
|
let mut l2 = chunk_two;
|
||||||
|
l2.append(&mut chunk_one);
|
||||||
|
|
||||||
|
vec![l1, l2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like `complex_is_superselector`, but compares `complex_one` and `complex_two` as
|
||||||
|
/// though they shared an implicit base `SimpleSelector`.
|
||||||
|
///
|
||||||
|
/// For example, `B` is not normally a superselector of `B A`, since it doesn't
|
||||||
|
/// match elements that match `A`. However, it *is* a parent superselector,
|
||||||
|
/// since `B X` is a superselector of `B A X`.
|
||||||
|
fn complex_is_parent_superselector(
|
||||||
|
mut complex_one: Vec<ComplexSelectorComponent>,
|
||||||
|
mut complex_two: Vec<ComplexSelectorComponent>,
|
||||||
|
) -> bool {
|
||||||
|
if let Some(ComplexSelectorComponent::Combinator(..)) = complex_one.first() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if let Some(ComplexSelectorComponent::Combinator(..)) = complex_two.first() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if complex_one.len() > complex_two.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let base = CompoundSelector {
|
||||||
|
components: vec![SimpleSelector::Placeholder(String::new())],
|
||||||
|
};
|
||||||
|
complex_one.push(ComplexSelectorComponent::Compound(base.clone()));
|
||||||
|
complex_two.push(ComplexSelectorComponent::Compound(base));
|
||||||
|
|
||||||
|
ComplexSelector {
|
||||||
|
components: complex_one,
|
||||||
|
line_break: false,
|
||||||
|
}
|
||||||
|
.is_super_selector(&ComplexSelector {
|
||||||
|
components: complex_two,
|
||||||
|
line_break: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a list of all possible paths through the given lists.
|
||||||
|
///
|
||||||
|
/// For example, given `[[1, 2], [3, 4], [5]]`, this returns:
|
||||||
|
///
|
||||||
|
/// ```norun
|
||||||
|
/// [[1, 3, 5],
|
||||||
|
/// [2, 3, 5],
|
||||||
|
/// [1, 4, 5],
|
||||||
|
/// [2, 4, 5]]
|
||||||
|
/// ```
|
||||||
|
pub(crate) fn paths<T: Clone>(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 `complex_one` and `complex_two` need to be unified to produce a
|
||||||
|
/// valid combined selector.
|
||||||
|
///
|
||||||
|
/// This is necessary when both selectors contain the same unique simple
|
||||||
|
/// selector, such as an ID.
|
||||||
|
fn must_unify(
|
||||||
|
complex_one: &[ComplexSelectorComponent],
|
||||||
|
complex_two: &[ComplexSelectorComponent],
|
||||||
|
) -> bool {
|
||||||
|
let mut unique_selectors = Vec::new();
|
||||||
|
for component in complex_one {
|
||||||
|
if let ComplexSelectorComponent::Compound(c) = component {
|
||||||
|
unique_selectors.extend(c.components.iter().filter(|f| is_unique(f)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if unique_selectors.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
complex_two.iter().any(|component| {
|
||||||
|
if let ComplexSelectorComponent::Compound(compound) = component {
|
||||||
|
compound
|
||||||
|
.components
|
||||||
|
.iter()
|
||||||
|
.any(|simple| is_unique(simple) && unique_selectors.contains(&simple))
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether a `CompoundSelector` may contain only one simple selector of
|
||||||
|
/// the same type as `simple`.
|
||||||
|
fn is_unique(simple: &SimpleSelector) -> bool {
|
||||||
|
matches!(simple, SimpleSelector::Id(..) | SimpleSelector::Pseudo(Pseudo { is_class: false, .. }))
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,7 @@ use crate::value::Value;
|
|||||||
///
|
///
|
||||||
/// A selector list is composed of `ComplexSelector`s. It matches an element
|
/// A selector list is composed of `ComplexSelector`s. It matches an element
|
||||||
/// that matches any of the component selectors.
|
/// that matches any of the component selectors.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub(crate) struct SelectorList {
|
pub(crate) struct SelectorList {
|
||||||
/// The components of this selector.
|
/// The components of this selector.
|
||||||
///
|
///
|
||||||
|
@ -7,7 +7,7 @@ use super::{
|
|||||||
|
|
||||||
const SUBSELECTOR_PSEUDOS: [&str; 4] = ["matches", "any", "nth-child", "nth-last-child"];
|
const SUBSELECTOR_PSEUDOS: [&str; 4] = ["matches", "any", "nth-child", "nth-last-child"];
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub(crate) enum SimpleSelector {
|
pub(crate) enum SimpleSelector {
|
||||||
/// *
|
/// *
|
||||||
Universal(Namespace),
|
Universal(Namespace),
|
||||||
@ -80,12 +80,12 @@ impl SimpleSelector {
|
|||||||
/// Specifity is represented in base 1000. The spec says this should be
|
/// Specifity is represented in base 1000. The spec says this should be
|
||||||
/// "sufficiently high"; it's extremely unlikely that any single selector
|
/// "sufficiently high"; it's extremely unlikely that any single selector
|
||||||
/// sequence will contain 1000 simple selectors.
|
/// sequence will contain 1000 simple selectors.
|
||||||
pub fn min_specificity(&self) -> u32 {
|
pub fn min_specificity(&self) -> i32 {
|
||||||
match self {
|
match self {
|
||||||
Self::Universal(..) => 0,
|
Self::Universal(..) => 0,
|
||||||
Self::Type(..) => 1,
|
Self::Type(..) => 1,
|
||||||
Self::Pseudo { .. } => todo!(),
|
Self::Pseudo { .. } => todo!(),
|
||||||
Self::Id(..) => 1000_u32.pow(2_u32),
|
Self::Id(..) => 1000_i32.pow(2_u32),
|
||||||
_ => 1000,
|
_ => 1000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,7 +94,7 @@ impl SimpleSelector {
|
|||||||
///
|
///
|
||||||
/// Pseudo selectors that contain selectors, like `:not()` and `:matches()`,
|
/// Pseudo selectors that contain selectors, like `:not()` and `:matches()`,
|
||||||
/// can have a range of possible specificities.
|
/// can have a range of possible specificities.
|
||||||
pub fn max_specificity(&self) -> u32 {
|
pub fn max_specificity(&self) -> i32 {
|
||||||
match self {
|
match self {
|
||||||
Self::Universal(..) => 0,
|
Self::Universal(..) => 0,
|
||||||
_ => self.min_specificity(),
|
_ => self.min_specificity(),
|
||||||
@ -128,7 +128,7 @@ impl SimpleSelector {
|
|||||||
selector: None,
|
selector: None,
|
||||||
..
|
..
|
||||||
}) => name.push_str(suffix),
|
}) => name.push_str(suffix),
|
||||||
_ => todo!(),
|
_ => todo!("Invalid parent selector"), //return Err((format!("Invalid parent selector \"{}\"", self), SPAN)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,7 +367,7 @@ impl SimpleSelector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub(crate) struct Pseudo {
|
pub(crate) struct Pseudo {
|
||||||
/// The name of this selector.
|
/// The name of this selector.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@ -563,6 +563,10 @@ impl Pseudo {
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_selector(self, selector: Option<SelectorList>) -> Self {
|
||||||
|
Self { selector, ..self }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns all pseudo selectors in `compound` that have a selector argument,
|
/// Returns all pseudo selectors in `compound` that have a selector argument,
|
||||||
|
175
tests/selector-extend.rs
Normal file
175
tests/selector-extend.rs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
#![cfg(test)]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod macros;
|
||||||
|
|
||||||
|
test!(
|
||||||
|
simple_attribute_equal,
|
||||||
|
"a {\n color: selector-extend(\"[c=d]\", \"[c=d]\", \"e\");\n}\n",
|
||||||
|
"a {\n color: [c=d], e;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_attribute_unequal_name,
|
||||||
|
"a {\n color: selector-extend(\"[c=d]\", \"[e=d]\", \"f\");\n}\n",
|
||||||
|
"a {\n color: [c=d];\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_attribute_unequal_value,
|
||||||
|
"a {\n color: selector-extend(\"[c=d]\", \"[c=e]\", \"f\");\n}\n",
|
||||||
|
"a {\n color: [c=d];\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_attribute_unequal_operator,
|
||||||
|
"a {\n color: selector-extend(\"[c=d]\", \"[c^=e]\", \"f\");\n}\n",
|
||||||
|
"a {\n color: [c=d];\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_class_equal,
|
||||||
|
"a {\n color: selector-extend(\".c\", \".c\", \"e\");\n}\n",
|
||||||
|
"a {\n color: .c, e;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_class_unequal,
|
||||||
|
"a {\n color: selector-extend(\".c\", \".d\", \"e\");\n}\n",
|
||||||
|
"a {\n color: .c;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_id_equal,
|
||||||
|
"a {\n color: selector-extend(\"#c\", \"#c\", \"e\");\n}\n",
|
||||||
|
"a {\n color: #c, e;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_id_unequal,
|
||||||
|
"a {\n color: selector-extend(\"#c\", \"#d\", \"e\");\n}\n",
|
||||||
|
"a {\n color: #c;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_placeholder_equal,
|
||||||
|
"a {\n color: selector-extend(\"%c\", \"%c\", \"e\");\n}\n",
|
||||||
|
"a {\n color: %c, e;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_placeholder_unequal,
|
||||||
|
"a {\n color: selector-extend(\"%c\", \"%d\", \"e\");\n}\n",
|
||||||
|
"a {\n color: %c;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_type_equal,
|
||||||
|
"a {\n color: selector-extend(\"c\", \"c\", \"e\");\n}\n",
|
||||||
|
"a {\n color: c, e;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_type_unequal,
|
||||||
|
"a {\n color: selector-extend(\"c\", \"d\", \"e\");\n}\n",
|
||||||
|
"a {\n color: c;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_type_and_universal,
|
||||||
|
"a {\n color: selector-extend(\"c\", \"*\", \"d\");\n}\n",
|
||||||
|
"a {\n color: c;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_type_explicit_namespace_and_type_explicit_namespace_equal,
|
||||||
|
"a {\n color: selector-extend(\"c|d\", \"c|d\", \"e\");\n}\n",
|
||||||
|
"a {\n color: c|d, e;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_type_explicit_namespace_and_type_implicit_namespace,
|
||||||
|
"a {\n color: selector-extend(\"c|d\", \"d\", \"e\");\n}\n",
|
||||||
|
"a {\n color: c|d;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_type_explicit_namespace_and_type_empty_namespace,
|
||||||
|
"a {\n color: selector-extend(\"c|d\", \"|d\", \"e\");\n}\n",
|
||||||
|
"a {\n color: c|d;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_type_explicit_namespace_and_type_universal_namespace,
|
||||||
|
"a {\n color: selector-extend(\"c|d\", \"*|d\", \"e\");\n}\n",
|
||||||
|
"a {\n color: c|d;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_type_empty_namespace_and_type_explicit_namespace_equal,
|
||||||
|
"a {\n color: selector-extend(\"|c\", \"d|c\", \"e\");\n}\n",
|
||||||
|
"a {\n color: |c;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_type_empty_namespace_and_type_implicit_namespace,
|
||||||
|
"a {\n color: selector-extend(\"|c\", \"c\", \"d\");\n}\n",
|
||||||
|
"a {\n color: |c;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_type_empty_namespace_and_type_empty_namespace,
|
||||||
|
"a {\n color: selector-extend(\"|c\", \"|c\", \"d\");\n}\n",
|
||||||
|
"a {\n color: |c, d;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_type_empty_namespace_and_type_universal_namespace,
|
||||||
|
"a {\n color: selector-extend(\"|c\", \"*|c\", \"d\");\n}\n",
|
||||||
|
"a {\n color: |c;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_type_universal_namespace_and_type_explicit_namespace_equal,
|
||||||
|
"a {\n color: selector-extend(\"*|c\", \"d|c\", \"d\");\n}\n",
|
||||||
|
"a {\n color: *|c;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_type_universal_namespace_and_type_implicit_namespace,
|
||||||
|
"a {\n color: selector-extend(\"*|c\", \"c\", \"d\");\n}\n",
|
||||||
|
"a {\n color: *|c;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_type_universal_namespace_and_type_empty_namespace,
|
||||||
|
"a {\n color: selector-extend(\"*|c\", \"|c\", \"d\");\n}\n",
|
||||||
|
"a {\n color: *|c;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
simple_type_universal_namespace_and_type_universal_namespace,
|
||||||
|
"a {\n color: selector-extend(\"*|c\", \"*|c\", \"d\");\n}\n",
|
||||||
|
"a {\n color: *|c, d;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
complex_parent_without_grandparents_simple,
|
||||||
|
"a {\n color: selector-extend(\".c .d\", \".c\", \".e\");\n}\n",
|
||||||
|
"a {\n color: .c .d, .e .d;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
complex_parent_without_grandparents_complex,
|
||||||
|
"a {\n color: selector-extend(\".c .d\", \".c\", \".e .f\");\n}\n",
|
||||||
|
"a {\n color: .c .d, .e .f .d;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
complex_parent_without_grandparents_list,
|
||||||
|
"a {\n color: selector-extend(\".c .d\", \".c\", \".e, .f\");\n}\n",
|
||||||
|
"a {\n color: .c .d, .e .d, .f .d;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
complex_parent_with_grandparents_simple,
|
||||||
|
"a {\n color: selector-extend(\".c .d .e\", \".d\", \".f\");\n}\n",
|
||||||
|
"a {\n color: .c .d .e, .c .f .e;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
complex_parent_with_grandparents_complex,
|
||||||
|
"a {\n color: selector-extend(\".c .d .e\", \".d\", \".f .g\");\n}\n",
|
||||||
|
"a {\n color: .c .d .e, .c .f .g .e, .f .c .g .e;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
complex_parent_with_grandparents_list,
|
||||||
|
"a {\n color: selector-extend(\".c .d .e\", \".d\", \".f, .g\");\n}\n",
|
||||||
|
"a {\n color: .c .d .e, .c .f .e, .c .g .e;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
complex_trailing_combinator_child,
|
||||||
|
"a {\n color: selector-extend(\".c .d\", \".c\", \".e >\");\n}\n",
|
||||||
|
"a {\n color: .c .d, .e > .d;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
complex_trailing_combinator_sibling,
|
||||||
|
"a {\n color: selector-extend(\".c .d\", \".c\", \".e ~\");\n}\n",
|
||||||
|
"a {\n color: .c .d, .e ~ .d;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
complex_trailing_combinator_next_sibling,
|
||||||
|
"a {\n color: selector-extend(\".c .d\", \".c\", \".e +\");\n}\n",
|
||||||
|
"a {\n color: .c .d, .e + .d;\n}\n"
|
||||||
|
);
|
Loading…
x
Reference in New Issue
Block a user