Skip to content

Commit aa13001

Browse files
authored
fix: CSS-wide keywords and none in @keyframes cannot remove quotes (parcel-bundler#267)
1 parent 4485c3d commit aa13001

File tree

4 files changed

+125
-26
lines changed

4 files changed

+125
-26
lines changed

src/lib.rs

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5396,37 +5396,77 @@ mod tests {
53965396
fn test_keyframes() {
53975397
minify_test(
53985398
r#"
5399-
@keyframes test {
5400-
from {
5401-
background: green;
5402-
}
5403-
5404-
50% {
5405-
background: red;
5406-
}
5407-
5399+
@keyframes "test" {
54085400
100% {
54095401
background: blue
54105402
}
54115403
}
54125404
"#,
5413-
"@keyframes test{0%{background:green}50%{background:red}to{background:#00f}}",
5405+
"@keyframes test{to{background:#00f}}",
54145406
);
54155407
minify_test(
54165408
r#"
54175409
@keyframes test {
5410+
100% {
5411+
background: blue
5412+
}
5413+
}
5414+
"#,
5415+
"@keyframes test{to{background:#00f}}",
5416+
);
5417+
5418+
// CSS-wide keywords and `none` cannot remove quotes.
5419+
minify_test(
5420+
r#"
5421+
@keyframes "revert" {
54185422
from {
54195423
background: green;
5420-
background-color: red;
54215424
}
5425+
}
5426+
"#,
5427+
"@keyframes \"revert\"{0%{background:green}}",
5428+
);
54225429

5423-
100% {
5424-
background: blue
5430+
minify_test(
5431+
r#"
5432+
@keyframes "none" {
5433+
from {
5434+
background: green;
54255435
}
54265436
}
54275437
"#,
5428-
"@keyframes test{0%{background:red}to{background:#00f}}",
5438+
"@keyframes \"none\"{0%{background:green}}",
5439+
);
5440+
5441+
// CSS-wide keywords without quotes throws an error.
5442+
error_test(
5443+
r#"
5444+
@keyframes revert {}
5445+
"#,
5446+
ParserError::UnexpectedToken(Token::Ident("revert".into())),
5447+
);
5448+
5449+
error_test(
5450+
r#"
5451+
@keyframes revert-layer {}
5452+
"#,
5453+
ParserError::UnexpectedToken(Token::Ident("revert-layer".into())),
5454+
);
5455+
5456+
error_test(
5457+
r#"
5458+
@keyframes none {}
5459+
"#,
5460+
ParserError::UnexpectedToken(Token::Ident("none".into())),
54295461
);
5462+
5463+
error_test(
5464+
r#"
5465+
@keyframes NONE {}
5466+
"#,
5467+
ParserError::UnexpectedToken(Token::Ident("NONE".into())),
5468+
);
5469+
54305470
minify_test(
54315471
r#"
54325472
@-webkit-keyframes test {

src/parser.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::rules::{
1313
document::MozDocumentRule,
1414
font_face::{FontFaceDeclarationParser, FontFaceRule},
1515
import::ImportRule,
16-
keyframes::{KeyframeListParser, KeyframesRule},
16+
keyframes::{KeyframeListParser, KeyframesName, KeyframesRule},
1717
layer::LayerName,
1818
media::MediaRule,
1919
namespace::NamespaceRule,
@@ -122,7 +122,7 @@ pub enum AtRulePrelude<'i> {
122122
/// A @viewport rule prelude.
123123
Viewport(VendorPrefix),
124124
/// A @keyframes rule, with its animation name and vendor prefix if exists.
125-
Keyframes(CustomIdent<'i>, VendorPrefix),
125+
Keyframes(KeyframesName<'i>, VendorPrefix),
126126
/// A @page rule prelude.
127127
Page(Vec<PageSelector<'i>>),
128128
/// A @-moz-document rule.
@@ -425,14 +425,8 @@ impl<'a, 'o, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'o, 'i> {
425425
VendorPrefix::None
426426
};
427427

428-
let location = input.current_source_location();
429-
let name = match *input.next()? {
430-
Token::Ident(ref s) => s.into(),
431-
Token::QuotedString(ref s) => s.into(),
432-
ref t => return Err(location.new_unexpected_token_error(t.clone())),
433-
};
434-
435-
Ok(AtRulePrelude::Keyframes(CustomIdent(name), prefix))
428+
let name = input.try_parse(KeyframesName::parse)?;
429+
Ok(AtRulePrelude::Keyframes(name, prefix))
436430
},
437431
"page" => {
438432
let selectors = input.try_parse(|input| input.parse_comma_separated(PageSelector::parse)).unwrap_or_default();

src/rules/keyframes.rs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::traits::{Parse, ToCss};
1515
use crate::values::color::ColorFallbackKind;
1616
use crate::values::ident::CustomIdent;
1717
use crate::values::percentage::Percentage;
18+
use crate::values::string::CowArcStr;
1819
use crate::vendor_prefix::VendorPrefix;
1920
use cssparser::*;
2021

@@ -23,8 +24,9 @@ use cssparser::*;
2324
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2425
pub struct KeyframesRule<'i> {
2526
/// The animation name.
27+
/// <keyframes-name> = <custom-ident> | <string>
2628
#[cfg_attr(feature = "serde", serde(borrow))]
27-
pub name: CustomIdent<'i>,
29+
pub name: KeyframesName<'i>,
2830
/// A list of keyframes in the animation.
2931
pub keyframes: Vec<Keyframe<'i>>,
3032
/// A vendor prefix for the rule, e.g. `@-webkit-keyframes`.
@@ -33,6 +35,65 @@ pub struct KeyframesRule<'i> {
3335
pub loc: Location,
3436
}
3537

38+
/// KeyframesName
39+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
40+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
41+
pub enum KeyframesName<'i> {
42+
/// `<custom-ident>` of a `@keyframes` name.
43+
#[cfg_attr(feature = "serde", serde(borrow))]
44+
Ident(CustomIdent<'i>),
45+
46+
/// `<string>` of a `@keyframes` name.
47+
#[cfg_attr(feature = "serde", serde(borrow))]
48+
Custom(CowArcStr<'i>),
49+
}
50+
51+
impl<'i> Parse<'i> for KeyframesName<'i> {
52+
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
53+
match input.next()?.clone() {
54+
Token::Ident(ref s) => {
55+
// CSS-wide keywords without quotes throws an error.
56+
match_ignore_ascii_case! { &*s,
57+
"none" | "initial" | "inherit" | "unset" | "default" | "revert" | "revert-layer" => {
58+
Err(input.new_unexpected_token_error(Token::Ident(s.clone())))
59+
},
60+
_ => {
61+
Ok(KeyframesName::Ident(CustomIdent(s.into())))
62+
}
63+
}
64+
}
65+
66+
Token::QuotedString(ref s) => Ok(KeyframesName::Custom(s.into())),
67+
t => return Err(input.new_unexpected_token_error(t.clone())),
68+
}
69+
}
70+
}
71+
72+
impl<'i> ToCss for KeyframesName<'i> {
73+
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
74+
where
75+
W: std::fmt::Write,
76+
{
77+
match self {
78+
KeyframesName::Ident(ident) => {
79+
dest.write_ident(ident.0.as_ref())?;
80+
}
81+
KeyframesName::Custom(s) => {
82+
// CSS-wide keywords and `none` cannot remove quotes.
83+
match_ignore_ascii_case! { &*s,
84+
"none" | "initial" | "inherit" | "unset" | "default" | "revert" | "revert-layer" => {
85+
serialize_string(&s, dest)?;
86+
},
87+
_ => {
88+
dest.write_ident(s.as_ref())?;
89+
}
90+
}
91+
}
92+
}
93+
Ok(())
94+
}
95+
}
96+
3697
impl<'i> KeyframesRule<'i> {
3798
pub(crate) fn minify(&mut self, context: &mut MinifyContext<'_, 'i>) {
3899
context.handler_context.context = DeclarationContext::Keyframes;

src/rules/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ use nesting::NestingRule;
8585
use page::PageRule;
8686
use serde::Serialize;
8787
use std::collections::{HashMap, HashSet};
88+
use std::default::Default;
8889
use style::StyleRule;
8990
use supports::SupportsRule;
9091
use unknown::UnknownAtRule;
@@ -259,7 +260,10 @@ impl<'i> CssRuleList<'i> {
259260
for mut rule in self.0.drain(..) {
260261
match &mut rule {
261262
CssRule::Keyframes(keyframes) => {
262-
if context.unused_symbols.contains(keyframes.name.0.as_ref()) {
263+
if context
264+
.unused_symbols
265+
.contains(&keyframes.name.to_css_string(Default::default()).unwrap())
266+
{
263267
continue;
264268
}
265269
keyframes.minify(context);

0 commit comments

Comments
 (0)