Skip to content

Commit c7913b0

Browse files
committed
Fix custom top-level style blocks when not nested
To fix this we have to pass through whether the rule is nested or not to custom at rule parsers, and propagate that to parse_style_block so that we know whether to parse selectors as relative or not.
1 parent eff8ce1 commit c7913b0

File tree

7 files changed

+73
-24
lines changed

7 files changed

+73
-24
lines changed

examples/custom_at_rule.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ impl<'i> AtRuleParser<'i> for TailwindAtRuleParser {
137137
prelude: Self::Prelude,
138138
start: &ParserState,
139139
_options: &ParserOptions<'_, 'i>,
140+
_is_nested: bool,
140141
) -> Result<Self::AtRule, ()> {
141142
let loc = start.source_location();
142143
match prelude {

node/src/at_rule_parser.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ impl<'i> AtRuleParser<'i> for CustomAtRuleParser {
103103
start: &ParserState,
104104
input: &mut Parser<'i, 't>,
105105
options: &ParserOptions<'_, 'i>,
106+
is_nested: bool,
106107
) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {
107108
let config = self.configs.get(prelude.name.as_ref()).unwrap();
108109
let body = if let Some(body) = &config.body {
@@ -114,7 +115,7 @@ impl<'i> AtRuleParser<'i> for CustomAtRuleParser {
114115
Some(AtRuleBody::RuleList(CssRuleList::parse_with(input, options, self)?))
115116
}
116117
CustomAtRuleBodyType::StyleBlock => Some(AtRuleBody::RuleList(CssRuleList::parse_style_block_with(
117-
input, options, self,
118+
input, options, self, is_nested,
118119
)?)),
119120
}
120121
} else {
@@ -139,6 +140,7 @@ impl<'i> AtRuleParser<'i> for CustomAtRuleParser {
139140
prelude: Self::Prelude,
140141
start: &ParserState,
141142
options: &ParserOptions<'_, 'i>,
143+
_is_nested: bool,
142144
) -> Result<Self::AtRule, ()> {
143145
let config = self.configs.get(prelude.name.as_ref()).unwrap();
144146
if config.body.is_some() {

node/test/customAtRules.mjs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ test('mixin', () => {
5959
code: Buffer.from(`
6060
@mixin color {
6161
color: red;
62-
62+
6363
&.bar {
6464
color: yellow;
6565
}
6666
}
67-
67+
6868
.foo {
6969
@apply color;
7070
}
@@ -187,6 +187,30 @@ test('style block', () => {
187187
assert.equal(res.code.toString(), '@media (width<=1024px){.foo{color:#ff0}.foo.bar{color:red}}');
188188
});
189189

190+
test('style block top level', () => {
191+
let res = transform({
192+
filename: 'test.css',
193+
minify: true,
194+
code: Buffer.from(`
195+
@test {
196+
.foo {
197+
background: black;
198+
}
199+
}
200+
`),
201+
drafts: {
202+
nesting: true
203+
},
204+
customAtRules: {
205+
test: {
206+
body: 'style-block'
207+
}
208+
}
209+
});
210+
211+
assert.equal(res.code.toString(), '@test{.foo{background:#000}}');
212+
});
213+
190214
test('multiple', () => {
191215
let res = transform({
192216
filename: 'test.css',

src/parser.rs

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for Ne
643643
loc,
644644
})),
645645
AtRulePrelude::Custom(prelude) => {
646-
parse_custom_at_rule_body(prelude, input, start, self.options, self.at_rule_parser)
646+
parse_custom_at_rule_body(prelude, input, start, self.options, self.at_rule_parser, false)
647647
}
648648
}
649649
}
@@ -670,7 +670,7 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for Ne
670670
loc,
671671
})),
672672
AtRulePrelude::Custom(prelude) => {
673-
parse_custom_at_rule_without_block(prelude, start, self.options, self.at_rule_parser)
673+
parse_custom_at_rule_without_block(prelude, start, self.options, self.at_rule_parser, false)
674674
}
675675
_ => Err(()),
676676
}
@@ -703,7 +703,7 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> QualifiedRuleParser<'i>
703703
) -> Result<CssRule<'i, T::AtRule>, ParseError<'i, Self::Error>> {
704704
let loc = self.loc(start);
705705
let (declarations, rules) = if self.options.flags.contains(ParserFlags::NESTING) {
706-
parse_declarations_and_nested_rules(input, self.options, self.at_rule_parser)?
706+
parse_declarations_and_nested_rules(input, self.options, self.at_rule_parser, true)?
707707
} else {
708708
(DeclarationBlock::parse(input, self.options)?, CssRuleList(vec![]))
709709
};
@@ -752,9 +752,10 @@ fn parse_custom_at_rule_body<'i, 't, T: crate::traits::AtRuleParser<'i>>(
752752
start: &ParserState,
753753
options: &ParserOptions<'_, 'i>,
754754
at_rule_parser: &mut T,
755+
is_nested: bool,
755756
) -> Result<CssRule<'i, T::AtRule>, ParseError<'i, ParserError<'i>>> {
756757
at_rule_parser
757-
.parse_block(prelude, start, input, options)
758+
.parse_block(prelude, start, input, options, is_nested)
758759
.map(|prelude| CssRule::Custom(prelude))
759760
.map_err(|err| match &err.kind {
760761
ParseErrorKind::Basic(kind) => ParseError {
@@ -770,16 +771,18 @@ fn parse_custom_at_rule_without_block<'i, 't, T: crate::traits::AtRuleParser<'i>
770771
start: &ParserState,
771772
options: &ParserOptions<'_, 'i>,
772773
at_rule_parser: &mut T,
774+
is_nested: bool,
773775
) -> Result<CssRule<'i, T::AtRule>, ()> {
774776
at_rule_parser
775-
.rule_without_block(prelude, start, options)
777+
.rule_without_block(prelude, start, options, is_nested)
776778
.map(|prelude| CssRule::Custom(prelude))
777779
}
778780

779781
fn parse_declarations_and_nested_rules<'a, 'o, 'i, 't, T: crate::traits::AtRuleParser<'i>>(
780782
input: &mut Parser<'i, 't>,
781783
options: &'a ParserOptions<'o, 'i>,
782784
at_rule_parser: &mut T,
785+
is_nested: bool,
783786
) -> Result<(DeclarationBlock<'i>, CssRuleList<'i, T::AtRule>), ParseError<'i, ParserError<'i>>> {
784787
let mut important_declarations = DeclarationList::new();
785788
let mut declarations = DeclarationList::new();
@@ -790,6 +793,7 @@ fn parse_declarations_and_nested_rules<'a, 'o, 'i, 't, T: crate::traits::AtRuleP
790793
important_declarations: &mut important_declarations,
791794
rules: &mut rules,
792795
at_rule_parser,
796+
is_nested,
793797
};
794798

795799
// In the v2 nesting spec, declarations and nested rules may be mixed.
@@ -840,6 +844,7 @@ pub struct StyleRuleParser<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> {
840844
important_declarations: &'a mut DeclarationList<'i>,
841845
rules: &'a mut CssRuleList<'i, T::AtRule>,
842846
at_rule_parser: &'a mut T,
847+
is_nested: bool,
843848
}
844849

845850
/// Parse a declaration within {} block: `color: blue`
@@ -925,15 +930,15 @@ impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for StyleR
925930
AtRulePrelude::Media(query) => {
926931
self.rules.0.push(CssRule::Media(MediaRule {
927932
query,
928-
rules: parse_nested_at_rule(input, self.options, self.at_rule_parser)?,
933+
rules: parse_style_block(input, self.options, self.at_rule_parser, true)?,
929934
loc,
930935
}));
931936
Ok(())
932937
}
933938
AtRulePrelude::Supports(condition) => {
934939
self.rules.0.push(CssRule::Supports(SupportsRule {
935940
condition,
936-
rules: parse_nested_at_rule(input, self.options, self.at_rule_parser)?,
941+
rules: parse_style_block(input, self.options, self.at_rule_parser, true)?,
937942
loc,
938943
}));
939944
Ok(())
@@ -942,28 +947,29 @@ impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for StyleR
942947
self.rules.0.push(CssRule::Container(ContainerRule {
943948
name,
944949
condition,
945-
rules: parse_nested_at_rule(input, self.options, self.at_rule_parser)?,
950+
rules: parse_style_block(input, self.options, self.at_rule_parser, true)?,
946951
loc,
947952
}));
948953
Ok(())
949954
}
950955
AtRulePrelude::LayerBlock(name) => {
951956
self.rules.0.push(CssRule::LayerBlock(LayerBlockRule {
952957
name,
953-
rules: parse_nested_at_rule(input, self.options, self.at_rule_parser)?,
958+
rules: parse_style_block(input, self.options, self.at_rule_parser, true)?,
954959
loc,
955960
}));
956961
Ok(())
957962
}
958963
AtRulePrelude::StartingStyle => {
959964
self.rules.0.push(CssRule::StartingStyle(StartingStyleRule {
960-
rules: parse_nested_at_rule(input, self.options, self.at_rule_parser)?,
965+
rules: parse_style_block(input, self.options, self.at_rule_parser, true)?,
961966
loc,
962967
}));
963968
Ok(())
964969
}
965970
AtRulePrelude::Nest(selectors) => {
966-
let (declarations, rules) = parse_declarations_and_nested_rules(input, self.options, self.at_rule_parser)?;
971+
let (declarations, rules) =
972+
parse_declarations_and_nested_rules(input, self.options, self.at_rule_parser, true)?;
967973
self.rules.0.push(CssRule::Nesting(NestingRule {
968974
style: StyleRule {
969975
selectors,
@@ -992,6 +998,7 @@ impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for StyleR
992998
start,
993999
self.options,
9941000
self.at_rule_parser,
1001+
true,
9951002
)?);
9961003
Ok(())
9971004
}
@@ -1021,6 +1028,7 @@ impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for StyleR
10211028
start,
10221029
self.options,
10231030
self.at_rule_parser,
1031+
true,
10241032
)?);
10251033
Ok(())
10261034
}
@@ -1029,10 +1037,11 @@ impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for StyleR
10291037
}
10301038
}
10311039

1032-
pub fn parse_nested_at_rule<'a, 'o, 'i, 't, T: crate::traits::AtRuleParser<'i>>(
1040+
pub fn parse_style_block<'a, 'o, 'i, 't, T: crate::traits::AtRuleParser<'i>>(
10331041
input: &mut Parser<'i, 't>,
10341042
options: &'a ParserOptions<'o, 'i>,
10351043
at_rule_parser: &mut T,
1044+
is_nested: bool,
10361045
) -> Result<CssRuleList<'i, T::AtRule>, ParseError<'i, ParserError<'i>>> {
10371046
let loc = input.current_source_location();
10381047
let loc = Location {
@@ -1043,7 +1052,7 @@ pub fn parse_nested_at_rule<'a, 'o, 'i, 't, T: crate::traits::AtRuleParser<'i>>(
10431052

10441053
// Declarations can be immediately within @media and @supports blocks that are nested within a parent style rule.
10451054
// These act the same way as if they were nested within a `& { ... }` block.
1046-
let (declarations, mut rules) = parse_declarations_and_nested_rules(input, options, at_rule_parser)?;
1055+
let (declarations, mut rules) = parse_declarations_and_nested_rules(input, options, at_rule_parser, is_nested)?;
10471056

10481057
if declarations.len() > 0 {
10491058
rules.0.insert(
@@ -1073,10 +1082,14 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> QualifiedRuleParser<'i>
10731082
input: &mut Parser<'i, 't>,
10741083
) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
10751084
let selector_parser = SelectorParser {
1076-
is_nesting_allowed: true,
1085+
is_nesting_allowed: self.options.flags.contains(ParserFlags::NESTING),
10771086
options: &self.options,
10781087
};
1079-
SelectorList::parse_relative(&selector_parser, input, NestingRequirement::Implicit)
1088+
if self.is_nested {
1089+
SelectorList::parse_relative(&selector_parser, input, NestingRequirement::Implicit)
1090+
} else {
1091+
SelectorList::parse(&selector_parser, input, NestingRequirement::None)
1092+
}
10801093
}
10811094

10821095
fn parse_block<'t>(
@@ -1086,7 +1099,8 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> QualifiedRuleParser<'i>
10861099
input: &mut Parser<'i, 't>,
10871100
) -> Result<(), ParseError<'i, Self::Error>> {
10881101
let loc = start.source_location();
1089-
let (declarations, rules) = parse_declarations_and_nested_rules(input, self.options, self.at_rule_parser)?;
1102+
let (declarations, rules) =
1103+
parse_declarations_and_nested_rules(input, self.options, self.at_rule_parser, true)?;
10901104
self.rules.0.push(CssRule::Style(StyleRule {
10911105
selectors,
10921106
vendor_prefix: VendorPrefix::empty(),

src/rules/mod.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,7 @@ use crate::context::PropertyHandlerContext;
6363
use crate::declaration::{DeclarationBlock, DeclarationHandler};
6464
use crate::dependencies::{Dependency, ImportDependency};
6565
use crate::error::{MinifyError, ParserError, PrinterError, PrinterErrorKind};
66-
use crate::parser::{
67-
parse_nested_at_rule, DefaultAtRule, DefaultAtRuleParser, NestedRuleParser, TopLevelRuleParser,
68-
};
66+
use crate::parser::{parse_style_block, DefaultAtRule, DefaultAtRuleParser, NestedRuleParser, TopLevelRuleParser};
6967
use crate::prefixes::Feature;
7068
use crate::printer::Printer;
7169
use crate::rules::keyframes::KeyframesName;
@@ -422,8 +420,9 @@ impl<'i> CssRuleList<'i, DefaultAtRule> {
422420
pub fn parse_style_block<'t>(
423421
input: &mut Parser<'i, 't>,
424422
options: &ParserOptions<'_, 'i>,
423+
is_nested: bool,
425424
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
426-
Self::parse_style_block_with(input, options, &mut DefaultAtRuleParser)
425+
Self::parse_style_block_with(input, options, &mut DefaultAtRuleParser, is_nested)
427426
}
428427
}
429428

@@ -447,8 +446,9 @@ impl<'i, T> CssRuleList<'i, T> {
447446
input: &mut Parser<'i, 't>,
448447
options: &ParserOptions<'_, 'i>,
449448
at_rule_parser: &mut P,
449+
is_nested: bool,
450450
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
451-
parse_nested_at_rule(input, options, at_rule_parser)
451+
parse_style_block(input, options, at_rule_parser, is_nested)
452452
}
453453
}
454454

src/traits.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ pub trait AtRuleParser<'i>: Sized {
306306
/// representation of the at-rule.
307307
///
308308
/// The location passed in is source location of the start of the prelude.
309+
/// `is_nested` indicates whether the rule is nested inside a style rule.
309310
///
310311
/// This is only called when either the `;` semicolon indeed follows the prelude,
311312
/// or parser is at the end of the input.
@@ -314,16 +315,19 @@ pub trait AtRuleParser<'i>: Sized {
314315
prelude: Self::Prelude,
315316
start: &ParserState,
316317
options: &ParserOptions<'_, 'i>,
318+
is_nested: bool,
317319
) -> Result<Self::AtRule, ()> {
318320
let _ = prelude;
319321
let _ = start;
320322
let _ = options;
323+
let _ = is_nested;
321324
Err(())
322325
}
323326

324327
/// Parse the content of a `{ /* ... */ }` block for the body of the at-rule.
325328
///
326329
/// The location passed in is source location of the start of the prelude.
330+
/// `is_nested` indicates whether the rule is nested inside a style rule.
327331
///
328332
/// Return the finished representation of the at-rule
329333
/// as returned by `RuleListParser::next` or `DeclarationListParser::next`,
@@ -336,11 +340,13 @@ pub trait AtRuleParser<'i>: Sized {
336340
start: &ParserState,
337341
input: &mut Parser<'i, 't>,
338342
options: &ParserOptions<'_, 'i>,
343+
is_nested: bool,
339344
) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {
340345
let _ = prelude;
341346
let _ = start;
342347
let _ = input;
343348
let _ = options;
349+
let _ = is_nested;
344350
Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid))
345351
}
346352
}

tests/test_custom_parser.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ impl<'i> AtRuleParser<'i> for TestAtRuleParser {
101101
prelude: Self::Prelude,
102102
_start: &ParserState,
103103
_options: &ParserOptions<'_, 'i>,
104+
_is_nested: bool,
104105
) -> Result<Self::AtRule, ()> {
105106
match prelude {
106107
Prelude::Inline(name) => Ok(AtRule::Inline(InlineRule { name })),
@@ -114,6 +115,7 @@ impl<'i> AtRuleParser<'i> for TestAtRuleParser {
114115
_start: &ParserState,
115116
input: &mut Parser<'i, 't>,
116117
_options: &ParserOptions<'_, 'i>,
118+
_is_nested: bool,
117119
) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {
118120
match prelude {
119121
Prelude::Block(name) => Ok(AtRule::Block(BlockRule {

0 commit comments

Comments
 (0)