From c7913b0e4f0aa78c3cc26b227aee89203d75b20d Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sat, 8 Jul 2023 11:11:34 -0600 Subject: [PATCH] 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. --- examples/custom_at_rule.rs | 1 + node/src/at_rule_parser.rs | 4 +++- node/test/customAtRules.mjs | 28 ++++++++++++++++++++-- src/parser.rs | 46 ++++++++++++++++++++++++------------- src/rules/mod.rs | 10 ++++---- src/traits.rs | 6 +++++ tests/test_custom_parser.rs | 2 ++ 7 files changed, 73 insertions(+), 24 deletions(-) diff --git a/examples/custom_at_rule.rs b/examples/custom_at_rule.rs index e4364bfa..3404a701 100644 --- a/examples/custom_at_rule.rs +++ b/examples/custom_at_rule.rs @@ -137,6 +137,7 @@ impl<'i> AtRuleParser<'i> for TailwindAtRuleParser { prelude: Self::Prelude, start: &ParserState, _options: &ParserOptions<'_, 'i>, + _is_nested: bool, ) -> Result { let loc = start.source_location(); match prelude { diff --git a/node/src/at_rule_parser.rs b/node/src/at_rule_parser.rs index 3f85607a..111bd106 100644 --- a/node/src/at_rule_parser.rs +++ b/node/src/at_rule_parser.rs @@ -103,6 +103,7 @@ impl<'i> AtRuleParser<'i> for CustomAtRuleParser { start: &ParserState, input: &mut Parser<'i, 't>, options: &ParserOptions<'_, 'i>, + is_nested: bool, ) -> Result> { let config = self.configs.get(prelude.name.as_ref()).unwrap(); let body = if let Some(body) = &config.body { @@ -114,7 +115,7 @@ impl<'i> AtRuleParser<'i> for CustomAtRuleParser { Some(AtRuleBody::RuleList(CssRuleList::parse_with(input, options, self)?)) } CustomAtRuleBodyType::StyleBlock => Some(AtRuleBody::RuleList(CssRuleList::parse_style_block_with( - input, options, self, + input, options, self, is_nested, )?)), } } else { @@ -139,6 +140,7 @@ impl<'i> AtRuleParser<'i> for CustomAtRuleParser { prelude: Self::Prelude, start: &ParserState, options: &ParserOptions<'_, 'i>, + _is_nested: bool, ) -> Result { let config = self.configs.get(prelude.name.as_ref()).unwrap(); if config.body.is_some() { diff --git a/node/test/customAtRules.mjs b/node/test/customAtRules.mjs index e5cb00d3..d0c4ea58 100644 --- a/node/test/customAtRules.mjs +++ b/node/test/customAtRules.mjs @@ -59,12 +59,12 @@ test('mixin', () => { code: Buffer.from(` @mixin color { color: red; - + &.bar { color: yellow; } } - + .foo { @apply color; } @@ -187,6 +187,30 @@ test('style block', () => { assert.equal(res.code.toString(), '@media (width<=1024px){.foo{color:#ff0}.foo.bar{color:red}}'); }); +test('style block top level', () => { + let res = transform({ + filename: 'test.css', + minify: true, + code: Buffer.from(` + @test { + .foo { + background: black; + } + } + `), + drafts: { + nesting: true + }, + customAtRules: { + test: { + body: 'style-block' + } + } + }); + + assert.equal(res.code.toString(), '@test{.foo{background:#000}}'); +}); + test('multiple', () => { let res = transform({ filename: 'test.css', diff --git a/src/parser.rs b/src/parser.rs index 91bc3d15..ec7e415b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -643,7 +643,7 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for Ne loc, })), AtRulePrelude::Custom(prelude) => { - parse_custom_at_rule_body(prelude, input, start, self.options, self.at_rule_parser) + parse_custom_at_rule_body(prelude, input, start, self.options, self.at_rule_parser, false) } } } @@ -670,7 +670,7 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for Ne loc, })), AtRulePrelude::Custom(prelude) => { - parse_custom_at_rule_without_block(prelude, start, self.options, self.at_rule_parser) + parse_custom_at_rule_without_block(prelude, start, self.options, self.at_rule_parser, false) } _ => Err(()), } @@ -703,7 +703,7 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> QualifiedRuleParser<'i> ) -> Result, ParseError<'i, Self::Error>> { let loc = self.loc(start); let (declarations, rules) = if self.options.flags.contains(ParserFlags::NESTING) { - parse_declarations_and_nested_rules(input, self.options, self.at_rule_parser)? + parse_declarations_and_nested_rules(input, self.options, self.at_rule_parser, true)? } else { (DeclarationBlock::parse(input, self.options)?, CssRuleList(vec![])) }; @@ -752,9 +752,10 @@ fn parse_custom_at_rule_body<'i, 't, T: crate::traits::AtRuleParser<'i>>( start: &ParserState, options: &ParserOptions<'_, 'i>, at_rule_parser: &mut T, + is_nested: bool, ) -> Result, ParseError<'i, ParserError<'i>>> { at_rule_parser - .parse_block(prelude, start, input, options) + .parse_block(prelude, start, input, options, is_nested) .map(|prelude| CssRule::Custom(prelude)) .map_err(|err| match &err.kind { ParseErrorKind::Basic(kind) => ParseError { @@ -770,9 +771,10 @@ fn parse_custom_at_rule_without_block<'i, 't, T: crate::traits::AtRuleParser<'i> start: &ParserState, options: &ParserOptions<'_, 'i>, at_rule_parser: &mut T, + is_nested: bool, ) -> Result, ()> { at_rule_parser - .rule_without_block(prelude, start, options) + .rule_without_block(prelude, start, options, is_nested) .map(|prelude| CssRule::Custom(prelude)) } @@ -780,6 +782,7 @@ fn parse_declarations_and_nested_rules<'a, 'o, 'i, 't, T: crate::traits::AtRuleP input: &mut Parser<'i, 't>, options: &'a ParserOptions<'o, 'i>, at_rule_parser: &mut T, + is_nested: bool, ) -> Result<(DeclarationBlock<'i>, CssRuleList<'i, T::AtRule>), ParseError<'i, ParserError<'i>>> { let mut important_declarations = DeclarationList::new(); let mut declarations = DeclarationList::new(); @@ -790,6 +793,7 @@ fn parse_declarations_and_nested_rules<'a, 'o, 'i, 't, T: crate::traits::AtRuleP important_declarations: &mut important_declarations, rules: &mut rules, at_rule_parser, + is_nested, }; // 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>> { important_declarations: &'a mut DeclarationList<'i>, rules: &'a mut CssRuleList<'i, T::AtRule>, at_rule_parser: &'a mut T, + is_nested: bool, } /// Parse a declaration within {} block: `color: blue` @@ -925,7 +930,7 @@ impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for StyleR AtRulePrelude::Media(query) => { self.rules.0.push(CssRule::Media(MediaRule { query, - rules: parse_nested_at_rule(input, self.options, self.at_rule_parser)?, + rules: parse_style_block(input, self.options, self.at_rule_parser, true)?, loc, })); Ok(()) @@ -933,7 +938,7 @@ impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for StyleR AtRulePrelude::Supports(condition) => { self.rules.0.push(CssRule::Supports(SupportsRule { condition, - rules: parse_nested_at_rule(input, self.options, self.at_rule_parser)?, + rules: parse_style_block(input, self.options, self.at_rule_parser, true)?, loc, })); Ok(()) @@ -942,7 +947,7 @@ impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for StyleR self.rules.0.push(CssRule::Container(ContainerRule { name, condition, - rules: parse_nested_at_rule(input, self.options, self.at_rule_parser)?, + rules: parse_style_block(input, self.options, self.at_rule_parser, true)?, loc, })); Ok(()) @@ -950,20 +955,21 @@ impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for StyleR AtRulePrelude::LayerBlock(name) => { self.rules.0.push(CssRule::LayerBlock(LayerBlockRule { name, - rules: parse_nested_at_rule(input, self.options, self.at_rule_parser)?, + rules: parse_style_block(input, self.options, self.at_rule_parser, true)?, loc, })); Ok(()) } AtRulePrelude::StartingStyle => { self.rules.0.push(CssRule::StartingStyle(StartingStyleRule { - rules: parse_nested_at_rule(input, self.options, self.at_rule_parser)?, + rules: parse_style_block(input, self.options, self.at_rule_parser, true)?, loc, })); Ok(()) } AtRulePrelude::Nest(selectors) => { - let (declarations, rules) = parse_declarations_and_nested_rules(input, self.options, self.at_rule_parser)?; + let (declarations, rules) = + parse_declarations_and_nested_rules(input, self.options, self.at_rule_parser, true)?; self.rules.0.push(CssRule::Nesting(NestingRule { style: StyleRule { selectors, @@ -992,6 +998,7 @@ impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for StyleR start, self.options, self.at_rule_parser, + true, )?); Ok(()) } @@ -1021,6 +1028,7 @@ impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for StyleR start, self.options, self.at_rule_parser, + true, )?); Ok(()) } @@ -1029,10 +1037,11 @@ impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for StyleR } } -pub fn parse_nested_at_rule<'a, 'o, 'i, 't, T: crate::traits::AtRuleParser<'i>>( +pub fn parse_style_block<'a, 'o, 'i, 't, T: crate::traits::AtRuleParser<'i>>( input: &mut Parser<'i, 't>, options: &'a ParserOptions<'o, 'i>, at_rule_parser: &mut T, + is_nested: bool, ) -> Result, ParseError<'i, ParserError<'i>>> { let loc = input.current_source_location(); let loc = Location { @@ -1043,7 +1052,7 @@ pub fn parse_nested_at_rule<'a, 'o, 'i, 't, T: crate::traits::AtRuleParser<'i>>( // Declarations can be immediately within @media and @supports blocks that are nested within a parent style rule. // These act the same way as if they were nested within a `& { ... }` block. - let (declarations, mut rules) = parse_declarations_and_nested_rules(input, options, at_rule_parser)?; + let (declarations, mut rules) = parse_declarations_and_nested_rules(input, options, at_rule_parser, is_nested)?; if declarations.len() > 0 { rules.0.insert( @@ -1073,10 +1082,14 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> QualifiedRuleParser<'i> input: &mut Parser<'i, 't>, ) -> Result> { let selector_parser = SelectorParser { - is_nesting_allowed: true, + is_nesting_allowed: self.options.flags.contains(ParserFlags::NESTING), options: &self.options, }; - SelectorList::parse_relative(&selector_parser, input, NestingRequirement::Implicit) + if self.is_nested { + SelectorList::parse_relative(&selector_parser, input, NestingRequirement::Implicit) + } else { + SelectorList::parse(&selector_parser, input, NestingRequirement::None) + } } fn parse_block<'t>( @@ -1086,7 +1099,8 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> QualifiedRuleParser<'i> input: &mut Parser<'i, 't>, ) -> Result<(), ParseError<'i, Self::Error>> { let loc = start.source_location(); - let (declarations, rules) = parse_declarations_and_nested_rules(input, self.options, self.at_rule_parser)?; + let (declarations, rules) = + parse_declarations_and_nested_rules(input, self.options, self.at_rule_parser, true)?; self.rules.0.push(CssRule::Style(StyleRule { selectors, vendor_prefix: VendorPrefix::empty(), diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 92e54006..f13f69e0 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -63,9 +63,7 @@ use crate::context::PropertyHandlerContext; use crate::declaration::{DeclarationBlock, DeclarationHandler}; use crate::dependencies::{Dependency, ImportDependency}; use crate::error::{MinifyError, ParserError, PrinterError, PrinterErrorKind}; -use crate::parser::{ - parse_nested_at_rule, DefaultAtRule, DefaultAtRuleParser, NestedRuleParser, TopLevelRuleParser, -}; +use crate::parser::{parse_style_block, DefaultAtRule, DefaultAtRuleParser, NestedRuleParser, TopLevelRuleParser}; use crate::prefixes::Feature; use crate::printer::Printer; use crate::rules::keyframes::KeyframesName; @@ -422,8 +420,9 @@ impl<'i> CssRuleList<'i, DefaultAtRule> { pub fn parse_style_block<'t>( input: &mut Parser<'i, 't>, options: &ParserOptions<'_, 'i>, + is_nested: bool, ) -> Result>> { - Self::parse_style_block_with(input, options, &mut DefaultAtRuleParser) + Self::parse_style_block_with(input, options, &mut DefaultAtRuleParser, is_nested) } } @@ -447,8 +446,9 @@ impl<'i, T> CssRuleList<'i, T> { input: &mut Parser<'i, 't>, options: &ParserOptions<'_, 'i>, at_rule_parser: &mut P, + is_nested: bool, ) -> Result>> { - parse_nested_at_rule(input, options, at_rule_parser) + parse_style_block(input, options, at_rule_parser, is_nested) } } diff --git a/src/traits.rs b/src/traits.rs index 5237e016..81f62760 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -306,6 +306,7 @@ pub trait AtRuleParser<'i>: Sized { /// representation of the at-rule. /// /// The location passed in is source location of the start of the prelude. + /// `is_nested` indicates whether the rule is nested inside a style rule. /// /// This is only called when either the `;` semicolon indeed follows the prelude, /// or parser is at the end of the input. @@ -314,16 +315,19 @@ pub trait AtRuleParser<'i>: Sized { prelude: Self::Prelude, start: &ParserState, options: &ParserOptions<'_, 'i>, + is_nested: bool, ) -> Result { let _ = prelude; let _ = start; let _ = options; + let _ = is_nested; Err(()) } /// Parse the content of a `{ /* ... */ }` block for the body of the at-rule. /// /// The location passed in is source location of the start of the prelude. + /// `is_nested` indicates whether the rule is nested inside a style rule. /// /// Return the finished representation of the at-rule /// as returned by `RuleListParser::next` or `DeclarationListParser::next`, @@ -336,11 +340,13 @@ pub trait AtRuleParser<'i>: Sized { start: &ParserState, input: &mut Parser<'i, 't>, options: &ParserOptions<'_, 'i>, + is_nested: bool, ) -> Result> { let _ = prelude; let _ = start; let _ = input; let _ = options; + let _ = is_nested; Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)) } } diff --git a/tests/test_custom_parser.rs b/tests/test_custom_parser.rs index b75fa9be..8e360ef2 100644 --- a/tests/test_custom_parser.rs +++ b/tests/test_custom_parser.rs @@ -101,6 +101,7 @@ impl<'i> AtRuleParser<'i> for TestAtRuleParser { prelude: Self::Prelude, _start: &ParserState, _options: &ParserOptions<'_, 'i>, + _is_nested: bool, ) -> Result { match prelude { Prelude::Inline(name) => Ok(AtRule::Inline(InlineRule { name })), @@ -114,6 +115,7 @@ impl<'i> AtRuleParser<'i> for TestAtRuleParser { _start: &ParserState, input: &mut Parser<'i, 't>, _options: &ParserOptions<'_, 'i>, + _is_nested: bool, ) -> Result> { match prelude { Prelude::Block(name) => Ok(AtRule::Block(BlockRule {