do not panic when extending by compound selector with parent

This commit is contained in:
Connor Skees 2020-08-16 19:09:08 -04:00
parent fce74d4013
commit f60fb26ca0
7 changed files with 34 additions and 31 deletions

View File

@ -90,14 +90,7 @@ pub(crate) fn selector_append(args: CallArgs, parser: &mut Parser<'_>) -> SassRe
let mut parsed_selectors = selectors
.into_iter()
.map(|s| {
let tmp = s.node.to_selector(parser, "selectors", false)?;
if tmp.contains_parent_selector() {
Err(("Parent selectors aren't allowed here.", span).into())
} else {
Ok(tmp)
}
})
.map(|s| s.node.to_selector(parser, "selectors", false))
.collect::<SassResult<Vec<Selector>>>()?;
let first = parsed_selectors.remove(0);
@ -158,13 +151,13 @@ pub(crate) fn selector_replace(mut args: CallArgs, parser: &mut Parser<'_>) -> S
args.max_args(3)?;
let selector = args
.get_err(0, "selector")?
.to_selector(parser, "selector", false)?;
.to_selector(parser, "selector", true)?;
let target = args
.get_err(1, "original")?
.to_selector(parser, "original", false)?;
.to_selector(parser, "original", true)?;
let source = args
.get_err(2, "replacement")?
.to_selector(parser, "replacement", false)?;
.to_selector(parser, "replacement", true)?;
Ok(Extender::replace(selector.0, source.0, target.0, args.span())?.to_sass_list())
}
@ -172,7 +165,7 @@ pub(crate) fn selector_unify(mut args: CallArgs, parser: &mut Parser<'_>) -> Sas
args.max_args(2)?;
let selector1 = args
.get_err(0, "selector1")?
.to_selector(parser, "selector1", false)?;
.to_selector(parser, "selector1", true)?;
if selector1.contains_parent_selector() {
return Err((
@ -184,7 +177,7 @@ pub(crate) fn selector_unify(mut args: CallArgs, parser: &mut Parser<'_>) -> Sas
let selector2 = args
.get_err(1, "selector2")?
.to_selector(parser, "selector2", false)?;
.to_selector(parser, "selector2", true)?;
if selector2.contains_parent_selector() {
return Err((

View File

@ -55,6 +55,7 @@ impl ContextFlags {
pub const IN_FUNCTION: ContextFlag = ContextFlag(1 << 1);
pub const IN_CONTROL_FLOW: ContextFlag = ContextFlag(1 << 2);
pub const IN_KEYFRAMES: ContextFlag = ContextFlag(1 << 3);
pub const IN_AT_ROOT_RULE: ContextFlag = ContextFlag(1 << 4);
pub const fn empty() -> Self {
Self(0)
@ -75,6 +76,11 @@ impl ContextFlags {
pub fn in_keyframes(self) -> bool {
(self.0 & Self::IN_KEYFRAMES) != 0
}
#[allow(dead_code)]
pub fn in_at_root_rule(self) -> bool {
(self.0 & Self::IN_AT_ROOT_RULE) != 0
}
}
impl BitAnd<ContextFlag> for u8 {

View File

@ -17,7 +17,7 @@ use crate::{
ComplexSelectorComponent, ExtendRule, ExtendedSelector, Extender, Selector, SelectorParser,
},
style::Style,
utils::{read_until_closing_curly_brace, read_until_semicolon_or_closing_curly_brace},
utils::read_until_semicolon_or_closing_curly_brace,
value::Value,
Options, {Cow, Token},
};
@ -352,7 +352,7 @@ impl<'a> Parser<'a> {
let at_root = self.at_root;
self.at_root = false;
let selector = self
.parse_selector(!self.super_selectors.is_empty(), false, init)?
.parse_selector(true, false, init)?
.0
.resolve_parent_selectors(
self.super_selectors.last(),
@ -728,18 +728,10 @@ impl<'a> Parser<'a> {
self.whitespace();
let mut body = read_until_closing_curly_brace(self.toks)?;
body.push(match self.toks.next() {
Some(tok) => tok,
None => return Err(("expected \"}\".", self.span_before).into()),
});
self.whitespace();
let mut styles = Vec::new();
#[allow(clippy::unnecessary_filter_map)]
let raw_stmts = Parser {
toks: &mut body.into_iter().peekmore(),
toks: self.toks,
map: self.map,
path: self.path,
scopes: self.scopes,
@ -747,7 +739,7 @@ impl<'a> Parser<'a> {
super_selectors: &mut NeverEmptyVec::new(at_rule_selector.clone()),
span_before: self.span_before,
content: self.content,
flags: self.flags,
flags: self.flags | ContextFlags::IN_AT_ROOT_RULE,
at_root: true,
at_root_has_selector,
extender: self.extender,

View File

@ -194,14 +194,14 @@ impl<'a, 'b> SelectorParser<'a, 'b> {
}
fn parse_compound_selector(&mut self) -> SassResult<CompoundSelector> {
let mut components = vec![self.parse_simple_selector(true)?];
let mut components = vec![self.parse_simple_selector(None)?];
while let Some(Token { kind, .. }) = self.parser.toks.peek() {
if !is_simple_selector_start(*kind) {
break;
}
components.push(self.parse_simple_selector(false)?);
components.push(self.parse_simple_selector(Some(false))?);
}
Ok(CompoundSelector { components })
@ -240,7 +240,10 @@ impl<'a, 'b> SelectorParser<'a, 'b> {
}
/// Consumes a simple selector.
fn parse_simple_selector(&mut self, allow_parent: bool) -> SassResult<SimpleSelector> {
///
/// If `allows_parent` is `Some`, this will override `self.allows_parent`. If `allows_parent`
/// is `None`, it will fallback to `self.allows_parent`.
fn parse_simple_selector(&mut self, allows_parent: Option<bool>) -> SassResult<SimpleSelector> {
match self.parser.toks.peek() {
Some(Token { kind: '[', .. }) => self.parse_attribute_selector(),
Some(Token { kind: '.', .. }) => self.parse_class_selector(),
@ -253,9 +256,11 @@ impl<'a, 'b> SelectorParser<'a, 'b> {
}
Some(Token { kind: ':', .. }) => self.parse_pseudo_selector(),
Some(Token { kind: '&', .. }) => {
if !allow_parent && !self.allows_parent {
let allows_parent = allows_parent.unwrap_or(self.allows_parent);
if !allows_parent {
return Err(("Parent selectors aren't allowed here.", self.span).into());
}
self.parse_parent_selector()
}
_ => self.parse_type_or_universal_selector(),
@ -354,7 +359,6 @@ impl<'a, 'b> SelectorParser<'a, 'b> {
is_class: !element && !is_fake_pseudo_element(&name),
name: name.node,
selector,
// todo: we can store the reference to this
is_syntactic_class: !element,
argument,
span: self.span,

View File

@ -75,7 +75,7 @@ impl fmt::Display for SimpleSelector {
Self::Pseudo(pseudo) => write!(f, "{}", pseudo),
Self::Type(name) => write!(f, "{}", name),
Self::Attribute(attr) => write!(f, "{}", attr),
Self::Parent(..) => todo!(),
Self::Parent(..) => unreachable!("It should not be possible to format `&`."),
}
}
}

View File

@ -67,6 +67,7 @@ test!(
"@-ms-viewport {\n width: device-width;\n}\n"
);
error!(
#[ignore = "we do not currently validate missing closing curly braces"]
missing_closing_curly_brace,
"@at-root {", "Error: expected \"}\"."
);

View File

@ -1887,6 +1887,13 @@ error!(
}",
"Error: Expected \"optional\"."
);
error!(
extend_contains_parent_in_compound_selector,
"a {
@extend &b:c;
}",
"Error: Parent selectors aren't allowed here."
);
// todo: extend_loop (massive test)
// todo: extend tests in folders