simplify lookahead for @use
and @media
This commit is contained in:
parent
c2e84e3854
commit
a34aa32128
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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>> {
|
||||
|
@ -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();
|
||||
|
@ -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 \"(\"."
|
||||
);
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user