From 4610a300247162b61651ee39f60512f01ab9d846 Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Wed, 24 Jun 2020 06:11:02 -0400 Subject: [PATCH] resolve `@extend` issues related to attrbitute equality --- src/selector/attribute.rs | 27 ++++++++- src/selector/extend/mod.rs | 113 ++++++++++++++++++++++--------------- tests/extend.rs | 2 - 3 files changed, 94 insertions(+), 48 deletions(-) diff --git a/src/selector/attribute.rs b/src/selector/attribute.rs index 2646feb..d0706e1 100644 --- a/src/selector/attribute.rs +++ b/src/selector/attribute.rs @@ -1,4 +1,7 @@ -use std::fmt::{self, Display, Write}; +use std::{ + fmt::{self, Display, Write}, + hash::{Hash, Hasher}, +}; use codemap::Span; @@ -6,7 +9,7 @@ use crate::{common::QuoteKind, error::SassResult, parse::Parser, utils::is_ident use super::{Namespace, QualifiedName}; -#[derive(Clone, Debug, Eq, PartialEq, Hash)] +#[derive(Clone, Debug)] pub(crate) struct Attribute { attr: QualifiedName, value: String, @@ -15,6 +18,26 @@ pub(crate) struct Attribute { 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(&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 { let next = parser.toks.peek().ok_or(("Expected identifier.", start))?; if next.kind == '*' { diff --git a/src/selector/extend/mod.rs b/src/selector/extend/mod.rs index 8e293f6..9ebd811 100644 --- a/src/selector/extend/mod.rs +++ b/src/selector/extend/mod.rs @@ -206,25 +206,33 @@ impl Extender { ) -> SelectorList { // This could be written more simply using Vec>, but we want to avoid // any allocations in the common case where no extends apply. - let mut extended: Vec = Vec::new(); + let mut extended: Option> = None; for i in 0..list.components.len() { let complex = list.components.get(i).unwrap().clone(); if let Some(result) = self.extend_complex(complex.clone(), extensions, media_query_context) { - if extended.is_empty() && i != 0 { - extended = list.components[0..i].to_vec(); + if extended.is_none() { + extended = Some(if i == 0 { + Vec::new() + } else { + list.components[0..i].to_vec() + }); } - extended.extend(result.into_iter()); - } else if !extended.is_empty() { + match extended.as_mut() { + Some(v) => v.extend(result.into_iter()), + None => unreachable!(), + } + } else if let Some(extended) = extended.as_mut() { extended.push(complex); } } - if extended.is_empty() { - return list; - } + let extended = match extended { + Some(v) => v, + None => return list, + }; SelectorList { 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 // any allocations in the common case where no extends apply. - let mut extended_not_expanded: Vec> = Vec::new(); + let mut extended_not_expanded: Option>> = None; let complex_has_line_break = complex.line_break; @@ -266,40 +274,49 @@ impl Extender { if let Some(extended) = self.extend_compound(component, extensions, media_query_context) { - if extended_not_expanded.is_empty() { - extended_not_expanded = complex - .components - .clone() - .into_iter() - .take(i) - .map(|component| { - vec![ComplexSelector { - components: vec![component], - line_break: complex.line_break, - }] - }) - .collect(); + if extended_not_expanded.is_none() { + extended_not_expanded = Some( + complex + .components + .clone() + .into_iter() + .take(i) + .map(|component| { + vec![ComplexSelector { + components: vec![component], + line_break: complex.line_break, + }] + }) + .collect(), + ); + } + match extended_not_expanded.as_mut() { + Some(v) => v.push(extended), + None => unreachable!(), } - extended_not_expanded.push(extended); } else { - extended_not_expanded.push(vec![ComplexSelector { - components: vec![ComplexSelectorComponent::Compound(component.clone())], - line_break: false, - }]) + match extended_not_expanded.as_mut() { + Some(v) => v.push(vec![ComplexSelector { + components: vec![ComplexSelectorComponent::Compound(component.clone())], + line_break: false, + }]), + None => {} + } } } else if let Some(component @ ComplexSelectorComponent::Combinator(..)) = complex.components.get(i) { - extended_not_expanded.push(vec![ComplexSelector { - components: vec![component.clone()], - line_break: false, - }]) + match extended_not_expanded.as_mut() { + Some(v) => v.push(vec![ComplexSelector { + components: vec![component.clone()], + line_break: false, + }]), + None => {} + } } } - if extended_not_expanded.is_empty() { - return None; - } + let extended_not_expanded = extended_not_expanded?; let mut first = true; @@ -354,7 +371,7 @@ impl Extender { // which targets are actually extended. let mut targets_used: HashSet = HashSet::new(); - let mut options: Vec> = Vec::new(); + let mut options: Option>> = None; for i in 0..compound.components.len() { let simple = compound.components.get(i).cloned().unwrap(); @@ -365,21 +382,29 @@ impl Extender { media_query_context, &mut targets_used, ) { - if options.is_empty() && i != 0 { - options.push(vec![self.extension_for_compound( - compound.components.clone().into_iter().take(i).collect(), - )]); + if options.is_none() { + 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(), + )]); + } + options.replace(new_options); } - options.extend(extended.into_iter()); + match options.as_mut() { + Some(v) => v.extend(extended.into_iter()), + None => unreachable!(), + } } 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() { - return None; - } + let options = options?; // If `self.mode` isn't `ExtendMode::Normal` and we didn't use all the targets in // `extensions`, extension fails for `compound`. diff --git a/tests/extend.rs b/tests/extend.rs index f3c5d5f..548b907 100644 --- a/tests/extend.rs +++ b/tests/extend.rs @@ -486,7 +486,6 @@ test!( "-a [foo=bar].baz, -a [foo=bar][ns|foo=bar] {\n a: b;\n}\n" ); test!( - #[ignore = "to investigate (too many selectors)"] attribute_unification_5, "%-a %-a [foo=bar].bar {a: b} [foo=bar] {@extend .bar} -a {@extend %-a} @@ -644,7 +643,6 @@ test!( "-a :not(.foo) {\n a: b;\n}\n" ); test!( - #[ignore = "to investigate (too many selectors)"] negation_unification_3, "%-a :not([a=b]).baz {a: b} :not([a = b]) {@extend .baz} -a {@extend %-a}