more robustly parse !optional in selectors

This commit is contained in:
Connor Skees 2020-08-07 13:40:22 -04:00
parent 36a55e582c
commit 38a37a3997
4 changed files with 26 additions and 10 deletions

View File

@ -7,7 +7,7 @@ use crate::{
use super::Parser; use super::Parser;
impl<'a> Parser<'a> { impl<'a> Parser<'a> {
pub fn scan_identifier(&mut self, ident: &str) -> SassResult<bool> { pub fn scan_identifier(&mut self, ident: &'static str) -> SassResult<bool> {
let peeked_identifier = let peeked_identifier =
match peek_ident_no_interpolation(self.toks, false, self.span_before) { match peek_ident_no_interpolation(self.toks, false, self.span_before) {
Ok(v) => v.node, Ok(v) => v.node,

View File

@ -17,10 +17,7 @@ use crate::{
ComplexSelectorComponent, ExtendRule, ExtendedSelector, Extender, Selector, SelectorParser, ComplexSelectorComponent, ExtendRule, ExtendedSelector, Extender, Selector, SelectorParser,
}, },
style::Style, style::Style,
utils::{ utils::{read_until_closing_curly_brace, read_until_semicolon_or_closing_curly_brace},
peek_ident_no_interpolation, read_until_closing_curly_brace,
read_until_semicolon_or_closing_curly_brace,
},
value::Value, value::Value,
Options, {Cow, Token}, Options, {Cow, Token},
}; };
@ -134,6 +131,16 @@ impl<'a> Parser<'a> {
false false
} }
pub fn expect_identifier(&mut self, ident: &'static str) -> SassResult<()> {
let this_ident = self.parse_identifier_no_interpolation(false)?;
self.span_before = this_ident.span;
if this_ident.node == ident {
return Ok(());
}
Err((format!("Expected \"{}\".", ident), self.span_before).into())
}
fn parse_stmt(&mut self) -> SassResult<Vec<Stmt>> { fn parse_stmt(&mut self) -> SassResult<Vec<Stmt>> {
let mut stmts = Vec::new(); let mut stmts = Vec::new();
while let Some(Token { kind, pos }) = self.toks.peek() { while let Some(Token { kind, pos }) = self.toks.peek() {
@ -420,13 +427,11 @@ impl<'a> Parser<'a> {
} }
} }
'!' => { '!' => {
if peek_ident_no_interpolation(self.toks, false, self.span_before)?.node if from_fn {
== "optional" self.expect_identifier("optional")?;
{
self.toks.truncate_iterator_to_cursor();
optional = true; optional = true;
} else { } else {
string.push('!'); return Err(("expected \"{\".", tok.pos).into());
} }
} }
c => string.push(c), c => string.push(c),

View File

@ -1882,6 +1882,13 @@ test!(
}", }",
":has(a >) b, :has(a >) :has(a >) :has(a >) b, :has(a >) :has(a >) :has(a >) b {\n color: red;\n}\n" ":has(a >) b, :has(a >) :has(a >) :has(a >) b, :has(a >) :has(a >) :has(a >) b {\n color: red;\n}\n"
); );
error!(
extend_optional_keyword_not_complete,
"a {
@extend a !opt;
}",
"Error: Expected \"optional\"."
);
// todo: extend_loop (massive test) // todo: extend_loop (massive test)
// todo: extend tests in folders // todo: extend tests in folders

View File

@ -712,3 +712,7 @@ error!(
toplevel_parent_selector_after_element, toplevel_parent_selector_after_element,
"a&{}", "Error: \"&\" may only used at the beginning of a compound selector." "a&{}", "Error: \"&\" may only used at the beginning of a compound selector."
); );
error!(
denies_optional_in_selector,
"a !optional {}", "Error: expected \"{\"."
);