Coverage report for lib/src/chunk_builder.dart

Line coverage: 276 / 283 (97.5%)

All files > lib/src/chunk_builder.dart

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
library dart_style.src.chunk_builder;
6
7
import 'chunk.dart';
8
import 'dart_formatter.dart';
9
import 'debug.dart' as debug;
10
import 'line_writer.dart';
11
import 'nesting_builder.dart';
12
import 'nesting_level.dart';
13
import 'rule/rule.dart';
14
import 'source_code.dart';
15
import 'style_fix.dart';
16
import 'whitespace.dart';
17
18
/// Matches if the last character of a string is an identifier character.
19
final _trailingIdentifierChar = new RegExp(r"[a-zA-Z0-9_]$");
20
21
/// Matches a JavaDoc-style doc comment that starts with "/**" and ends with
22
/// "*/" or "**/".
23
final _javaDocComment = new RegExp(r"^/\*\*([^*/][\s\S]*?)\*?\*/$");
24
25
/// Matches the leading "*" in a line in the middle of a JavaDoc-style comment.
26
var _javaDocLine = new RegExp(r"^\s*\*(.*)");
27
28
/// Takes the incremental serialized output of [SourceVisitor]--the source text
29
/// along with any comments and preserved whitespace--and produces a coherent
30
/// tree of [Chunk]s which can then be split into physical lines.
31
///
32
/// Keeps track of leading indentation, expression nesting, and all of the hairy
33
/// code required to seamlessly integrate existing comments into the pure
34
/// output produced by [SourceVisitor].
35
class ChunkBuilder {
36
  final DartFormatter _formatter;
37
38
  /// The builder for the code surrounding the block that this writer is for, or
39
  /// `null` if this is writing the top-level code.
40
  final ChunkBuilder _parent;
41
42
  final SourceCode _source;
43
44
  final List<Chunk> _chunks;
45
46
  /// The whitespace that should be written to [_chunks] before the next
47
  ///  non-whitespace token.
48
  ///
49
  /// This ensures that changes to indentation and nesting also apply to the
50
  /// most recent split, even if the visitor "creates" the split before changing
51
  /// indentation or nesting.
52
  Whitespace _pendingWhitespace = Whitespace.none;
53
54
  /// The nested stack of rules that are currently in use.
55
  ///
56
  /// New chunks are implicitly split by the innermost rule when the chunk is
57
  /// ended.
58
  final _rules = <Rule>[];
59
60
  /// The set of rules known to contain hard splits that will in turn force
61
  /// these rules to harden.
62
  ///
63
  /// This is accumulated lazily while chunks are being built. Then, once they
64
  /// are all done, the rules are all hardened. We do this later because some
65
  /// rules may not have all of their constraints fully wired up until after
66
  /// the hard split appears. For example, a hard split in a positional
67
  /// argument list needs to force the named arguments to split too, but we
68
  /// don't create that rule until after the positional arguments are done.
69
  final _hardSplitRules = new Set<Rule>();
70
71
  /// The list of rules that are waiting until the next whitespace has been
72
  /// written before they start.
73
  final _lazyRules = <Rule>[];
74
75
  /// The nested stack of spans that are currently being written.
76
  final _openSpans = <OpenSpan>[];
77
78
  /// The current state.
79
  final _nesting = new NestingBuilder();
80
81
  /// The stack of nesting levels where block arguments may start.
82
  ///
83
  /// A block argument's contents will nest at the last level in this stack.
84
  final _blockArgumentNesting = <NestingLevel>[];
85
86
  /// The index of the "current" chunk being written.
87
  ///
88
  /// If the last chunk is still being appended to, this is its index.
89
  /// Otherwise, it is the index of the next chunk which will be created.
903
  int get _currentChunkIndex {
916
    if (_chunks.isEmpty) return 0;
9218
    if (_chunks.last.canAddText) return _chunks.length - 1;
936
    return _chunks.length;
94
  }
95
96
  /// Whether or not there was a leading comment that was flush left before any
97
  /// other content was written.
98
  ///
99
  /// This is used when writing child blocks to make the parent chunk have the
100
  /// right flush left value when a comment appears immediately inside the
101
  /// block.
102
  bool _firstFlushLeft = false;
103
104
  /// The number of calls to [preventSplit()] that have not been ended by a
105
  /// call to [endPreventSplit()].
106
  ///
107
  /// Splitting is completely disabled inside string interpolation. We do want
108
  /// to fix the whitespace inside interpolation, though, so we still format
109
  /// them. This tracks whether we're inside an interpolation. We can't use a
110
  /// simple bool because interpolation can nest.
111
  ///
112
  /// When this is non-zero, splits are ignored.
113
  int _preventSplitNesting = 0;
114
115
  /// Whether there is pending whitespace that depends on the number of
116
  /// newlines in the source.
117
  ///
118
  /// This is used to avoid calculating the newlines between tokens unless
119
  /// actually needed since doing so is slow when done between every single
120
  /// token pair.
1213
  bool get needsToPreserveNewlines =>
1226
      _pendingWhitespace == Whitespace.oneOrTwoNewlines ||
1236
      _pendingWhitespace == Whitespace.splitOrTwoNewlines ||
1246
      _pendingWhitespace == Whitespace.splitOrNewline;
125
126
  /// The number of characters of code that can fit in a single line.
1270
  int get pageWidth => _formatter.pageWidth;
128
129
  /// The current innermost rule.
1306
  Rule get rule => _rules.last;
131
1323
  ChunkBuilder(this._formatter, this._source)
133
      : _parent = null,
1343
        _chunks = [] {
1359
    indent(_formatter.indent);
1363
    startBlockArgumentNesting();
137
  }
138
1392
  ChunkBuilder._(this._parent, this._formatter, this._source, this._chunks) {
1402
    startBlockArgumentNesting();
141
  }
142
143
  /// Writes [string], the text for a single token, to the output.
144
  ///
145
  /// By default, this also implicitly adds one level of nesting if we aren't
146
  /// currently nested at all. We do this here so that if a comment appears
147
  /// after any token within a statement or top-level form and that comment
148
  /// leads to splitting, we correctly nest. Even pathological cases like:
149
  ///
150
  ///
151
  ///     import // comment
152
  ///         "this_gets_nested.dart";
153
  ///
154
  /// If we didn't do this here, we'd have to call [nestExpression] after the
155
  /// first token of practically every grammar production.
1563
  void write(String string) {
1573
    _emitPendingWhitespace();
1583
    _writeText(string);
159
1609
    _lazyRules.forEach(_activateRule);
1616
    _lazyRules.clear();
162
1636
    _nesting.commitNesting();
164
  }
165
166
  /// Writes a [WhitespaceChunk] of [type].
1673
  void writeWhitespace(Whitespace type) {
1683
    _pendingWhitespace = type;
169
  }
170
171
  /// Write a split owned by the current innermost rule.
172
  ///
173
  /// If [flushLeft] is `true`, then forces the next line to start at column
174
  /// one regardless of any indentation or nesting.
175
  ///
176
  /// If [isDouble] is passed, forces the split to either be a single or double
177
  /// newline. Otherwise, leaves it indeterminate.
178
  ///
179
  /// If [nest] is `false`, ignores any current expression nesting. Otherwise,
180
  /// uses the current nesting level. If unsplit, it expands to a space if
181
  /// [space] is `true`.
1823
  Chunk split({bool flushLeft, bool isDouble, bool nest, bool space}) {
183
    space ??= false;
184
185
    // If we are not allowed to split at all, don't. Returning null for the
186
    // chunk is safe since the rule that uses the chunk will itself get
187
    // discarded because no chunk references it.
1886
    if (_preventSplitNesting > 0) {
1890
      if (space) _pendingWhitespace = Whitespace.space;
190
      return null;
191
    }
192
1939
    return _writeSplit(_rules.last,
194
        flushLeft: flushLeft, isDouble: isDouble, nest: nest, space: space);
195
  }
196
197
  /// Outputs the series of [comments] and associated whitespace that appear
198
  /// before [token] (which is not written by this).
199
  ///
200
  /// The list contains each comment as it appeared in the source between the
201
  /// last token written and the next one that's about to be written.
202
  ///
203
  /// [linesBeforeToken] is the number of lines between the last comment (or
204
  /// previous token if there are no comments) and the next token.
2052
  void writeComments(
206
      List<SourceComment> comments, int linesBeforeToken, String token) {
207
    // Edge case: if we require a blank line, but there exists one between
208
    // some of the comments, or after the last one, then we don't need to
209
    // enforce one before the first comment. Example:
210
    //
211
    //     library foo;
212
    //     // comment
213
    //
214
    //     class Bar {}
215
    //
216
    // Normally, a blank line is required after `library`, but since there is
217
    // one after the comment, we don't need one before it. This is mainly so
218
    // that commented out directives stick with their preceding group.
2194
    if (_pendingWhitespace == Whitespace.twoNewlines &&
2206
        comments.first.linesBefore < 2) {
2212
      if (linesBeforeToken > 1) {
2221
        _pendingWhitespace = Whitespace.newline;
223
      } else {
2245
        for (var i = 1; i < comments.length; i++) {
2253
          if (comments[i].linesBefore > 1) {
2260
            _pendingWhitespace = Whitespace.newline;
227
            break;
228
          }
229
        }
230
      }
231
    }
232
233
    // Edge case: if the previous output was also from a call to
234
    // [writeComments()] which ended with a line comment, force a newline.
235
    // Normally, comments are strictly interleaved with tokens and you never
236
    // get two sequences of comments in a row. However, when applying a fix
237
    // that removes a token (like `new`), it's possible to get two sets of
238
    // comments in a row, as in:
239
    //
240
    //     // a
241
    //     new // b
242
    //     Foo();
243
    //
244
    // When that happens, we need to make sure the preserve the split at the
245
    // end of the first sequence of comments if there is one.
2462
    if (_pendingWhitespace == null) {
2472
      comments.first.linesBefore = 1;
2481
      _pendingWhitespace = Whitespace.none;
249
    }
250
251
    // Edge case: if the comments are completely inline (i.e. just a series of
252
    // block comments with no newlines before, after, or between them), then
253
    // they will eat any pending newlines. Make sure that doesn't happen by
254
    // putting the pending whitespace before the first comment and moving them
255
    // to their own line. Turns this:
256
    //
257
    //     library foo; /* a */ /* b */ import 'a.dart';
258
    //
259
    // into:
260
    //
261
    //     library foo;
262
    //
263
    //     /* a */ /* b */
264
    //     import 'a.dart';
2652
    if (linesBeforeToken == 0 &&
2666
        comments.every((comment) => comment.isInline) &&
2676
        _pendingWhitespace.minimumLines > 0) {
2684
      comments.first.linesBefore = _pendingWhitespace.minimumLines;
269
      linesBeforeToken = 1;
270
    }
271
272
    // Write each comment and the whitespace between them.
2736
    for (var i = 0; i < comments.length; i++) {
2742
      var comment = comments[i];
275
2764
      preserveNewlines(comment.linesBefore);
277
278
      // Don't emit a space because we'll handle it below. If we emit it here,
279
      // we may get a trailing space if the comment needs a line before it.
2804
      if (_pendingWhitespace == Whitespace.space) {
2812
        _pendingWhitespace = Whitespace.none;
282
      }
2832
      _emitPendingWhitespace();
284
2854
      if (comment.linesBefore == 0) {
286
        // If we're sitting on a split, move the comment before it to adhere it
287
        // to the preceding text.
2884
        if (_shouldMoveCommentBeforeSplit(comment.text)) {
2896
          _chunks.last.allowText();
290
        }
291
292
        // The comment follows other text, so we need to decide if it gets a
293
        // space before it or not.
2944
        if (_needsSpaceBeforeComment(comment)) _writeText(" ");
295
      } else {
296
        // The comment starts a line, so make sure it stays on its own line.
2972
        _writeHardSplit(
2982
            flushLeft: comment.flushLeft,
2994
            isDouble: comment.linesBefore > 1,
300
            nest: true);
301
      }
302
3032
      _writeCommentText(comment);
304
3052
      if (comment.selectionStart != null) {
3065
        startSelectionFromEnd(comment.text.length - comment.selectionStart);
307
      }
308
3092
      if (comment.selectionEnd != null) {
3105
        endSelectionFromEnd(comment.text.length - comment.selectionEnd);
311
      }
312
313
      // Make sure there is at least one newline after a line comment and allow
314
      // one or two after a block comment that has nothing after it.
315
      var linesAfter;
3166
      if (i < comments.length - 1) {
3176
        linesAfter = comments[i + 1].linesBefore;
318
      } else {
319
        linesAfter = linesBeforeToken;
320
321
        // Always force a newline after multi-line block comments. Prevents
322
        // mistakes like:
323
        //
324
        //     /**
325
        //      * Some doc comment.
326
        //      */ someFunction() { ... }
3278
        if (linesAfter == 0 && comments.last.text.contains("\n")) {
328
          linesAfter = 1;
329
        }
330
      }
331
3326
      if (linesAfter > 0) _writeHardSplit(isDouble: linesAfter > 1, nest: true);
333
    }
334
335
    // If the comment has text following it (aside from a grouping character),
336
    // it needs a trailing space.
3372
    if (_needsSpaceAfterLastComment(comments, token)) {
3382
      _pendingWhitespace = Whitespace.space;
339
    }
340
3412
    preserveNewlines(linesBeforeToken);
342
  }
343
344
  /// Writes the text of [comment].
345
  ///
346
  /// If it's a JavaDoc comment that should be fixed to use `///`, fixes it.
3472
  void _writeCommentText(SourceComment comment) {
3486
    if (!_formatter.fixes.contains(StyleFix.docComments)) {
3494
      _writeText(comment.text);
350
      return;
351
    }
352
353
    // See if it's a JavaDoc comment.
3543
    var match = _javaDocComment.firstMatch(comment.text);
355
    if (match == null) {
3562
      _writeText(comment.text);
357
      return;
358
    }
359
360
    // Remove a leading "*" from the middle lines.
3613
    var lines = match.group(1).split("\n").toList();
3624
    for (var i = 1; i < lines.length - 1; i++) {
3631
      var line = lines[i];
3642
      var match = _javaDocLine.firstMatch(line);
365
      if (match != null) {
3661
        line = match.group(1);
367
      } else {
368
        // Note that this may remove deliberate leading whitespace. In tests on
369
        // a large corpus, though, I couldn't find examples of that.
3701
        line = line.trimLeft();
371
      }
3721
      lines[i] = line;
373
    }
374
375
    // Trim the first and last lines if empty.
3764
    if (lines.first.trim().isEmpty) lines.removeAt(0);
3775
    if (lines.isNotEmpty && lines.last.trim().isEmpty) lines.removeLast();
378
379
    // Don't completely eliminate an empty block comment.
3802
    if (lines.isEmpty) lines.add("");
381
3822
    for (var line in lines) {
3833
      if (line.isNotEmpty && !line.startsWith(" ")) line = " $line";
3843
      _writeText("///${line.trimRight()}");
3851
      _pendingWhitespace = Whitespace.newline;
3861
      _emitPendingWhitespace();
387
    }
388
  }
389
390
  /// If the current pending whitespace allows some source discretion, pins
391
  /// that down given that the source contains [numLines] newlines at that
392
  /// point.
3932
  void preserveNewlines(int numLines) {
394
    // If we didn't know how many newlines the user authored between the last
395
    // token and this one, now we do.
3962
    switch (_pendingWhitespace) {
3972
      case Whitespace.splitOrNewline:
3981
        if (numLines > 0) {
3991
          _pendingWhitespace = Whitespace.nestedNewline;
400
        } else {
4011
          _pendingWhitespace = Whitespace.none;
4021
          split(space: true);
403
        }
404
        break;
405
4062
      case Whitespace.splitOrTwoNewlines:
4071
        if (numLines > 1) {
4081
          _pendingWhitespace = Whitespace.twoNewlines;
409
        } else {
4101
          _pendingWhitespace = Whitespace.none;
4111
          split(space: true);
412
        }
413
        break;
414
4152
      case Whitespace.oneOrTwoNewlines:
4162
        if (numLines > 1) {
4171
          _pendingWhitespace = Whitespace.twoNewlines;
418
        } else {
4192
          _pendingWhitespace = Whitespace.newline;
420
        }
421
        break;
422
    }
423
  }
424
425
  /// Creates a new indentation level [spaces] deeper than the current one.
426
  ///
427
  /// If omitted, [spaces] defaults to [Indent.block].
4283
  void indent([int spaces]) {
4296
    _nesting.indent(spaces);
430
  }
431
432
  /// Discards the most recent indentation level.
4332
  void unindent() {
4344
    _nesting.unindent();
435
  }
436
437
  /// Starts a new span with [cost].
438
  ///
439
  /// Each call to this needs a later matching call to [endSpan].
4403
  void startSpan([int cost = Cost.normal]) {
44112
    _openSpans.add(new OpenSpan(_currentChunkIndex, cost));
442
  }
443
444
  /// Ends the innermost span.
4453
  void endSpan() {
4466
    var openSpan = _openSpans.removeLast();
447
448
    // A span that just covers a single chunk can't be split anyway.
4493
    var end = _currentChunkIndex;
4506
    if (openSpan.start == end) return;
451
452
    // Add the span to every chunk that can split it.
4536
    var span = new Span(openSpan.cost);
4549
    for (var i = openSpan.start; i < end; i++) {
4556
      var chunk = _chunks[i];
45612
      if (!chunk.rule.isHardened) chunk.spans.add(span);
457
    }
458
  }
459
460
  /// Starts a new [Rule].
461
  ///
462
  /// If omitted, defaults to a new [Rule].
4633
  void startRule([Rule rule]) {
4642
    if (rule == null) rule = new Rule();
465
466
    // If there are any pending lazy rules, start them now so that the proper
467
    // stack ordering of rules is maintained.
4689
    _lazyRules.forEach(_activateRule);
4696
    _lazyRules.clear();
470
4713
    _activateRule(rule);
472
  }
473
4743
  void _activateRule(Rule rule) {
475
    // See if any of the rules that contain this one care if it splits.
4768
    _rules.forEach((outer) {
4772
      if (!outer.splitsOnInnerRules) return;
4782
      rule.imply(outer);
479
    });
4806
    _rules.add(rule);
481
  }
482
483
  /// Starts a new [Rule] that comes into play *after* the next whitespace
484
  /// (including comments) is written.
485
  ///
486
  /// This is used for operators who want to start a rule before the first
487
  /// operand but not get forced to split if a comment appears before the
488
  /// entire expression.
489
  ///
490
  /// If [rule] is omitted, defaults to a new [Rule].
4913
  void startLazyRule([Rule rule]) {
4922
    if (rule == null) rule = new Rule();
493
4946
    _lazyRules.add(rule);
495
  }
496
497
  /// Ends the innermost rule.
4983
  void endRule() {
4996
    if (_lazyRules.isNotEmpty) {
5002
      _lazyRules.removeLast();
501
    } else {
5026
      _rules.removeLast();
503
    }
504
  }
505
506
  /// Pre-emptively forces all of the current rules to become hard splits.
507
  ///
508
  /// This is called by [SourceVisitor] when it can determine that a rule will
509
  /// will always be split. Turning it (and the surrounding rules) into hard
510
  /// splits lets the writer break the output into smaller pieces for the line
511
  /// splitter, which helps performance and avoids failing on very large input.
512
  ///
513
  /// In particular, it's easy for the visitor to know that collections with a
514
  /// large number of items must split. Doing that early avoids crashing the
515
  /// splitter when it tries to recurse on huge collection literals.
5164
  void forceRules() => _handleHardSplit();
517
518
  /// Begins a new expression nesting level [indent] spaces deeper than the
519
  /// current one if it splits.
520
  ///
521
  /// If [indent] is omitted, defaults to [Indent.expression]. If [now] is
522
  /// `true`, commits the nesting change immediately instead of waiting until
523
  /// after the next chunk of text is written.
5243
  void nestExpression({int indent, bool now}) {
525
    if (now == null) now = false;
526
5276
    _nesting.nest(indent);
5284
    if (now) _nesting.commitNesting();
529
  }
530
531
  /// Discards the most recent level of expression nesting.
532
  ///
533
  /// Expressions that are more nested will get increased indentation when split
534
  /// if the previous line has a lower level of nesting.
535
  ///
536
  /// If [now] is `false`, does not commit the nesting change until after the
537
  /// next chunk of text is written.
5383
  void unnest({bool now}) {
539
    if (now == null) now = true;
540
5416
    _nesting.unnest();
5426
    if (now) _nesting.commitNesting();
543
  }
544
545
  /// Marks the selection starting point as occurring [fromEnd] characters to
546
  /// the left of the end of what's currently been written.
547
  ///
548
  /// It counts backwards from the end because this is called *after* the chunk
549
  /// of text containing the selection has been output.
5501
  void startSelectionFromEnd(int fromEnd) {
5512
    assert(_chunks.isNotEmpty);
5523
    _chunks.last.startSelectionFromEnd(fromEnd);
553
  }
554
555
  /// Marks the selection ending point as occurring [fromEnd] characters to the
556
  /// left of the end of what's currently been written.
557
  ///
558
  /// It counts backwards from the end because this is called *after* the chunk
559
  /// of text containing the selection has been output.
5601
  void endSelectionFromEnd(int fromEnd) {
5612
    assert(_chunks.isNotEmpty);
5623
    _chunks.last.endSelectionFromEnd(fromEnd);
563
  }
564
565
  /// Captures the current nesting level as marking where subsequent block
566
  /// arguments should start.
5673
  void startBlockArgumentNesting() {
56812
    _blockArgumentNesting.add(_nesting.currentNesting);
569
  }
570
571
  /// Releases the last nesting level captured by [startBlockArgumentNesting].
5723
  void endBlockArgumentNesting() {
5736
    _blockArgumentNesting.removeLast();
574
  }
575
576
  /// Starts a new block as a child of the current chunk.
577
  ///
578
  /// Nested blocks are handled using their own independent [LineWriter].
5792
  ChunkBuilder startBlock(Chunk argumentChunk) {
5804
    var chunk = _chunks.last;
5812
    chunk.makeBlock(argumentChunk);
582
583
    var builder =
58410
        new ChunkBuilder._(this, _formatter, _source, chunk.block.chunks);
585
586
    // A block always starts off indented one level.
5872
    builder.indent();
588
589
    return builder;
590
  }
591
592
  /// Ends this [ChunkBuilder], which must have been created by [startBlock()].
593
  ///
594
  /// Forces the chunk that owns the block to split if it can tell that the
595
  /// block contents will always split. It does that by looking for hard splits
596
  /// in the block. If [ignoredSplit] is given, that rule will be ignored
597
  /// when determining if a block contains a hard split. If [forceSplit] is
598
  /// `true`, the block is considered to always split.
599
  ///
600
  /// Returns the previous writer for the surrounding block.
6012
  ChunkBuilder endBlock(Rule ignoredSplit, {bool forceSplit}) {
6022
    _divideChunks();
603
604
    // If we don't already know if the block is going to split, see if it
605
    // contains any hard splits or is longer than a page.
606
    if (!forceSplit) {
607
      var length = 0;
6084
      for (var chunk in _chunks) {
6098
        length += chunk.length + chunk.unsplitBlockLength;
6106
        if (length > _formatter.pageWidth) {
611
          forceSplit = true;
612
          break;
613
        }
614
6152
        if (chunk.rule != null &&
6164
            chunk.rule.isHardened &&
6174
            chunk.rule != ignoredSplit) {
618
          forceSplit = true;
619
          break;
620
        }
621
      }
622
    }
623
6244
    _parent._endChildBlock(
6252
        firstFlushLeft: _firstFlushLeft, forceSplit: forceSplit);
626
6272
    return _parent;
628
  }
629
630
  /// Finishes off the last chunk in a child block of this parent.
6312
  void _endChildBlock({bool firstFlushLeft, bool forceSplit}) {
632
    // If there is a hard newline within the block, force the surrounding rule
633
    // for it so that we apply that constraint.
6342
    if (forceSplit) forceRules();
635
636
    // Write the split for the block contents themselves.
6374
    var chunk = _chunks.last;
63812
    chunk.applySplit(rule, _nesting.indentation, _blockArgumentNesting.last,
639
        flushLeft: firstFlushLeft);
640
6415
    if (chunk.rule.isHardened) _handleHardSplit();
642
  }
643
644
  /// Finishes writing and returns a [SourceCode] containing the final output
645
  /// and updated selection, if any.
6463
  SourceCode end() {
6473
    _writeHardSplit();
6483
    _divideChunks();
649
650
    if (debug.traceChunkBuilder) {
6510
      debug.log(debug.green("\nBuilt:"));
6520
      debug.dumpChunks(0, _chunks);
6530
      debug.log();
654
    }
655
6569
    var writer = new LineWriter(_formatter, _chunks);
6579
    var result = writer.writeLines(_formatter.indent,
6586
        isCompilationUnit: _source.isCompilationUnit);
659
660
    var selectionStart;
661
    var selectionLength;
6626
    if (_source.selectionStart != null) {
6631
      selectionStart = result.selectionStart;
6641
      var selectionEnd = result.selectionEnd;
665
666
      // If we haven't hit the beginning and/or end of the selection yet, they
667
      // must be at the very end of the code.
6681
      if (selectionStart == null) selectionStart = writer.length;
6691
      if (selectionEnd == null) selectionEnd = writer.length;
670
6711
      selectionLength = selectionEnd - selectionStart;
672
    }
673
6746
    return new SourceCode(result.text,
6756
        uri: _source.uri,
6766
        isCompilationUnit: _source.isCompilationUnit,
677
        selectionStart: selectionStart,
678
        selectionLength: selectionLength);
679
  }
680
6812
  void preventSplit() {
6824
    _preventSplitNesting++;
683
  }
684
6852
  void endPreventSplit() {
6864
    _preventSplitNesting--;
6874
    assert(_preventSplitNesting >= 0, "Mismatched calls.");
688
  }
689
690
  /// Writes the current pending [Whitespace] to the output, if any.
691
  ///
692
  /// This should only be called after source lines have been preserved to turn
693
  /// any ambiguous whitespace into a concrete choice.
6943
  void _emitPendingWhitespace() {
695
    // Output any pending whitespace first now that we know it won't be
696
    // trailing.
6973
    switch (_pendingWhitespace) {
6983
      case Whitespace.space:
6993
        _writeText(" ");
700
        break;
701
7023
      case Whitespace.newline:
7032
        _writeHardSplit();
704
        break;
705
7063
      case Whitespace.nestedNewline:
7071
        _writeHardSplit(nest: true);
708
        break;
709
7103
      case Whitespace.newlineFlushLeft:
7111
        _writeHardSplit(flushLeft: true, nest: true);
712
        break;
713
7143
      case Whitespace.twoNewlines:
7153
        _writeHardSplit(isDouble: true);
716
        break;
717
7183
      case Whitespace.splitOrNewline:
7193
      case Whitespace.splitOrTwoNewlines:
7203
      case Whitespace.oneOrTwoNewlines:
721
        // We should have pinned these down before getting here.
7220
        assert(false);
723
        break;
724
    }
725
7263
    _pendingWhitespace = Whitespace.none;
727
  }
728
729
  /// Returns `true` if the last chunk is a split that should be moved after the
730
  /// comment that is about to be written.
7312
  bool _shouldMoveCommentBeforeSplit(String comment) {
732
    // Not if there is nothing before it.
7334
    if (_chunks.isEmpty) return false;
734
735
    // Multi-line comments are always pushed to the next line.
7362
    if (comment.contains("\n")) return false;
737
7386
    var text = _chunks.last.text;
739
740
    // A block comment following a comma probably refers to the following item.
7414
    if (text.endsWith(",") && comment.startsWith("/*")) return false;
742
743
    // If the text before the split is an open grouping character, it looks
744
    // better to keep it with the elements than with the bracket itself.
7456
    return !text.endsWith("(") && !text.endsWith("[") && !text.endsWith("{");
746
  }
747
748
  /// Returns `true` if [comment] appears to be a magic generic method comment.
749
  ///
750
  /// Those get spaced a little differently to look more like real syntax:
751
  ///
752
  ///     int f/*<S, T>*/(int x) => 3;
7532
  bool _isGenericMethodComment(SourceComment comment) {
7548
    return comment.text.startsWith("/*<") || comment.text.startsWith("/*=");
755
  }
756
757
  /// Returns `true` if a space should be output between the end of the current
758
  /// output and the subsequent comment which is about to be written.
759
  ///
760
  /// This is only called if the comment is trailing text in the unformatted
761
  /// source. In most cases, a space will be output to separate the comment
762
  /// from what precedes it. This returns false if:
763
  ///
764
  /// *   This comment does begin the line in the output even if it didn't in
765
  ///     the source.
766
  /// *   The comment is a block comment immediately following a grouping
767
  ///     character (`(`, `[`, or `{`). This is to allow `foo(/* comment */)`,
768
  ///     et. al.
7692
  bool _needsSpaceBeforeComment(SourceComment comment) {
770
    // Not at the start of the file.
7714
    if (_chunks.isEmpty) return false;
772
773
    // Not at the start of a line.
7746
    if (!_chunks.last.canAddText) return false;
775
7766
    var text = _chunks.last.text;
7772
    if (text.endsWith("\n")) return false;
778
779
    // Always put a space before line comments.
7802
    if (comment.isLineComment) return true;
781
782
    // Magic generic method comments like "Foo/*<T>*/" don't get spaces.
7832
    if (_isGenericMethodComment(comment) &&
7842
        _trailingIdentifierChar.hasMatch(text)) {
785
      return false;
786
    }
787
788
    // Block comments do not get a space if following a grouping character.
7896
    return !text.endsWith("(") && !text.endsWith("[") && !text.endsWith("{");
790
  }
791
792
  /// Returns `true` if a space should be output after the last comment which
793
  /// was just written and the token that will be written.
7942
  bool _needsSpaceAfterLastComment(List<SourceComment> comments, String token) {
795
    // Not if there are no comments.
7962
    if (comments.isEmpty) return false;
797
798
    // Not at the beginning of a line.
7996
    if (!_chunks.last.canAddText) return false;
800
801
    // Magic generic method comments like "Foo/*<T>*/" don't get spaces.
8025
    if (_isGenericMethodComment(comments.last) && token == "(") {
803
      return false;
804
    }
805
806
    // Otherwise, it gets a space if the following token is not a delimiter or
807
    // the empty string, for EOF.
8082
    return token != ")" &&
8092
        token != "]" &&
8102
        token != "}" &&
8112
        token != "," &&
8122
        token != ";" &&
8132
        token != "";
814
  }
815
816
  /// Appends a hard split with the current indentation and nesting (the latter
817
  /// only if [nest] is `true`).
818
  ///
819
  /// If [double] is `true` or `false`, forces a single or double line to be
820
  /// output. Otherwise, it is left indeterminate.
821
  ///
822
  /// If [flushLeft] is `true`, then the split will always cause the next line
823
  /// to be at column zero. Otherwise, it uses the normal indentation and
824
  /// nesting behavior.
8253
  void _writeHardSplit({bool isDouble, bool flushLeft, bool nest: false}) {
826
    // A hard split overrides any other whitespace.
8273
    _pendingWhitespace = null;
8286
    _writeSplit(new Rule.hard(),
829
        flushLeft: flushLeft, isDouble: isDouble, nest: nest);
830
  }
831
832
  /// Ends the current chunk (if any) with the given split information.
833
  ///
834
  /// Returns the chunk.
8353
  Chunk _writeSplit(Rule rule,
836
      {bool flushLeft, bool isDouble, bool nest, bool space}) {
837
    nest ??= true;
838
    space ??= false;
839
8406
    if (_chunks.isEmpty) {
8411
      if (flushLeft != null) _firstFlushLeft = flushLeft;
842
843
      return null;
844
    }
845
84615
    _chunks.last.applySplit(rule, _nesting.indentation,
8479
        nest ? _nesting.nesting : new NestingLevel(),
848
        flushLeft: flushLeft, isDouble: isDouble, space: space);
849
85015
    if (_chunks.last.rule.isHardened) _handleHardSplit();
8516
    return _chunks.last;
852
  }
853
854
  /// Writes [text] to either the current chunk or a new one if the current
855
  /// chunk is complete.
8563
  void _writeText(String text) {
85715
    if (_chunks.isNotEmpty && _chunks.last.canAddText) {
8589
      _chunks.last.appendText(text);
859
    } else {
8609
      _chunks.add(new Chunk(text));
861
    }
862
  }
863
864
  /// Returns true if we can divide the chunks at [index] and line split the
865
  /// ones before and after that separately.
8663
  bool _canDivideAt(int i) {
867
    // Don't divide after the last chunk.
86812
    if (i == _chunks.length - 1) return false;
869
8706
    var chunk = _chunks[i];
8716
    if (!chunk.rule.isHardened) return false;
8724
    if (chunk.nesting.isNested) return false;
8732
    if (chunk.isBlock) return false;
874
875
    return true;
876
  }
877
878
  /// Pre-processes the chunks after they are done being written by the visitor
879
  /// but before they are run through the line splitter.
880
  ///
881
  /// Marks ranges of chunks that can be line split independently to keep the
882
  /// batches we send to [LineSplitter] small.
8833
  void _divideChunks() {
884
    // Harden all of the rules that we know get forced by containing hard
885
    // splits, along with all of the other rules they constrain.
8863
    _hardenRules();
887
888
    // Now that we know where all of the divided chunk sections are, mark the
889
    // chunks.
89012
    for (var i = 0; i < _chunks.length; i++) {
89112
      _chunks[i].markDivide(_canDivideAt(i));
892
    }
893
  }
894
895
  /// Hardens the active rules when a hard split occurs within them.
8963
  void _handleHardSplit() {
8976
    if (_rules.isEmpty) return;
898
899
    // If the current rule doesn't care, it will "eat" the hard split and no
900
    // others will care either.
9016
    if (!_rules.last.splitsOnInnerRules) return;
902
903
    // Start with the innermost rule. This will traverse the other rules it
904
    // constrains.
9058
    _hardSplitRules.add(_rules.last);
906
  }
907
908
  /// Replaces all of the previously hardened rules with hard splits, along
909
  /// with every rule that those constrain to also split.
910
  ///
911
  /// This should only be called after all chunks have been written.
9123
  void _hardenRules() {
9136
    if (_hardSplitRules.isEmpty) return;
914
9152
    walkConstraints(rule) {
9162
      rule.harden();
917
918
      // Follow this rule's constraints, recursively.
9192
      for (var other in rule.constrainedRules) {
9202
        if (other == rule) continue;
921
9222
        if (!other.isHardened &&
9236
            rule.constrain(rule.fullySplitValue, other) ==
9242
                other.fullySplitValue) {
9252
          walkConstraints(other);
926
        }
927
      }
928
    }
929
9304
    for (var rule in _hardSplitRules) {
9312
      walkConstraints(rule);
932
    }
933
934
    // Discard spans in hardened chunks since we know for certain they will
935
    // split anyway.
9364
    for (var chunk in _chunks) {
9376
      if (chunk.rule != null && chunk.rule.isHardened) {
9384
        chunk.spans.clear();
939
      }
940
    }
941
  }
942
}