do not panic when extending by compound selector with parent
This commit is contained in:
parent
fce74d4013
commit
f60fb26ca0
@ -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((
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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 `&`."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 \"}\"."
|
||||
);
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user