Skip to content

Commit 0c77b63

Browse files
committed
Preserve unknown at rules as raw tokens
Closes parcel-bundler#227
1 parent bbb64b7 commit 0c77b63

File tree

5 files changed

+154
-20
lines changed

5 files changed

+154
-20
lines changed

src/lib.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19855,4 +19855,53 @@ mod tests {
1985519855
ParserError::UnexpectedToken(crate::properties::custom::Token::Ident("bar".into())),
1985619856
);
1985719857
}
19858+
19859+
#[test]
19860+
fn test_unknown_at_rules() {
19861+
minify_test("@foo;", "@foo;");
19862+
minify_test("@foo bar;", "@foo bar;");
19863+
minify_test("@foo (bar: baz);", "@foo (bar: baz);");
19864+
test(
19865+
r#"@foo test {
19866+
div {
19867+
color: red;
19868+
}
19869+
}"#,
19870+
indoc! {r#"
19871+
@foo test {
19872+
div { color: red; }
19873+
}
19874+
"#},
19875+
);
19876+
minify_test(
19877+
r#"@foo test {
19878+
div {
19879+
color: red;
19880+
}
19881+
}"#,
19882+
"@foo test{div { color: red; }}",
19883+
);
19884+
minify_test(
19885+
r#"@foo test {
19886+
foo: bar;
19887+
}"#,
19888+
"@foo test{foo: bar;}",
19889+
);
19890+
test(
19891+
r#"@foo {
19892+
foo: bar;
19893+
}"#,
19894+
indoc! {r#"
19895+
@foo {
19896+
foo: bar;
19897+
}
19898+
"#},
19899+
);
19900+
minify_test(
19901+
r#"@foo {
19902+
foo: bar;
19903+
}"#,
19904+
"@foo{foo: bar;}",
19905+
);
19906+
}
1985819907
}

src/parser.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::declaration::{parse_declaration, DeclarationBlock, DeclarationList};
22
use crate::error::{Error, ParserError};
33
use crate::media_query::*;
4+
use crate::properties::custom::TokenList;
45
use crate::rules::container::{ContainerName, ContainerRule};
56
use crate::rules::font_palette_values::FontPaletteValuesRule;
67
use crate::rules::layer::{LayerBlockRule, LayerStatementRule};
@@ -20,6 +21,7 @@ use crate::rules::{
2021
page::{PageRule, PageSelector},
2122
style::StyleRule,
2223
supports::{SupportsCondition, SupportsRule},
24+
unknown::UnknownAtRule,
2325
CssRule, CssRuleList, Location,
2426
};
2527
use crate::selector::{SelectorParser, Selectors};
@@ -144,6 +146,8 @@ pub enum AtRulePrelude<'i> {
144146
Property(DashedIdent<'i>),
145147
/// A @container prelude.
146148
Container(Option<ContainerName<'i>>, MediaCondition<'i>),
149+
/// An unknown prelude.
150+
Unknown(CowArcStr<'i>, TokenList<'i>),
147151
}
148152

149153
impl<'a, 'o, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a, 'o, 'i> {
@@ -276,6 +280,12 @@ impl<'a, 'o, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a, 'o, 'i> {
276280
AtRuleParser::rule_without_block(&mut self.nested(), prelude, start)?
277281
}
278282
AtRulePrelude::Charset => CssRule::Ignored,
283+
AtRulePrelude::Unknown(name, prelude) => CssRule::Unknown(UnknownAtRule {
284+
name,
285+
prelude,
286+
block: None,
287+
loc,
288+
}),
279289
_ => return Err(()),
280290
};
281291

@@ -461,7 +471,12 @@ impl<'a, 'o, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'o, 'i> {
461471
let condition = MediaCondition::parse(input, true)?;
462472
Ok(AtRulePrelude::Container(name, condition))
463473
},
464-
_ => Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name)))
474+
_ => {
475+
self.options.warn(input.new_error(BasicParseErrorKind::AtRuleInvalid(name.clone())));
476+
input.skip_whitespace();
477+
let tokens = TokenList::parse(input, &self.options, 0)?;
478+
Ok(AtRulePrelude::Unknown(name.into(), tokens))
479+
}
465480
}
466481
}
467482

@@ -575,6 +590,12 @@ impl<'a, 'o, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'o, 'i> {
575590
Err(input.new_unexpected_token_error(Token::CurlyBracketBlock))
576591
}
577592
AtRulePrelude::FontFeatureValues | AtRulePrelude::Nest(..) => unreachable!(),
593+
AtRulePrelude::Unknown(name, prelude) => Ok(CssRule::Unknown(UnknownAtRule {
594+
name,
595+
prelude,
596+
block: Some(TokenList::parse(input, &self.options, 0)?),
597+
loc,
598+
})),
578599
}
579600
}
580601

@@ -589,6 +610,12 @@ impl<'a, 'o, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'o, 'i> {
589610

590611
Ok(CssRule::LayerStatement(LayerStatementRule { names, loc }))
591612
}
613+
AtRulePrelude::Unknown(name, prelude) => Ok(CssRule::Unknown(UnknownAtRule {
614+
name,
615+
prelude,
616+
block: None,
617+
loc,
618+
})),
592619
_ => Err(()),
593620
}
594621
}

src/properties/custom.rs

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ impl<'i> CustomProperty<'i> {
3838
input: &mut Parser<'i, 't>,
3939
options: &ParserOptions,
4040
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
41-
let value = TokenList::parse(input, options, 0)?;
41+
let value = input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
42+
TokenList::parse(input, options, 0)
43+
})?;
4244
Ok(CustomProperty { name, value })
4345
}
4446
}
@@ -65,7 +67,9 @@ impl<'i> UnparsedProperty<'i> {
6567
input: &mut Parser<'i, 't>,
6668
options: &ParserOptions,
6769
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
68-
let value = TokenList::parse(input, options, 0)?;
70+
let value = input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
71+
TokenList::parse(input, options, 0)
72+
})?;
6973
Ok(UnparsedProperty { property_id, value })
7074
}
7175

@@ -133,25 +137,23 @@ impl<'i> TokenList<'i> {
133137
options: &ParserOptions,
134138
depth: usize,
135139
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
136-
input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
137-
let mut tokens = vec![];
138-
TokenList::parse_into(input, &mut tokens, options, depth)?;
139-
140-
// Slice off leading and trailing whitespace if there are at least two tokens.
141-
// If there is only one token, we must preserve it. e.g. `--foo: ;` is valid.
142-
if tokens.len() >= 2 {
143-
let mut slice = &tokens[..];
144-
if matches!(tokens.first(), Some(token) if token.is_whitespace()) {
145-
slice = &slice[1..];
146-
}
147-
if matches!(tokens.last(), Some(token) if token.is_whitespace()) {
148-
slice = &slice[..slice.len() - 1];
149-
}
150-
return Ok(TokenList(slice.to_vec()));
140+
let mut tokens = vec![];
141+
TokenList::parse_into(input, &mut tokens, options, depth)?;
142+
143+
// Slice off leading and trailing whitespace if there are at least two tokens.
144+
// If there is only one token, we must preserve it. e.g. `--foo: ;` is valid.
145+
if tokens.len() >= 2 {
146+
let mut slice = &tokens[..];
147+
if matches!(tokens.first(), Some(token) if token.is_whitespace()) {
148+
slice = &slice[1..];
151149
}
150+
if matches!(tokens.last(), Some(token) if token.is_whitespace()) {
151+
slice = &slice[..slice.len() - 1];
152+
}
153+
return Ok(TokenList(slice.to_vec()));
154+
}
152155

153-
return Ok(TokenList(tokens));
154-
})
156+
return Ok(TokenList(tokens));
155157
}
156158

157159
fn parse_into<'t>(

src/rules/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ pub mod page;
5252
pub mod property;
5353
pub mod style;
5454
pub mod supports;
55+
pub mod unknown;
5556
pub mod viewport;
5657

5758
use self::font_palette_values::FontPaletteValuesRule;
@@ -86,6 +87,7 @@ use serde::Serialize;
8687
use std::collections::{HashMap, HashSet};
8788
use style::StyleRule;
8889
use supports::SupportsRule;
90+
use unknown::UnknownAtRule;
8991
use viewport::ViewportRule;
9092

9193
pub(crate) trait ToCssWithContext<'a, 'i> {
@@ -163,6 +165,8 @@ pub enum CssRule<'i> {
163165
Container(ContainerRule<'i>),
164166
/// A placeholder for a rule that was removed.
165167
Ignored,
168+
/// An unknown at-rule.
169+
Unknown(UnknownAtRule<'i>),
166170
}
167171

168172
impl<'a, 'i> ToCssWithContext<'a, 'i> for CssRule<'i> {
@@ -193,6 +197,7 @@ impl<'a, 'i> ToCssWithContext<'a, 'i> for CssRule<'i> {
193197
CssRule::LayerBlock(layer) => layer.to_css(dest),
194198
CssRule::Property(property) => property.to_css(dest),
195199
CssRule::Container(container) => container.to_css_with_context(dest, context),
200+
CssRule::Unknown(unknown) => unknown.to_css(dest),
196201
CssRule::Ignored => Ok(()),
197202
}
198203
}

src/rules/unknown.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//! An unknown at-rule.
2+
3+
use super::Location;
4+
use crate::error::PrinterError;
5+
use crate::printer::Printer;
6+
use crate::properties::custom::TokenList;
7+
use crate::traits::ToCss;
8+
use crate::values::string::CowArcStr;
9+
10+
/// An unknown at-rule, stored as raw tokens.
11+
#[derive(Debug, PartialEq, Clone)]
12+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13+
pub struct UnknownAtRule<'i> {
14+
/// The name of the at-rule (without the @).
15+
pub name: CowArcStr<'i>,
16+
/// The prelude of the rule.
17+
pub prelude: TokenList<'i>,
18+
/// The contents of the block, if any.
19+
pub block: Option<TokenList<'i>>,
20+
/// The location of the rule in the source file.
21+
pub loc: Location,
22+
}
23+
24+
impl<'i> ToCss for UnknownAtRule<'i> {
25+
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
26+
where
27+
W: std::fmt::Write,
28+
{
29+
dest.add_mapping(self.loc);
30+
dest.write_char('@')?;
31+
dest.write_str(&self.name)?;
32+
33+
if !self.prelude.0.is_empty() {
34+
dest.write_char(' ')?;
35+
self.prelude.to_css(dest, false)?;
36+
}
37+
38+
if let Some(block) = &self.block {
39+
dest.whitespace()?;
40+
dest.write_char('{')?;
41+
dest.indent();
42+
dest.newline()?;
43+
block.to_css(dest, false)?;
44+
dest.dedent();
45+
dest.newline()?;
46+
dest.write_char('}')
47+
} else {
48+
dest.write_char(';')
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)