Skip to content

Commit 9009a64

Browse files
authored
Implement the new mixed declaration/comment/at-rule behavior (#2629)
1 parent 6d3d928 commit 9009a64

File tree

14 files changed

+111
-180
lines changed

14 files changed

+111
-180
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
## 1.91.1-dev
1+
## 1.92.0-dev
2+
3+
* **Breaking change:** Emit declarations, childless at-rules, and comments in
4+
the order they appear in the source even when they're interleaved with nested
5+
rules. This obsoletes the `mixed-decls` deprecation.
26

37
* Fix a bug where `@extend` rules loaded through a mixture of `@import` and
48
`@use` rules could fail to apply correctly.

lib/src/ast/css/declaration.dart

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22
// MIT-style license that can be found in the LICENSE file or at
33
// https://opensource.org/licenses/MIT.
44

5-
import 'package:meta/meta.dart';
65
import 'package:source_span/source_span.dart';
76
import 'package:stack_trace/stack_trace.dart';
87

98
import '../../value.dart';
109
import 'node.dart';
11-
import 'style_rule.dart';
1210
import 'value.dart';
1311

1412
/// A plain CSS declaration (that is, a `name: value` pair).
@@ -19,16 +17,6 @@ abstract interface class CssDeclaration implements CssNode {
1917
/// The value of this declaration.
2018
CssValue<Value> get value;
2119

22-
/// A list of style rules that appeared before this declaration in the Sass
23-
/// input but after it in the CSS output.
24-
///
25-
/// These are used to emit mixed declaration deprecation warnings during
26-
/// serialization, so we can check based on specificity whether the warnings
27-
/// are really necessary without worrying about `@extend` potentially changing
28-
/// things up.
29-
@internal
30-
List<CssStyleRule> get interleavedRules;
31-
3220
/// The stack trace indicating where this node was created.
3321
///
3422
/// This is used to emit interleaved declaration warnings, and is only set if

lib/src/ast/css/modifiable/declaration.dart

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import '../../../value.dart';
99
import '../../../visitor/interface/modifiable_css.dart';
1010
import '../declaration.dart';
1111
import '../value.dart';
12-
import '../style_rule.dart';
1312
import 'node.dart';
1413

1514
/// A modifiable version of [CssDeclaration] for use in the evaluation step.
@@ -18,7 +17,6 @@ final class ModifiableCssDeclaration extends ModifiableCssNode
1817
final CssValue<String> name;
1918
final CssValue<Value> value;
2019
final bool parsedAsCustomProperty;
21-
final List<CssStyleRule> interleavedRules;
2220
final Trace? trace;
2321
final FileSpan valueSpanForMap;
2422
final FileSpan span;
@@ -31,13 +29,9 @@ final class ModifiableCssDeclaration extends ModifiableCssNode
3129
this.value,
3230
this.span, {
3331
required this.parsedAsCustomProperty,
34-
Iterable<CssStyleRule>? interleavedRules,
3532
this.trace,
3633
FileSpan? valueSpanForMap,
37-
}) : interleavedRules = interleavedRules == null
38-
? const []
39-
: List.unmodifiable(interleavedRules),
40-
valueSpanForMap = valueSpanForMap ?? value.span {
34+
}) : valueSpanForMap = valueSpanForMap ?? value.span {
4135
if (parsedAsCustomProperty) {
4236
if (!isCustomProperty) {
4337
throw ArgumentError(

lib/src/deprecation.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ enum Deprecation {
1515
// DO NOT EDIT. This section was generated from the language repo.
1616
// See tool/grind/generate_deprecations.dart for details.
1717
//
18-
// Checksum: 1c1efc2604729aea755453fefd1e8f56e100698e
18+
// Checksum: 2a2a94a3dd8ab2f7e3880b24e8e7850d66998b33
1919

2020
/// Deprecation for passing a string directly to meta.call().
2121
callString('call-string',
@@ -93,6 +93,7 @@ enum Deprecation {
9393
/// Deprecation for declarations after or between nested rules.
9494
mixedDecls('mixed-decls',
9595
deprecatedIn: '1.77.7',
96+
obsoleteIn: '1.92.0',
9697
description: 'Declarations after or between nested rules.'),
9798

9899
/// Deprecation for meta.feature-exists
@@ -189,9 +190,10 @@ enum Deprecation {
189190
Version? get obsoleteIn => _obsoleteIn?.andThen(Version.parse);
190191

191192
/// Constructs a regular deprecation.
192-
const Deprecation(this.id, {required String? deprecatedIn, this.description})
193+
const Deprecation(this.id,
194+
{required String? deprecatedIn, this.description, String? obsoleteIn})
193195
: _deprecatedIn = deprecatedIn,
194-
_obsoleteIn = null,
196+
_obsoleteIn = obsoleteIn,
195197
isFuture = false;
196198

197199
/// Constructs a future deprecation.

lib/src/js/deprecations.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ final Map<String, Deprecation?> deprecations = {
4242
})(),
4343
description: deprecation.description,
4444
deprecatedIn: deprecation.deprecatedIn,
45-
obsoleteIn: deprecation.deprecatedIn,
45+
obsoleteIn: deprecation.obsoleteIn,
4646
),
4747
};
4848

lib/src/visitor/async_evaluate.dart

Lines changed: 23 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,46 +1342,6 @@ final class _EvaluateVisitor
13421342
);
13431343
}
13441344

1345-
var siblings = _parent.parent!.children;
1346-
var interleavedRules = <CssStyleRule>[];
1347-
if (siblings.last != _parent &&
1348-
// Reproduce this condition from [_warn] so that we don't add anything to
1349-
// [interleavedRules] for declarations in dependencies.
1350-
!(_quietDeps && _inDependency)) {
1351-
loop:
1352-
for (var sibling in siblings.skip(siblings.indexOf(_parent) + 1)) {
1353-
switch (sibling) {
1354-
case CssComment():
1355-
continue loop;
1356-
1357-
case CssStyleRule rule:
1358-
interleavedRules.add(rule);
1359-
1360-
case _:
1361-
// Always warn for siblings that aren't style rules, because they
1362-
// add no specificity and they're nested in the same parent as this
1363-
// declaration.
1364-
_warn(
1365-
"Sass's behavior for declarations that appear after nested\n"
1366-
"rules will be changing to match the behavior specified by CSS "
1367-
"in an upcoming\n"
1368-
"version. To keep the existing behavior, move the declaration "
1369-
"above the nested\n"
1370-
"rule. To opt into the new behavior, wrap the declaration in "
1371-
"`& {}`.\n"
1372-
"\n"
1373-
"More info: https://sass-lang.com/d/mixed-decls",
1374-
MultiSpan(node.span, 'declaration', {
1375-
sibling.span: 'nested rule',
1376-
}),
1377-
Deprecation.mixedDecls,
1378-
);
1379-
interleavedRules.clear();
1380-
break;
1381-
}
1382-
}
1383-
}
1384-
13851345
var name = await _interpolationToValue(node.name, warnForColor: true);
13861346
if (_declarationName case var declarationName?) {
13871347
name = CssValue("$declarationName-${name.value}", name.span);
@@ -1395,14 +1355,14 @@ final class _EvaluateVisitor
13951355
_isEmptyList(value) ||
13961356
// Custom properties are allowed to have empty values, per spec.
13971357
name.value.startsWith('--')) {
1358+
_copyParentAfterSibling();
13981359
_parent.addChild(
13991360
ModifiableCssDeclaration(
14001361
name,
14011362
CssValue(value, expression.span),
14021363
node.span,
14031364
parsedAsCustomProperty: node.isCustomProperty,
1404-
interleavedRules: interleavedRules,
1405-
trace: interleavedRules.isEmpty ? null : _stackTrace(node.span),
1365+
trace: _stackTrace(node.span),
14061366
valueSpanForMap:
14071367
_sourceMap ? node.value.andThen(_expressionNode)?.span : null,
14081368
),
@@ -1565,6 +1525,7 @@ final class _EvaluateVisitor
15651525

15661526
var children = node.children;
15671527
if (children == null) {
1528+
_copyParentAfterSibling();
15681529
_parent.addChild(
15691530
ModifiableCssAtRule(name, node.span, childless: true, value: value),
15701531
);
@@ -2081,6 +2042,7 @@ final class _EvaluateVisitor
20812042
);
20822043

20832044
if (_parent != _root) {
2045+
_copyParentAfterSibling();
20842046
_parent.addChild(node);
20852047
} else if (_endOfImports == _root.children.length) {
20862048
_root.addChild(node);
@@ -2231,6 +2193,8 @@ final class _EvaluateVisitor
22312193
var text = await _performInterpolation(node.text);
22322194
// Indented syntax doesn't require */
22332195
if (!text.endsWith("*/")) text += " */";
2196+
2197+
_copyParentAfterSibling();
22342198
_parent.addChild(ModifiableCssComment(text, node.span));
22352199
return null;
22362200
}
@@ -3920,6 +3884,7 @@ final class _EvaluateVisitor
39203884
}
39213885

39223886
if (node.isChildless) {
3887+
_copyParentAfterSibling();
39233888
_parent.addChild(
39243889
ModifiableCssAtRule(
39253890
node.name,
@@ -3966,10 +3931,12 @@ final class _EvaluateVisitor
39663931
_endOfImports++;
39673932
}
39683933

3934+
_copyParentAfterSibling();
39693935
_parent.addChild(ModifiableCssComment(node.text, node.span));
39703936
}
39713937

39723938
Future<void> visitCssDeclaration(CssDeclaration node) async {
3939+
_copyParentAfterSibling();
39733940
_parent.addChild(
39743941
ModifiableCssDeclaration(
39753942
node.name,
@@ -3991,6 +3958,7 @@ final class _EvaluateVisitor
39913958
modifiers: node.modifiers,
39923959
);
39933960
if (_parent != _root) {
3961+
_copyParentAfterSibling();
39943962
_parent.addChild(modifiableNode);
39953963
} else if (_endOfImports == _root.children.length) {
39963964
_root.addChild(modifiableNode);
@@ -4377,6 +4345,19 @@ final class _EvaluateVisitor
43774345
return result;
43784346
}
43794347

4348+
/// If the current [_parent] is not the last child of its grandparent, makes a
4349+
/// new childless copy of it and sets [_parent] to that.
4350+
///
4351+
/// Otherwise, leaves [_parent] as-is.
4352+
void _copyParentAfterSibling() {
4353+
if (_parent.parent case var grandparent?
4354+
when grandparent.children.last != _parent) {
4355+
var newParent = _parent.copyWithoutChildren();
4356+
grandparent.addChild(newParent);
4357+
_parent = newParent;
4358+
}
4359+
}
4360+
43804361
/// Adds [node] as a child of the current parent.
43814362
///
43824363
/// If [through] is passed, [node] is added as a child of the first parent for

lib/src/visitor/evaluate.dart

Lines changed: 24 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// DO NOT EDIT. This file was generated from async_evaluate.dart.
66
// See tool/grind/synchronize.dart for details.
77
//
8-
// Checksum: 70d926fd13a7d35cd805bf7053f073cc123b260f
8+
// Checksum: 100082e5b65dc126357c027e76f34ae97b3f9e48
99
//
1010
// ignore_for_file: unused_import
1111

@@ -1350,46 +1350,6 @@ final class _EvaluateVisitor
13501350
);
13511351
}
13521352

1353-
var siblings = _parent.parent!.children;
1354-
var interleavedRules = <CssStyleRule>[];
1355-
if (siblings.last != _parent &&
1356-
// Reproduce this condition from [_warn] so that we don't add anything to
1357-
// [interleavedRules] for declarations in dependencies.
1358-
!(_quietDeps && _inDependency)) {
1359-
loop:
1360-
for (var sibling in siblings.skip(siblings.indexOf(_parent) + 1)) {
1361-
switch (sibling) {
1362-
case CssComment():
1363-
continue loop;
1364-
1365-
case CssStyleRule rule:
1366-
interleavedRules.add(rule);
1367-
1368-
case _:
1369-
// Always warn for siblings that aren't style rules, because they
1370-
// add no specificity and they're nested in the same parent as this
1371-
// declaration.
1372-
_warn(
1373-
"Sass's behavior for declarations that appear after nested\n"
1374-
"rules will be changing to match the behavior specified by CSS "
1375-
"in an upcoming\n"
1376-
"version. To keep the existing behavior, move the declaration "
1377-
"above the nested\n"
1378-
"rule. To opt into the new behavior, wrap the declaration in "
1379-
"`& {}`.\n"
1380-
"\n"
1381-
"More info: https://sass-lang.com/d/mixed-decls",
1382-
MultiSpan(node.span, 'declaration', {
1383-
sibling.span: 'nested rule',
1384-
}),
1385-
Deprecation.mixedDecls,
1386-
);
1387-
interleavedRules.clear();
1388-
break;
1389-
}
1390-
}
1391-
}
1392-
13931353
var name = _interpolationToValue(node.name, warnForColor: true);
13941354
if (_declarationName case var declarationName?) {
13951355
name = CssValue("$declarationName-${name.value}", name.span);
@@ -1403,14 +1363,14 @@ final class _EvaluateVisitor
14031363
_isEmptyList(value) ||
14041364
// Custom properties are allowed to have empty values, per spec.
14051365
name.value.startsWith('--')) {
1366+
_copyParentAfterSibling();
14061367
_parent.addChild(
14071368
ModifiableCssDeclaration(
14081369
name,
14091370
CssValue(value, expression.span),
14101371
node.span,
14111372
parsedAsCustomProperty: node.isCustomProperty,
1412-
interleavedRules: interleavedRules,
1413-
trace: interleavedRules.isEmpty ? null : _stackTrace(node.span),
1373+
trace: _stackTrace(node.span),
14141374
valueSpanForMap:
14151375
_sourceMap ? node.value.andThen(_expressionNode)?.span : null,
14161376
),
@@ -1573,6 +1533,7 @@ final class _EvaluateVisitor
15731533

15741534
var children = node.children;
15751535
if (children == null) {
1536+
_copyParentAfterSibling();
15761537
_parent.addChild(
15771538
ModifiableCssAtRule(name, node.span, childless: true, value: value),
15781539
);
@@ -2089,6 +2050,7 @@ final class _EvaluateVisitor
20892050
);
20902051

20912052
if (_parent != _root) {
2053+
_copyParentAfterSibling();
20922054
_parent.addChild(node);
20932055
} else if (_endOfImports == _root.children.length) {
20942056
_root.addChild(node);
@@ -2238,6 +2200,8 @@ final class _EvaluateVisitor
22382200
var text = _performInterpolation(node.text);
22392201
// Indented syntax doesn't require */
22402202
if (!text.endsWith("*/")) text += " */";
2203+
2204+
_copyParentAfterSibling();
22412205
_parent.addChild(ModifiableCssComment(text, node.span));
22422206
return null;
22432207
}
@@ -3921,6 +3885,7 @@ final class _EvaluateVisitor
39213885
}
39223886

39233887
if (node.isChildless) {
3888+
_copyParentAfterSibling();
39243889
_parent.addChild(
39253890
ModifiableCssAtRule(
39263891
node.name,
@@ -3967,10 +3932,12 @@ final class _EvaluateVisitor
39673932
_endOfImports++;
39683933
}
39693934

3935+
_copyParentAfterSibling();
39703936
_parent.addChild(ModifiableCssComment(node.text, node.span));
39713937
}
39723938

39733939
void visitCssDeclaration(CssDeclaration node) {
3940+
_copyParentAfterSibling();
39743941
_parent.addChild(
39753942
ModifiableCssDeclaration(
39763943
node.name,
@@ -3992,6 +3959,7 @@ final class _EvaluateVisitor
39923959
modifiers: node.modifiers,
39933960
);
39943961
if (_parent != _root) {
3962+
_copyParentAfterSibling();
39953963
_parent.addChild(modifiableNode);
39963964
} else if (_endOfImports == _root.children.length) {
39973965
_root.addChild(modifiableNode);
@@ -4378,6 +4346,19 @@ final class _EvaluateVisitor
43784346
return result;
43794347
}
43804348

4349+
/// If the current [_parent] is not the last child of its grandparent, makes a
4350+
/// new childless copy of it and sets [_parent] to that.
4351+
///
4352+
/// Otherwise, leaves [_parent] as-is.
4353+
void _copyParentAfterSibling() {
4354+
if (_parent.parent case var grandparent?
4355+
when grandparent.children.last != _parent) {
4356+
var newParent = _parent.copyWithoutChildren();
4357+
grandparent.addChild(newParent);
4358+
_parent = newParent;
4359+
}
4360+
}
4361+
43814362
/// Adds [node] as a child of the current parent.
43824363
///
43834364
/// If [through] is passed, [node] is added as a child of the first parent for

0 commit comments

Comments
 (0)