Skip to content

Commit 8927c0c

Browse files
committed
Parsing support for @supports rule
1 parent 1d443ec commit 8927c0c

File tree

4 files changed

+321
-23
lines changed

4 files changed

+321
-23
lines changed

src/lib.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3725,6 +3725,115 @@ mod tests {
37253725
minify_test("@page toc, index {margin: 0.5cm}", "@page toc,index{margin:.5cm}");
37263726
}
37273727

3728+
#[test]
3729+
fn test_supports_rule() {
3730+
test(r#"
3731+
@supports (foo: bar) {
3732+
.test {
3733+
foo: bar;
3734+
}
3735+
}
3736+
"#, indoc! { r#"
3737+
@supports (foo: bar) {
3738+
.test {
3739+
foo: bar;
3740+
}
3741+
}
3742+
"#});
3743+
test(r#"
3744+
@supports not (foo: bar) {
3745+
.test {
3746+
foo: bar;
3747+
}
3748+
}
3749+
"#, indoc! { r#"
3750+
@supports not (foo: bar) {
3751+
.test {
3752+
foo: bar;
3753+
}
3754+
}
3755+
"#});
3756+
test(r#"
3757+
@supports (foo: bar) or (bar: baz) {
3758+
.test {
3759+
foo: bar;
3760+
}
3761+
}
3762+
"#, indoc! { r#"
3763+
@supports (foo: bar) or (bar: baz) {
3764+
.test {
3765+
foo: bar;
3766+
}
3767+
}
3768+
"#});
3769+
test(r#"
3770+
@supports (foo: bar) and (bar: baz) {
3771+
.test {
3772+
foo: bar;
3773+
}
3774+
}
3775+
"#, indoc! { r#"
3776+
@supports (foo: bar) and (bar: baz) {
3777+
.test {
3778+
foo: bar;
3779+
}
3780+
}
3781+
"#});
3782+
test(r#"
3783+
@supports selector(a > b) {
3784+
.test {
3785+
foo: bar;
3786+
}
3787+
}
3788+
"#, indoc! { r#"
3789+
@supports selector(a > b) {
3790+
.test {
3791+
foo: bar;
3792+
}
3793+
}
3794+
"#});
3795+
test(r#"
3796+
@supports unknown(test) {
3797+
.test {
3798+
foo: bar;
3799+
}
3800+
}
3801+
"#, indoc! { r#"
3802+
@supports unknown(test) {
3803+
.test {
3804+
foo: bar;
3805+
}
3806+
}
3807+
"#});
3808+
test(r#"
3809+
@supports (unknown) {
3810+
.test {
3811+
foo: bar;
3812+
}
3813+
}
3814+
"#, indoc! { r#"
3815+
@supports (unknown) {
3816+
.test {
3817+
foo: bar;
3818+
}
3819+
}
3820+
"#});
3821+
test(r#"
3822+
@supports (display: grid) and (not (display: inline-grid)) {
3823+
.test {
3824+
foo: bar;
3825+
}
3826+
}
3827+
"#, indoc! { r#"
3828+
@supports (display: grid) and (not (display: inline-grid)) {
3829+
.test {
3830+
foo: bar;
3831+
}
3832+
}
3833+
"#});
3834+
3835+
}
3836+
37283837
#[test]
37293838
fn test_prefixes() {
37303839
prefix_test(

src/parser.rs

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::selector::{Selectors, SelectorParser};
1010
use crate::rules::keyframes::{KeyframeListParser, KeyframesRule};
1111
use crate::rules::font_face::{FontFaceRule, FontFaceDeclarationParser};
1212
use crate::rules::page::{PageSelector, PageRule};
13+
use crate::rules::supports::{SupportsCondition, SupportsRule};
1314
use crate::declaration::{Declaration, DeclarationHandler};
1415
use crate::properties::VendorPrefix;
1516

@@ -85,7 +86,7 @@ pub enum AtRulePrelude {
8586
/// A @media rule prelude, with its media queries.
8687
Media(MediaList),//(Arc<Locked<MediaList>>),
8788
/// An @supports rule, with its conditional
88-
Supports,//(SupportsCondition),
89+
Supports(SupportsCondition),
8990
/// A @viewport rule prelude.
9091
Viewport,
9192
/// A @keyframes rule, with its animation name and vendor prefix if exists.
@@ -328,7 +329,8 @@ pub enum CssRule {
328329
Style(StyleRule),
329330
Keyframes(KeyframesRule),
330331
FontFace(FontFaceRule),
331-
Page(PageRule)
332+
Page(PageRule),
333+
Supports(SupportsRule)
332334
}
333335

334336
impl ToCss for CssRule {
@@ -340,6 +342,7 @@ impl ToCss for CssRule {
340342
CssRule::Keyframes(keyframes) => keyframes.to_css(dest),
341343
CssRule::FontFace(font_face) => font_face.to_css(dest),
342344
CssRule::Page(font_face) => font_face.to_css(dest),
345+
CssRule::Supports(supports) => supports.to_css(dest),
343346
}
344347
}
345348
}
@@ -379,10 +382,10 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser {
379382
let media = MediaList::parse(input);
380383
Ok(AtRuleType::WithBlock(AtRulePrelude::Media(media)))
381384
},
382-
// "supports" => {
383-
// let cond = SupportsCondition::parse(input)?;
384-
// Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::Supports(cond)))
385-
// },
385+
"supports" => {
386+
let cond = SupportsCondition::parse(input)?;
387+
Ok(AtRuleType::WithBlock(AtRulePrelude::Supports(cond)))
388+
},
386389
"font-face" => {
387390
Ok(AtRuleType::WithBlock(AtRulePrelude::FontFace))
388391
},
@@ -506,23 +509,12 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser {
506509
rules: self.parse_nested_rules(input)
507510
}))
508511
},
509-
// AtRuleBlockPrelude::Supports(condition) => {
510-
// let eval_context = ParserContext::new_with_rule_type(
511-
// self.context,
512-
// CssRuleType::Style,
513-
// self.namespaces,
514-
// );
515-
516-
// let enabled = condition.eval(&eval_context, self.namespaces);
517-
// Ok(CssRule::Supports(Arc::new(self.shared_lock.wrap(
518-
// SupportsRule {
519-
// condition,
520-
// rules: self.parse_nested_rules(input, CssRuleType::Supports),
521-
// enabled,
522-
// source_location: start.source_location(),
523-
// },
524-
// ))))
525-
// },
512+
AtRulePrelude::Supports(condition) => {
513+
Ok(CssRule::Supports(SupportsRule {
514+
condition,
515+
rules: self.parse_nested_rules(input),
516+
}))
517+
},
526518
// AtRuleBlockPrelude::Viewport => {
527519
// let context = ParserContext::new_with_rule_type(
528520
// self.context,

src/rules/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub mod keyframes;
22
pub mod font_face;
33
pub mod page;
4+
pub mod supports;

src/rules/supports.rs

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
use cssparser::*;
2+
use crate::traits::{Parse, ToCss};
3+
use crate::printer::Printer;
4+
use std::fmt::Write;
5+
use crate::parser::CssRule;
6+
7+
#[derive(Debug, PartialEq)]
8+
pub struct SupportsRule {
9+
pub condition: SupportsCondition,
10+
pub rules: Vec<CssRule>
11+
}
12+
13+
impl ToCss for SupportsRule {
14+
fn to_css<W>(&self, dest: &mut Printer<W>) -> std::fmt::Result where W: std::fmt::Write {
15+
dest.write_str("@supports ")?;
16+
self.condition.to_css(dest)?;
17+
dest.whitespace()?;
18+
dest.write_char('{')?;
19+
dest.indent();
20+
for rule in self.rules.iter() {
21+
dest.newline()?;
22+
rule.to_css(dest)?;
23+
}
24+
dest.dedent();
25+
dest.newline()?;
26+
dest.write_char('}')
27+
}
28+
}
29+
30+
#[derive(Debug, PartialEq, Clone)]
31+
pub enum SupportsCondition {
32+
Not(Box<SupportsCondition>),
33+
And(Vec<SupportsCondition>),
34+
Or(Vec<SupportsCondition>),
35+
Declaration(String),
36+
Selector(String),
37+
// FontTechnology()
38+
Parens(Box<SupportsCondition>),
39+
Unknown(String)
40+
}
41+
42+
impl Parse for SupportsCondition {
43+
fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ()>> {
44+
if input.try_parse(|input| input.expect_ident_matching("not")).is_ok() {
45+
let in_parens = Self::parse_in_parens(input)?;
46+
return Ok(SupportsCondition::Not(Box::new(in_parens)))
47+
}
48+
49+
let in_parens = Self::parse_in_parens(input)?;
50+
let mut expected_type = None;
51+
let mut conditions = Vec::new();
52+
53+
loop {
54+
let condition = input.try_parse(|input| {
55+
let location = input.current_source_location();
56+
let s = input.expect_ident()?;
57+
let found_type = match_ignore_ascii_case! { &s,
58+
"and" => 1,
59+
"or" => 2,
60+
_ => return Err(location.new_unexpected_token_error(
61+
cssparser::Token::Ident(s.clone())
62+
))
63+
};
64+
65+
if let Some(expected) = expected_type {
66+
if found_type != expected {
67+
return Err(location.new_unexpected_token_error(
68+
cssparser::Token::Ident(s.clone())
69+
))
70+
}
71+
} else {
72+
expected_type = Some(found_type);
73+
}
74+
75+
Self::parse_in_parens(input)
76+
});
77+
78+
if let Ok(condition) = condition {
79+
if conditions.is_empty() {
80+
conditions.push(in_parens.clone())
81+
}
82+
conditions.push(condition)
83+
} else {
84+
break
85+
}
86+
}
87+
88+
match expected_type {
89+
Some(1) => Ok(SupportsCondition::And(conditions)),
90+
Some(2) => Ok(SupportsCondition::Or(conditions)),
91+
_ => Ok(in_parens)
92+
}
93+
}
94+
}
95+
96+
impl SupportsCondition {
97+
fn parse_in_parens<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ()>> {
98+
input.skip_whitespace();
99+
let location = input.current_source_location();
100+
let pos = input.position();
101+
match input.next()? {
102+
Token::Function(ref f) => {
103+
match_ignore_ascii_case! { &*f,
104+
"selector" => {
105+
let res = input.try_parse(|input| {
106+
input.parse_nested_block(|input| {
107+
let pos = input.position();
108+
input.expect_no_error_token()?;
109+
Ok(SupportsCondition::Selector(input.slice_from(pos).to_owned()))
110+
})
111+
});
112+
if res.is_ok() {
113+
return res
114+
}
115+
},
116+
_ => {}
117+
}
118+
}
119+
Token::ParenthesisBlock => {
120+
let res = input.try_parse(|input| {
121+
input.parse_nested_block(|input| {
122+
if let Ok(condition) = input.try_parse(SupportsCondition::parse) {
123+
return Ok(SupportsCondition::Parens(Box::new(condition)))
124+
}
125+
126+
let pos = input.position();
127+
input.expect_ident()?;
128+
input.expect_colon()?;
129+
input.expect_no_error_token()?;
130+
Ok(SupportsCondition::Declaration(input.slice_from(pos).to_owned()))
131+
})
132+
});
133+
if res.is_ok() {
134+
return res
135+
}
136+
}
137+
t => return Err(location.new_unexpected_token_error(t.clone())),
138+
};
139+
140+
input.parse_nested_block(|input| input.expect_no_error_token().map_err(|err| err.into()))?;
141+
Ok(SupportsCondition::Unknown(input.slice_from(pos).to_owned()))
142+
}
143+
}
144+
145+
impl ToCss for SupportsCondition {
146+
fn to_css<W>(&self, dest: &mut Printer<W>) -> std::fmt::Result where W: std::fmt::Write {
147+
match self {
148+
SupportsCondition::Not(condition) => {
149+
dest.write_str("not ")?;
150+
condition.to_css(dest)
151+
}
152+
SupportsCondition::And(conditions) => {
153+
let mut first = true;
154+
for condition in conditions {
155+
if first {
156+
first = false;
157+
} else {
158+
dest.write_str(" and ")?;
159+
}
160+
condition.to_css(dest)?;
161+
}
162+
Ok(())
163+
}
164+
SupportsCondition::Or(conditions) => {
165+
let mut first = true;
166+
for condition in conditions {
167+
if first {
168+
first = false;
169+
} else {
170+
dest.write_str(" or ")?;
171+
}
172+
condition.to_css(dest)?;
173+
}
174+
Ok(())
175+
}
176+
SupportsCondition::Parens(condition) => {
177+
dest.write_char('(')?;
178+
condition.to_css(dest)?;
179+
dest.write_char(')')
180+
}
181+
SupportsCondition::Declaration(decl) => {
182+
dest.write_char('(')?;
183+
dest.write_str(&decl)?;
184+
dest.write_char(')')
185+
}
186+
SupportsCondition::Selector(sel) => {
187+
dest.write_str("selector(")?;
188+
dest.write_str(sel)?;
189+
dest.write_char(')')
190+
}
191+
SupportsCondition::Unknown(unknown) => {
192+
dest.write_str(&unknown)
193+
}
194+
}
195+
}
196+
}

0 commit comments

Comments
 (0)