|  | // Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  | import 'package:analyzer/dart/ast/ast.dart'; | 
|  | import 'package:analyzer/dart/ast/token.dart'; | 
|  |  | 
|  | import '../ast_extensions.dart'; | 
|  | import '../back_end/code_writer.dart'; | 
|  | import '../piece/piece.dart'; | 
|  | import '../piece/sequence.dart'; | 
|  | import '../piece/text.dart'; | 
|  | import 'piece_factory.dart'; | 
|  |  | 
|  | /// Incrementally builds a [SequencePiece], including handling comments and | 
|  | /// newlines that may appear before, between, or after its contents. | 
|  | /// | 
|  | /// Comments are handled specially here so that we can give them better | 
|  | /// formatting than we would be able to if we treated all comments generally. | 
|  | /// | 
|  | /// Most comments appear around statements in a block, members in a class, or | 
|  | /// at the top level of a file. For those, we treat them essentially like | 
|  | /// separate statements inside the sequence. This lets us gracefully handle | 
|  | /// indenting them and supporting blank lines around them the same way we handle | 
|  | /// other statements or members in a sequence. | 
|  | final class SequenceBuilder { | 
|  | final PieceFactory _visitor; | 
|  |  | 
|  | /// The opening bracket before the elements, if any. | 
|  | Piece? _leftBracket; | 
|  |  | 
|  | /// The series of elements in the sequence. | 
|  | final List<SequenceElementPiece> _elements = []; | 
|  |  | 
|  | /// The closing bracket after the elements, if any. | 
|  | Piece? _rightBracket; | 
|  |  | 
|  | /// Whether a blank line should be allowed after the current element. | 
|  | bool _allowBlank = false; | 
|  |  | 
|  | SequenceBuilder(this._visitor); | 
|  |  | 
|  | Piece build({bool forceSplit = false}) { | 
|  | // If the sequence only contains a single piece, just return it directly | 
|  | // and discard the unnecessary wrapping. | 
|  | if (_leftBracket == null && | 
|  | _elements.length == 1 && | 
|  | _elements.single.hangingComments.isEmpty && | 
|  | _rightBracket == null) { | 
|  | return _elements.single.piece; | 
|  | } | 
|  |  | 
|  | // If there are no elements, don't bother making a SequencePiece or | 
|  | // BlockPiece. | 
|  | if (_elements.isEmpty) { | 
|  | return _visitor.pieces.build(() { | 
|  | if (_leftBracket case var bracket?) _visitor.pieces.add(bracket); | 
|  |  | 
|  | if (forceSplit || _leftBracket == null) { | 
|  | _visitor.pieces.add(NewlinePiece()); | 
|  | } | 
|  |  | 
|  | if (_rightBracket case var bracket?) _visitor.pieces.add(bracket); | 
|  | }); | 
|  | } | 
|  |  | 
|  | // Discard any trailing blank line after the last element. | 
|  | _elements.last.blankAfter = false; | 
|  |  | 
|  | var sequence = SequencePiece(_elements); | 
|  | if ((_leftBracket, _rightBracket) case (var left?, var right?)) { | 
|  | return BlockPiece(left, sequence, right); | 
|  | } | 
|  |  | 
|  | return sequence; | 
|  | } | 
|  |  | 
|  | /// Adds the opening [bracket] to the built sequence. | 
|  | void leftBracket(Token bracket) { | 
|  | _leftBracket = _visitor.tokenPiece(bracket); | 
|  | } | 
|  |  | 
|  | /// Adds the closing [bracket] to the built sequence along with any comments | 
|  | /// that precede it. | 
|  | void rightBracket(Token bracket) { | 
|  | // Place any comments before the bracket inside the block. | 
|  | addCommentsBefore(bracket); | 
|  | _rightBracket = _visitor.tokenPiece(bracket); | 
|  | } | 
|  |  | 
|  | /// Adds [piece] to this sequence. | 
|  | /// | 
|  | /// The caller should have already called [addCommentsBefore()] with the | 
|  | /// first token in [piece]. | 
|  | void add(Piece piece, {Indent? indent, bool allowBlankAfter = true}) { | 
|  | _elements.add(SequenceElementPiece(indent ?? Indent.none, piece)); | 
|  |  | 
|  | _allowBlank = allowBlankAfter; | 
|  | } | 
|  |  | 
|  | /// Visits [node] and adds the resulting [Piece] to this sequence, handling | 
|  | /// any comments or blank lines that appear before it. | 
|  | void visit(AstNode node, {Indent? indent, bool allowBlankAfter = true}) { | 
|  | addCommentsBefore(node.firstNonCommentToken, indent: indent); | 
|  | add( | 
|  | _visitor.nodePiece(node), | 
|  | indent: indent, | 
|  | allowBlankAfter: allowBlankAfter, | 
|  | ); | 
|  | } | 
|  |  | 
|  | /// Appends a blank line before the next piece in the sequence. | 
|  | void addBlank() { | 
|  | if (_elements.isEmpty) return; | 
|  | if (!_allowBlank) return; | 
|  | _elements.last.blankAfter = true; | 
|  | } | 
|  |  | 
|  | /// Writes any comments appearing before [token] to the sequence. | 
|  | /// | 
|  | /// Also handles blank lines between preceding comments and elements and the | 
|  | /// subsequent element. | 
|  | /// | 
|  | /// Comments between sequence elements get special handling where comments | 
|  | /// on their own line become standalone sequence elements. | 
|  | void addCommentsBefore(Token token, {Indent? indent}) { | 
|  | indent ??= Indent.none; | 
|  |  | 
|  | var comments = _visitor.comments.takeCommentsBefore(token); | 
|  |  | 
|  | // Edge case: if we require a blank line, but there exists one between | 
|  | // some of the comments, or after the last one, then we don't need to | 
|  | // enforce one before the first comment. Example: | 
|  | // | 
|  | //     library foo; | 
|  | //     // comment | 
|  | // | 
|  | //     class Bar {} | 
|  | // | 
|  | // Normally, a blank line is required after `library`, but since there is | 
|  | // one after the comment, we don't need one before it. This is mainly so | 
|  | // that commented out directives stick with their preceding group. | 
|  | if (comments.containsBlank && _elements.isNotEmpty) { | 
|  | _elements.last.blankAfter = false; | 
|  | } | 
|  |  | 
|  | for (var i = 0; i < comments.length; i++) { | 
|  | var comment = _visitor.pieces.commentPiece(comments[i]); | 
|  |  | 
|  | if (_elements.isNotEmpty && comments.isHanging(i)) { | 
|  | // Attach the comment to the previous element. | 
|  | _elements.last.hangingComments.add(comment); | 
|  | } else { | 
|  | if (comments.linesBefore(i) > 1) { | 
|  | // Always preserve a blank line above sequence-level comments. | 
|  | _allowBlank = true; | 
|  | addBlank(); | 
|  | } | 
|  |  | 
|  | // Write the comment as its own sequence piece. | 
|  | _elements.add(SequenceElementPiece(indent, comment)); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Write a blank before the token if there should be one. | 
|  | if (comments.linesBeforeNextToken > 1) { | 
|  | // If we just wrote a comment, then allow a blank line between it and the | 
|  | // element. | 
|  | if (comments.isNotEmpty) _allowBlank = true; | 
|  |  | 
|  | addBlank(); | 
|  | } | 
|  | } | 
|  | } |