resolve @extend issues related to attrbitute equality

This commit is contained in:
ConnorSkees 2020-06-24 06:11:02 -04:00
parent 6fec0835f8
commit 4610a30024
3 changed files with 94 additions and 48 deletions

View File

@ -1,4 +1,7 @@
use std::fmt::{self, Display, Write}; use std::{
fmt::{self, Display, Write},
hash::{Hash, Hasher},
};
use codemap::Span; use codemap::Span;
@ -6,7 +9,7 @@ use crate::{common::QuoteKind, error::SassResult, parse::Parser, utils::is_ident
use super::{Namespace, QualifiedName}; use super::{Namespace, QualifiedName};
#[derive(Clone, Debug, Eq, PartialEq, Hash)] #[derive(Clone, Debug)]
pub(crate) struct Attribute { pub(crate) struct Attribute {
attr: QualifiedName, attr: QualifiedName,
value: String, value: String,
@ -15,6 +18,26 @@ pub(crate) struct Attribute {
span: Span, span: Span,
} }
impl PartialEq for Attribute {
fn eq(&self, other: &Self) -> bool {
self.attr == other.attr
&& self.value == other.value
&& self.modifier == other.modifier
&& self.op == other.op
}
}
impl Eq for Attribute {}
impl Hash for Attribute {
fn hash<H: Hasher>(&self, state: &mut H) {
self.attr.hash(state);
self.value.hash(state);
self.modifier.hash(state);
self.op.hash(state);
}
}
fn attribute_name(parser: &mut Parser<'_>, start: Span) -> SassResult<QualifiedName> { fn attribute_name(parser: &mut Parser<'_>, start: Span) -> SassResult<QualifiedName> {
let next = parser.toks.peek().ok_or(("Expected identifier.", start))?; let next = parser.toks.peek().ok_or(("Expected identifier.", start))?;
if next.kind == '*' { if next.kind == '*' {

View File

@ -206,25 +206,33 @@ impl Extender {
) -> SelectorList { ) -> SelectorList {
// This could be written more simply using Vec<Vec<T>>, but we want to avoid // This could be written more simply using Vec<Vec<T>>, but we want to avoid
// any allocations in the common case where no extends apply. // any allocations in the common case where no extends apply.
let mut extended: Vec<ComplexSelector> = Vec::new(); let mut extended: Option<Vec<ComplexSelector>> = None;
for i in 0..list.components.len() { for i in 0..list.components.len() {
let complex = list.components.get(i).unwrap().clone(); let complex = list.components.get(i).unwrap().clone();
if let Some(result) = if let Some(result) =
self.extend_complex(complex.clone(), extensions, media_query_context) self.extend_complex(complex.clone(), extensions, media_query_context)
{ {
if extended.is_empty() && i != 0 { if extended.is_none() {
extended = list.components[0..i].to_vec(); extended = Some(if i == 0 {
Vec::new()
} else {
list.components[0..i].to_vec()
});
} }
extended.extend(result.into_iter()); match extended.as_mut() {
} else if !extended.is_empty() { Some(v) => v.extend(result.into_iter()),
None => unreachable!(),
}
} else if let Some(extended) = extended.as_mut() {
extended.push(complex); extended.push(complex);
} }
} }
if extended.is_empty() { let extended = match extended {
return list; Some(v) => v,
} None => return list,
};
SelectorList { SelectorList {
components: self.trim(extended, |complex| self.originals.contains(complex)), components: self.trim(extended, |complex| self.originals.contains(complex)),
@ -257,7 +265,7 @@ impl Extender {
// //
// This could be written more simply using `Vec::into_iter::map`, but we want to avoid // This could be written more simply using `Vec::into_iter::map`, but we want to avoid
// any allocations in the common case where no extends apply. // any allocations in the common case where no extends apply.
let mut extended_not_expanded: Vec<Vec<ComplexSelector>> = Vec::new(); let mut extended_not_expanded: Option<Vec<Vec<ComplexSelector>>> = None;
let complex_has_line_break = complex.line_break; let complex_has_line_break = complex.line_break;
@ -266,8 +274,9 @@ impl Extender {
if let Some(extended) = if let Some(extended) =
self.extend_compound(component, extensions, media_query_context) self.extend_compound(component, extensions, media_query_context)
{ {
if extended_not_expanded.is_empty() { if extended_not_expanded.is_none() {
extended_not_expanded = complex extended_not_expanded = Some(
complex
.components .components
.clone() .clone()
.into_iter() .into_iter()
@ -278,28 +287,36 @@ impl Extender {
line_break: complex.line_break, line_break: complex.line_break,
}] }]
}) })
.collect(); .collect(),
);
}
match extended_not_expanded.as_mut() {
Some(v) => v.push(extended),
None => unreachable!(),
} }
extended_not_expanded.push(extended);
} else { } else {
extended_not_expanded.push(vec![ComplexSelector { match extended_not_expanded.as_mut() {
Some(v) => v.push(vec![ComplexSelector {
components: vec![ComplexSelectorComponent::Compound(component.clone())], components: vec![ComplexSelectorComponent::Compound(component.clone())],
line_break: false, line_break: false,
}]) }]),
None => {}
}
} }
} else if let Some(component @ ComplexSelectorComponent::Combinator(..)) = } else if let Some(component @ ComplexSelectorComponent::Combinator(..)) =
complex.components.get(i) complex.components.get(i)
{ {
extended_not_expanded.push(vec![ComplexSelector { match extended_not_expanded.as_mut() {
Some(v) => v.push(vec![ComplexSelector {
components: vec![component.clone()], components: vec![component.clone()],
line_break: false, line_break: false,
}]) }]),
None => {}
}
} }
} }
if extended_not_expanded.is_empty() { let extended_not_expanded = extended_not_expanded?;
return None;
}
let mut first = true; let mut first = true;
@ -354,7 +371,7 @@ impl Extender {
// which targets are actually extended. // which targets are actually extended.
let mut targets_used: HashSet<SimpleSelector> = HashSet::new(); let mut targets_used: HashSet<SimpleSelector> = HashSet::new();
let mut options: Vec<Vec<Extension>> = Vec::new(); let mut options: Option<Vec<Vec<Extension>>> = None;
for i in 0..compound.components.len() { for i in 0..compound.components.len() {
let simple = compound.components.get(i).cloned().unwrap(); let simple = compound.components.get(i).cloned().unwrap();
@ -365,21 +382,29 @@ impl Extender {
media_query_context, media_query_context,
&mut targets_used, &mut targets_used,
) { ) {
if options.is_empty() && i != 0 { if options.is_none() {
options.push(vec![self.extension_for_compound( let mut new_options = Vec::new();
if i != 0 {
new_options.push(vec![self.extension_for_compound(
compound.components.clone().into_iter().take(i).collect(), compound.components.clone().into_iter().take(i).collect(),
)]); )]);
} }
options.replace(new_options);
}
options.extend(extended.into_iter()); match options.as_mut() {
Some(v) => v.extend(extended.into_iter()),
None => unreachable!(),
}
} else { } else {
options.push(vec![self.extension_for_simple(simple)]); match options.as_mut() {
Some(v) => v.push(vec![self.extension_for_simple(simple)]),
None => {}
}
} }
} }
if options.is_empty() { let options = options?;
return None;
}
// If `self.mode` isn't `ExtendMode::Normal` and we didn't use all the targets in // If `self.mode` isn't `ExtendMode::Normal` and we didn't use all the targets in
// `extensions`, extension fails for `compound`. // `extensions`, extension fails for `compound`.

View File

@ -486,7 +486,6 @@ test!(
"-a [foo=bar].baz, -a [foo=bar][ns|foo=bar] {\n a: b;\n}\n" "-a [foo=bar].baz, -a [foo=bar][ns|foo=bar] {\n a: b;\n}\n"
); );
test!( test!(
#[ignore = "to investigate (too many selectors)"]
attribute_unification_5, attribute_unification_5,
"%-a %-a [foo=bar].bar {a: b} "%-a %-a [foo=bar].bar {a: b}
[foo=bar] {@extend .bar} -a {@extend %-a} [foo=bar] {@extend .bar} -a {@extend %-a}
@ -644,7 +643,6 @@ test!(
"-a :not(.foo) {\n a: b;\n}\n" "-a :not(.foo) {\n a: b;\n}\n"
); );
test!( test!(
#[ignore = "to investigate (too many selectors)"]
negation_unification_3, negation_unification_3,
"%-a :not([a=b]).baz {a: b} "%-a :not([a=b]).baz {a: b}
:not([a = b]) {@extend .baz} -a {@extend %-a} :not([a = b]) {@extend .baz} -a {@extend %-a}