Skip to content

Commit abcbbbe

Browse files
committed
Implement support for @Property rule
Closes parcel-bundler#85
1 parent 8b34d7e commit abcbbbe

File tree

6 files changed

+830
-1
lines changed

6 files changed

+830
-1
lines changed

src/lib.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10296,4 +10296,134 @@ mod tests {
1029610296
minify_test("@import 'test.css' layer(foo.bar);", "@import \"test.css\" layer(foo.bar);");
1029710297
error_test("@import 'test.css' layer(foo, bar) {};", ParserError::UnexpectedToken(Token::Comma));
1029810298
}
10299+
10300+
#[test]
10301+
fn test_property() {
10302+
minify_test(r#"
10303+
@property --property-name {
10304+
syntax: '<color>';
10305+
inherits: false;
10306+
initial-value: yellow;
10307+
}
10308+
"#, "@property --property-name{syntax:\"<color>\";inherits:false;initial-value:#ff0}");
10309+
10310+
minify_test(r#"
10311+
@property --property-name {
10312+
syntax: '<length>';
10313+
inherits: true;
10314+
initial-value: 25px;
10315+
}
10316+
"#, "@property --property-name{syntax:\"<length>\";inherits:true;initial-value:25px}");
10317+
10318+
error_test(r#"
10319+
@property --property-name {
10320+
syntax: '<color>';
10321+
inherits: false;
10322+
initial-value: 25px;
10323+
}
10324+
"#, ParserError::UnexpectedToken(crate::properties::custom::Token::Dimension { has_sign: false, value: 25.0, int_value: Some(25), unit: "px".into() }));
10325+
10326+
error_test(r#"
10327+
@property --property-name {
10328+
syntax: '<length>';
10329+
inherits: false;
10330+
initial-value: var(--some-value);
10331+
}
10332+
"#, ParserError::UnexpectedToken(crate::properties::custom::Token::Function("var".into())));
10333+
10334+
error_test(r#"
10335+
@property --property-name {
10336+
syntax: '<color>';
10337+
inherits: false;
10338+
}
10339+
"#, ParserError::AtRuleBodyInvalid);
10340+
10341+
minify_test(r#"
10342+
@property --property-name {
10343+
syntax: '*';
10344+
inherits: false;
10345+
}
10346+
"#, "@property --property-name{syntax:\"*\";inherits:false}");
10347+
10348+
error_test(r#"
10349+
@property --property-name {
10350+
syntax: '*';
10351+
}
10352+
"#, ParserError::AtRuleBodyInvalid);
10353+
10354+
error_test(r#"
10355+
@property --property-name {
10356+
inherits: false;
10357+
}
10358+
"#, ParserError::AtRuleBodyInvalid);
10359+
10360+
error_test(r#"
10361+
@property property-name {
10362+
syntax: '*';
10363+
inherits: false;
10364+
}
10365+
"#, ParserError::UnexpectedToken(crate::properties::custom::Token::Ident("property-name".into())));
10366+
10367+
minify_test(r#"
10368+
@property --property-name {
10369+
syntax: 'custom | <color>';
10370+
inherits: false;
10371+
initial-value: yellow;
10372+
}
10373+
"#, "@property --property-name{syntax:\"custom|<color>\";inherits:false;initial-value:#ff0}");
10374+
10375+
minify_test(r#"
10376+
@property --property-name {
10377+
syntax: '<transform-list>';
10378+
inherits: false;
10379+
initial-value: translate(200px,300px) translate(100px,200px) scale(2);
10380+
}
10381+
"#, "@property --property-name{syntax:\"<transform-list>\";inherits:false;initial-value:matrix(2,0,0,2,300,500)}");
10382+
10383+
minify_test(r#"
10384+
@property --property-name {
10385+
syntax: '<time>';
10386+
inherits: false;
10387+
initial-value: 1000ms;
10388+
}
10389+
"#, "@property --property-name{syntax:\"<time>\";inherits:false;initial-value:1s}");
10390+
10391+
minify_test(r#"
10392+
@property --property-name {
10393+
syntax: '<url>';
10394+
inherits: false;
10395+
initial-value: url("foo.png");
10396+
}
10397+
"#, "@property --property-name{syntax:\"<url>\";inherits:false;initial-value:url(foo.png)}");
10398+
10399+
minify_test(r#"
10400+
@property --property-name {
10401+
syntax: '<image>';
10402+
inherits: false;
10403+
initial-value: linear-gradient(yellow, blue);
10404+
}
10405+
"#, "@property --property-name{syntax:\"<image>\";inherits:false;initial-value:linear-gradient(#ff0,#00f)}");
10406+
10407+
minify_test(r#"
10408+
@property --property-name {
10409+
initial-value: linear-gradient(yellow, blue);
10410+
inherits: false;
10411+
syntax: '<image>';
10412+
}
10413+
"#, "@property --property-name{syntax:\"<image>\";inherits:false;initial-value:linear-gradient(#ff0,#00f)}");
10414+
10415+
test(r#"
10416+
@property --property-name {
10417+
syntax: '<length>|none';
10418+
inherits: false;
10419+
initial-value: none;
10420+
}
10421+
"#, indoc!{r#"
10422+
@property --property-name {
10423+
syntax: "<length> | none";
10424+
inherits: false;
10425+
initial-value: none;
10426+
}
10427+
"#});
10428+
}
1029910429
}

src/parser.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::rules::layer::{LayerBlockRule, LayerStatementRule};
2+
use crate::rules::property::PropertyRule;
23
use crate::values::string::CowArcStr;
34
use cssparser::*;
45
use parcel_selectors::{SelectorList, parser::NestingRequirement};
@@ -106,7 +107,10 @@ pub enum AtRulePrelude<'i> {
106107
Charset,
107108
/// A @nest prelude.
108109
Nest(SelectorList<'i, Selectors>),
109-
Layer(Vec<LayerName<'i>>)
110+
/// An @layer prelude.
111+
Layer(Vec<LayerName<'i>>),
112+
/// An @property prelude.
113+
Property(CowRcStr<'i>)
110114
}
111115

112116
impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a, 'i> {
@@ -171,6 +175,13 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a, 'i> {
171175
let media = MediaList::parse(input)?;
172176
return Ok(AtRulePrelude::CustomMedia(name, media))
173177
},
178+
"property" => {
179+
let name = input.expect_ident_cloned()?;
180+
if !name.starts_with("--") {
181+
return Err(input.new_unexpected_token_error(Token::Ident(name.into())));
182+
}
183+
return Ok(AtRulePrelude::Property(name))
184+
},
174185
_ => {}
175186
}
176187

@@ -511,6 +522,9 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'i> {
511522
loc
512523
}))
513524
},
525+
AtRulePrelude::Property(name) => {
526+
Ok(CssRule::Property(PropertyRule::parse(name.into(), input, loc)?))
527+
},
514528
AtRulePrelude::Import(..) |
515529
AtRulePrelude::Namespace(..) |
516530
AtRulePrelude::CustomMedia(..) |

src/rules/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub mod nesting;
1212
pub mod viewport;
1313
pub mod custom_media;
1414
pub mod layer;
15+
pub mod property;
1516

1617
use crate::values::string::CowArcStr;
1718
use media::MediaRule;
@@ -39,6 +40,7 @@ use crate::error::{MinifyError, PrinterError};
3940
use crate::logical::LogicalProperties;
4041
use crate::dependencies::{Dependency, ImportDependency};
4142
use self::layer::{LayerBlockRule, LayerStatementRule};
43+
use self::property::PropertyRule;
4244

4345
pub(crate) trait ToCssWithContext<'a, 'i> {
4446
fn to_css_with_context<W>(&self, dest: &mut Printer<W>, context: Option<&StyleContext<'a, 'i>>) -> Result<(), PrinterError> where W: std::fmt::Write;
@@ -77,6 +79,7 @@ pub enum CssRule<'i> {
7779
CustomMedia(CustomMediaRule<'i>),
7880
LayerStatement(LayerStatementRule<'i>),
7981
LayerBlock(LayerBlockRule<'i>),
82+
Property(PropertyRule<'i>),
8083
Ignored
8184
}
8285

@@ -98,6 +101,7 @@ impl<'a, 'i> ToCssWithContext<'a, 'i> for CssRule<'i> {
98101
CssRule::CustomMedia(custom_media) => custom_media.to_css(dest),
99102
CssRule::LayerStatement(layer) => layer.to_css(dest),
100103
CssRule::LayerBlock(layer) => layer.to_css(dest),
104+
CssRule::Property(property) => property.to_css(dest),
101105
CssRule::Ignored => Ok(())
102106
}
103107
}

src/rules/property.rs

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
use cssparser::*;
2+
use crate::{
3+
values::{syntax::{SyntaxString, ParsedComponent}, string::CowArcStr},
4+
error::{ParserError, PrinterError},
5+
printer::Printer,
6+
traits::{Parse, ToCss}
7+
};
8+
use super::Location;
9+
10+
/// https://drafts.css-houdini.org/css-properties-values-api/#at-property-rule
11+
#[derive(Debug, PartialEq, Clone)]
12+
pub struct PropertyRule<'i> {
13+
name: CowArcStr<'i>,
14+
syntax: SyntaxString,
15+
inherits: bool,
16+
initial_value: Option<ParsedComponent<'i>>,
17+
loc: Location
18+
}
19+
20+
impl<'i> PropertyRule<'i> {
21+
pub fn parse<'t>(name: CowArcStr<'i>, input: &mut Parser<'i, 't>, loc: Location) -> Result<Self, ParseError<'i, ParserError<'i>>> {
22+
let parser = PropertyRuleDeclarationParser {
23+
syntax: None,
24+
inherits: None,
25+
initial_value: None
26+
};
27+
28+
let mut decl_parser = DeclarationListParser::new(input, parser);
29+
while let Some(decl) = decl_parser.next() {
30+
match decl {
31+
Ok(()) => {},
32+
Err((e, _)) => return Err(e)
33+
}
34+
}
35+
36+
// `syntax` and `inherits` are always required.
37+
let parser = decl_parser.parser;
38+
let syntax = parser.syntax.ok_or(input.new_custom_error(ParserError::AtRuleBodyInvalid))?;
39+
let inherits = parser.inherits.ok_or(input.new_custom_error(ParserError::AtRuleBodyInvalid))?;
40+
41+
// `initial-value` is required unless the syntax is a universal definition.
42+
let initial_value = match syntax {
43+
SyntaxString::Universal => match parser.initial_value {
44+
None => None,
45+
Some(val) => {
46+
let mut input = ParserInput::new(val);
47+
let mut parser = Parser::new(&mut input);
48+
Some(syntax.parse_value(&mut parser)?)
49+
}
50+
},
51+
_ => {
52+
let val = parser.initial_value.ok_or(input.new_custom_error(ParserError::AtRuleBodyInvalid))?;
53+
let mut input = ParserInput::new(val);
54+
let mut parser = Parser::new(&mut input);
55+
Some(syntax.parse_value(&mut parser)?)
56+
}
57+
};
58+
59+
return Ok(PropertyRule {
60+
name,
61+
syntax,
62+
inherits,
63+
initial_value,
64+
loc
65+
})
66+
}
67+
}
68+
69+
impl<'i> ToCss for PropertyRule<'i> {
70+
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError> where W: std::fmt::Write {
71+
dest.add_mapping(self.loc);
72+
dest.write_str("@property ")?;
73+
serialize_identifier(&self.name, dest)?;
74+
dest.whitespace()?;
75+
dest.write_char('{')?;
76+
dest.indent();
77+
dest.newline()?;
78+
79+
dest.write_str("syntax:")?;
80+
dest.whitespace()?;
81+
self.syntax.to_css(dest)?;
82+
dest.write_char(';')?;
83+
dest.newline()?;
84+
85+
dest.write_str("inherits:")?;
86+
dest.whitespace()?;
87+
match self.inherits {
88+
true => dest.write_str("true")?,
89+
false => dest.write_str("false")?
90+
}
91+
92+
if let Some(initial_value) = &self.initial_value {
93+
dest.write_char(';')?;
94+
dest.newline()?;
95+
96+
dest.write_str("initial-value:")?;
97+
dest.whitespace()?;
98+
initial_value.to_css(dest)?;
99+
if !dest.minify {
100+
dest.write_char(';')?;
101+
}
102+
}
103+
104+
dest.dedent();
105+
dest.newline()?;
106+
dest.write_char('}')
107+
}
108+
}
109+
110+
pub(crate) struct PropertyRuleDeclarationParser<'i> {
111+
syntax: Option<SyntaxString>,
112+
inherits: Option<bool>,
113+
initial_value: Option<&'i str>
114+
}
115+
116+
impl<'i> cssparser::DeclarationParser<'i> for PropertyRuleDeclarationParser<'i> {
117+
type Declaration = ();
118+
type Error = ParserError<'i>;
119+
120+
fn parse_value<'t>(
121+
&mut self,
122+
name: CowRcStr<'i>,
123+
input: &mut cssparser::Parser<'i, 't>,
124+
) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {
125+
match_ignore_ascii_case! { &name,
126+
"syntax" => {
127+
let syntax = SyntaxString::parse(input)?;
128+
self.syntax = Some(syntax);
129+
},
130+
"inherits" => {
131+
let location = input.current_source_location();
132+
let ident = input.expect_ident()?;
133+
let inherits = match_ignore_ascii_case! {&*ident,
134+
"true" => true,
135+
"false" => false,
136+
_ => return Err(location.new_unexpected_token_error(
137+
cssparser::Token::Ident(ident.clone())
138+
))
139+
};
140+
self.inherits = Some(inherits);
141+
},
142+
"initial-value" => {
143+
// Buffer the value into a string. We will parse it later.
144+
let start = input.position();
145+
while input.next().is_ok() {}
146+
let initial_value = input.slice_from(start);
147+
self.initial_value = Some(initial_value);
148+
},
149+
_ => return Err(input.new_custom_error(ParserError::InvalidDeclaration))
150+
}
151+
152+
return Ok(())
153+
}
154+
}
155+
156+
/// Default methods reject all at rules.
157+
impl<'i> AtRuleParser<'i> for PropertyRuleDeclarationParser<'i> {
158+
type Prelude = ();
159+
type AtRule = ();
160+
type Error = ParserError<'i>;
161+
}

src/values/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ pub mod ratio;
1818
pub mod url;
1919
pub mod shape;
2020
pub(crate) mod string;
21+
pub mod syntax;

0 commit comments

Comments
 (0)