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 let mut parsed_selectors = selectors
.into_iter() .into_iter()
.map(|s| { .map(|s| s.node.to_selector(parser, "selectors", false))
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)
}
})
.collect::<SassResult<Vec<Selector>>>()?; .collect::<SassResult<Vec<Selector>>>()?;
let first = parsed_selectors.remove(0); 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)?; args.max_args(3)?;
let selector = args let selector = args
.get_err(0, "selector")? .get_err(0, "selector")?
.to_selector(parser, "selector", false)?; .to_selector(parser, "selector", true)?;
let target = args let target = args
.get_err(1, "original")? .get_err(1, "original")?
.to_selector(parser, "original", false)?; .to_selector(parser, "original", true)?;
let source = args let source = args
.get_err(2, "replacement")? .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()) 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)?; args.max_args(2)?;
let selector1 = args let selector1 = args
.get_err(0, "selector1")? .get_err(0, "selector1")?
.to_selector(parser, "selector1", false)?; .to_selector(parser, "selector1", true)?;
if selector1.contains_parent_selector() { if selector1.contains_parent_selector() {
return Err(( return Err((
@ -184,7 +177,7 @@ pub(crate) fn selector_unify(mut args: CallArgs, parser: &mut Parser<'_>) -> Sas
let selector2 = args let selector2 = args
.get_err(1, "selector2")? .get_err(1, "selector2")?
.to_selector(parser, "selector2", false)?; .to_selector(parser, "selector2", true)?;
if selector2.contains_parent_selector() { if selector2.contains_parent_selector() {
return Err(( return Err((

View File

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

View File

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

View File

@ -194,14 +194,14 @@ impl<'a, 'b> SelectorParser<'a, 'b> {
} }
fn parse_compound_selector(&mut self) -> SassResult<CompoundSelector> { 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() { while let Some(Token { kind, .. }) = self.parser.toks.peek() {
if !is_simple_selector_start(*kind) { if !is_simple_selector_start(*kind) {
break; break;
} }
components.push(self.parse_simple_selector(false)?); components.push(self.parse_simple_selector(Some(false))?);
} }
Ok(CompoundSelector { components }) Ok(CompoundSelector { components })
@ -240,7 +240,10 @@ impl<'a, 'b> SelectorParser<'a, 'b> {
} }
/// Consumes a simple selector. /// 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() { match self.parser.toks.peek() {
Some(Token { kind: '[', .. }) => self.parse_attribute_selector(), Some(Token { kind: '[', .. }) => self.parse_attribute_selector(),
Some(Token { kind: '.', .. }) => self.parse_class_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: ':', .. }) => self.parse_pseudo_selector(),
Some(Token { kind: '&', .. }) => { 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()); return Err(("Parent selectors aren't allowed here.", self.span).into());
} }
self.parse_parent_selector() self.parse_parent_selector()
} }
_ => self.parse_type_or_universal_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), is_class: !element && !is_fake_pseudo_element(&name),
name: name.node, name: name.node,
selector, selector,
// todo: we can store the reference to this
is_syntactic_class: !element, is_syntactic_class: !element,
argument, argument,
span: self.span, span: self.span,

View File

@ -75,7 +75,7 @@ impl fmt::Display for SimpleSelector {
Self::Pseudo(pseudo) => write!(f, "{}", pseudo), Self::Pseudo(pseudo) => write!(f, "{}", pseudo),
Self::Type(name) => write!(f, "{}", name), Self::Type(name) => write!(f, "{}", name),
Self::Attribute(attr) => write!(f, "{}", attr), 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" "@-ms-viewport {\n width: device-width;\n}\n"
); );
error!( error!(
#[ignore = "we do not currently validate missing closing curly braces"]
missing_closing_curly_brace, missing_closing_curly_brace,
"@at-root {", "Error: expected \"}\"." "@at-root {", "Error: expected \"}\"."
); );

View File

@ -1887,6 +1887,13 @@ error!(
}", }",
"Error: Expected \"optional\"." "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_loop (massive test)
// todo: extend tests in folders // todo: extend tests in folders