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 {