Skip to content

Commit 1056428

Browse files
authored
Remove preserveWhitespace() and related code. (#1112)
* Remove preserveWhitespace() and related code. There are some places where a newline or blank line in the original source code affects the output formatting. For example, users can put a blank line between statements if they want. The formatter used to track that lazily like it did other split information. It used a Whitespace enum that could be in a fully pinned down or still partially unknown state. Then when the subsequent comment or text was written, it would count the newlines and figure out how to split. Since there is already a lot of laziness and pending state in ChunkBuilder, that gets pretty complex. This removes all that. Instead, right at the point in SourceVisitor where we write whitespace that depends on the input newlines, we look ahead and eagerly decide how to handle it. * Rewrite doc comment.
1 parent fe04e8b commit 1056428

File tree

10 files changed

+212
-267
lines changed

10 files changed

+212
-267
lines changed

lib/src/argument_list_visitor.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:analyzer/dart/ast/token.dart';
88

99
import 'ast_extensions.dart';
1010
import 'chunk.dart';
11+
import 'constants.dart';
1112
import 'rule/argument.dart';
1213
import 'rule/rule.dart';
1314
import 'source_visitor.dart';

lib/src/chunk.dart

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -250,58 +250,6 @@ class ChunkBlock {
250250
ChunkBlock(this.argument);
251251
}
252252

253-
/// Constants for the cost heuristics used to determine which set of splits is
254-
/// most desirable.
255-
class Cost {
256-
/// The cost of splitting after the `=>` in a lambda or arrow-bodied member.
257-
///
258-
/// We make this zero because there is already a span around the entire body
259-
/// and we generally do prefer splitting after the `=>` over other places.
260-
static const arrow = 0;
261-
262-
/// The default cost.
263-
///
264-
/// This isn't zero because we want to ensure all splitting has *some* cost,
265-
/// otherwise, the formatter won't try to keep things on one line at all.
266-
/// Most splits and spans use this. Greater costs tend to come from a greater
267-
/// number of nested spans.
268-
static const normal = 1;
269-
270-
/// Splitting after a "=".
271-
static const assign = 1;
272-
273-
/// Splitting after a "=" when the right-hand side is a collection or cascade.
274-
static const assignBlock = 2;
275-
276-
/// Splitting before the first argument when it happens to be a function
277-
/// expression with a block body.
278-
static const firstBlockArgument = 2;
279-
280-
/// The series of positional arguments.
281-
static const positionalArguments = 2;
282-
283-
/// Splitting inside the brackets of a list with only one element.
284-
static const singleElementList = 2;
285-
286-
/// Splitting the internals of block arguments.
287-
///
288-
/// Used to prefer splitting at the argument boundary over splitting the block
289-
/// contents.
290-
static const splitBlocks = 2;
291-
292-
/// Splitting on the "." in a named constructor.
293-
static const constructorName = 4;
294-
295-
/// Splitting a `[...]` index operator.
296-
static const index = 4;
297-
298-
/// Splitting before a type argument or type parameter.
299-
static const typeArgument = 4;
300-
301-
/// Split between a formal parameter name and its type.
302-
static const parameterType = 4;
303-
}
304-
305253
/// The in-progress state for a [Span] that has been started but has not yet
306254
/// been completed.
307255
class OpenSpan {

lib/src/chunk_builder.dart

Lines changed: 37 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import 'dart:math' as math;
55

66
import 'chunk.dart';
7+
import 'constants.dart';
78
import 'dart_formatter.dart';
89
import 'debug.dart' as debug;
910
import 'line_writer.dart';
@@ -12,7 +13,6 @@ import 'nesting_level.dart';
1213
import 'rule/rule.dart';
1314
import 'source_code.dart';
1415
import 'style_fix.dart';
15-
import 'whitespace.dart';
1616

1717
/// Matches if the last character of a string is an identifier character.
1818
final _trailingIdentifierChar = RegExp(r'[a-zA-Z0-9_]$');
@@ -45,13 +45,11 @@ class ChunkBuilder {
4545

4646
final List<Chunk> _chunks;
4747

48-
/// The whitespace that should be written to [_chunks] before the next
49-
/// non-whitespace token.
48+
/// The number of newlines that should be written before the next
49+
/// non-whitespace token.
5050
///
51-
/// This ensures that changes to indentation and nesting also apply to the
52-
/// most recent split, even if the visitor "creates" the split before changing
53-
/// indentation or nesting.
54-
Whitespace _pendingWhitespace = Whitespace.none;
51+
/// This will always be 0, 1, or 2.
52+
int _pendingNewlines = 0;
5553

5654
/// Whether a non-breaking space should be written before the next text.
5755
bool _pendingSpace = false;
@@ -108,17 +106,6 @@ class ChunkBuilder {
108106
/// When this is non-zero, splits are ignored.
109107
int _preventSplitNesting = 0;
110108

111-
/// Whether there is pending whitespace that depends on the number of
112-
/// newlines in the source.
113-
///
114-
/// This is used to avoid calculating the newlines between tokens unless
115-
/// actually needed since doing so is slow when done between every single
116-
/// token pair.
117-
bool get needsToPreserveNewlines =>
118-
_pendingWhitespace == Whitespace.oneOrTwoNewlines ||
119-
_pendingWhitespace == Whitespace.splitOrTwoNewlines ||
120-
_pendingWhitespace == Whitespace.splitOrNewline;
121-
122109
/// The number of characters of code that can fit in a single line.
123110
int get pageWidth => _formatter.pageWidth;
124111

@@ -171,10 +158,18 @@ class ChunkBuilder {
171158
_afterComment = false;
172159
}
173160

174-
/// Writes a [WhitespaceChunk] of [type].
175-
void writeWhitespace(Whitespace type,
176-
{bool flushLeft = false, bool nest = false}) {
177-
_pendingWhitespace = type;
161+
/// Writes one or two hard newlines.
162+
///
163+
/// Doesn't immediately write them. That way line breaking is correctly
164+
/// interleaved with any comments that appear before the next token.
165+
///
166+
/// If [isDouble] is `true`, inserts an extra blank line. If [flushLeft] is
167+
/// `true`, the next line will start at column 1 and ignore indentation and
168+
/// nesting. If [nest] is `true` then the next line will use expression
169+
/// nesting.
170+
void writeNewline(
171+
{bool isDouble = false, bool flushLeft = false, bool nest = false}) {
172+
_pendingNewlines = isDouble ? 2 : 1;
178173
_pendingFlushLeft = flushLeft;
179174
_pendingNested = nest;
180175
}
@@ -194,7 +189,7 @@ class ChunkBuilder {
194189
// chunk is safe since the rule that uses the chunk will itself get
195190
// discarded because no chunk references it.
196191
if (_preventSplitNesting > 0) {
197-
_pendingWhitespace = Whitespace.none;
192+
_pendingNewlines = 0;
198193
_pendingNested = false;
199194

200195
if (space) _pendingSpace = true;
@@ -203,12 +198,9 @@ class ChunkBuilder {
203198

204199
// If a hard split after a comment is already pending, then prefer that over
205200
// a soft split.
206-
if (_pendingWhitespace.minimumLines > 0) {
207-
return Chunk.dummy();
208-
}
201+
if (_pendingNewlines > 0) return Chunk.dummy();
209202

210-
return _writeSplit(
211-
isHard: false, isDouble: false, nest: nest, space: space);
203+
return _writeSplit(isHard: false, nest: nest, space: space);
212204
}
213205

214206
/// Outputs the series of [comments] and associated whitespace that appear
@@ -233,14 +225,13 @@ class ChunkBuilder {
233225
// Normally, a blank line is required after `library`, but since there is
234226
// one after the comment, we don't need one before it. This is mainly so
235227
// that commented out directives stick with their preceding group.
236-
if (_pendingWhitespace == Whitespace.twoNewlines &&
237-
comments.first.linesBefore < 2) {
228+
if (_pendingNewlines == 2 && comments.first.linesBefore < 2) {
238229
if (linesBeforeToken > 1) {
239-
writeWhitespace(Whitespace.newline);
230+
writeNewline();
240231
} else {
241232
for (var i = 1; i < comments.length; i++) {
242233
if (comments[i].linesBefore > 1) {
243-
writeWhitespace(Whitespace.newline);
234+
writeNewline();
244235
break;
245236
}
246237
}
@@ -260,9 +251,9 @@ class ChunkBuilder {
260251
//
261252
// When that happens, we need to make sure to preserve the split at the end
262253
// of the first sequence of comments if there is one.
263-
if (_afterComment && _pendingWhitespace != Whitespace.none) {
254+
if (_afterComment && _pendingNewlines > 0) {
264255
comments.first.linesBefore = 1;
265-
writeWhitespace(Whitespace.none);
256+
_pendingNewlines = 0;
266257
}
267258

268259
// Edge case: if the comments are completely inline (i.e. just a series of
@@ -278,17 +269,15 @@ class ChunkBuilder {
278269
//
279270
// /* a */ /* b */ import 'a.dart';
280271
if (linesBeforeToken == 0 &&
281-
_pendingWhitespace.minimumLines > comments.first.linesBefore &&
272+
_pendingNewlines > comments.first.linesBefore &&
282273
comments.every((comment) => comment.type == CommentType.inlineBlock)) {
283-
comments.first.linesBefore = _pendingWhitespace.minimumLines;
274+
comments.first.linesBefore = _pendingNewlines;
284275
}
285276

286277
// Write each comment and the whitespace between them.
287278
for (var i = 0; i < comments.length; i++) {
288279
var comment = comments[i];
289280

290-
preserveNewlines(comment.linesBefore);
291-
292281
// See if the comment should follow text on the current line.
293282
var chunk = _chunkForComment(comment, token);
294283
if (chunk != null) {
@@ -302,13 +291,11 @@ class ChunkBuilder {
302291
}
303292
} else {
304293
// Split before the comment if it starts a line.
305-
if (_pendingWhitespace == Whitespace.none) {
294+
if (_pendingNewlines == 0) {
306295
if (comment.linesBefore > 0 &&
307296
(_afterComment || comment.type != CommentType.inlineBlock)) {
308-
writeWhitespace(
309-
_needsBlankLineBeforeComment(comment)
310-
? Whitespace.twoNewlines
311-
: Whitespace.newline,
297+
writeNewline(
298+
isDouble: _needsBlankLineBeforeComment(comment),
312299
flushLeft: comment.flushLeft,
313300
nest: true);
314301
} else if (_chunks.isNotEmpty) {
@@ -351,19 +338,14 @@ class ChunkBuilder {
351338
}
352339

353340
if (linesAfter > 0) {
354-
writeWhitespace(
355-
_pendingWhitespace == Whitespace.twoNewlines || linesAfter > 1
356-
? Whitespace.twoNewlines
357-
: Whitespace.newline,
358-
nest: true);
341+
writeNewline(
342+
isDouble: _pendingNewlines == 2 || linesAfter > 1, nest: true);
359343
}
360344
}
361345

362346
// If the comment has text following it (aside from a grouping character),
363347
// it needs a trailing space.
364348
_pendingSpace = _needsSpaceAfterComment(comments.last, token);
365-
366-
preserveNewlines(linesBeforeToken);
367349
_afterComment = true;
368350
}
369351

@@ -422,41 +404,11 @@ class ChunkBuilder {
422404
_writeText(' $line', chunk);
423405
}
424406

425-
writeWhitespace(Whitespace.newline);
407+
writeNewline();
426408
_emitPendingWhitespace();
427409
}
428410
}
429411

430-
/// If the current pending whitespace allows some source discretion, pins
431-
/// that down given that the source contains [numLines] newlines at that
432-
/// point and writes any needed split.
433-
void preserveNewlines(int numLines) {
434-
switch (_pendingWhitespace) {
435-
case Whitespace.splitOrNewline:
436-
if (numLines > 0) {
437-
_writeSplit(nest: true);
438-
} else {
439-
split(space: true);
440-
}
441-
break;
442-
443-
case Whitespace.splitOrTwoNewlines:
444-
if (numLines > 1) {
445-
_writeSplit(isDouble: true, nest: true);
446-
} else {
447-
split(space: true);
448-
}
449-
break;
450-
451-
case Whitespace.oneOrTwoNewlines:
452-
_writeSplit(isDouble: numLines > 1, nest: false);
453-
break;
454-
455-
default:
456-
break;
457-
}
458-
}
459-
460412
/// Creates a new indentation level [spaces] deeper than the current one.
461413
///
462414
/// If omitted, [spaces] defaults to [Indent.block].
@@ -739,9 +691,9 @@ class ChunkBuilder {
739691
/// any ambiguous whitespace into a concrete choice.
740692
void _emitPendingWhitespace(
741693
{bool isDouble = false, bool mergeEmptySplits = true}) {
742-
if (_pendingWhitespace == Whitespace.none) return;
694+
if (_pendingNewlines == 0) return;
743695

744-
if (_pendingWhitespace == Whitespace.twoNewlines) isDouble = true;
696+
if (_pendingNewlines == 2) isDouble = true;
745697
_writeSplit(
746698
isDouble: isDouble,
747699
nest: _pendingNested,
@@ -838,7 +790,7 @@ class ChunkBuilder {
838790
// Not at the beginning of a line.
839791
if (_chunks.last.text.isEmpty) return false;
840792

841-
if (_pendingWhitespace != Whitespace.none) return false;
793+
if (_pendingNewlines > 0) return false;
842794

843795
// Magic generic method comments like "Foo/*<T>*/" don't get spaces.
844796
if (_isGenericMethodComment(comment) && token == '(') {
@@ -903,7 +855,7 @@ class ChunkBuilder {
903855
isHard: isHard, isDouble: isDouble, space: space);
904856
}
905857

906-
_pendingWhitespace = Whitespace.none;
858+
_pendingNewlines = 0;
907859
_pendingNested = false;
908860

909861
if (chunk.rule.isHardened) _handleHardSplit();

lib/src/constants.dart

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
/// Constants for the cost heuristics used to determine which set of splits is
6+
/// most desirable.
7+
class Cost {
8+
/// The cost of splitting after the `=>` in a lambda or arrow-bodied member.
9+
///
10+
/// We make this zero because there is already a span around the entire body
11+
/// and we generally do prefer splitting after the `=>` over other places.
12+
static const arrow = 0;
13+
14+
/// The default cost.
15+
///
16+
/// This isn't zero because we want to ensure all splitting has *some* cost,
17+
/// otherwise, the formatter won't try to keep things on one line at all.
18+
/// Most splits and spans use this. Greater costs tend to come from a greater
19+
/// number of nested spans.
20+
static const normal = 1;
21+
22+
/// Splitting after a "=".
23+
static const assign = 1;
24+
25+
/// Splitting after a "=" when the right-hand side is a collection or cascade.
26+
static const assignBlock = 2;
27+
28+
/// Splitting before the first argument when it happens to be a function
29+
/// expression with a block body.
30+
static const firstBlockArgument = 2;
31+
32+
/// The series of positional arguments.
33+
static const positionalArguments = 2;
34+
35+
/// Splitting inside the brackets of a list with only one element.
36+
static const singleElementList = 2;
37+
38+
/// Splitting the internals of block arguments.
39+
///
40+
/// Used to prefer splitting at the argument boundary over splitting the block
41+
/// contents.
42+
static const splitBlocks = 2;
43+
44+
/// Splitting on the "." in a named constructor.
45+
static const constructorName = 4;
46+
47+
/// Splitting a `[...]` index operator.
48+
static const index = 4;
49+
50+
/// Splitting before a type argument or type parameter.
51+
static const typeArgument = 4;
52+
53+
/// Split between a formal parameter name and its type.
54+
static const parameterType = 4;
55+
}
56+
57+
/// Constants for the number of spaces for various kinds of indentation.
58+
class Indent {
59+
/// The number of spaces in a block or collection body.
60+
static const block = 2;
61+
62+
/// How much wrapped cascade sections indent.
63+
static const cascade = 2;
64+
65+
/// The number of spaces in a single level of expression nesting.
66+
static const expression = 4;
67+
68+
/// The ":" on a wrapped constructor initialization list.
69+
static const constructorInitializer = 4;
70+
}

0 commit comments

Comments
 (0)