improve selector error handling

This commit is contained in:
ConnorSkees 2020-06-22 12:39:09 -04:00
parent aea7c9c408
commit a3a33db47a
14 changed files with 201 additions and 104 deletions

View File

@ -73,9 +73,12 @@ fn selector_nest(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
.map(|sel| sel.node.to_selector(parser, "selectors", true))
.collect::<SassResult<Vec<Selector>>>()?
.into_iter()
.fold(Selector::new(), |parent, child| {
child.resolve_parent_selectors(&parent, true)
})
.try_fold(
Selector::new(span),
|parent, child| -> SassResult<Selector> {
Ok(child.resolve_parent_selectors(&parent, true)?)
},
)?
.into_value())
}
@ -130,8 +133,9 @@ fn selector_append(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value>
}
})
.collect::<SassResult<Vec<ComplexSelector>>>()?,
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<Va
.arg(&mut args, 2, "extender")?
.to_selector(parser, "extender", false)?;
Ok(Extender::extend(selector.0, source.0, target.0)?.to_sass_list())
Ok(Extender::extend(selector.0, source.0, target.0, args.span())?.to_sass_list())
}
fn selector_replace(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
@ -163,7 +167,7 @@ fn selector_replace(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<V
parser
.arg(&mut args, 2, "replacement")?
.to_selector(parser, "replacement", false)?;
Ok(Extender::replace(selector.0, source.0, target.0)?.to_sass_list())
Ok(Extender::replace(selector.0, source.0, target.0, args.span())?.to_sass_list())
}
fn selector_unify(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {

View File

@ -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<String> {
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<String> {
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<String> {
#[cfg(not(feature = "wasm"))]
pub fn from_string(p: String) -> Result<String> {
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<String> {
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<String> {
#[cfg(feature = "wasm")]
#[wasm_bindgen]
pub fn from_string(p: String) -> std::result::Result<String, JsValue> {
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<String, JsValue> {
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,

View File

@ -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,
))
}

View File

@ -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::<Vec<Stmt>>();
.collect::<SassResult<Vec<Stmt>>>()?;
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),
}];
}

View File

@ -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<Vec<ComplexSelector>> {
pub fn resolve_parent_selectors(
self,
parent: SelectorList,
) -> SassResult<Option<Vec<ComplexSelector>>> {
match self {
Self::Compound(c) => c.resolve_parent_selectors(parent),
Self::Combinator(..) => todo!(),

View File

@ -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<Vec<ComplexSelector>> {
pub fn resolve_parent_selectors(
self,
parent: SelectorList,
) -> SassResult<Option<Vec<ComplexSelector>>> {
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<SimpleSelector> = 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::<SassResult<Vec<SimpleSelector>>>()?
} 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::<SassResult<Vec<ComplexSelector>>>()?,
))
}
/// Returns a `CompoundSelector` that matches only elements that are matched by

View File

@ -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<Vec<CssMediaQuery>>,
/// The span in which `extender` was defined.
// todo: no `Option<>`
pub span: Option<Span>,
pub left: Option<Box<Extension>>,

View File

@ -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<SelectorList> {
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<SelectorList> {
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<SelectorList> {
let extenders: IndexMap<ComplexSelector, Extension> = 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::<SassResult<Vec<CompoundSelector>>>()?;
@ -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<Vec<CssMediaQuery>>,
) -> Option<Vec<Pseudo>> {
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::<Vec<Pseudo>>();
@ -668,6 +679,7 @@ impl Extender {
} else {
Some(vec![pseudo.with_selector(Some(SelectorList {
components: complexes,
span: self.span,
}))])
}
}

View File

@ -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<ComplexSelector>,
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<Self> (the issue is figuring out the span)
pub fn resolve_parent_selectors(self, parent: Option<Self>, implicit_parent: bool) -> Self {
pub fn resolve_parent_selectors(
self,
parent: Option<Self>,
implicit_parent: bool,
) -> SassResult<Self> {
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<Vec<ComplexSelectorComponent>> =
@ -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::<SassResult<Vec<Vec<ComplexSelector>>>>()?,
),
}
span: self.span,
})
}
pub fn is_superselector(&self, other: &Self) -> bool {

View File

@ -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<Self> 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<Self> {
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 {

View File

@ -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,
}))
}

View File

@ -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<SelectorList>,
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

View File

@ -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 \"&\"."
);

View File

@ -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."
);