@@ -336,6 +336,26 @@ final class _EvaluateVisitor
336336 /// If this is empty, that indicates that the current module is not configured.
337337 var _configuration = const Configuration .empty ();
338338
339+ /// Returns whether the current position in the output stylesheet uses plain
340+ /// CSS nesting.
341+ ///
342+ /// If it does, it's safe to use other features of the CSS nesting spec
343+ /// because the user has already opted into a narrower browser compatibility
344+ /// window.
345+ bool get _hasCssNesting {
346+ ModifiableCssParentNode ? parent = _styleRule;
347+ while (true ) {
348+ switch (parent? .parent) {
349+ case CssStyleRule _:
350+ return true ;
351+ case var grandparent?:
352+ parent = grandparent;
353+ case null :
354+ return false ;
355+ }
356+ }
357+ }
358+
339359 /// Creates a new visitor.
340360 ///
341361 /// Most arguments are the same as those to [evaluateAsync] .
@@ -1554,8 +1574,23 @@ final class _EvaluateVisitor
15541574 _inUnknownAtRule = true ;
15551575 }
15561576
1577+ // If the user has already opted into plain CSS nesting, don't bother with
1578+ // any merging or bubbling; this rule is already only usable by browsers
1579+ // that support nesting natively anyway.
1580+ var rule = ModifiableCssAtRule (name, node.span, value: value);
1581+ if (_hasCssNesting) {
1582+ await _withParent (rule, () async {
1583+ for (var child in children) {
1584+ await child.accept (this );
1585+ }
1586+ }, scopeWhen: node.hasDeclarations);
1587+ _inUnknownAtRule = wasInUnknownAtRule;
1588+ _inKeyframes = wasInKeyframes;
1589+ return null ;
1590+ }
1591+
15571592 await _withParent (
1558- ModifiableCssAtRule (name, node.span, value : value) ,
1593+ rule ,
15591594 () async {
15601595 var styleRule = _styleRule;
15611596 if (styleRule == null || _inKeyframes || name.value == 'font-face' ) {
@@ -2224,6 +2259,19 @@ final class _EvaluateVisitor
22242259 }
22252260
22262261 var queries = await _visitMediaQueries (node.query);
2262+
2263+ // If the user has already opted into plain CSS nesting, don't bother with
2264+ // any merging or bubbling; this rule is already only usable by browsers
2265+ // that support nesting natively anyway.
2266+ if (_hasCssNesting) {
2267+ await _withParent (ModifiableCssMediaRule (queries, node.span), () async {
2268+ for (var child in node.children) {
2269+ await child.accept (this );
2270+ }
2271+ }, scopeWhen: false );
2272+ return null ;
2273+ }
2274+
22272275 var mergedQueries = _mediaQueries.andThen (
22282276 (mediaQueries) => _mergeMediaQueries (mediaQueries, queries),
22292277 );
@@ -2366,12 +2414,12 @@ final class _EvaluateVisitor
23662414 plainCss: _stylesheet.plainCss,
23672415 );
23682416
2369- var nest = switch (_styleRule) {
2417+ var merge = switch (_styleRule) {
23702418 null => true ,
23712419 CssStyleRule (fromPlainCss: true ) => false ,
23722420 _ => ! (_stylesheet.plainCss && parsedSelector.containsParentSelector)
23732421 };
2374- if (nest ) {
2422+ if (merge ) {
23752423 if (_stylesheet.plainCss) {
23762424 for (var complex in parsedSelector.components) {
23772425 if (complex.leadingCombinators
@@ -2412,7 +2460,7 @@ final class _EvaluateVisitor
24122460 }
24132461 });
24142462 },
2415- through: nest ? (node) => node is CssStyleRule : null ,
2463+ through: merge ? (node) => node is CssStyleRule : null ,
24162464 scopeWhen: node.hasDeclarations,
24172465 );
24182466 _atRootExcludingStyleRule = oldAtRootExcludingStyleRule;
@@ -2490,12 +2538,23 @@ final class _EvaluateVisitor
24902538 );
24912539 }
24922540
2493- var condition = CssValue (
2494- await _visitSupportsCondition (node.condition),
2495- node.condition.span,
2496- );
2541+ var rule = ModifiableCssSupportsRule (
2542+ CssValue (
2543+ await _visitSupportsCondition (node.condition),
2544+ node.condition.span,
2545+ ),
2546+ node.span);
2547+ if (_hasCssNesting) {
2548+ await _withParent (rule, () async {
2549+ for (var child in node.children) {
2550+ await child.accept (this );
2551+ }
2552+ }, scopeWhen: node.hasDeclarations);
2553+ return null ;
2554+ }
2555+
24972556 await _withParent (
2498- ModifiableCssSupportsRule (condition, node.span) ,
2557+ rule ,
24992558 () async {
25002559 if (_styleRule case var styleRule? ) {
25012560 // If we're in a style rule, copy it into the supports rule so that
@@ -3988,8 +4047,23 @@ final class _EvaluateVisitor
39884047 _inUnknownAtRule = true ;
39894048 }
39904049
4050+ // If the user has already opted into plain CSS nesting, don't bother with
4051+ // any merging or bubbling; this rule is already only usable by browsers
4052+ // that support nesting natively anyway.
4053+ var rule = ModifiableCssAtRule (node.name, node.span, value: node.value);
4054+ if (_hasCssNesting) {
4055+ await _withParent (rule, () async {
4056+ for (var child in node.children) {
4057+ await child.accept (this );
4058+ }
4059+ }, scopeWhen: false );
4060+ _inUnknownAtRule = wasInUnknownAtRule;
4061+ _inKeyframes = wasInKeyframes;
4062+ return ;
4063+ }
4064+
39914065 await _withParent (
3992- ModifiableCssAtRule (node.name, node.span, value : node.value) ,
4066+ rule ,
39934067 () async {
39944068 // We don't have to check for an unknown at-rule in a style rule here,
39954069 // because the previous compilation has already bubbled the at-rule to the
@@ -4080,6 +4154,19 @@ final class _EvaluateVisitor
40804154 );
40814155 }
40824156
4157+ // If the user has already opted into plain CSS nesting, don't bother with
4158+ // any merging or bubbling; this rule is already only usable by browsers
4159+ // that support nesting natively anyway.
4160+ if (_hasCssNesting) {
4161+ await _withParent (ModifiableCssMediaRule (node.queries, node.span),
4162+ () async {
4163+ for (var child in node.children) {
4164+ await child.accept (this );
4165+ }
4166+ }, scopeWhen: false );
4167+ return ;
4168+ }
4169+
40834170 var mergedQueries = _mediaQueries.andThen (
40844171 (mediaQueries) => _mergeMediaQueries (mediaQueries, node.queries),
40854172 );
@@ -4141,12 +4228,12 @@ final class _EvaluateVisitor
41414228 }
41424229
41434230 var styleRule = _styleRule;
4144- var nest = switch (_styleRule) {
4231+ var merge = switch (_styleRule) {
41454232 null => true ,
41464233 CssStyleRule (fromPlainCss: true ) => false ,
41474234 _ => ! (node.fromPlainCss && node.selector.containsParentSelector)
41484235 };
4149- var originalSelector = nest
4236+ var originalSelector = merge
41504237 ? node.selector.nestWithin (
41514238 styleRule? .originalSelector,
41524239 implicitParent: ! _atRootExcludingStyleRule,
@@ -4171,7 +4258,7 @@ final class _EvaluateVisitor
41714258 }
41724259 });
41734260 },
4174- through: nest ? (node) => node is CssStyleRule : null ,
4261+ through: merge ? (node) => node is CssStyleRule : null ,
41754262 scopeWhen: false ,
41764263 );
41774264 _atRootExcludingStyleRule = oldAtRootExcludingStyleRule;
@@ -4198,8 +4285,18 @@ final class _EvaluateVisitor
41984285 );
41994286 }
42004287
4288+ var rule = ModifiableCssSupportsRule (node.condition, node.span);
4289+ if (_hasCssNesting) {
4290+ await _withParent (rule, () async {
4291+ for (var child in node.children) {
4292+ await child.accept (this );
4293+ }
4294+ }, scopeWhen: false );
4295+ return ;
4296+ }
4297+
42014298 await _withParent (
4202- ModifiableCssSupportsRule (node.condition, node.span) ,
4299+ rule ,
42034300 () async {
42044301 if (_styleRule case var styleRule? ) {
42054302 // If we're in a style rule, copy it into the supports rule so that
@@ -4429,7 +4526,7 @@ final class _EvaluateVisitor
44294526 return result;
44304527 }
44314528
4432- /// If the current [_parent] is not the last child of its grandparent , makes a
4529+ /// If the current [_parent] is not the last child of its own parent , makes a
44334530 /// new childless copy of it and sets [_parent] to that.
44344531 ///
44354532 /// Otherwise, leaves [_parent] as-is.
0 commit comments