@@ -21,6 +21,7 @@ import '../util/character.dart';
21
21
import '../util/no_source_map_buffer.dart' ;
22
22
import '../util/number.dart' ;
23
23
import '../util/source_map_buffer.dart' ;
24
+ import '../util/span.dart' ;
24
25
import '../value.dart' ;
25
26
import 'interface/css.dart' ;
26
27
import 'interface/selector.dart' ;
@@ -153,14 +154,16 @@ class _SerializeVisitor
153
154
154
155
void visitCssStylesheet (CssStylesheet node) {
155
156
CssNode ? previous;
156
- for (var i = 0 ; i < node.children.length; i++ ) {
157
- var child = node.children[i];
157
+ for (var child in node.children) {
158
158
if (_isInvisible (child)) continue ;
159
-
160
159
if (previous != null ) {
161
160
if (_requiresSemicolon (previous)) _buffer.writeCharCode ($semicolon);
162
- _writeLineFeed ();
163
- if (previous.isGroupEnd) _writeLineFeed ();
161
+ if (_isTrailingComment (child, previous)) {
162
+ _writeOptionalSpace ();
163
+ } else {
164
+ _writeLineFeed ();
165
+ if (previous.isGroupEnd) _writeLineFeed ();
166
+ }
164
167
}
165
168
previous = child;
166
169
@@ -208,7 +211,7 @@ class _SerializeVisitor
208
211
209
212
if (! node.isChildless) {
210
213
_writeOptionalSpace ();
211
- _visitChildren (node.children );
214
+ _visitChildren (node);
212
215
}
213
216
}
214
217
@@ -226,7 +229,7 @@ class _SerializeVisitor
226
229
});
227
230
228
231
_writeOptionalSpace ();
229
- _visitChildren (node.children );
232
+ _visitChildren (node);
230
233
}
231
234
232
235
void visitCssImport (CssImport node) {
@@ -273,7 +276,7 @@ class _SerializeVisitor
273
276
() =>
274
277
_writeBetween (node.selector.value, _commaSeparator, _buffer.write));
275
278
_writeOptionalSpace ();
276
- _visitChildren (node.children );
279
+ _visitChildren (node);
277
280
}
278
281
279
282
void _visitMediaQuery (CssMediaQuery query) {
@@ -298,7 +301,7 @@ class _SerializeVisitor
298
301
299
302
_for (node.selector, () => node.selector.value.accept (this ));
300
303
_writeOptionalSpace ();
301
- _visitChildren (node.children );
304
+ _visitChildren (node);
302
305
}
303
306
304
307
void visitCssSupportsRule (CssSupportsRule node) {
@@ -315,7 +318,7 @@ class _SerializeVisitor
315
318
});
316
319
317
320
_writeOptionalSpace ();
318
- _visitChildren (node.children );
321
+ _visitChildren (node);
319
322
}
320
323
321
324
void visitCssDeclaration (CssDeclaration node) {
@@ -1286,45 +1289,80 @@ class _SerializeVisitor
1286
1289
void _write (CssValue <String > value) =>
1287
1290
_for (value, () => _buffer.write (value.value));
1288
1291
1289
- /// Emits [children] in a block.
1290
- void _visitChildren (List < CssNode > children ) {
1292
+ /// Emits [parent. children] in a block.
1293
+ void _visitChildren (CssParentNode parent ) {
1291
1294
_buffer.writeCharCode ($lbrace);
1292
- if (children.every (_isInvisible)) {
1293
- _buffer.writeCharCode ($rbrace);
1294
- return ;
1295
- }
1296
1295
1297
- _writeLineFeed ();
1298
- CssNode ? previous_;
1299
- _indent (() {
1300
- for (var i = 0 ; i < children.length; i++ ) {
1301
- var child = children[i];
1302
- if (_isInvisible (child)) continue ;
1296
+ CssNode ? prePrevious;
1297
+ CssNode ? previous;
1298
+ for (var child in parent.children) {
1299
+ if (_isInvisible (child)) continue ;
1303
1300
1304
- var previous = previous_; // dart-lang/sdk#45348
1305
- if (previous != null ) {
1306
- if (_requiresSemicolon (previous)) _buffer.writeCharCode ($semicolon);
1307
- _writeLineFeed ();
1308
- if (previous.isGroupEnd) _writeLineFeed ();
1309
- }
1310
- previous_ = child;
1301
+ if (previous != null && _requiresSemicolon (previous)) {
1302
+ _buffer.writeCharCode ($semicolon);
1303
+ }
1311
1304
1312
- child.accept (this );
1305
+ if (_isTrailingComment (child, previous ?? parent)) {
1306
+ _writeOptionalSpace ();
1307
+ _withoutIndendation (() => child.accept (this ));
1308
+ } else {
1309
+ _writeLineFeed ();
1310
+ _indent (() {
1311
+ child.accept (this );
1312
+ });
1313
1313
}
1314
- });
1315
1314
1316
- if ( _requiresSemicolon (previous_ ! ) && ! _isCompressed) {
1317
- _buffer. writeCharCode ($semicolon) ;
1315
+ prePrevious = previous;
1316
+ previous = child ;
1318
1317
}
1319
- _writeLineFeed ();
1320
- _writeIndentation ();
1318
+
1319
+ if (previous != null ) {
1320
+ if (_requiresSemicolon (previous) && ! _isCompressed) {
1321
+ _buffer.writeCharCode ($semicolon);
1322
+ }
1323
+
1324
+ if (prePrevious == null && _isTrailingComment (previous, parent)) {
1325
+ _writeOptionalSpace ();
1326
+ } else {
1327
+ _writeLineFeed ();
1328
+ _writeIndentation ();
1329
+ }
1330
+ }
1331
+
1321
1332
_buffer.writeCharCode ($rbrace);
1322
1333
}
1323
1334
1324
1335
/// Whether [node] requires a semicolon to be written after it.
1325
- bool _requiresSemicolon (CssNode ? node) =>
1336
+ bool _requiresSemicolon (CssNode node) =>
1326
1337
node is CssParentNode ? node.isChildless : node is ! CssComment ;
1327
1338
1339
+ /// Whether [node] represents a trailing comment when it appears after
1340
+ /// [previous] in a sequence of nodes being serialized.
1341
+ ///
1342
+ /// Note [previous] could be either a sibling of [node] or the parent of
1343
+ /// [node] , with [node] being the first visible child.
1344
+ bool _isTrailingComment (CssNode node, CssNode previous) {
1345
+ // Short-circuit in compressed mode to avoid expensive span shenanigans
1346
+ // (shespanigans?), since we're compressing all whitespace anyway.
1347
+ if (_isCompressed) return false ;
1348
+ if (node is ! CssComment ) return false ;
1349
+
1350
+ if (! previous.span.contains (node.span)) {
1351
+ return node.span.start.line == previous.span.end.line;
1352
+ }
1353
+
1354
+ // Walk back from just before the current node starts looking for the
1355
+ // parent's left brace (to open the child block). This is safer than a
1356
+ // simple forward search of the previous.span.text as that might contain
1357
+ // other left braces.
1358
+ var searchFrom = node.span.start.offset - previous.span.start.offset - 1 ;
1359
+ var endOffset = previous.span.text.lastIndexOf ("{" , searchFrom);
1360
+ endOffset = math.max (0 , endOffset);
1361
+ var span = previous.span.file.span (
1362
+ previous.span.start.offset, previous.span.start.offset + endOffset);
1363
+ return node.span.start.line == span.end.line;
1364
+ }
1365
+
1328
1366
/// Writes a line feed, unless this emitting compressed CSS.
1329
1367
void _writeLineFeed () {
1330
1368
if (! _isCompressed) _buffer.write (_lineFeed.text);
@@ -1373,6 +1411,14 @@ class _SerializeVisitor
1373
1411
_indentation-- ;
1374
1412
}
1375
1413
1414
+ /// Runs [callback] without any indentation.
1415
+ void _withoutIndendation (void callback ()) {
1416
+ var savedIndentation = _indentation;
1417
+ _indentation = 0 ;
1418
+ callback ();
1419
+ _indentation = savedIndentation;
1420
+ }
1421
+
1376
1422
/// Returns whether [node] is considered invisible.
1377
1423
bool _isInvisible (CssNode node) {
1378
1424
if (_inspect) return false ;
0 commit comments