@@ -6,17 +6,21 @@ import 'dart:math' as math;
6
6
import 'dart:typed_data' ;
7
7
8
8
import 'package:charcode/charcode.dart' ;
9
+ import 'package:collection/collection.dart' ;
9
10
import 'package:source_maps/source_maps.dart' ;
10
11
import 'package:string_scanner/string_scanner.dart' ;
11
12
12
13
import '../ast/css.dart' ;
13
14
import '../ast/node.dart' ;
14
15
import '../ast/selector.dart' ;
15
16
import '../color_names.dart' ;
17
+ import '../deprecation.dart' ;
16
18
import '../exception.dart' ;
19
+ import '../logger.dart' ;
17
20
import '../parse/parser.dart' ;
18
21
import '../utils.dart' ;
19
22
import '../util/character.dart' ;
23
+ import '../util/multi_span.dart' ;
20
24
import '../util/no_source_map_buffer.dart' ;
21
25
import '../util/nullable.dart' ;
22
26
import '../util/number.dart' ;
@@ -48,6 +52,7 @@ SerializeResult serialize(CssNode node,
48
52
bool useSpaces = true ,
49
53
int ? indentWidth,
50
54
LineFeed ? lineFeed,
55
+ Logger ? logger,
51
56
bool sourceMap = false ,
52
57
bool charset = true }) {
53
58
indentWidth ?? = 2 ;
@@ -57,6 +62,7 @@ SerializeResult serialize(CssNode node,
57
62
useSpaces: useSpaces,
58
63
indentWidth: indentWidth,
59
64
lineFeed: lineFeed,
65
+ logger: logger,
60
66
sourceMap: sourceMap);
61
67
node.accept (visitor);
62
68
var css = visitor._buffer.toString ();
@@ -128,6 +134,12 @@ final class _SerializeVisitor
128
134
/// The characters to use for a line feed.
129
135
final LineFeed _lineFeed;
130
136
137
+ /// The logger to use to print warnings.
138
+ ///
139
+ /// This should only be used for statement-level serialization. It's not
140
+ /// guaranteed to be the main user-provided logger for expressions.
141
+ final Logger _logger;
142
+
131
143
/// Whether we're emitting compressed output.
132
144
bool get _isCompressed => _style == OutputStyle .compressed;
133
145
@@ -138,14 +150,16 @@ final class _SerializeVisitor
138
150
bool useSpaces = true ,
139
151
int ? indentWidth,
140
152
LineFeed ? lineFeed,
153
+ Logger ? logger,
141
154
bool sourceMap = true })
142
155
: _buffer = sourceMap ? SourceMapBuffer () : NoSourceMapBuffer (),
143
156
_style = style ?? OutputStyle .expanded,
144
157
_inspect = inspect,
145
158
_quote = quote,
146
159
_indentCharacter = useSpaces ? $space : $tab,
147
160
_indentWidth = indentWidth ?? 2 ,
148
- _lineFeed = lineFeed ?? LineFeed .lf {
161
+ _lineFeed = lineFeed ?? LineFeed .lf,
162
+ _logger = logger ?? const Logger .stderr () {
149
163
RangeError .checkValueInInterval (_indentWidth, 0 , 10 , "indentWidth" );
150
164
}
151
165
@@ -329,6 +343,33 @@ final class _SerializeVisitor
329
343
}
330
344
331
345
void visitCssDeclaration (CssDeclaration node) {
346
+ if (node.interleavedRules.isNotEmpty) {
347
+ var declSpecificities = _specificities (node.parent! );
348
+ for (var rule in node.interleavedRules) {
349
+ var ruleSpecificities = _specificities (rule);
350
+
351
+ // If the declaration can never match with the same specificity as one
352
+ // of its sibling rules, then ordering will never matter and there's no
353
+ // need to warn about the declaration being re-ordered.
354
+ if (! declSpecificities.any (ruleSpecificities.contains)) continue ;
355
+
356
+ _logger.warnForDeprecation (
357
+ Deprecation .mixedDecls,
358
+ "Sass's behavior for declarations that appear after nested\n "
359
+ "rules will be changing to match the behavior specified by CSS in an "
360
+ "upcoming\n "
361
+ "version. To keep the existing behavior, move the declaration above "
362
+ "the nested\n "
363
+ "rule. To opt into the new behavior, wrap the declaration in `& "
364
+ "{}`.\n "
365
+ "\n "
366
+ "More info: https://sass-lang.com/d/mixed-decls" ,
367
+ span:
368
+ MultiSpan (node.span, 'declaration' , {rule.span: 'nested rule' }),
369
+ trace: node.trace);
370
+ }
371
+ }
372
+
332
373
_writeIndentation ();
333
374
334
375
_write (node.name);
@@ -363,6 +404,22 @@ final class _SerializeVisitor
363
404
}
364
405
}
365
406
407
+ /// Returns the set of possible specificities which which [node] might match.
408
+ Set <int > _specificities (CssParentNode node) {
409
+ if (node case CssStyleRule rule) {
410
+ // Plain CSS style rule nesting implicitly wraps parent selectors in
411
+ // `:is()`, so they all match with the highest specificity among any of
412
+ // them.
413
+ var parent = node.parent.andThen (_specificities)? .max ?? 0 ;
414
+ return {
415
+ for (var selector in rule.selector.components)
416
+ parent + selector.specificity
417
+ };
418
+ } else {
419
+ return node.parent.andThen (_specificities) ?? const {0 };
420
+ }
421
+ }
422
+
366
423
/// Emits the value of [node] , with all newlines followed by whitespace
367
424
void _writeFoldedValue (CssDeclaration node) {
368
425
var scanner = StringScanner ((node.value.value as SassString ).text);
0 commit comments