Skip to content

Commit 5a12639

Browse files
authored
Fix media query parens (parcel-bundler#343)
1 parent 5098cae commit 5a12639

File tree

2 files changed

+127
-43
lines changed

2 files changed

+127
-43
lines changed

src/lib.rs

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6717,6 +6717,8 @@ mod tests {
67176717
);
67186718
minify_test("@media { .foo { color: chartreuse }}", ".foo{color:#7fff00}");
67196719
minify_test("@media all { .foo { color: chartreuse }}", ".foo{color:#7fff00}");
6720+
minify_test("@media not (((color) or (hover))) { .foo { color: chartreuse }}", "@media not ((color) or (hover)){.foo{color:#7fff00}}");
6721+
minify_test("@media (hover) and ((color) and (test)) { .foo { color: chartreuse }}", "@media (hover) and (color) and (test){.foo{color:#7fff00}}");
67206722

67216723
prefix_test(
67226724
r#"
@@ -6865,6 +6867,69 @@ mod tests {
68656867
},
68666868
);
68676869

6870+
prefix_test(
6871+
r#"
6872+
@media not (100px <= width <= 200px) {
6873+
.foo {
6874+
color: chartreuse;
6875+
}
6876+
}
6877+
"#,
6878+
indoc! { r#"
6879+
@media not ((min-width: 100px) and (max-width: 200px)) {
6880+
.foo {
6881+
color: #7fff00;
6882+
}
6883+
}
6884+
"#},
6885+
Browsers {
6886+
firefox: Some(85 << 16),
6887+
..Browsers::default()
6888+
},
6889+
);
6890+
6891+
prefix_test(
6892+
r#"
6893+
@media (hover) and (100px <= width <= 200px) {
6894+
.foo {
6895+
color: chartreuse;
6896+
}
6897+
}
6898+
"#,
6899+
indoc! { r#"
6900+
@media (hover) and (min-width: 100px) and (max-width: 200px) {
6901+
.foo {
6902+
color: #7fff00;
6903+
}
6904+
}
6905+
"#},
6906+
Browsers {
6907+
firefox: Some(85 << 16),
6908+
..Browsers::default()
6909+
},
6910+
);
6911+
6912+
prefix_test(
6913+
r#"
6914+
@media (hover) or (100px <= width <= 200px) {
6915+
.foo {
6916+
color: chartreuse;
6917+
}
6918+
}
6919+
"#,
6920+
indoc! { r#"
6921+
@media (hover) or ((min-width: 100px) and (max-width: 200px)) {
6922+
.foo {
6923+
color: #7fff00;
6924+
}
6925+
}
6926+
"#},
6927+
Browsers {
6928+
firefox: Some(85 << 16),
6929+
..Browsers::default()
6930+
},
6931+
);
6932+
68686933
prefix_test(
68696934
r#"
68706935
@media (100px < width < 200px) {
@@ -22586,7 +22651,7 @@ mod tests {
2258622651
}
2258722652
}
2258822653
"#,
22589-
"@container my-layout (not (width>500px)){.foo{color:red}}",
22654+
"@container my-layout not (width>500px){.foo{color:red}}",
2259022655
);
2259122656

2259222657
minify_test(
@@ -22619,7 +22684,7 @@ mod tests {
2261922684
}
2262022685
}
2262122686
"#,
22622-
"@container my-layout ((width:100px) and (not (height:100px))){.foo{color:red}}",
22687+
"@container my-layout (width:100px) and (not (height:100px)){.foo{color:red}}",
2262322688
);
2262422689

2262522690
minify_test(

src/media_query.rs

Lines changed: 60 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::macros::enum_property;
66
use crate::printer::Printer;
77
use crate::rules::custom_media::CustomMediaRule;
88
use crate::rules::Location;
9+
use crate::targets::Browsers;
910
use crate::traits::{Parse, ToCss};
1011
use crate::values::ident::Ident;
1112
use crate::values::number::CSSNumber;
@@ -290,17 +291,8 @@ impl<'i> MediaQuery<'i> {
290291
if let Some(cond) = &b.condition {
291292
self.condition = if let Some(condition) = &self.condition {
292293
if condition != cond {
293-
macro_rules! parenthesize {
294-
($condition: ident) => {
295-
if matches!($condition, MediaCondition::Operation(_, Operator::Or)) {
296-
MediaCondition::InParens(Box::new($condition.clone()))
297-
} else {
298-
$condition.clone()
299-
}
300-
};
301-
}
302294
Some(MediaCondition::Operation(
303-
vec![parenthesize!(condition), parenthesize!(cond)],
295+
vec![condition.clone(), cond.clone()],
304296
Operator::And,
305297
))
306298
} else {
@@ -346,11 +338,14 @@ impl<'i> ToCss for MediaQuery<'i> {
346338
None => return Ok(()),
347339
};
348340

349-
if self.media_type != MediaType::All || self.qualifier.is_some() {
341+
let needs_parens = if self.media_type != MediaType::All || self.qualifier.is_some() {
350342
dest.write_str(" and ")?;
351-
}
343+
matches!(condition, MediaCondition::Operation(_, op) if *op != Operator::And)
344+
} else {
345+
false
346+
};
352347

353-
condition.to_css(dest)
348+
condition.to_css_with_parens_if_needed(dest, needs_parens)
354349
}
355350
}
356351

@@ -381,9 +376,6 @@ pub enum MediaCondition<'i> {
381376
/// A set of joint operations.
382377
#[skip_type]
383378
Operation(Vec<MediaCondition<'i>>, Operator),
384-
/// A condition wrapped in parenthesis.
385-
#[skip_type]
386-
InParens(Box<MediaCondition<'i>>),
387379
}
388380

389381
impl<'i> MediaCondition<'i> {
@@ -439,13 +431,40 @@ impl<'i> MediaCondition<'i> {
439431
fn parse_paren_block<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
440432
input.parse_nested_block(|input| {
441433
if let Ok(inner) = input.try_parse(|i| Self::parse(i, true)) {
442-
return Ok(MediaCondition::InParens(Box::new(inner)));
434+
return Ok(inner);
443435
}
444436

445437
let feature = MediaFeature::parse(input)?;
446438
Ok(MediaCondition::Feature(feature))
447439
})
448440
}
441+
442+
fn needs_parens(&self, parent_operator: Option<Operator>, targets: &Option<Browsers>) -> bool {
443+
match self {
444+
MediaCondition::Not(_) => true,
445+
MediaCondition::Operation(_, op) => Some(*op) != parent_operator,
446+
MediaCondition::Feature(f) => {
447+
parent_operator != Some(Operator::And)
448+
&& targets.is_some()
449+
&& matches!(f, MediaFeature::Interval { .. })
450+
&& !Feature::MediaIntervalSyntax.is_compatible(targets.unwrap())
451+
}
452+
}
453+
}
454+
455+
fn to_css_with_parens_if_needed<W>(&self, dest: &mut Printer<W>, needs_parens: bool) -> Result<(), PrinterError>
456+
where
457+
W: std::fmt::Write,
458+
{
459+
if needs_parens {
460+
dest.write_char('(')?;
461+
}
462+
self.to_css(dest)?;
463+
if needs_parens {
464+
dest.write_char(')')?;
465+
}
466+
Ok(())
467+
}
449468
}
450469

451470
impl<'i> ToCss for MediaCondition<'i> {
@@ -457,21 +476,17 @@ impl<'i> ToCss for MediaCondition<'i> {
457476
MediaCondition::Feature(ref f) => f.to_css(dest),
458477
MediaCondition::Not(ref c) => {
459478
dest.write_str("not ")?;
460-
c.to_css(dest)
461-
}
462-
MediaCondition::InParens(ref c) => {
463-
dest.write_char('(')?;
464-
c.to_css(dest)?;
465-
dest.write_char(')')
479+
c.to_css_with_parens_if_needed(dest, c.needs_parens(None, &dest.targets))
466480
}
467481
MediaCondition::Operation(ref list, op) => {
468482
let mut iter = list.iter();
469-
iter.next().unwrap().to_css(dest)?;
483+
let first = iter.next().unwrap();
484+
first.to_css_with_parens_if_needed(dest, first.needs_parens(Some(op), &dest.targets))?;
470485
for item in iter {
471486
dest.write_char(' ')?;
472487
op.to_css(dest)?;
473488
dest.write_char(' ')?;
474-
item.to_css(dest)?;
489+
item.to_css_with_parens_if_needed(dest, item.needs_parens(Some(op), &dest.targets))?;
475490
}
476491
Ok(())
477492
}
@@ -879,21 +894,9 @@ fn process_condition<'i>(
879894
MediaCondition::Not(cond) => {
880895
*condition = (**cond).clone();
881896
}
882-
MediaCondition::InParens(parens) => {
883-
if let MediaCondition::Not(cond) = &**parens {
884-
*condition = (**cond).clone();
885-
}
886-
}
887897
_ => {}
888898
}
889899
}
890-
MediaCondition::InParens(cond) => {
891-
let res = process_condition(loc, custom_media, media_type, qualifier, &mut *cond, seen);
892-
if let MediaCondition::InParens(cond) = &**cond {
893-
*condition = (**cond).clone();
894-
}
895-
return res;
896-
}
897900
MediaCondition::Operation(conditions, _) => {
898901
let mut res = Ok(true);
899902
conditions.retain_mut(|condition| {
@@ -963,8 +966,8 @@ fn process_condition<'i>(
963966
}
964967
// Parentheses are required around the condition unless there is a single media feature.
965968
match condition {
966-
MediaCondition::Feature(..) | MediaCondition::InParens(..) => Some(condition),
967-
_ => Some(MediaCondition::InParens(Box::new(condition))),
969+
MediaCondition::Feature(..) => Some(condition),
970+
_ => Some(condition),
968971
}
969972
} else {
970973
None
@@ -985,7 +988,7 @@ fn process_condition<'i>(
985988
if conditions.len() == 1 {
986989
*condition = conditions.pop().unwrap();
987990
} else {
988-
*condition = MediaCondition::InParens(Box::new(MediaCondition::Operation(conditions, Operator::Or)));
991+
*condition = MediaCondition::Operation(conditions, Operator::Or);
989992
}
990993
}
991994
_ => {}
@@ -997,7 +1000,7 @@ fn process_condition<'i>(
9971000
#[cfg(test)]
9981001
mod tests {
9991002
use super::*;
1000-
use crate::stylesheet::PrinterOptions;
1003+
use crate::{stylesheet::PrinterOptions, targets::Browsers};
10011004

10021005
fn parse(s: &str) -> MediaQuery {
10031006
let mut input = ParserInput::new(&s);
@@ -1044,4 +1047,20 @@ mod tests {
10441047
assert_eq!(and("only screen", "all"), "only screen");
10451048
assert_eq!(and("print", "print"), "print");
10461049
}
1050+
1051+
#[test]
1052+
fn test_negated_interval_parens() {
1053+
let media_query = parse("screen and not (200px <= width < 500px)");
1054+
let printer_options = PrinterOptions {
1055+
targets: Some(Browsers {
1056+
chrome: Some(95 << 16),
1057+
..Default::default()
1058+
}),
1059+
..Default::default()
1060+
};
1061+
assert_eq!(
1062+
media_query.to_css_string(printer_options).unwrap(),
1063+
"screen and not ((min-width: 200px) and (max-width: 499.999px))"
1064+
);
1065+
}
10471066
}

0 commit comments

Comments
 (0)