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 { pub fn equals_symbol(&self, s: Symbol) -> bool {
self.kind.equals_symbol(s) self.kind.equals_symbol(s)
} }
pub fn from_string(s: String) -> Self {
Token {
kind: TokenKind::Ident(s),
pos: Pos::new(),
}
}
} }
impl IsWhitespace for Token { impl IsWhitespace for Token {

View File

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

View File

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

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 c: d {\n e: f;\n }\n }\n}\n",
"foo {\n a: b;\n a-c: d;\n a-c-e: f;\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"
);