blob: 0ae70f455743c6e189d015cda35dc9e8c2b5f6a5 [file] [log] [blame]
// 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 '../piece/adjacent.dart';
import '../piece/list.dart';
import '../piece/piece.dart';
import 'delimited_list_builder.dart';
import 'piece_factory.dart';
import 'sequence_builder.dart';
/// Incrementally builds an [AdjacentPiece].
///
/// Can also be used to attach metadata annotations to the [Piece] being built.
class AdjacentBuilder {
final PieceFactory _visitor;
final List<Piece> _metadataPieces = [];
/// Whether the annotations added by a call to [metadata] should be allowed
/// on the same line as the code they annotate, or whether a mandatory
/// newline should be inserted after each annotation.
bool _isMetadataInline = false;
/// The series of adjacent pieces.
final List<Piece> _pieces = [];
AdjacentBuilder(this._visitor);
/// Creates pieces for all of the annotations in [metadata].
///
/// When this builder is built, if there are any annotations, the returned
/// Piece will contain the annotation pieces followed by the [AdjacentPiece]
/// being built.
void metadata(List<Annotation> metadata, {bool inline = false}) {
_isMetadataInline = inline;
for (var annotation in metadata) {
_metadataPieces.add(_visitor.nodePiece(annotation));
}
}
/// Yields a new piece containing all of the pieces added to or created by
/// this builder. The caller must ensure it doesn't build an empty piece.
///
/// Also clears the builder's list of pieces so that this builder can be
/// reused to build more pieces.
Piece build() {
assert(_pieces.isNotEmpty);
var result = _flattenPieces();
_pieces.clear();
// If there is metadata, wrap the AdjacentPiece in another piece containing
// the annotations first.
if (_metadataPieces.isNotEmpty) {
if (_isMetadataInline) {
var list = DelimitedListBuilder(
_visitor,
const ListStyle(
commas: Commas.none,
spaceWhenUnsplit: true,
));
for (var piece in _metadataPieces) {
list.add(piece);
}
list.add(result);
result = list.build();
} else {
var sequence = SequenceBuilder(_visitor);
for (var piece in _metadataPieces) {
sequence.add(piece);
}
sequence.add(result);
result = sequence.build(forceSplit: true);
}
_metadataPieces.clear();
}
return result;
}
/// Adds [piece] to this builder.
void add(Piece piece) {
_pieces.add(piece);
}
/// Emit [token], along with any comments and formatted whitespace that comes
/// before it.
///
/// If [lexeme] is given, uses that for the token's lexeme instead of its own.
///
/// Does nothing if [token] is `null`. If [spaceBefore] is `true`, writes a
/// space before the token, likewise with [spaceAfter].
void token(Token? token,
{bool spaceBefore = false, bool spaceAfter = false, String? lexeme}) {
if (token == null) return;
if (spaceBefore) space();
add(_visitor.pieces.tokenPiece(token, lexeme: lexeme));
if (spaceAfter) space();
}
/// Writes any comments that appear before [token], which will be discarded.
///
/// Used to ensure comments before a discarded token are preserved.
void commentsBefore(Token? token) {
if (token == null) return;
var piece = _visitor.pieces.writeCommentsBefore(token);
if (piece != null) add(piece);
}
/// Writes an optional modifier that precedes other code.
void modifier(Token? keyword) {
token(keyword, spaceAfter: true);
}
/// Visits [node] if not `null` and adds the resulting [Piece] to this
/// builder.
void visit(AstNode? node,
{bool spaceBefore = false,
bool commaAfter = false,
bool spaceAfter = false}) {
if (node == null) return;
if (spaceBefore) space();
add(_visitor.nodePiece(node, commaAfter: commaAfter));
if (spaceAfter) space();
}
/// Appends a space before the previous piece and the next one.
void space() {
_pieces.add(SpacePiece());
}
/// Removes redundant [AdjacentPiece] wrappers from [_pieces].
Piece _flattenPieces() {
var flattened = <Piece>[];
void traverse(List<Piece> pieces) {
for (var piece in pieces) {
if (piece is AdjacentPiece) {
traverse(piece.pieces);
} else {
flattened.add(piece);
}
}
}
traverse(_pieces);
// If there's only one piece, don't wrap it in a pointless AdjacentPiece.
if (flattened.length == 1) return flattened[0];
return AdjacentPiece(flattened);
}
}