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
|
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((
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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 `&`."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 \"}\"."
|
||||||
);
|
);
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user