diff --git a/src/atrule/keyframes.rs b/src/atrule/keyframes.rs index e48b56a..ea7e246 100644 --- a/src/atrule/keyframes.rs +++ b/src/atrule/keyframes.rs @@ -2,6 +2,10 @@ use crate::parse::Stmt; #[derive(Debug, Clone)] 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 body: Vec, } diff --git a/src/atrule/kind.rs b/src/atrule/kind.rs index 9938452..3e63f32 100644 --- a/src/atrule/kind.rs +++ b/src/atrule/kind.rs @@ -2,7 +2,7 @@ use std::convert::TryFrom; use codemap::Spanned; -use crate::error::SassError; +use crate::{common::unvendor, error::SassError}; #[derive(Debug)] pub enum AtRuleKind { @@ -72,31 +72,35 @@ pub enum AtRuleKind { impl TryFrom<&Spanned> for AtRuleKind { type Error = Box; fn try_from(c: &Spanned) -> Result> { - Ok(match c.node.as_str() { - "use" => Self::Use, - "forward" => Self::Forward, - "import" => Self::Import, - "mixin" => Self::Mixin, - "include" => Self::Include, - "function" => Self::Function, - "return" => Self::Return, - "extend" => Self::Extend, - "at-root" => Self::AtRoot, - "error" => Self::Error, - "warn" => Self::Warn, - "debug" => Self::Debug, - "if" => Self::If, - "each" => Self::Each, - "for" => Self::For, - "while" => Self::While, - "charset" => Self::Charset, - "supports" => Self::Supports, - "keyframes" => Self::Keyframes, - "content" => Self::Content, - "media" => Self::Media, + match c.node.as_str() { + "use" => return Ok(Self::Use), + "forward" => return Ok(Self::Forward), + "import" => return Ok(Self::Import), + "mixin" => return Ok(Self::Mixin), + "include" => return Ok(Self::Include), + "function" => return Ok(Self::Function), + "return" => return Ok(Self::Return), + "extend" => return Ok(Self::Extend), + "at-root" => return Ok(Self::AtRoot), + "error" => return Ok(Self::Error), + "warn" => return Ok(Self::Warn), + "debug" => return Ok(Self::Debug), + "if" => return Ok(Self::If), + "each" => return Ok(Self::Each), + "for" => return Ok(Self::For), + "while" => return Ok(Self::While), + "charset" => return Ok(Self::Charset), + "supports" => return Ok(Self::Supports), + "content" => return Ok(Self::Content), + "media" => return Ok(Self::Media), "else" => return Err(("This at-rule is not allowed here.", 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()), }) } } diff --git a/src/output.rs b/src/output.rs index 0a14ada..e6a08c3 100644 --- a/src/output.rs +++ b/src/output.rs @@ -157,8 +157,12 @@ impl Css { })? } Stmt::Keyframes(k) => { - let Keyframes { name, body } = *k; - vals.push(Toplevel::Keyframes(Box::new(Keyframes { name, body }))) + let Keyframes { rule, name, body } = *k; + vals.push(Toplevel::Keyframes(Box::new(Keyframes { + rule, + name, + body, + }))) } k @ Stmt::KeyframesRuleSet(..) => { unreachable!("@keyframes ruleset {:?}", k) @@ -324,13 +328,13 @@ impl Css { writeln!(buf, "{}}}", padding)?; } Toplevel::Keyframes(k) => { - let Keyframes { name, body } = *k; + let Keyframes { rule, name, body } = *k; if should_emit_newline { should_emit_newline = false; writeln!(buf)?; } - write!(buf, "{}@keyframes", padding)?; + write!(buf, "{}@{}", padding, rule)?; if !name.is_empty() { write!(buf, " {}", name)?; diff --git a/src/parse/keyframes.rs b/src/parse/keyframes.rs index b24ec30..0e6e1fe 100644 --- a/src/parse/keyframes.rs +++ b/src/parse/keyframes.rs @@ -55,7 +55,14 @@ impl<'a, 'b> KeyframesSelectorParser<'a, 'b> { } } '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: '%', .. })) { return Err(("expected \"%\".", tok.pos).into()); } @@ -177,7 +184,7 @@ impl<'a> Parser<'a> { Err(("expected \"{\".", span).into()) } - pub(super) fn parse_keyframes(&mut self) -> SassResult { + pub(super) fn parse_keyframes(&mut self, rule: String) -> SassResult { let name = self.parse_keyframes_name()?; self.whitespace(); @@ -200,6 +207,6 @@ impl<'a> Parser<'a> { } .parse_stmt()?; - Ok(Stmt::Keyframes(Box::new(Keyframes { name, body }))) + Ok(Stmt::Keyframes(Box::new(Keyframes { rule, name, body }))) } } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 5c641c5..06f4dc3 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -200,7 +200,9 @@ impl<'a> Parser<'a> { AtRuleKind::Forward => todo!("@forward not yet implemented"), AtRuleKind::Extend => self.parse_extend()?, 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()?, diff --git a/tests/keyframes.rs b/tests/keyframes.rs index ea22d8b..1bd77ad 100644 --- a/tests/keyframes.rs +++ b/tests/keyframes.rs @@ -121,3 +121,21 @@ test!( }", "@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" +);