From a3a33db47af937a05b5728ee6dd636a9d4f4acbb Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Mon, 22 Jun 2020 12:39:09 -0400 Subject: [PATCH] improve selector error handling --- src/builtin/selector.rs | 16 +++--- src/lib.rs | 21 ++++---- src/output.rs | 4 +- src/parse/mod.rs | 37 +++++++------ src/selector/complex.rs | 7 ++- src/selector/compound.rs | 92 ++++++++++++++++++++------------ src/selector/extend/extension.rs | 2 +- src/selector/extend/mod.rs | 30 +++++++---- src/selector/list.rs | 42 ++++++++++----- src/selector/mod.rs | 19 ++++--- src/selector/parse.rs | 7 ++- src/selector/simple.rs | 16 ++++-- tests/if.rs | 4 ++ tests/selectors.rs | 8 +++ 14 files changed, 201 insertions(+), 104 deletions(-) diff --git a/src/builtin/selector.rs b/src/builtin/selector.rs index 3e998d3..28d0520 100644 --- a/src/builtin/selector.rs +++ b/src/builtin/selector.rs @@ -73,9 +73,12 @@ fn selector_nest(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { .map(|sel| sel.node.to_selector(parser, "selectors", true)) .collect::>>()? .into_iter() - .fold(Selector::new(), |parent, child| { - child.resolve_parent_selectors(&parent, true) - }) + .try_fold( + Selector::new(span), + |parent, child| -> SassResult { + Ok(child.resolve_parent_selectors(&parent, true)?) + }, + )? .into_value()) } @@ -130,8 +133,9 @@ fn selector_append(args: CallArgs, parser: &mut Parser<'_>) -> SassResult } }) .collect::>>()?, + span, }) - .resolve_parent_selectors(&parent, false)) + .resolve_parent_selectors(&parent, false)?) })? .into_value()) } @@ -148,7 +152,7 @@ fn selector_extend(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { @@ -163,7 +167,7 @@ fn selector_replace(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { diff --git a/src/lib.rs b/src/lib.rs index 809f73a..be2719f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -143,8 +143,9 @@ fn raw_to_parse_error(map: &CodeMap, err: Error) -> Error { #[cfg(not(feature = "wasm"))] pub fn from_path(p: &str) -> Result { let mut map = CodeMap::new(); - let mut extender = Extender::new(); let file = map.add_file(p.into(), String::from_utf8(fs::read(p)?)?); + let empty_span = file.span.subspan(0, 0); + let mut extender = Extender::new(empty_span); Css::from_stmts( Parser { toks: &mut Lexer::new(&file) @@ -155,8 +156,8 @@ pub fn from_path(p: &str) -> Result { path: p.as_ref(), scopes: &mut NeverEmptyVec::new(Scope::new()), global_scope: &mut Scope::new(), - super_selectors: &mut NeverEmptyVec::new(Selector::new()), - span_before: file.span.subspan(0, 0), + super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)), + span_before: empty_span, content: None, in_mixin: false, in_function: false, @@ -188,8 +189,9 @@ pub fn from_path(p: &str) -> Result { #[cfg(not(feature = "wasm"))] pub fn from_string(p: String) -> Result { let mut map = CodeMap::new(); - let mut extender = Extender::new(); let file = map.add_file("stdin".into(), p); + let empty_span = file.span.subspan(0, 0); + let mut extender = Extender::new(empty_span); Css::from_stmts( Parser { toks: &mut Lexer::new(&file) @@ -200,8 +202,8 @@ pub fn from_string(p: String) -> Result { path: Path::new(""), scopes: &mut NeverEmptyVec::new(Scope::new()), global_scope: &mut Scope::new(), - super_selectors: &mut NeverEmptyVec::new(Selector::new()), - span_before: file.span.subspan(0, 0), + super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)), + span_before: empty_span, content: None, in_mixin: false, in_function: false, @@ -222,9 +224,10 @@ pub fn from_string(p: String) -> Result { #[cfg(feature = "wasm")] #[wasm_bindgen] pub fn from_string(p: String) -> std::result::Result { - let mut extender = Extender::new(); let mut map = CodeMap::new(); let file = map.add_file("stdin".into(), p); + let empty_span = file.span.subspan(0, 0); + let mut extender = Extender::new(empty_span); Ok(Css::from_stmts( Parser { toks: &mut Lexer::new(&file) @@ -235,8 +238,8 @@ pub fn from_string(p: String) -> std::result::Result { path: Path::new(""), scopes: &mut NeverEmptyVec::new(Scope::new()), global_scope: &mut Scope::new(), - super_selectors: &mut NeverEmptyVec::new(Selector::new()), - span_before: file.span.subspan(0, 0), + super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)), + span_before: empty_span, content: None, in_mixin: false, in_function: false, diff --git a/src/output.rs b/src/output.rs index a3fecab..d6caa3e 100644 --- a/src/output.rs +++ b/src/output.rs @@ -89,10 +89,10 @@ impl Css { return Ok(Vec::new()); } let selector = if extender.is_empty() { - selector.resolve_parent_selectors(&super_selector, true) + selector.resolve_parent_selectors(&super_selector, true)? } else { Selector(extender.add_selector( - selector.resolve_parent_selectors(&super_selector, true).0, + selector.resolve_parent_selectors(&super_selector, true)?.0, None, )) } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 997f8fd..66e2d53 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -267,7 +267,7 @@ impl<'a> Parser<'a> { self.super_selectors.push(selector.resolve_parent_selectors( &super_selector, !at_root || self.at_root_has_selector, - )); + )?); let body = self.parse_stmt()?; self.scopes.pop(); self.super_selectors.pop(); @@ -985,7 +985,7 @@ impl<'a> Parser<'a> { self.toks.next(); return Ok(Stmt::UnknownAtRule { name, - super_selector: Selector::new(), + super_selector: Selector::new(self.span_before), params: String::new(), body: Vec::new(), }); @@ -1029,7 +1029,7 @@ impl<'a> Parser<'a> { body = vec![Stmt::RuleSet { selector: self.super_selectors.last().clone(), body, - super_selector: Selector::new(), + super_selector: Selector::new(self.span_before), }]; } @@ -1037,7 +1037,7 @@ impl<'a> Parser<'a> { Ok(Stmt::UnknownAtRule { name, - super_selector: Selector::new(), + super_selector: Selector::new(self.span_before), params: params.trim().to_owned(), body, }) @@ -1082,14 +1082,14 @@ impl<'a> Parser<'a> { body = vec![Stmt::RuleSet { selector: self.super_selectors.last().clone(), body, - super_selector: Selector::new(), + super_selector: Selector::new(self.span_before), }]; } body.append(&mut rules); Ok(Stmt::Media { - super_selector: Selector::new(), + super_selector: Selector::new(self.span_before), params: params.trim().to_owned(), body, }) @@ -1105,7 +1105,7 @@ impl<'a> Parser<'a> { at_root_has_selector = true; self.parse_selector(true, false, String::new())? } - .resolve_parent_selectors(self.super_selectors.last(), false); + .resolve_parent_selectors(self.super_selectors.last(), false)?; self.whitespace(); @@ -1142,18 +1142,23 @@ impl<'a> Parser<'a> { styles.push(s); None } - Stmt::RuleSet { selector, body, .. } if !at_root_has_selector => Some(Stmt::RuleSet { - super_selector: Selector::new(), - selector: selector.resolve_parent_selectors(&at_rule_selector, false), - body, - }), - _ => Some(s), + Stmt::RuleSet { selector, body, .. } if !at_root_has_selector => { + Some(Ok(Stmt::RuleSet { + super_selector: Selector::new(self.span_before), + selector: match selector.resolve_parent_selectors(&at_rule_selector, false) { + Ok(v) => v, + Err(e) => return Some(Err(e)), + }, + body, + })) + } + _ => Some(Ok(s)), }) - .collect::>(); + .collect::>>()?; let mut stmts = vec![Stmt::RuleSet { selector: at_rule_selector, body: styles, - super_selector: Selector::new(), + super_selector: Selector::new(self.span_before), }]; stmts.extend(raw_stmts); Ok(stmts) @@ -1274,7 +1279,7 @@ impl<'a> Parser<'a> { body = vec![Stmt::RuleSet { selector: self.super_selectors.last().clone(), body, - super_selector: Selector::new(), + super_selector: Selector::new(self.span_before), }]; } diff --git a/src/selector/complex.rs b/src/selector/complex.rs index 30e94ed..c3a8afe 100644 --- a/src/selector/complex.rs +++ b/src/selector/complex.rs @@ -1,5 +1,7 @@ use std::fmt::{self, Display, Write}; +use crate::error::SassResult; + use super::{CompoundSelector, Pseudo, SelectorList, SimpleSelector, Specificity}; /// A complex selector. @@ -264,7 +266,10 @@ impl ComplexSelectorComponent { matches!(self, Self::Combinator(..)) } - pub fn resolve_parent_selectors(self, parent: SelectorList) -> Option> { + pub fn resolve_parent_selectors( + self, + parent: SelectorList, + ) -> SassResult>> { match self { Self::Compound(c) => c.resolve_parent_selectors(parent), Self::Combinator(..) => todo!(), diff --git a/src/selector/compound.rs b/src/selector/compound.rs index c3f3612..cd1b47e 100644 --- a/src/selector/compound.rs +++ b/src/selector/compound.rs @@ -1,5 +1,7 @@ use std::fmt::{self, Write}; +use crate::error::SassResult; + use super::{ ComplexSelector, ComplexSelectorComponent, Namespace, Pseudo, SelectorList, SimpleSelector, Specificity, @@ -102,7 +104,10 @@ impl CompoundSelector { /// `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> { + pub fn resolve_parent_selectors( + self, + parent: SelectorList, + ) -> SassResult>> { let contains_selector_pseudo = self.components.iter().any(|simple| { if let SimpleSelector::Pseudo(Pseudo { selector: Some(sel), @@ -116,7 +121,7 @@ impl CompoundSelector { }); if !contains_selector_pseudo && !self.components[0].is_parent() { - return None; + return Ok(None); } let resolved_members: Vec = if contains_selector_pseudo { @@ -127,64 +132,81 @@ impl CompoundSelector { if let SimpleSelector::Pseudo(mut pseudo) = simple { if let Some(sel) = pseudo.selector.clone() { if !sel.contains_parent_selector() { - return SimpleSelector::Pseudo(pseudo); + return Ok(SimpleSelector::Pseudo(pseudo)); } pseudo.selector = - Some(sel.resolve_parent_selectors(Some(parent.clone()), false)); - SimpleSelector::Pseudo(pseudo) + Some(sel.resolve_parent_selectors(Some(parent.clone()), false)?); + Ok(SimpleSelector::Pseudo(pseudo)) } else { - SimpleSelector::Pseudo(pseudo) + Ok(SimpleSelector::Pseudo(pseudo)) } } else { - simple + Ok(simple) } }) - .collect() + .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); + return Ok(Some(parent.components)); } } else { - return Some(vec![ComplexSelector { + return Ok(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 span = parent.span; - 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 } - }; + Ok(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 { + return Err(( + format!("Parent \"{}\" is incompatible with this selector.", complex), + span, + ) + .into()); + }; - complex.components.pop(); + 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, span)?; + 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 } + }; - let mut components = complex.components; - components.push(ComplexSelectorComponent::Compound(last)); + complex.components.pop(); - ComplexSelector { components, line_break: complex.line_break } - }).collect()) + let mut components = complex.components; + components.push(ComplexSelectorComponent::Compound(last)); + + Ok(ComplexSelector { + components, + line_break: complex.line_break, + }) + }) + .collect::>>()?, + )) } /// Returns a `CompoundSelector` that matches only elements that are matched by diff --git a/src/selector/extend/extension.rs b/src/selector/extend/extension.rs index c5bba0e..54f188f 100644 --- a/src/selector/extend/extension.rs +++ b/src/selector/extend/extension.rs @@ -25,10 +25,10 @@ pub(crate) struct Extension { /// The media query context to which this extend is restricted, or `None` if /// it can apply within any context. - // todo: Option pub media_context: Option>, /// The span in which `extender` was defined. + // todo: no `Option<>` pub span: Option, pub left: Option>, diff --git a/src/selector/extend/mod.rs b/src/selector/extend/mod.rs index ce7a9d4..ef4f765 100644 --- a/src/selector/extend/mod.rs +++ b/src/selector/extend/mod.rs @@ -55,7 +55,7 @@ impl Default for ExtendMode { } } -#[derive(Clone, Debug, Eq, PartialEq, Default)] +#[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct Extender { /// A map from all simple selectors in the stylesheet to the selector lists /// that contain them. @@ -100,6 +100,8 @@ pub(crate) struct Extender { /// The mode that controls this extender's behavior. mode: ExtendMode, + + span: Span, } impl Extender { @@ -112,11 +114,12 @@ impl Extender { selector: SelectorList, source: SelectorList, targets: SelectorList, + span: Span, ) -> SassResult { - Self::extend_or_replace(selector, source, targets, ExtendMode::AllTargets) + Self::extend_or_replace(selector, source, targets, ExtendMode::AllTargets, span) } - pub fn new() -> Self { + pub fn new(span: Span) -> Self { Self { selectors: HashMap::new(), extensions: HashMap::new(), @@ -125,6 +128,7 @@ impl Extender { source_specificity: HashMap::new(), originals: HashSet::new(), mode: ExtendMode::Normal, + span, } } @@ -137,8 +141,9 @@ impl Extender { selector: SelectorList, source: SelectorList, targets: SelectorList, + span: Span, ) -> SassResult { - Self::extend_or_replace(selector, source, targets, ExtendMode::Replace) + Self::extend_or_replace(selector, source, targets, ExtendMode::Replace, span) } fn extend_or_replace( @@ -146,6 +151,7 @@ impl Extender { source: SelectorList, targets: SelectorList, mode: ExtendMode, + span: Span, ) -> SassResult { let extenders: IndexMap = source .components @@ -160,7 +166,7 @@ impl Extender { if complex.components.len() == 1 { Ok(complex.components.first().unwrap().as_compound().clone()) } else { - todo!("Can't extend complex selector $complex.") + return Err(("Can't extend complex selector $complex.", span).into()); } }) .collect::>>()?; @@ -176,7 +182,7 @@ impl Extender { }) .collect(); - let mut extender = Extender::with_mode(mode); + let mut extender = Extender::with_mode(mode, span); if !selector.is_invisible() { extender @@ -187,10 +193,10 @@ impl Extender { Ok(extender.extend_list(selector, &extensions, &None)) } - fn with_mode(mode: ExtendMode) -> Self { + fn with_mode(mode: ExtendMode, span: Span) -> Self { Self { mode, - ..Extender::default() + ..Extender::new(span) } } @@ -225,6 +231,7 @@ impl Extender { SelectorList { components: self.trim(extended, |complex| self.originals.contains(complex)), + span: self.span, } } @@ -550,7 +557,10 @@ impl Extender { media_query_context: &Option>, ) -> Option> { let extended = self.extend_list( - pseudo.selector.clone().unwrap_or_else(SelectorList::new), + pseudo + .selector + .clone() + .unwrap_or_else(|| SelectorList::new(self.span)), extensions, media_query_context, ); @@ -657,6 +667,7 @@ impl Extender { .map(|complex| { pseudo.clone().with_selector(Some(SelectorList { components: vec![complex], + span: self.span, })) }) .collect::>(); @@ -668,6 +679,7 @@ impl Extender { } else { Some(vec![pseudo.with_selector(Some(SelectorList { components: complexes, + span: self.span, }))]) } } diff --git a/src/selector/list.rs b/src/selector/list.rs index 09a3a6d..7000fae 100644 --- a/src/selector/list.rs +++ b/src/selector/list.rs @@ -4,10 +4,13 @@ use std::{ mem, }; +use codemap::Span; + use super::{unify_complex, ComplexSelector, ComplexSelectorComponent}; use crate::{ common::{Brackets, ListSeparator, QuoteKind}, + error::SassResult, value::Value, }; @@ -21,6 +24,7 @@ pub(crate) struct SelectorList { /// /// This is never empty. pub components: Vec, + pub span: Span, } impl fmt::Display for SelectorList { @@ -58,9 +62,10 @@ impl SelectorList { .any(ComplexSelector::contains_parent_selector) } - pub const fn new() -> Self { + pub const fn new(span: Span) -> Self { Self { components: Vec::new(), + span, } } @@ -126,6 +131,7 @@ impl SelectorList { Some(Self { components: contents, + span: self.span.merge(other.span), }) } @@ -137,28 +143,35 @@ impl SelectorList { /// The given `parent` may be `None`, indicating that this has no parents. If /// so, this list is returned as-is if it doesn't contain any explicit /// `SimpleSelector::Parent`s. If it does, this returns a `SassError`. - // todo: return SassResult (the issue is figuring out the span) - pub fn resolve_parent_selectors(self, parent: Option, implicit_parent: bool) -> Self { + pub fn resolve_parent_selectors( + self, + parent: Option, + implicit_parent: bool, + ) -> SassResult { let parent = match parent { Some(p) => p, None => { if !self.contains_parent_selector() { - return self; + return Ok(self); } - todo!("Top-level selectors may not contain the parent selector \"&\".") + return Err(( + "Top-level selectors may not contain the parent selector \"&\".", + self.span, + ) + .into()); } }; - Self { + Ok(Self { components: flatten_vertically( self.components .into_iter() .map(|complex| { if !complex.contains_parent_selector() { if !implicit_parent { - return vec![complex]; + return Ok(vec![complex]); } - return parent + return Ok(parent .clone() .components .into_iter() @@ -170,7 +183,7 @@ impl SelectorList { line_break: complex.line_break || parent_complex.line_break, } }) - .collect(); + .collect()); } let mut new_complexes: Vec> = @@ -181,7 +194,7 @@ impl SelectorList { if component.is_compound() { let resolved = match component .clone() - .resolve_parent_selectors(parent.clone()) + .resolve_parent_selectors(parent.clone())? { Some(r) => r, None => { @@ -213,7 +226,7 @@ impl SelectorList { } let mut i = 0; - new_complexes + Ok(new_complexes .into_iter() .map(|new_complex| { i += 1; @@ -222,11 +235,12 @@ impl SelectorList { line_break: line_breaks[i - 1], } }) - .collect() + .collect()) }) - .collect(), + .collect::>>>()?, ), - } + span: self.span, + }) } pub fn is_superselector(&self, other: &Self) -> bool { diff --git a/src/selector/mod.rs b/src/selector/mod.rs index 68268cf..21122e5 100644 --- a/src/selector/mod.rs +++ b/src/selector/mod.rs @@ -1,6 +1,8 @@ use std::fmt; -use crate::value::Value; +use codemap::Span; + +use crate::{error::SassResult, value::Value}; pub(crate) use attribute::Attribute; pub(crate) use common::*; @@ -33,15 +35,19 @@ impl Selector { /// Small wrapper around `SelectorList`'s method that turns an empty parent selector /// into `None`. This is a hack and in the future should be replaced. // todo: take Option for parent - pub fn resolve_parent_selectors(&self, parent: &Self, implicit_parent: bool) -> Self { - Self(self.0.clone().resolve_parent_selectors( + pub fn resolve_parent_selectors( + &self, + parent: &Self, + implicit_parent: bool, + ) -> SassResult { + Ok(Self(self.0.clone().resolve_parent_selectors( if parent.is_empty() { None } else { Some(parent.0.clone()) }, implicit_parent, - )) + )?)) } pub fn is_super_selector(&self, other: &Self) -> bool { @@ -54,6 +60,7 @@ impl Selector { pub fn remove_placeholders(self) -> Selector { Self(SelectorList { + span: self.0.span, components: self .0 .components @@ -67,8 +74,8 @@ impl Selector { self.0.is_empty() } - pub const fn new() -> Selector { - Selector(SelectorList::new()) + pub const fn new(span: Span) -> Selector { + Selector(SelectorList::new(span)) } pub fn into_value(self) -> Value { diff --git a/src/selector/parse.rs b/src/selector/parse.rs index c592589..611e308 100644 --- a/src/selector/parse.rs +++ b/src/selector/parse.rs @@ -103,7 +103,10 @@ impl<'a, 'b> SelectorParser<'a, 'b> { line_break = false; } - Ok(SelectorList { components }) + Ok(SelectorList { + components, + span: self.span, + }) } fn eat_whitespace(&mut self) -> DevouredWhitespace { @@ -299,6 +302,7 @@ impl<'a, 'b> SelectorParser<'a, 'b> { selector: None, is_syntactic_class: !element, argument: None, + span: self.span, })); } }; @@ -351,6 +355,7 @@ impl<'a, 'b> SelectorParser<'a, 'b> { // todo: we can store the reference to this is_syntactic_class: !element, argument, + span: self.span, })) } diff --git a/src/selector/simple.rs b/src/selector/simple.rs index ebecab1..90a7bff 100644 --- a/src/selector/simple.rs +++ b/src/selector/simple.rs @@ -1,5 +1,9 @@ use std::fmt::{self, Write}; +use codemap::Span; + +use crate::error::SassResult; + use super::{ Attribute, ComplexSelector, ComplexSelectorComponent, CompoundSelector, Namespace, QualifiedName, SelectorList, Specificity, @@ -119,8 +123,8 @@ impl SimpleSelector { } } - pub fn add_suffix(&mut self, suffix: &str) { - match self { + pub fn add_suffix(&mut self, suffix: &str, span: Span) -> SassResult<()> { + Ok(match self { Self::Type(name) => name.ident.push_str(suffix), Self::Placeholder(name) | Self::Id(name) @@ -131,8 +135,9 @@ impl SimpleSelector { selector: None, .. }) => name.push_str(suffix), - _ => todo!("Invalid parent selector"), //return Err((format!("Invalid parent selector \"{}\"", self), SPAN)), - } + // todo: add test for this? + _ => return Err((format!("Invalid parent selector \"{}\"", self), span).into()), + }) } pub fn is_universal(&self) -> bool { @@ -403,6 +408,8 @@ pub(crate) struct Pseudo { /// This is `None` if there's no selector. If `argument` and `selector` are /// both non-`None`, the selector follows the argument. pub selector: Option, + + pub span: Span, } impl fmt::Display for Pseudo { @@ -535,6 +542,7 @@ impl Pseudo { } sel.is_superselector(&SelectorList { components: vec![complex.clone()], + span: self.span, }) } else { false diff --git a/tests/if.rs b/tests/if.rs index f8a5fcd..55d2a9f 100644 --- a/tests/if.rs +++ b/tests/if.rs @@ -153,3 +153,7 @@ error!( nothing_after_i_after_else, "@if true {} @else i", "Error: expected \"{\"." ); +error!( + invalid_toplevel_selector, + "@if true { & { } }", "Error: Top-level selectors may not contain the parent selector \"&\"." +); diff --git a/tests/selectors.rs b/tests/selectors.rs index a9412c8..111870b 100644 --- a/tests/selectors.rs +++ b/tests/selectors.rs @@ -685,3 +685,11 @@ error!( ":#ab {}", "Error: Expected identifier." ); error!(nothing_after_colon, "a:{}", "Error: Expected identifier."); +error!( + toplevel_parent_selector_after_combinator, + "~&{}", "Error: Top-level selectors may not contain the parent selector \"&\"." +); +error!( + toplevel_parent_selector_after_element, + "a&{}", "Error: \"&\" may only used at the beginning of a compound selector." +);