Skip to content

Commit 32e54c5

Browse files
authored
Refactor ListPiece and DelimitedListBuilder. (#1295)
Refactor ListPiece and DelimitedListBuilder. These classes started out just supporting argument lists, then grew to collection literals, then parameter lists, then switch expressions, and soon for loop headers. Each time, I need to tweak how it works a little. (But not enough to not use it completely, since the comment handling is complex and is the same across all of those constructors.) There was getting to be an unwieldy set of options being passed around. Cleaned that up by defining a separate ListStyle that contains those options and can be shuttled directly from DelimitedListBuilder to ListPiece. I made the before and after bracket pieces optional since I'll be using ListPiece in a future PR for the updaters list in a for loop, which isn't delimited. Did a bunch of other minor clean-up. Also, many of the docs were out of date. Fixed those.
1 parent 2cee560 commit 32e54c5

File tree

5 files changed

+215
-211
lines changed

5 files changed

+215
-211
lines changed

lib/src/back_end/code_writer.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,14 @@ class CodeWriter {
126126
///
127127
/// If [space] is `true` and [condition] is `false`, writes a space.
128128
///
129+
/// If [blank] is `true`, writes an extra newline to produce a blank line.
130+
///
129131
/// If [indent] is given, sets the amount of block-level indentation for this
130132
/// and all subsequent newlines to [indent].
131-
void splitIf(bool condition, {bool space = true, int? indent}) {
132-
if (indent != null) setIndent(indent);
133-
133+
void splitIf(bool condition,
134+
{bool space = true, bool blank = false, int? indent}) {
134135
if (condition) {
135-
newline();
136+
newline(blank: blank, indent: indent);
136137
} else if (space) {
137138
this.space();
138139
}

lib/src/front_end/ast_node_visitor.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import '../piece/block.dart';
1111
import '../piece/do_while.dart';
1212
import '../piece/if.dart';
1313
import '../piece/infix.dart';
14+
import '../piece/list.dart';
1415
import '../piece/piece.dart';
1516
import '../piece/variable.dart';
1617
import '../source_code.dart';
@@ -410,7 +411,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>
410411
builder.leftDelimiter(node.leftDelimiter!);
411412
}
412413

413-
builder.add(node.parameters[i]);
414+
builder.visit(node.parameters[i]);
414415
}
415416

416417
builder.rightBracket(node.rightParenthesis, delimiter: node.rightDelimiter);
@@ -930,15 +931,16 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>
930931

931932
@override
932933
void visitSwitchExpression(SwitchExpression node) {
933-
var list = DelimitedListBuilder.switchBody(this);
934+
var list = DelimitedListBuilder(this,
935+
const ListStyle(spaceWhenUnsplit: true, splitListIfBeforeSplits: true));
934936

935937
createSwitchValue(node.switchKeyword, node.leftParenthesis, node.expression,
936938
node.rightParenthesis);
937939
writer.space();
938940
list.leftBracket(node.leftBracket);
939941

940942
for (var member in node.cases) {
941-
list.add(member);
943+
list.visit(member);
942944
}
943945

944946
list.rightBracket(node.rightBracket);

lib/src/front_end/delimited_list_builder.dart

Lines changed: 35 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import 'piece_factory.dart';
2020
class DelimitedListBuilder {
2121
final PieceFactory _visitor;
2222

23-
late final Piece _leftBracket;
23+
/// The opening bracket before the elements, if any.
24+
Piece? _leftBracket;
2425

2526
/// The list of elements in the list.
2627
final List<ListElement> _elements = [];
@@ -29,83 +30,21 @@ class DelimitedListBuilder {
2930
/// next piece.
3031
final Set<ListElement> _blanksAfter = {};
3132

32-
late final Piece _rightBracket;
33+
/// The closing bracket after the elements, if any.
34+
Piece? _rightBracket;
3335

34-
/// Whether this list should have a trailing comma if it splits.
35-
///
36-
/// This is true for most lists but false for type parameters, type arguments,
37-
/// and switch values.
38-
final bool _trailingComma;
39-
40-
/// The cost of splitting this list. Normally 1, but higher for some lists
41-
/// that look worse when split.
42-
final int _splitCost;
43-
44-
/// Whether this list should have spaces inside the bracket when it doesn't
45-
/// split.
46-
///
47-
/// This is false for most lists, but true for switch expression bodies:
48-
///
49-
/// ```
50-
/// v = switch (e) { 1 => 'one', 2 => 'two' };
51-
/// // ^ ^
52-
/// ```
53-
final bool _spaceWhenUnsplit;
54-
55-
/// Whether a split in the [_before] piece should force the list to split too.
56-
///
57-
/// See [ListPiece._splitListIfBeforeSplits] for more details.
58-
final bool _splitListIfBeforeSplits;
36+
final ListStyle _style;
5937

6038
/// The list of comments following the most recently written element before
6139
/// any comma following the element.
6240
CommentSequence _commentsBeforeComma = CommentSequence.empty;
6341

6442
/// Creates a new [DelimitedListBuilder] for an argument list, collection
6543
/// literal, etc.
66-
DelimitedListBuilder(this._visitor)
67-
: _trailingComma = true,
68-
_splitCost = 1,
69-
_spaceWhenUnsplit = false,
70-
_splitListIfBeforeSplits = false;
71-
72-
/// Creates a new [DelimitedListBuilder] for a switch expression body.
73-
DelimitedListBuilder.switchBody(this._visitor)
74-
: _trailingComma = true,
75-
_splitCost = 1,
76-
_spaceWhenUnsplit = true,
77-
_splitListIfBeforeSplits = true;
78-
79-
/// Creates a new [DelimitedListBuilder] for the value part of a switch
80-
/// statement or expression:
81-
///
82-
/// ```
83-
/// switch (value) { ... }
84-
/// // ^^^^^^^
85-
/// ```
86-
DelimitedListBuilder.switchValue(this._visitor)
87-
: _trailingComma = false,
88-
_splitCost = 2,
89-
_spaceWhenUnsplit = false,
90-
_splitListIfBeforeSplits = false;
91-
92-
/// Creates a new [DelimitedListBuilder] for a type argument or type parameter
93-
/// list.
94-
DelimitedListBuilder.type(this._visitor)
95-
: _trailingComma = false,
96-
_splitCost = 2,
97-
_spaceWhenUnsplit = false,
98-
_splitListIfBeforeSplits = false;
99-
100-
ListPiece build() => ListPiece(
101-
_leftBracket,
102-
_elements,
103-
_blanksAfter,
104-
_rightBracket,
105-
_splitCost,
106-
_trailingComma,
107-
_spaceWhenUnsplit,
108-
_splitListIfBeforeSplits);
44+
DelimitedListBuilder(this._visitor, [this._style = const ListStyle()]);
45+
46+
ListPiece build() =>
47+
ListPiece(_leftBracket, _elements, _blanksAfter, _rightBracket, _style);
10948

11049
/// Adds the opening [bracket] to the built list.
11150
///
@@ -163,24 +102,37 @@ class DelimitedListBuilder {
163102
_rightBracket = _visitor.writer.pop();
164103
}
165104

166-
/// Adds [element] to the built list.
105+
/// Adds [piece] to the built list.
167106
///
168-
/// Includes any comments that appear before element. Also includes the
169-
/// subsequent comma, if any, and any comments that precede the comma.
170-
void add(AstNode element) {
107+
/// Use this when the piece is composed of more than one [AstNode] or [Token]
108+
/// and [visit()] can't be used. When calling this, make sure to call
109+
/// [addCommentsBefore()] for the first token in the [piece].
110+
///
111+
/// Assumes there is no comma after this piece.
112+
void add(Piece piece) {
113+
_elements.add(ListElement(piece));
114+
_commentsBeforeComma = CommentSequence.empty;
115+
}
116+
117+
/// Writes any comments appearing before [token] to the list.
118+
void addCommentsBefore(Token token) {
171119
// Handle comments between the preceding element and this one.
172-
var commentsBeforeElement = _visitor.takeCommentsBefore(element.beginToken);
120+
var commentsBeforeElement = _visitor.takeCommentsBefore(token);
173121
_addComments(commentsBeforeElement, hasElementAfter: true);
122+
}
123+
124+
/// Adds [element] to the built list.
125+
void visit(AstNode element) {
126+
// Handle comments between the preceding element and this one.
127+
addCommentsBefore(element.beginToken);
174128

175129
// Traverse the element itself.
176130
_visitor.visit(element);
177-
_elements.add(ListElement(_visitor.writer.pop()));
178131
_visitor.writer.split();
132+
add(_visitor.writer.pop());
179133

180134
var nextToken = element.endToken.next!;
181-
if (nextToken.lexeme != ',') {
182-
_commentsBeforeComma = CommentSequence.empty;
183-
} else {
135+
if (nextToken.lexeme == ',') {
184136
_commentsBeforeComma = _visitor.takeCommentsBefore(nextToken);
185137
}
186138
}
@@ -256,7 +208,7 @@ class DelimitedListBuilder {
256208
}
257209

258210
// Comments that are neither hanging nor leading are treated like their own
259-
// arguments.
211+
// elements.
260212
for (var i = 0; i < separateComments.length; i++) {
261213
var comment = separateComments[i];
262214
if (separateComments.linesBefore(i) > 1 && _elements.isNotEmpty) {
@@ -268,7 +220,7 @@ class DelimitedListBuilder {
268220
_visitor.writer.split();
269221
}
270222

271-
// Leading comments are written before the next argument.
223+
// Leading comments are written before the next element.
272224
for (var comment in leadingComments) {
273225
_visitor.writer.writeComment(comment);
274226
_visitor.writer.space();
@@ -311,15 +263,15 @@ class DelimitedListBuilder {
311263
CommentSequence leading
312264
}) _splitCommaComments(CommentSequence commentsBeforeElement,
313265
{required bool hasElementAfter}) {
314-
// If we're on the final comma after the last argument, the comma isn't
266+
// If we're on the final comma after the last element, the comma isn't
315267
// meaningful because there can't be leading comments after it.
316268
if (!hasElementAfter) {
317269
_commentsBeforeComma =
318270
_commentsBeforeComma.concatenate(commentsBeforeElement);
319271
commentsBeforeElement = CommentSequence.empty;
320272
}
321273

322-
// Edge case: A line comment on the same line as the preceding argument
274+
// Edge case: A line comment on the same line as the preceding element
323275
// but after the comma is treated as hanging.
324276
if (commentsBeforeElement.isNotEmpty &&
325277
commentsBeforeElement[0].type == CommentType.line &&

lib/src/front_end/piece_factory.dart

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import '../piece/function.dart';
1111
import '../piece/if.dart';
1212
import '../piece/import.dart';
1313
import '../piece/infix.dart';
14+
import '../piece/list.dart';
1415
import '../piece/piece.dart';
1516
import '../piece/postfix.dart';
1617
import 'ast_node_visitor.dart';
@@ -357,10 +358,11 @@ mixin PieceFactory implements CommentWriter {
357358

358359
/// Creates a [ListPiece] for the given bracket-delimited set of elements.
359360
void createList(
360-
Token leftBracket, Iterable<AstNode> elements, Token rightBracket) {
361-
var builder = DelimitedListBuilder(this);
361+
Token leftBracket, Iterable<AstNode> elements, Token rightBracket,
362+
{ListStyle style = const ListStyle()}) {
363+
var builder = DelimitedListBuilder(this, style);
362364
builder.leftBracket(leftBracket);
363-
elements.forEach(builder.add);
365+
elements.forEach(builder.visit);
364366
builder.rightBracket(rightBracket);
365367
writer.push(builder.build());
366368
}
@@ -370,14 +372,15 @@ mixin PieceFactory implements CommentWriter {
370372
Expression value, Token rightParenthesis) {
371373
// Format like an argument list since it is an expression surrounded by
372374
// parentheses.
373-
var builder = DelimitedListBuilder.switchValue(this);
375+
var builder = DelimitedListBuilder(
376+
this, const ListStyle(commas: Commas.none, splitCost: 2));
374377

375378
// Attach the `switch ` as part of the `(`.
376379
token(switchKeyword);
377380
writer.space();
378381

379382
builder.leftBracket(leftParenthesis);
380-
builder.add(value);
383+
builder.visit(value);
381384
builder.rightBracket(rightParenthesis);
382385

383386
writer.push(builder.build());
@@ -386,11 +389,8 @@ mixin PieceFactory implements CommentWriter {
386389
/// Creates a [ListPiece] for a type argument or type parameter list.
387390
void createTypeList(
388391
Token leftBracket, Iterable<AstNode> elements, Token rightBracket) {
389-
var builder = DelimitedListBuilder.type(this);
390-
builder.leftBracket(leftBracket);
391-
elements.forEach(builder.add);
392-
builder.rightBracket(rightBracket);
393-
writer.push(builder.build());
392+
return createList(leftBracket, elements, rightBracket,
393+
style: const ListStyle(commas: Commas.nonTrailing, splitCost: 2));
394394
}
395395

396396
/// Writes the parts of a formal parameter shared by all formal parameter

0 commit comments

Comments
 (0)