allow @keyframes to have vendor prefixes and decimal selectors

This commit is contained in:
Connor Skees 2020-07-29 07:18:47 -04:00
parent 627bd62bb9
commit e0cecb4e5c
6 changed files with 71 additions and 32 deletions

View File

@ -2,6 +2,10 @@ use crate::parse::Stmt;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct Keyframes { pub(crate) struct Keyframes {
/// `@keyframes` can contain a browser prefix,
/// e.g. `@-webkit-keyframes { ... }`, and therefore
/// we cannot be certain of the name of the at-rule
pub rule: String,
pub name: String, pub name: String,
pub body: Vec<Stmt>, pub body: Vec<Stmt>,
} }

View File

@ -2,7 +2,7 @@ use std::convert::TryFrom;
use codemap::Spanned; use codemap::Spanned;
use crate::error::SassError; use crate::{common::unvendor, error::SassError};
#[derive(Debug)] #[derive(Debug)]
pub enum AtRuleKind { pub enum AtRuleKind {
@ -72,31 +72,35 @@ pub enum AtRuleKind {
impl TryFrom<&Spanned<String>> for AtRuleKind { impl TryFrom<&Spanned<String>> for AtRuleKind {
type Error = Box<SassError>; type Error = Box<SassError>;
fn try_from(c: &Spanned<String>) -> Result<Self, Box<SassError>> { fn try_from(c: &Spanned<String>) -> Result<Self, Box<SassError>> {
Ok(match c.node.as_str() { match c.node.as_str() {
"use" => Self::Use, "use" => return Ok(Self::Use),
"forward" => Self::Forward, "forward" => return Ok(Self::Forward),
"import" => Self::Import, "import" => return Ok(Self::Import),
"mixin" => Self::Mixin, "mixin" => return Ok(Self::Mixin),
"include" => Self::Include, "include" => return Ok(Self::Include),
"function" => Self::Function, "function" => return Ok(Self::Function),
"return" => Self::Return, "return" => return Ok(Self::Return),
"extend" => Self::Extend, "extend" => return Ok(Self::Extend),
"at-root" => Self::AtRoot, "at-root" => return Ok(Self::AtRoot),
"error" => Self::Error, "error" => return Ok(Self::Error),
"warn" => Self::Warn, "warn" => return Ok(Self::Warn),
"debug" => Self::Debug, "debug" => return Ok(Self::Debug),
"if" => Self::If, "if" => return Ok(Self::If),
"each" => Self::Each, "each" => return Ok(Self::Each),
"for" => Self::For, "for" => return Ok(Self::For),
"while" => Self::While, "while" => return Ok(Self::While),
"charset" => Self::Charset, "charset" => return Ok(Self::Charset),
"supports" => Self::Supports, "supports" => return Ok(Self::Supports),
"keyframes" => Self::Keyframes, "content" => return Ok(Self::Content),
"content" => Self::Content, "media" => return Ok(Self::Media),
"media" => Self::Media,
"else" => return Err(("This at-rule is not allowed here.", c.span).into()), "else" => return Err(("This at-rule is not allowed here.", c.span).into()),
"" => return Err(("Expected identifier.", c.span).into()), "" => return Err(("Expected identifier.", c.span).into()),
s => Self::Unknown(s.to_owned()), _ => {}
}
Ok(match unvendor(&c.node) {
"keyframes" => Self::Keyframes,
_ => Self::Unknown(c.node.to_owned()),
}) })
} }
} }

View File

@ -157,8 +157,12 @@ impl Css {
})? })?
} }
Stmt::Keyframes(k) => { Stmt::Keyframes(k) => {
let Keyframes { name, body } = *k; let Keyframes { rule, name, body } = *k;
vals.push(Toplevel::Keyframes(Box::new(Keyframes { name, body }))) vals.push(Toplevel::Keyframes(Box::new(Keyframes {
rule,
name,
body,
})))
} }
k @ Stmt::KeyframesRuleSet(..) => { k @ Stmt::KeyframesRuleSet(..) => {
unreachable!("@keyframes ruleset {:?}", k) unreachable!("@keyframes ruleset {:?}", k)
@ -324,13 +328,13 @@ impl Css {
writeln!(buf, "{}}}", padding)?; writeln!(buf, "{}}}", padding)?;
} }
Toplevel::Keyframes(k) => { Toplevel::Keyframes(k) => {
let Keyframes { name, body } = *k; let Keyframes { rule, name, body } = *k;
if should_emit_newline { if should_emit_newline {
should_emit_newline = false; should_emit_newline = false;
writeln!(buf)?; writeln!(buf)?;
} }
write!(buf, "{}@keyframes", padding)?; write!(buf, "{}@{}", padding, rule)?;
if !name.is_empty() { if !name.is_empty() {
write!(buf, " {}", name)?; write!(buf, " {}", name)?;

View File

@ -55,7 +55,14 @@ impl<'a, 'b> KeyframesSelectorParser<'a, 'b> {
} }
} }
'0'..='9' => { '0'..='9' => {
let num = eat_whole_number(self.parser.toks); let mut num = eat_whole_number(self.parser.toks);
if let Some(Token { kind: '.', .. }) = self.parser.toks.peek() {
self.parser.toks.next();
num.push('.');
num.push_str(&eat_whole_number(self.parser.toks));
}
if !matches!(self.parser.toks.next(), Some(Token { kind: '%', .. })) { if !matches!(self.parser.toks.next(), Some(Token { kind: '%', .. })) {
return Err(("expected \"%\".", tok.pos).into()); return Err(("expected \"%\".", tok.pos).into());
} }
@ -177,7 +184,7 @@ impl<'a> Parser<'a> {
Err(("expected \"{\".", span).into()) Err(("expected \"{\".", span).into())
} }
pub(super) fn parse_keyframes(&mut self) -> SassResult<Stmt> { pub(super) fn parse_keyframes(&mut self, rule: String) -> SassResult<Stmt> {
let name = self.parse_keyframes_name()?; let name = self.parse_keyframes_name()?;
self.whitespace(); self.whitespace();
@ -200,6 +207,6 @@ impl<'a> Parser<'a> {
} }
.parse_stmt()?; .parse_stmt()?;
Ok(Stmt::Keyframes(Box::new(Keyframes { name, body }))) Ok(Stmt::Keyframes(Box::new(Keyframes { rule, name, body })))
} }
} }

View File

@ -200,7 +200,9 @@ impl<'a> Parser<'a> {
AtRuleKind::Forward => todo!("@forward not yet implemented"), AtRuleKind::Forward => todo!("@forward not yet implemented"),
AtRuleKind::Extend => self.parse_extend()?, AtRuleKind::Extend => self.parse_extend()?,
AtRuleKind::Supports => stmts.push(self.parse_supports()?), AtRuleKind::Supports => stmts.push(self.parse_supports()?),
AtRuleKind::Keyframes => stmts.push(self.parse_keyframes()?), AtRuleKind::Keyframes => {
stmts.push(self.parse_keyframes(kind_string.node)?)
}
} }
} }
'$' => self.parse_variable_declaration()?, '$' => self.parse_variable_declaration()?,

View File

@ -121,3 +121,21 @@ test!(
}", }",
"@keyframes {\n to {\n color: red;\n }\n from {\n color: green;\n }\n}\n" "@keyframes {\n to {\n color: red;\n }\n from {\n color: green;\n }\n}\n"
); );
test!(
keyframes_vendor_prefix,
"@-webkit-keyframes foo {
0% {
color: red;
}
}",
"@-webkit-keyframes foo {\n 0% {\n color: red;\n }\n}\n"
);
test!(
keyframes_allow_decimal_selector,
"@keyframes foo {
12.5% {
color: red;
}
}",
"@-webkit-keyframes foo {\n 0% {\n color: red;\n }\n}\n"
);