@@ -21,6 +21,7 @@ import '../util/character.dart';
2121import '../util/no_source_map_buffer.dart' ;
2222import '../util/number.dart' ;
2323import '../util/source_map_buffer.dart' ;
24+ import '../util/span.dart' ;
2425import '../value.dart' ;
2526import 'interface/css.dart' ;
2627import 'interface/selector.dart' ;
@@ -153,14 +154,16 @@ class _SerializeVisitor
153154
154155 void visitCssStylesheet (CssStylesheet node) {
155156 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) {
158158 if (_isInvisible (child)) continue ;
159-
160159 if (previous != null ) {
161160 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+ }
164167 }
165168 previous = child;
166169
@@ -208,7 +211,7 @@ class _SerializeVisitor
208211
209212 if (! node.isChildless) {
210213 _writeOptionalSpace ();
211- _visitChildren (node.children );
214+ _visitChildren (node);
212215 }
213216 }
214217
@@ -226,7 +229,7 @@ class _SerializeVisitor
226229 });
227230
228231 _writeOptionalSpace ();
229- _visitChildren (node.children );
232+ _visitChildren (node);
230233 }
231234
232235 void visitCssImport (CssImport node) {
@@ -273,7 +276,7 @@ class _SerializeVisitor
273276 () =>
274277 _writeBetween (node.selector.value, _commaSeparator, _buffer.write));
275278 _writeOptionalSpace ();
276- _visitChildren (node.children );
279+ _visitChildren (node);
277280 }
278281
279282 void _visitMediaQuery (CssMediaQuery query) {
@@ -298,7 +301,7 @@ class _SerializeVisitor
298301
299302 _for (node.selector, () => node.selector.value.accept (this ));
300303 _writeOptionalSpace ();
301- _visitChildren (node.children );
304+ _visitChildren (node);
302305 }
303306
304307 void visitCssSupportsRule (CssSupportsRule node) {
@@ -315,7 +318,7 @@ class _SerializeVisitor
315318 });
316319
317320 _writeOptionalSpace ();
318- _visitChildren (node.children );
321+ _visitChildren (node);
319322 }
320323
321324 void visitCssDeclaration (CssDeclaration node) {
@@ -1286,45 +1289,80 @@ class _SerializeVisitor
12861289 void _write (CssValue <String > value) =>
12871290 _for (value, () => _buffer.write (value.value));
12881291
1289- /// Emits [children] in a block.
1290- void _visitChildren (List < CssNode > children ) {
1292+ /// Emits [parent. children] in a block.
1293+ void _visitChildren (CssParentNode parent ) {
12911294 _buffer.writeCharCode ($lbrace);
1292- if (children.every (_isInvisible)) {
1293- _buffer.writeCharCode ($rbrace);
1294- return ;
1295- }
12961295
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 ;
13031300
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+ }
13111304
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+ });
13131313 }
1314- });
13151314
1316- if ( _requiresSemicolon (previous_ ! ) && ! _isCompressed) {
1317- _buffer. writeCharCode ($semicolon) ;
1315+ prePrevious = previous;
1316+ previous = child ;
13181317 }
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+
13211332 _buffer.writeCharCode ($rbrace);
13221333 }
13231334
13241335 /// Whether [node] requires a semicolon to be written after it.
1325- bool _requiresSemicolon (CssNode ? node) =>
1336+ bool _requiresSemicolon (CssNode node) =>
13261337 node is CssParentNode ? node.isChildless : node is ! CssComment ;
13271338
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+
13281366 /// Writes a line feed, unless this emitting compressed CSS.
13291367 void _writeLineFeed () {
13301368 if (! _isCompressed) _buffer.write (_lineFeed.text);
@@ -1373,6 +1411,14 @@ class _SerializeVisitor
13731411 _indentation-- ;
13741412 }
13751413
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+
13761422 /// Returns whether [node] is considered invisible.
13771423 bool _isInvisible (CssNode node) {
13781424 if (_inspect) return false ;
0 commit comments