simplify lookahead for @use and @media

This commit is contained in:
Connor Skees 2021-07-19 20:50:12 -04:00
parent c2e84e3854
commit a34aa32128
6 changed files with 106 additions and 80 deletions

View File

@ -4,7 +4,7 @@ use codemap::Spanned;
use crate::{common::unvendor, error::SassError};
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq)]
pub enum AtRuleKind {
// Sass specific @rules
/// Loads mixins, functions, and variables from other Sass

View File

@ -1,6 +1,6 @@
use crate::{
error::SassResult,
utils::{is_name_start, peek_ident_no_interpolation},
utils::is_name_start,
{Cow, Token},
};
@ -10,23 +10,25 @@ impl<'a> Parser<'a> {
/// Peeks to see if the `ident` is at the current position. If it is,
/// consume the identifier
pub fn scan_identifier(&mut self, ident: &'static str, case_insensitive: bool) -> bool {
let mut peeked_identifier =
match peek_ident_no_interpolation(self.toks, false, self.span_before) {
Ok(v) => v.node,
Err(..) => return false,
};
let start = self.toks.cursor();
let mut peeked_identifier = match self.parse_identifier_no_interpolation(false) {
Ok(v) => v.node,
Err(..) => {
self.toks.set_cursor(start);
return false;
}
};
if case_insensitive {
peeked_identifier.make_ascii_lowercase();
}
if peeked_identifier == ident {
self.toks.truncate_iterator_to_cursor();
self.toks.next();
return true;
}
self.toks.reset_cursor();
self.toks.set_cursor(start);
false
}

View File

@ -136,12 +136,14 @@ impl<'a> Parser<'a> {
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())
Err((format!("Expected \"{}\".", ident), this_ident.span).into())
}
fn parse_stmt(&mut self) -> SassResult<Vec<Stmt>> {

View File

@ -13,79 +13,89 @@ use crate::{
lexer::Lexer,
parse::{common::Comment, Parser, Stmt, VariableValue},
scope::Scope,
utils::peek_ident_no_interpolation,
Token,
};
impl<'a> Parser<'a> {
fn parse_module_alias(&mut self) -> SassResult<Option<String>> {
if let Some(Token { kind: 'a', .. }) | Some(Token { kind: 'A', .. }) = self.toks.peek() {
let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?;
ident.node.make_ascii_lowercase();
if ident.node != "as" {
return Err(("expected \";\".", ident.span).into());
}
self.whitespace_or_comment();
if let Some(Token { kind: '*', .. }) = self.toks.peek() {
self.toks.next();
return Ok(Some('*'.to_string()));
}
let name = self.parse_identifier_no_interpolation(false)?;
return Ok(Some(name.node));
if !matches!(
self.toks.peek(),
Some(Token { kind: 'a', .. }) | Some(Token { kind: 'A', .. })
) {
return Ok(None);
}
Ok(None)
let mut ident = self.parse_identifier_no_interpolation(false)?;
ident.node.make_ascii_lowercase();
if ident.node != "as" {
return Err(("expected \";\".", ident.span).into());
}
self.whitespace_or_comment();
if let Some(Token { kind: '*', .. }) = self.toks.peek() {
self.toks.next();
return Ok(Some('*'.to_string()));
}
let name = self.parse_identifier_no_interpolation(false)?;
Ok(Some(name.node))
}
fn parse_module_config(&mut self) -> SassResult<ModuleConfig> {
let mut config = ModuleConfig::default();
if let Some(Token { kind: 'w', .. }) | Some(Token { kind: 'W', .. }) = self.toks.peek() {
let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?;
ident.node.make_ascii_lowercase();
if ident.node != "with" {
return Err(("expected \";\".", ident.span).into());
}
if !matches!(
self.toks.peek(),
Some(Token { kind: 'w', .. }) | Some(Token { kind: 'W', .. })
) {
return Ok(config);
}
let mut ident = self.parse_identifier_no_interpolation(false)?;
ident.node.make_ascii_lowercase();
if ident.node != "with" {
return Err(("expected \";\".", ident.span).into());
}
self.whitespace_or_comment();
self.span_before = ident.span;
self.expect_char('(')?;
loop {
self.whitespace_or_comment();
self.expect_char('$')?;
let name = self.parse_identifier_no_interpolation(false)?;
self.whitespace_or_comment();
self.expect_char(':')?;
self.whitespace_or_comment();
self.span_before = ident.span;
let value = self.parse_value(false, &|toks| {
matches!(
toks.peek(),
Some(Token { kind: ',', .. }) | Some(Token { kind: ')', .. })
)
})?;
self.expect_char('(')?;
config.insert(name.map_node(|n| n.into()), value)?;
loop {
self.whitespace_or_comment();
self.expect_char('$')?;
let name = self.parse_identifier_no_interpolation(false)?;
self.whitespace_or_comment();
self.expect_char(':')?;
self.whitespace_or_comment();
let value = self.parse_value(false, &|toks| {
matches!(
toks.peek(),
Some(Token { kind: ',', .. }) | Some(Token { kind: ')', .. })
)
})?;
config.insert(name.map_node(|n| n.into()), value)?;
match self.toks.next() {
Some(Token { kind: ',', .. }) => {
continue;
}
Some(Token { kind: ')', .. }) => {
break;
}
Some(..) | None => {
return Err(("expected \")\".", self.span_before).into());
}
match self.toks.next() {
Some(Token { kind: ',', .. }) => {
continue;
}
Some(Token { kind: ')', .. }) => {
break;
}
Some(..) | None => {
return Err(("expected \")\".", self.span_before).into());
}
}
}
@ -157,27 +167,25 @@ impl<'a> Parser<'a> {
loop {
self.whitespace();
match self.toks.peek() {
Some(Token { kind: '@', .. }) => {
self.toks.advance_cursor();
let start = self.toks.cursor();
self.toks.next();
if let Some(Token { kind, .. }) = self.toks.peek() {
if !matches!(kind, 'a'..='z' | 'A'..='Z' | '\\') {
if !matches!(kind, 'u' | 'U' | '\\') {
self.toks.set_cursor(start);
break;
}
}
match AtRuleKind::try_from(&peek_ident_no_interpolation(
self.toks,
false,
self.span_before,
)?)? {
AtRuleKind::Use => {
self.toks.truncate_iterator_to_cursor();
}
_ => {
break;
}
let ident = self.parse_identifier_no_interpolation(false)?;
if AtRuleKind::try_from(&ident)? != AtRuleKind::Use {
self.toks.set_cursor(start);
break;
}
self.whitespace_or_comment();

View File

@ -231,3 +231,8 @@ error!(
invalid_toplevel_selector,
"@if true { & { } }", "Error: Top-level selectors may not contain the parent selector \"&\"."
);
error!(
#[ignore = "unsure what the exact rule is here wrt denying interpolation (@media allows this)"]
denies_interpolated_at_rule,
"@#{if} true { a { color: red; } }", "Error: expected \"(\"."
);

View File

@ -161,6 +161,15 @@ test!(
}",
"@media foo {\n a {\n color: red;\n }\n}\n@media bar {\n a {\n color: green;\n }\n}\n"
);
test!(
allows_interpolated_at_rule,
"@#{media} (true) {
a {
color: red;
}
}",
"@media (true) {\n a {\n color: red;\n }\n}\n"
);
error!(
media_feature_missing_closing_paren,