Skip to content

Commit

Permalink
Fix custom top-level style blocks when not nested
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
devongovett committed Jul 8, 2023
1 parent eff8ce1 commit c7913b0
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 24 deletions.
1 change: 1 addition & 0 deletions examples/custom_at_rule.rs
Expand Up @@ -137,6 +137,7 @@ impl<'i> AtRuleParser<'i> for TailwindAtRuleParser {
prelude: Self::Prelude,
start: &ParserState,
_options: &ParserOptions<'_, 'i>,
_is_nested: bool,
) -> Result<Self::AtRule, ()> {
let loc = start.source_location();
match prelude {
Expand Down
4 changes: 3 additions & 1 deletion node/src/at_rule_parser.rs
Expand Up @@ -103,6 +103,7 @@ impl<'i> AtRuleParser<'i> for CustomAtRuleParser {
start: &ParserState,
input: &mut Parser<'i, 't>,
options: &ParserOptions<'_, 'i>,
is_nested: bool,
) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {
let config = self.configs.get(prelude.name.as_ref()).unwrap();
let body = if let Some(body) = &config.body {
Expand All @@ -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 {
Expand All @@ -139,6 +140,7 @@ impl<'i> AtRuleParser<'i> for CustomAtRuleParser {
prelude: Self::Prelude,
start: &ParserState,
options: &ParserOptions<'_, 'i>,
_is_nested: bool,
) -> Result<Self::AtRule, ()> {
let config = self.configs.get(prelude.name.as_ref()).unwrap();
if config.body.is_some() {
Expand Down
28 changes: 26 additions & 2 deletions node/test/customAtRules.mjs
Expand Up @@ -59,12 +59,12 @@ test('mixin', () => {
code: Buffer.from(`
@mixin color {
color: red;
&.bar {
color: yellow;
}
}
.foo {
@apply color;
}
Expand Down Expand Up @@ -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',
Expand Down
46 changes: 30 additions & 16 deletions src/parser.rs
Expand Up @@ -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)
}
}
}
Expand All @@ -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(()),
}
Expand Down Expand Up @@ -703,7 +703,7 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> QualifiedRuleParser<'i>
) -> Result<CssRule<'i, T::AtRule>, 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![]))
};
Expand Down Expand Up @@ -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<CssRule<'i, T::AtRule>, 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 {
Expand All @@ -770,16 +771,18 @@ 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<CssRule<'i, T::AtRule>, ()> {
at_rule_parser
.rule_without_block(prelude, start, options)
.rule_without_block(prelude, start, options, is_nested)
.map(|prelude| CssRule::Custom(prelude))
}

fn parse_declarations_and_nested_rules<'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<(DeclarationBlock<'i>, CssRuleList<'i, T::AtRule>), ParseError<'i, ParserError<'i>>> {
let mut important_declarations = DeclarationList::new();
let mut declarations = DeclarationList::new();
Expand All @@ -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.
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -925,15 +930,15 @@ 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(())
}
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(())
Expand All @@ -942,28 +947,29 @@ 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(())
}
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,
Expand Down Expand Up @@ -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(())
}
Expand Down Expand Up @@ -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(())
}
Expand All @@ -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<CssRuleList<'i, T::AtRule>, ParseError<'i, ParserError<'i>>> {
let loc = input.current_source_location();
let loc = Location {
Expand All @@ -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(
Expand Down Expand Up @@ -1073,10 +1082,14 @@ impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> QualifiedRuleParser<'i>
input: &mut Parser<'i, 't>,
) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
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>(
Expand All @@ -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(),
Expand Down
10 changes: 5 additions & 5 deletions src/rules/mod.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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, ParseError<'i, ParserError<'i>>> {
Self::parse_style_block_with(input, options, &mut DefaultAtRuleParser)
Self::parse_style_block_with(input, options, &mut DefaultAtRuleParser, is_nested)
}
}

Expand All @@ -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<Self, ParseError<'i, ParserError<'i>>> {
parse_nested_at_rule(input, options, at_rule_parser)
parse_style_block(input, options, at_rule_parser, is_nested)
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/traits.rs
Expand Up @@ -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.
Expand All @@ -314,16 +315,19 @@ pub trait AtRuleParser<'i>: Sized {
prelude: Self::Prelude,
start: &ParserState,
options: &ParserOptions<'_, 'i>,
is_nested: bool,
) -> Result<Self::AtRule, ()> {
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`,
Expand All @@ -336,11 +340,13 @@ pub trait AtRuleParser<'i>: Sized {
start: &ParserState,
input: &mut Parser<'i, 't>,
options: &ParserOptions<'_, 'i>,
is_nested: bool,
) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {
let _ = prelude;
let _ = start;
let _ = input;
let _ = options;
let _ = is_nested;
Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid))
}
}
2 changes: 2 additions & 0 deletions tests/test_custom_parser.rs
Expand Up @@ -101,6 +101,7 @@ impl<'i> AtRuleParser<'i> for TestAtRuleParser {
prelude: Self::Prelude,
_start: &ParserState,
_options: &ParserOptions<'_, 'i>,
_is_nested: bool,
) -> Result<Self::AtRule, ()> {
match prelude {
Prelude::Inline(name) => Ok(AtRule::Inline(InlineRule { name })),
Expand All @@ -114,6 +115,7 @@ impl<'i> AtRuleParser<'i> for TestAtRuleParser {
_start: &ParserState,
input: &mut Parser<'i, 't>,
_options: &ParserOptions<'_, 'i>,
_is_nested: bool,
) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {
match prelude {
Prelude::Block(name) => Ok(AtRule::Block(BlockRule {
Expand Down

0 comments on commit c7913b0

Please sign in to comment.