Handle curly braces in quotes in styles

This commit is contained in:
ConnorSkees 2020-02-24 17:47:32 -05:00
parent c2e339fb5b
commit 8c6be57872
4 changed files with 55 additions and 10 deletions

View File

@ -100,6 +100,13 @@ impl Token {
pub fn equals_symbol(&self, s: Symbol) -> bool {
self.kind.equals_symbol(s)
}
pub fn from_string(s: String) -> Self {
Token {
kind: TokenKind::Ident(s),
pos: Pos::new(),
}
}
}
impl IsWhitespace for Token {

View File

@ -1,7 +1,7 @@
use crate::common::{Pos, Scope, Symbol};
use crate::error::SassResult;
use crate::selector::Selector;
use crate::utils::{devour_whitespace, parse_interpolation};
use crate::utils::{devour_whitespace, parse_interpolation, parse_quoted_string};
use crate::value::Value;
use crate::{Expr, Token, TokenKind};
use std::fmt::{self, Display};
@ -35,7 +35,7 @@ impl Style {
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
StyleParser::new(scope, super_selector).parse_style_value(toks)
StyleParser::new(scope, super_selector).parse_style_value(toks, scope)
}
pub fn from_tokens<I: Iterator<Item = Token>>(
@ -44,7 +44,7 @@ impl Style {
super_selector: &Selector,
super_property: String,
) -> SassResult<Expr> {
StyleParser::new(scope, super_selector).eat_style_group(toks, super_property)
StyleParser::new(scope, super_selector).eat_style_group(toks, super_property, scope)
}
}
@ -64,6 +64,7 @@ impl<'a> StyleParser<'a> {
pub(crate) fn parse_style_value<I: Iterator<Item = Token>>(
&self,
toks: &mut Peekable<I>,
scope: &Scope,
) -> SassResult<Value> {
let mut style = Vec::new();
let mut n = 0;
@ -83,6 +84,16 @@ impl<'a> StyleParser<'a> {
n -= 1;
}
}
ref q @ TokenKind::Symbol(Symbol::DoubleQuote)
| ref q @ TokenKind::Symbol(Symbol::SingleQuote) => {
let q = q.clone();
let tok = toks.next().unwrap();
style.push(tok.clone());
style.push(Token::from_string(
parse_quoted_string(toks, scope, q)?.unquote().to_string(),
));
style.push(tok);
}
TokenKind::Symbol(Symbol::OpenCurlyBrace)
| TokenKind::Symbol(Symbol::SemiColon) => break,
TokenKind::Symbol(Symbol::BitAnd) => {
@ -104,6 +115,7 @@ impl<'a> StyleParser<'a> {
&self,
toks: &mut Peekable<I>,
super_property: String,
scope: &Scope,
) -> SassResult<Expr> {
let mut styles = Vec::new();
devour_whitespace(toks);
@ -116,7 +128,7 @@ impl<'a> StyleParser<'a> {
let property = self.parse_property(toks, super_property.clone())?;
if let Some(tok) = toks.peek() {
if tok.equals_symbol(Symbol::OpenCurlyBrace) {
match self.eat_style_group(toks, property)? {
match self.eat_style_group(toks, property, scope)? {
Expr::Styles(s) => styles.extend(s),
Expr::Style(s) => styles.push(*s),
_ => unreachable!(),
@ -134,7 +146,7 @@ impl<'a> StyleParser<'a> {
continue;
}
}
let value = self.parse_style_value(toks)?;
let value = self.parse_style_value(toks, scope)?;
match toks.peek().unwrap().kind {
TokenKind::Symbol(Symbol::CloseCurlyBrace) => {
styles.push(Style { property, value });
@ -149,7 +161,7 @@ impl<'a> StyleParser<'a> {
property: property.clone(),
value,
});
match self.eat_style_group(toks, property)? {
match self.eat_style_group(toks, property, scope)? {
Expr::Style(s) => styles.push(*s),
Expr::Styles(s) => styles.extend(s),
_ => unreachable!(),
@ -173,7 +185,7 @@ impl<'a> StyleParser<'a> {
}
}
_ => {
let val = self.parse_style_value(toks)?;
let val = self.parse_style_value(toks, scope)?;
match toks.peek().unwrap().kind {
TokenKind::Symbol(Symbol::CloseCurlyBrace) => {}
TokenKind::Symbol(Symbol::SemiColon) => {
@ -185,7 +197,7 @@ impl<'a> StyleParser<'a> {
property: super_property.clone(),
value: val,
}];
match self.eat_style_group(toks, super_property)? {
match self.eat_style_group(toks, super_property, scope)? {
Expr::Style(s) => v.push(*s),
Expr::Styles(s) => v.extend(s),
_ => unreachable!(),

View File

@ -171,14 +171,26 @@ pub(crate) fn parse_quoted_string<I: Iterator<Item = Token>>(
{
break
}
TokenKind::Symbol(Symbol::DoubleQuote) if is_escaped => {
s.push('\\');
s.push('"');
is_escaped = false;
continue;
}
TokenKind::Symbol(Symbol::SingleQuote)
if !is_escaped && q == TokenKind::Symbol(Symbol::SingleQuote) =>
{
break
}
TokenKind::Symbol(Symbol::SingleQuote) if is_escaped => {
s.push('\\');
s.push('\'');
is_escaped = false;
continue;
}
TokenKind::Symbol(Symbol::BackSlash) if !is_escaped => is_escaped = true,
TokenKind::Symbol(Symbol::BackSlash) => s.push('\\'),
TokenKind::Interpolation => {
TokenKind::Interpolation if !is_escaped => {
found_interpolation = true;
s.push_str(
&parse_interpolation(toks, scope)?
@ -188,13 +200,21 @@ pub(crate) fn parse_quoted_string<I: Iterator<Item = Token>>(
);
continue;
}
TokenKind::Interpolation => {
s.push('#');
s.push('{');
is_escaped = false;
continue;
}
_ => {}
}
if is_escaped && tok.kind != TokenKind::Symbol(Symbol::BackSlash) {
is_escaped = false;
}
if tok.kind != TokenKind::Symbol(Symbol::BackSlash) {
s.push_str(&tok.kind.to_string());
}
}
let quotes = if found_interpolation {
QuoteKind::Double
} else {

View File

@ -141,3 +141,9 @@ test!(
"foo {\n a: b {\n c: d {\n e: f;\n }\n }\n}\n",
"foo {\n a: b;\n a-c: d;\n a-c-e: f;\n}\n"
);
test!(curly_braces_in_quotes, "a {\n color: \"{foo}\";\n}\n");
test!(
escaped_interpolation,
"a {\n color: \"\\#{foo}\";\n}\n",
"a {\n color: \"#{foo}\";\n}\n"
);