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};
|
use crate::{common::unvendor, error::SassError};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum AtRuleKind {
|
pub enum AtRuleKind {
|
||||||
// Sass specific @rules
|
// Sass specific @rules
|
||||||
/// Loads mixins, functions, and variables from other Sass
|
/// Loads mixins, functions, and variables from other Sass
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
error::SassResult,
|
error::SassResult,
|
||||||
utils::{is_name_start, peek_ident_no_interpolation},
|
utils::is_name_start,
|
||||||
{Cow, Token},
|
{Cow, Token},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -10,23 +10,25 @@ impl<'a> Parser<'a> {
|
|||||||
/// Peeks to see if the `ident` is at the current position. If it is,
|
/// Peeks to see if the `ident` is at the current position. If it is,
|
||||||
/// consume the identifier
|
/// consume the identifier
|
||||||
pub fn scan_identifier(&mut self, ident: &'static str, case_insensitive: bool) -> bool {
|
pub fn scan_identifier(&mut self, ident: &'static str, case_insensitive: bool) -> bool {
|
||||||
let mut peeked_identifier =
|
let start = self.toks.cursor();
|
||||||
match peek_ident_no_interpolation(self.toks, false, self.span_before) {
|
|
||||||
Ok(v) => v.node,
|
let mut peeked_identifier = match self.parse_identifier_no_interpolation(false) {
|
||||||
Err(..) => return false,
|
Ok(v) => v.node,
|
||||||
};
|
Err(..) => {
|
||||||
|
self.toks.set_cursor(start);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if case_insensitive {
|
if case_insensitive {
|
||||||
peeked_identifier.make_ascii_lowercase();
|
peeked_identifier.make_ascii_lowercase();
|
||||||
}
|
}
|
||||||
|
|
||||||
if peeked_identifier == ident {
|
if peeked_identifier == ident {
|
||||||
self.toks.truncate_iterator_to_cursor();
|
|
||||||
self.toks.next();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.toks.reset_cursor();
|
self.toks.set_cursor(start);
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -136,12 +136,14 @@ impl<'a> Parser<'a> {
|
|||||||
|
|
||||||
pub fn expect_identifier(&mut self, ident: &'static str) -> SassResult<()> {
|
pub fn expect_identifier(&mut self, ident: &'static str) -> SassResult<()> {
|
||||||
let this_ident = self.parse_identifier_no_interpolation(false)?;
|
let this_ident = self.parse_identifier_no_interpolation(false)?;
|
||||||
|
|
||||||
self.span_before = this_ident.span;
|
self.span_before = this_ident.span;
|
||||||
|
|
||||||
if this_ident.node == ident {
|
if this_ident.node == ident {
|
||||||
return Ok(());
|
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>> {
|
fn parse_stmt(&mut self) -> SassResult<Vec<Stmt>> {
|
||||||
|
@ -13,79 +13,89 @@ use crate::{
|
|||||||
lexer::Lexer,
|
lexer::Lexer,
|
||||||
parse::{common::Comment, Parser, Stmt, VariableValue},
|
parse::{common::Comment, Parser, Stmt, VariableValue},
|
||||||
scope::Scope,
|
scope::Scope,
|
||||||
utils::peek_ident_no_interpolation,
|
|
||||||
Token,
|
Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<'a> Parser<'a> {
|
impl<'a> Parser<'a> {
|
||||||
fn parse_module_alias(&mut self) -> SassResult<Option<String>> {
|
fn parse_module_alias(&mut self) -> SassResult<Option<String>> {
|
||||||
if let Some(Token { kind: 'a', .. }) | Some(Token { kind: 'A', .. }) = self.toks.peek() {
|
if !matches!(
|
||||||
let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?;
|
self.toks.peek(),
|
||||||
ident.node.make_ascii_lowercase();
|
Some(Token { kind: 'a', .. }) | Some(Token { kind: 'A', .. })
|
||||||
if ident.node != "as" {
|
) {
|
||||||
return Err(("expected \";\".", ident.span).into());
|
return Ok(None);
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
fn parse_module_config(&mut self) -> SassResult<ModuleConfig> {
|
||||||
let mut config = ModuleConfig::default();
|
let mut config = ModuleConfig::default();
|
||||||
|
|
||||||
if let Some(Token { kind: 'w', .. }) | Some(Token { kind: 'W', .. }) = self.toks.peek() {
|
if !matches!(
|
||||||
let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?;
|
self.toks.peek(),
|
||||||
ident.node.make_ascii_lowercase();
|
Some(Token { kind: 'w', .. }) | Some(Token { kind: 'W', .. })
|
||||||
if ident.node != "with" {
|
) {
|
||||||
return Err(("expected \";\".", ident.span).into());
|
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.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 {
|
match self.toks.next() {
|
||||||
self.whitespace_or_comment();
|
Some(Token { kind: ',', .. }) => {
|
||||||
self.expect_char('$')?;
|
continue;
|
||||||
|
}
|
||||||
let name = self.parse_identifier_no_interpolation(false)?;
|
Some(Token { kind: ')', .. }) => {
|
||||||
|
break;
|
||||||
self.whitespace_or_comment();
|
}
|
||||||
self.expect_char(':')?;
|
Some(..) | None => {
|
||||||
self.whitespace_or_comment();
|
return Err(("expected \")\".", self.span_before).into());
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,27 +167,25 @@ impl<'a> Parser<'a> {
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
self.whitespace();
|
self.whitespace();
|
||||||
|
|
||||||
match self.toks.peek() {
|
match self.toks.peek() {
|
||||||
Some(Token { kind: '@', .. }) => {
|
Some(Token { kind: '@', .. }) => {
|
||||||
self.toks.advance_cursor();
|
let start = self.toks.cursor();
|
||||||
|
|
||||||
|
self.toks.next();
|
||||||
|
|
||||||
if let Some(Token { kind, .. }) = self.toks.peek() {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match AtRuleKind::try_from(&peek_ident_no_interpolation(
|
let ident = self.parse_identifier_no_interpolation(false)?;
|
||||||
self.toks,
|
|
||||||
false,
|
if AtRuleKind::try_from(&ident)? != AtRuleKind::Use {
|
||||||
self.span_before,
|
self.toks.set_cursor(start);
|
||||||
)?)? {
|
break;
|
||||||
AtRuleKind::Use => {
|
|
||||||
self.toks.truncate_iterator_to_cursor();
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.whitespace_or_comment();
|
self.whitespace_or_comment();
|
||||||
|
@ -231,3 +231,8 @@ error!(
|
|||||||
invalid_toplevel_selector,
|
invalid_toplevel_selector,
|
||||||
"@if true { & { } }", "Error: Top-level selectors may not contain the parent 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"
|
"@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!(
|
error!(
|
||||||
media_feature_missing_closing_paren,
|
media_feature_missing_closing_paren,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user