Skip to content

Commit 6f71ef5

Browse files
authored
Support @scope rule (parcel-bundler#586)
1 parent e674bc5 commit 6f71ef5

File tree

8 files changed

+287
-7
lines changed

8 files changed

+287
-7
lines changed

node/src/transformer.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ impl<'i> Visitor<'i, AtRule<'i>> for JsVisitor {
281281
CssRule::LayerStatement(..) => "layer-statement",
282282
CssRule::Property(..) => "property",
283283
CssRule::Container(..) => "container",
284+
CssRule::Scope(..) => "scope",
284285
CssRule::MozDocument(..) => "moz-document",
285286
CssRule::Nesting(..) => "nesting",
286287
CssRule::Viewport(..) => "viewport",

selectors/parser.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ impl<'i, Impl: SelectorImpl<'i>> SelectorList<'i, Impl> {
405405
pub fn parse<'t, P>(
406406
parser: &P,
407407
input: &mut CssParser<'i, 't>,
408+
error_recovery: ParseErrorRecovery,
408409
nesting_requirement: NestingRequirement,
409410
) -> Result<Self, ParseError<'i, P::Error>>
410411
where
@@ -414,7 +415,7 @@ impl<'i, Impl: SelectorImpl<'i>> SelectorList<'i, Impl> {
414415
parser,
415416
input,
416417
&mut SelectorParsingState::empty(),
417-
ParseErrorRecovery::DiscardList,
418+
error_recovery,
418419
nesting_requirement,
419420
)
420421
}
@@ -466,6 +467,7 @@ impl<'i, Impl: SelectorImpl<'i>> SelectorList<'i, Impl> {
466467
pub fn parse_relative<'t, P>(
467468
parser: &P,
468469
input: &mut CssParser<'i, 't>,
470+
error_recovery: ParseErrorRecovery,
469471
nesting_requirement: NestingRequirement,
470472
) -> Result<Self, ParseError<'i, P::Error>>
471473
where
@@ -475,7 +477,7 @@ impl<'i, Impl: SelectorImpl<'i>> SelectorList<'i, Impl> {
475477
parser,
476478
input,
477479
&mut SelectorParsingState::empty(),
478-
ParseErrorRecovery::DiscardList,
480+
error_recovery,
479481
nesting_requirement,
480482
)
481483
}
@@ -3265,7 +3267,12 @@ pub mod tests {
32653267
expected: Option<&'a str>,
32663268
) -> Result<SelectorList<'i, DummySelectorImpl>, SelectorParseError<'i>> {
32673269
let mut parser_input = ParserInput::new(input);
3268-
let result = SelectorList::parse(parser, &mut CssParser::new(&mut parser_input), NestingRequirement::None);
3270+
let result = SelectorList::parse(
3271+
parser,
3272+
&mut CssParser::new(&mut parser_input),
3273+
ParseErrorRecovery::DiscardList,
3274+
NestingRequirement::None,
3275+
);
32693276
if let Ok(ref selectors) = result {
32703277
assert_eq!(selectors.0.len(), 1);
32713278
// We can't assume that the serialized parsed selector will equal
@@ -3292,6 +3299,7 @@ pub mod tests {
32923299
let list = SelectorList::parse(
32933300
&DummyParser::default(),
32943301
&mut CssParser::new(&mut input),
3302+
ParseErrorRecovery::DiscardList,
32953303
NestingRequirement::None,
32963304
);
32973305
assert!(list.is_ok());

src/lib.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24485,6 +24485,122 @@ mod tests {
2448524485
);
2448624486
}
2448724487

24488+
#[test]
24489+
fn test_at_scope() {
24490+
minify_test(
24491+
r#"
24492+
@scope {
24493+
.foo {
24494+
display: flex;
24495+
}
24496+
}
24497+
"#,
24498+
"@scope{.foo{display:flex}}",
24499+
);
24500+
minify_test(
24501+
r#"
24502+
@scope {
24503+
:scope {
24504+
display: flex;
24505+
color: lightblue;
24506+
}
24507+
}"#,
24508+
"@scope{:scope{color:#add8e6;display:flex}}",
24509+
);
24510+
minify_test(
24511+
r#"
24512+
@scope (.light-scheme) {
24513+
a { color: yellow; }
24514+
}
24515+
"#,
24516+
"@scope(.light-scheme){a{color:#ff0}}",
24517+
);
24518+
minify_test(
24519+
r#"
24520+
@scope (.media-object) to (.content > *) {
24521+
a { color: yellow; }
24522+
}
24523+
"#,
24524+
"@scope(.media-object) to (.content>*){a{color:#ff0}}",
24525+
);
24526+
minify_test(
24527+
r#"
24528+
@scope to (.content > *) {
24529+
a { color: yellow; }
24530+
}
24531+
"#,
24532+
"@scope to (.content>*){a{color:#ff0}}",
24533+
);
24534+
minify_test(
24535+
r#"
24536+
@scope (#my-component) {
24537+
& { color: yellow; }
24538+
}
24539+
"#,
24540+
"@scope(#my-component){&{color:#ff0}}",
24541+
);
24542+
minify_test(
24543+
r#"
24544+
@scope (.parent-scope) {
24545+
@scope (:scope > .child-scope) to (:scope .limit) {
24546+
.content { color: yellow; }
24547+
}
24548+
}
24549+
"#,
24550+
"@scope(.parent-scope){@scope(:scope>.child-scope) to (:scope .limit){.content{color:#ff0}}}",
24551+
);
24552+
minify_test(
24553+
r#"
24554+
.foo {
24555+
@scope (.bar) {
24556+
color: yellow;
24557+
}
24558+
}
24559+
"#,
24560+
".foo{@scope(.bar){&{color:#ff0}}}",
24561+
);
24562+
nesting_test(
24563+
r#"
24564+
.foo {
24565+
@scope (.bar) {
24566+
color: yellow;
24567+
}
24568+
}
24569+
"#,
24570+
indoc! {r#"
24571+
@scope (.bar) {
24572+
:scope {
24573+
color: #ff0;
24574+
}
24575+
}
24576+
"#},
24577+
);
24578+
nesting_test(
24579+
r#"
24580+
.parent {
24581+
color: blue;
24582+
24583+
@scope (& > .scope) to (& .limit) {
24584+
& .content {
24585+
color: yellow;
24586+
}
24587+
}
24588+
}
24589+
"#,
24590+
indoc! {r#"
24591+
.parent {
24592+
color: #00f;
24593+
}
24594+
24595+
@scope (.parent > .scope) to (.parent > .scope .limit) {
24596+
:scope .content {
24597+
color: #ff0;
24598+
}
24599+
}
24600+
"#},
24601+
);
24602+
}
24603+
2448824604
#[test]
2448924605
fn test_custom_media() {
2449024606
custom_media_test(

src/parser.rs

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ use crate::rules::container::{ContainerCondition, ContainerName, ContainerRule};
77
use crate::rules::font_palette_values::FontPaletteValuesRule;
88
use crate::rules::layer::{LayerBlockRule, LayerStatementRule};
99
use crate::rules::property::PropertyRule;
10+
use crate::rules::scope::ScopeRule;
1011
use crate::rules::starting_style::StartingStyleRule;
1112
use crate::rules::viewport::ViewportRule;
13+
1214
use crate::rules::{
1315
counter_style::CounterStyleRule,
1416
custom_media::CustomMediaRule,
@@ -35,7 +37,7 @@ use crate::vendor_prefix::VendorPrefix;
3537
use crate::visitor::{Visit, VisitTypes, Visitor};
3638
use bitflags::bitflags;
3739
use cssparser::*;
38-
use parcel_selectors::parser::NestingRequirement;
40+
use parcel_selectors::parser::{NestingRequirement, ParseErrorRecovery};
3941
use std::sync::{Arc, RwLock};
4042

4143
bitflags! {
@@ -202,6 +204,8 @@ pub enum AtRulePrelude<'i, T> {
202204
Container(Option<ContainerName<'i>>, ContainerCondition<'i>),
203205
/// A @starting-style prelude.
204206
StartingStyle,
207+
/// A @scope rule prelude.
208+
Scope(Option<SelectorList<'i>>, Option<SelectorList<'i>>),
205209
/// An unknown prelude.
206210
Unknown(CowArcStr<'i>, TokenList<'i>),
207211
/// A custom prelude.
@@ -221,6 +225,7 @@ impl<'i, T> AtRulePrelude<'i, T> {
221225
| Self::MozDocument
222226
| Self::Layer(..)
223227
| Self::StartingStyle
228+
| Self::Scope(..)
224229
| Self::Nest(..)
225230
| Self::Unknown(..)
226231
| Self::Custom(..) => true,
@@ -629,13 +634,40 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for Ne
629634
"starting-style" => {
630635
AtRulePrelude::StartingStyle
631636
},
637+
"scope" => {
638+
let selector_parser = SelectorParser {
639+
is_nesting_allowed: true,
640+
options: &self.options,
641+
};
642+
643+
let scope_start = if input.try_parse(|input| input.expect_parenthesis_block()).is_ok() {
644+
Some(input.parse_nested_block(|input| {
645+
// https://drafts.csswg.org/css-cascade-6/#scoped-rules
646+
// TODO: disallow pseudo elements?
647+
SelectorList::parse_relative(&selector_parser, input, ParseErrorRecovery::IgnoreInvalidSelector, NestingRequirement::None)
648+
})?)
649+
} else {
650+
None
651+
};
652+
653+
let scope_end = if input.try_parse(|input| input.expect_ident_matching("to")).is_ok() {
654+
input.expect_parenthesis_block()?;
655+
Some(input.parse_nested_block(|input| {
656+
SelectorList::parse_relative(&selector_parser, input, ParseErrorRecovery::IgnoreInvalidSelector, NestingRequirement::None)
657+
})?)
658+
} else {
659+
None
660+
};
661+
662+
AtRulePrelude::Scope(scope_start, scope_end)
663+
},
632664
"nest" if self.is_in_style_rule => {
633665
self.options.warn(input.new_custom_error(ParserError::DeprecatedNestRule));
634666
let selector_parser = SelectorParser {
635667
is_nesting_allowed: true,
636668
options: &self.options,
637669
};
638-
let selectors = SelectorList::parse(&selector_parser, input, NestingRequirement::Contained)?;
670+
let selectors = SelectorList::parse(&selector_parser, input, ParseErrorRecovery::DiscardList, NestingRequirement::Contained)?;
639671
AtRulePrelude::Nest(selectors)
640672
},
641673
_ => parse_custom_at_rule_prelude(&name, input, self.options, self.at_rule_parser)?
@@ -717,6 +749,16 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for Ne
717749
}));
718750
Ok(())
719751
}
752+
AtRulePrelude::Scope(scope_start, scope_end) => {
753+
let rules = self.parse_style_block(input)?;
754+
self.rules.0.push(CssRule::Scope(ScopeRule {
755+
scope_start,
756+
scope_end,
757+
rules,
758+
loc,
759+
}));
760+
Ok(())
761+
}
720762
AtRulePrelude::Viewport(vendor_prefix) => {
721763
self.rules.0.push(CssRule::Viewport(ViewportRule {
722764
vendor_prefix,
@@ -871,9 +913,19 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> QualifiedRuleParser<'i>
871913
options: &self.options,
872914
};
873915
if self.is_in_style_rule {
874-
SelectorList::parse_relative(&selector_parser, input, NestingRequirement::Implicit)
916+
SelectorList::parse_relative(
917+
&selector_parser,
918+
input,
919+
ParseErrorRecovery::DiscardList,
920+
NestingRequirement::Implicit,
921+
)
875922
} else {
876-
SelectorList::parse(&selector_parser, input, NestingRequirement::None)
923+
SelectorList::parse(
924+
&selector_parser,
925+
input,
926+
ParseErrorRecovery::DiscardList,
927+
NestingRequirement::None,
928+
)
877929
}
878930
}
879931

src/printer.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,16 @@ impl<'a, 'b, 'c, W: std::fmt::Write + Sized> Printer<'a, 'b, 'c, W> {
353353
res
354354
}
355355

356+
pub(crate) fn with_cleared_context<T, U, F: FnOnce(&mut Printer<'a, 'b, 'c, W>) -> Result<T, U>>(
357+
&mut self,
358+
f: F,
359+
) -> Result<T, U> {
360+
let parent = std::mem::take(&mut self.context);
361+
let res = f(self);
362+
self.context = parent;
363+
res
364+
}
365+
356366
pub(crate) fn context(&self) -> Option<&'a StyleContext<'a, 'b>> {
357367
self.context.clone()
358368
}

src/rules/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pub mod namespace;
5050
pub mod nesting;
5151
pub mod page;
5252
pub mod property;
53+
pub mod scope;
5354
pub mod starting_style;
5455
pub mod style;
5556
pub mod supports;
@@ -88,6 +89,7 @@ use media::MediaRule;
8889
use namespace::NamespaceRule;
8990
use nesting::NestingRule;
9091
use page::PageRule;
92+
use scope::ScopeRule;
9193
use smallvec::{smallvec, SmallVec};
9294
use starting_style::StartingStyleRule;
9395
use std::collections::{HashMap, HashSet};
@@ -166,6 +168,8 @@ pub enum CssRule<'i, R = DefaultAtRule> {
166168
Property(PropertyRule<'i>),
167169
/// A `@container` rule.
168170
Container(ContainerRule<'i, R>),
171+
/// A `@scope` rule.
172+
Scope(ScopeRule<'i, R>),
169173
/// A `@starting-style` rule.
170174
StartingStyle(StartingStyleRule<'i, R>),
171175
/// A placeholder for a rule that was removed.
@@ -304,6 +308,10 @@ impl<'i, 'de: 'i, R: serde::Deserialize<'de>> serde::Deserialize<'de> for CssRul
304308
let rule = ContainerRule::deserialize(deserializer)?;
305309
Ok(CssRule::Container(rule))
306310
}
311+
"scope" => {
312+
let rule = ScopeRule::deserialize(deserializer)?;
313+
Ok(CssRule::Scope(rule))
314+
}
307315
"starting-style" => {
308316
let rule = StartingStyleRule::deserialize(deserializer)?;
309317
Ok(CssRule::StartingStyle(rule))
@@ -347,6 +355,7 @@ impl<'a, 'i, T: ToCss> ToCss for CssRule<'i, T> {
347355
CssRule::Property(property) => property.to_css(dest),
348356
CssRule::StartingStyle(rule) => rule.to_css(dest),
349357
CssRule::Container(container) => container.to_css(dest),
358+
CssRule::Scope(scope) => scope.to_css(dest),
350359
CssRule::Unknown(unknown) => unknown.to_css(dest),
351360
CssRule::Custom(rule) => rule.to_css(dest).map_err(|_| PrinterError {
352361
kind: PrinterErrorKind::FmtError,
@@ -758,6 +767,7 @@ impl<'i, T: Clone> CssRuleList<'i, T> {
758767
continue;
759768
}
760769
}
770+
CssRule::Scope(scope) => scope.minify(context)?,
761771
CssRule::Nesting(nesting) => {
762772
if nesting.minify(context, parent_is_unused)? {
763773
continue;

0 commit comments

Comments
 (0)