Skip to content

Commit 6c465c1

Browse files
committed
Update nesting implementation for new spec
Fixes #806, fixes #941, fixes #781, fixes #612 The spec now allows mixing declarations and rules (https://drafts.csswg.org/css-nesting/#mixing), and nesting declarations in at-rules when the parent style rule has pseudo elements (https://drafts.csswg.org/css-nesting/#nested-declarations-rule).
1 parent d398c1b commit 6c465c1

File tree

9 files changed

+309
-71
lines changed

9 files changed

+309
-71
lines changed

napi/src/transformer.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ impl<'i> Visitor<'i, AtRule<'i>> for JsVisitor {
311311
CssRule::Scope(..) => "scope",
312312
CssRule::MozDocument(..) => "moz-document",
313313
CssRule::Nesting(..) => "nesting",
314+
CssRule::NestedDeclarations(..) => "nested-declarations",
314315
CssRule::Viewport(..) => "viewport",
315316
CssRule::StartingStyle(..) => "starting-style",
316317
CssRule::ViewTransition(..) => "view-transition",

node/ast.d.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ export type Rule<D = Declaration, M = MediaQuery> = | {
6161
type: "nesting";
6262
value: NestingRule<D, M>;
6363
}
64+
| {
65+
type: "nested-declarations";
66+
value: NestedDeclarationsRule<D>;
67+
}
6468
| {
6569
type: "viewport";
6670
value: ViewportRule<D>;
@@ -9523,6 +9527,19 @@ export interface NestingRule<D = Declaration, M = MediaQuery> {
95239527
*/
95249528
style: StyleRule<D, M>;
95259529
}
9530+
/**
9531+
* A [nested declarations](https://drafts.csswg.org/css-nesting/#nested-declarations-rule) rule.
9532+
*/
9533+
export interface NestedDeclarationsRule<D = Declaration> {
9534+
/**
9535+
* The style rule that defines the selector and declarations for the `@nest` rule.
9536+
*/
9537+
declarations: DeclarationBlock<D>;
9538+
/**
9539+
* The location of the rule in the source file.
9540+
*/
9541+
loc: Location2;
9542+
}
95269543
/**
95279544
* A [@viewport](https://drafts.csswg.org/css-device-adapt/#atviewport-rule) rule.
95289545
*/

src/declaration.rs

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::borrow::Cow;
44
use std::ops::Range;
55

66
use crate::context::{DeclarationContext, PropertyHandlerContext};
7-
use crate::error::{ParserError, PrinterError};
7+
use crate::error::{ParserError, PrinterError, PrinterErrorKind};
88
use crate::parser::ParserOptions;
99
use crate::printer::Printer;
1010
use crate::properties::box_shadow::BoxShadowHandler;
@@ -34,13 +34,15 @@ use crate::properties::{
3434
ui::ColorSchemeHandler,
3535
};
3636
use crate::properties::{Property, PropertyId};
37+
use crate::selector::SelectorList;
3738
use crate::traits::{PropertyHandler, ToCss};
3839
use crate::values::ident::DashedIdent;
3940
use crate::values::string::CowArcStr;
4041
#[cfg(feature = "visitor")]
4142
use crate::visitor::Visit;
4243
use cssparser::*;
4344
use indexmap::IndexMap;
45+
use smallvec::SmallVec;
4446

4547
/// A CSS declaration block.
4648
///
@@ -157,29 +159,78 @@ impl<'i> DeclarationBlock<'i> {
157159
dest.whitespace()?;
158160
dest.write_char('{')?;
159161
dest.indent();
162+
dest.newline()?;
163+
164+
self.to_css_declarations(dest, false, &parcel_selectors::SelectorList(SmallVec::new()), 0)?;
165+
166+
dest.dedent();
167+
dest.newline()?;
168+
dest.write_char('}')
169+
}
160170

171+
pub(crate) fn has_printable_declarations(&self) -> bool {
172+
if self.len() > 1 {
173+
return true;
174+
}
175+
176+
if self.declarations.len() == 1 {
177+
!matches!(self.declarations[0], crate::properties::Property::Composes(_))
178+
} else if self.important_declarations.len() == 1 {
179+
!matches!(self.important_declarations[0], crate::properties::Property::Composes(_))
180+
} else {
181+
false
182+
}
183+
}
184+
185+
/// Writes the declarations to a CSS declaration block.
186+
pub fn to_css_declarations<W>(
187+
&self,
188+
dest: &mut Printer<W>,
189+
has_nested_rules: bool,
190+
selectors: &SelectorList,
191+
source_index: u32,
192+
) -> Result<(), PrinterError>
193+
where
194+
W: std::fmt::Write,
195+
{
161196
let mut i = 0;
162197
let len = self.len();
163198

164199
macro_rules! write {
165200
($decls: expr, $important: literal) => {
166201
for decl in &$decls {
167-
dest.newline()?;
202+
// The CSS modules `composes` property is handled specially, and omitted during printing.
203+
// We need to add the classes it references to the list for the selectors in this rule.
204+
if let crate::properties::Property::Composes(composes) = &decl {
205+
if dest.is_nested() && dest.css_module.is_some() {
206+
return Err(dest.error(PrinterErrorKind::InvalidComposesNesting, composes.loc));
207+
}
208+
209+
if let Some(css_module) = &mut dest.css_module {
210+
css_module
211+
.handle_composes(&selectors, &composes, source_index)
212+
.map_err(|e| dest.error(e, composes.loc))?;
213+
continue;
214+
}
215+
}
216+
217+
if i > 0 {
218+
dest.newline()?;
219+
}
220+
168221
decl.to_css(dest, $important)?;
169-
if i != len - 1 || !dest.minify {
222+
if i != len - 1 || !dest.minify || has_nested_rules {
170223
dest.write_char(';')?;
171224
}
225+
172226
i += 1;
173227
}
174228
};
175229
}
176230

177231
write!(self.declarations, false);
178232
write!(self.important_declarations, true);
179-
180-
dest.dedent();
181-
dest.newline()?;
182-
dest.write_char('}')
233+
Ok(())
183234
}
184235
}
185236

src/lib.rs

Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24052,13 +24052,13 @@ mod tests {
2405224052
}
2405324053
"#,
2405424054
indoc! {r#"
24055-
.foo {
24056-
color: red;
24057-
}
24058-
2405924055
.foo .bar {
2406024056
color: #00f;
2406124057
}
24058+
24059+
.foo {
24060+
color: red;
24061+
}
2406224062
"#},
2406324063
);
2406424064

@@ -24072,12 +24072,16 @@ mod tests {
2407224072
"#,
2407324073
indoc! {r#"
2407424074
article {
24075-
color: red;
24075+
color: green;
2407624076
}
2407724077

2407824078
article {
2407924079
color: #00f;
2408024080
}
24081+
24082+
article {
24083+
color: red;
24084+
}
2408124085
"#},
2408224086
);
2408324087

@@ -24190,6 +24194,29 @@ mod tests {
2419024194
}
2419124195
"#},
2419224196
);
24197+
nesting_test(
24198+
r#"
24199+
.foo {
24200+
&::before, &::after {
24201+
background: blue;
24202+
@media screen {
24203+
background: orange;
24204+
}
24205+
}
24206+
}
24207+
"#,
24208+
indoc! {r#"
24209+
.foo:before, .foo:after {
24210+
background: #00f;
24211+
}
24212+
24213+
@media screen {
24214+
.foo:before, .foo:after {
24215+
background: orange;
24216+
}
24217+
}
24218+
"#},
24219+
);
2419324220

2419424221
nesting_test_no_targets(
2419524222
r#"
@@ -25225,9 +25252,7 @@ mod tests {
2522525252
indoc! {r#"
2522625253
.EgL3uq_box2 {
2522725254
@container EgL3uq_main (width >= 0) {
25228-
& {
25229-
background-color: #90ee90;
25230-
}
25255+
background-color: #90ee90;
2523125256
}
2523225257
}
2523325258
"#},
@@ -25251,9 +25276,7 @@ mod tests {
2525125276
indoc! {r#"
2525225277
.EgL3uq_box2 {
2525325278
@container main (width >= 0) {
25254-
& {
25255-
background-color: #90ee90;
25256-
}
25279+
background-color: #90ee90;
2525725280
}
2525825281
}
2525925282
"#},
@@ -25460,6 +25483,56 @@ mod tests {
2546025483
test_project_root("/foo", "/foo/test.css", "EgL3uq");
2546125484
test_project_root("/foo/bar", "/foo/bar/baz/test.css", "xLEkNW");
2546225485
test_project_root("/foo", "/foo/baz/test.css", "xLEkNW");
25486+
25487+
let mut stylesheet = StyleSheet::parse(
25488+
r#"
25489+
.foo {
25490+
color: red;
25491+
.bar {
25492+
color: green;
25493+
}
25494+
composes: test from "foo.css";
25495+
}
25496+
"#,
25497+
ParserOptions {
25498+
filename: "test.css".into(),
25499+
css_modules: Some(Default::default()),
25500+
..ParserOptions::default()
25501+
},
25502+
)
25503+
.unwrap();
25504+
stylesheet.minify(MinifyOptions::default()).unwrap();
25505+
let res = stylesheet
25506+
.to_css(PrinterOptions {
25507+
targets: Browsers {
25508+
chrome: Some(95 << 16),
25509+
..Browsers::default()
25510+
}
25511+
.into(),
25512+
..Default::default()
25513+
})
25514+
.unwrap();
25515+
assert_eq!(
25516+
res.code,
25517+
indoc! {r#"
25518+
.EgL3uq_foo {
25519+
color: red;
25520+
}
25521+
25522+
.EgL3uq_foo .EgL3uq_bar {
25523+
color: green;
25524+
}
25525+
25526+
25527+
"#}
25528+
);
25529+
assert_eq!(
25530+
res.exports.unwrap(),
25531+
map! {
25532+
"foo" => "EgL3uq_foo" "test" from "foo.css",
25533+
"bar" => "EgL3uq_bar"
25534+
}
25535+
);
2546325536
}
2546425537

2546525538
#[test]
@@ -26832,7 +26905,7 @@ mod tests {
2683226905
}
2683326906
}
2683426907
"#,
26835-
".foo{@scope(.bar){&{color:#ff0}}}",
26908+
".foo{@scope(.bar){color:#ff0}}",
2683626909
);
2683726910
nesting_test(
2683826911
r#"
@@ -26844,9 +26917,7 @@ mod tests {
2684426917
"#,
2684526918
indoc! {r#"
2684626919
@scope (.bar) {
26847-
:scope {
26848-
color: #ff0;
26849-
}
26920+
color: #ff0;
2685026921
}
2685126922
"#},
2685226923
);

0 commit comments

Comments
 (0)