Coverage report for lib/src/chunk.dart

Line coverage: 64 / 80 (80.0%)

All files > lib/src/chunk.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;
6
7
import 'fast_hash.dart';
8
import 'nesting_level.dart';
9
import 'rule/rule.dart';
10
11
/// Tracks where a selection start or end point may appear in some piece of
12
/// text.
13
abstract class Selection {
14
  /// The chunk of text.
15
  String get text;
16
17
  /// The offset from the beginning of [text] where the selection starts, or
18
  /// `null` if the selection does not start within this chunk.
196
  int get selectionStart => _selectionStart;
20
  int _selectionStart;
21
22
  /// The offset from the beginning of [text] where the selection ends, or
23
  /// `null` if the selection does not start within this chunk.
246
  int get selectionEnd => _selectionEnd;
25
  int _selectionEnd;
26
27
  /// Sets [selectionStart] to be [start] characters into [text].
281
  void startSelection(int start) {
291
    _selectionStart = start;
30
  }
31
32
  /// Sets [selectionStart] to be [fromEnd] characters from the end of [text].
331
  void startSelectionFromEnd(int fromEnd) {
344
    _selectionStart = text.length - fromEnd;
35
  }
36
37
  /// Sets [selectionEnd] to be [end] characters into [text].
381
  void endSelection(int end) {
391
    _selectionEnd = end;
40
  }
41
42
  /// Sets [selectionEnd] to be [fromEnd] characters from the end of [text].
431
  void endSelectionFromEnd(int fromEnd) {
444
    _selectionEnd = text.length - fromEnd;
45
  }
46
}
47
48
/// A chunk of non-breaking output text terminated by a hard or soft newline.
49
///
50
/// Chunks are created by [LineWriter] and fed into [LineSplitter]. Each
51
/// contains some text, along with the data needed to tell how the next line
52
/// should be formatted and how desireable it is to split after the chunk.
53
///
54
/// Line splitting after chunks comes in a few different forms.
55
///
56
/// *   A "hard" split is a mandatory newline. The formatted output will contain
57
///     at least one newline after the chunk's text.
58
/// *   A "soft" split is a discretionary newline. If a line doesn't fit within
59
///     the page width, one or more soft splits may be turned into newlines to
60
///     wrap the line to fit within the bounds. If a soft split is not turned
61
///     into a newline, it may instead appear as a space or zero-length string
62
///     in the output, depending on [spaceWhenUnsplit].
63
/// *   A "double" split expands to two newlines. In other words, it leaves a
64
///     blank line in the output. Hard or soft splits may be doubled. This is
65
///     determined by [isDouble].
66
///
67
/// A split controls the leading spacing of the subsequent line, both
68
/// block-based [indent] and expression-wrapping-based [nesting].
69
class Chunk extends Selection {
70
  /// The literal text output for the chunk.
716
  String get text => _text;
72
  String _text;
73
74
  /// The number of characters of indentation from the left edge of the block
75
  /// that contains this chunk.
76
  ///
77
  /// For top level chunks that are not inside any block, this also includes
78
  /// leading indentation.
794
  int get indent => _indent;
80
  int _indent;
81
82
  /// The expression nesting level following this chunk.
83
  ///
84
  /// This is used to determine how much to increase the indentation when a
85
  /// line starts after this chunk. A single statement may be indented multiple
86
  /// times if the splits occur in more deeply nested expressions, for example:
87
  ///
88
  ///     // 40 columns                           |
89
  ///     someFunctionName(argument, argument,
90
  ///         argument, anotherFunction(argument,
91
  ///             argument));
924
  NestingLevel get nesting => _nesting;
93
  NestingLevel _nesting;
94
95
  /// If this chunk marks the beginning of a block, this contains the child
96
  /// chunks and other data about that nested block.
974
  ChunkBlock get block => _block;
98
  ChunkBlock _block;
99
100
  /// Whether this chunk has a [block].
1016
  bool get isBlock => _block != null;
102
103
  /// Whether it's valid to add more text to this chunk or not.
104
  ///
105
  /// Chunks are built up by adding text and then "capped off" by having their
106
  /// split information set by calling [handleSplit]. Once the latter has been
107
  /// called, no more text should be added to the chunk since it would appear
108
  /// *before* the split.
1096
  bool get canAddText => _rule == null;
110
111
  /// The [Rule] that controls when a split should occur after this chunk.
112
  ///
113
  /// Multiple splits may share a [Rule].
1146
  Rule get rule => _rule;
115
  Rule _rule;
116
117
  /// Whether or not an extra blank line should be output after this chunk if
118
  /// it's split.
119
  ///
120
  /// Internally, this can be either `true`, `false`, or `null`. The latter is
121
  /// an indeterminate state that lets later modifications to the split decide
122
  /// whether it should be double or not.
123
  ///
124
  /// However, this getter does not expose that. It will return `false` if the
125
  /// chunk is still indeterminate.
1266
  bool get isDouble => _isDouble != null ? _isDouble : false;
127
  bool _isDouble;
128
129
  /// If `true`, then the line after this chunk should always be at column
130
  /// zero regardless of any indentation or expression nesting.
131
  ///
132
  /// Used for multi-line strings and commented out code.
1334
  bool get flushLeft => _flushLeft;
134
  bool _flushLeft = false;
135
136
  /// If `true`, then the line after this chunk and its contained block should
137
  /// be flush left.
1382
  bool get flushLeftAfter {
1394
    if (!isBlock) return _flushLeft;
140
1418
    return _block.chunks.last.flushLeftAfter;
142
  }
143
144
  /// Whether this chunk should append an extra space if it does not split.
145
  ///
146
  /// This is `true`, for example, in a chunk that ends with a ",".
1476
  bool get spaceWhenUnsplit => _spaceWhenUnsplit;
148
  bool _spaceWhenUnsplit = false;
149
150
  /// Whether this chunk marks the end of a range of chunks that can be line
151
  /// split independently of the following chunks.
1523
  bool get canDivide {
153
    // Have to call markDivide() before accessing this.
1543
    assert(_canDivide != null);
1553
    return _canDivide;
156
  }
157
158
  bool _canDivide;
159
160
  /// The number of characters in this chunk when unsplit.
16110
  int get length => _text.length + (spaceWhenUnsplit ? 1 : 0);
162
163
  /// The unsplit length of all of this chunk's block contents.
164
  ///
165
  /// Does not include this chunk's own length, just the length of its child
166
  /// block chunks (recursively).
1673
  int get unsplitBlockLength {
1683
    if (_block == null) return 0;
169
170
    var length = 0;
1716
    for (var chunk in _block.chunks) {
1728
      length += chunk.length + chunk.unsplitBlockLength;
173
    }
174
175
    return length;
176
  }
177
178
  /// The [Span]s that contain this chunk.
179
  final spans = <Span>[];
180
181
  /// Creates a new chunk starting with [_text].
1823
  Chunk(this._text);
183
184
  /// Discard the split for the chunk and put it back into the state where more
185
  /// text can be appended.
1862
  void allowText() {
1872
    _rule = null;
188
  }
189
190
  /// Append [text] to the end of the split's text.
1913
  void appendText(String text) {
1923
    assert(canAddText);
1936
    _text += text;
194
  }
195
196
  /// Finishes off this chunk with the given [rule] and split information.
197
  ///
198
  /// This may be called multiple times on the same split since the splits
199
  /// produced by walking the source and the splits coming from comments and
200
  /// preserved whitespace often overlap. When that happens, this has logic to
201
  /// combine that information into a single split.
2023
  void applySplit(Rule rule, int indent, NestingLevel nesting,
203
      {bool flushLeft, bool isDouble, bool space}) {
204
    if (flushLeft == null) flushLeft = false;
205
    if (space == null) space = false;
2063
    if (rule.isHardened) {
207
      // A hard split always wins.
2083
      _rule = rule;
2093
    } else if (_rule == null) {
210
      // If the chunk hasn't been initialized yet, just inherit the rule.
2113
      _rule = rule;
212
    }
213
214
    // Last split settings win.
2153
    _flushLeft = flushLeft;
2163
    _nesting = nesting;
2173
    _indent = indent;
218
2193
    _spaceWhenUnsplit = space;
220
221
    // Pin down the double state, if given and we haven't already.
2226
    if (_isDouble == null) _isDouble = isDouble;
223
  }
224
225
  /// Turns this chunk into one that can contain a block of child chunks.
2262
  void makeBlock(Chunk blockArgument) {
2272
    assert(_block == null);
2284
    _block = new ChunkBlock(blockArgument);
229
  }
230
231
  /// Returns `true` if the block body owned by this chunk should be expression
232
  /// indented given a set of rule values provided by [getValue].
2332
  bool indentBlock(int getValue(Rule rule)) {
2342
    if (_block == null) return false;
2354
    if (_block.argument == null) return false;
236
2373
    return _block.argument.rule
2387
        .isSplit(getValue(_block.argument.rule), _block.argument);
239
  }
240
241
  // Mark whether this chunk can divide the range of chunks.
2423
  void markDivide(canDivide) {
243
    // Should only do this once.
2443
    assert(_canDivide == null);
245
2463
    _canDivide = canDivide;
247
  }
248
2490
  String toString() {
2500
    var parts = [];
251
2520
    if (text.isNotEmpty) parts.add(text);
253
2540
    if (_indent != null) parts.add("indent:$_indent");
2550
    if (spaceWhenUnsplit == true) parts.add("space");
2560
    if (_isDouble == true) parts.add("double");
2570
    if (_flushLeft == true) parts.add("flush");
258
2590
    if (_rule == null) {
2600
      parts.add("(no split)");
261
    } else {
2620
      parts.add(rule.toString());
2630
      if (rule.isHardened) parts.add("(hard)");
264
2650
      if (_rule.constrainedRules.isNotEmpty) {
2660
        parts.add("-> ${_rule.constrainedRules.join(' ')}");
267
      }
268
    }
269
2700
    return parts.join(" ");
271
  }
272
}
273
274
/// The child chunks owned by a chunk that begins a "block" -- an actual block
275
/// statement, function expression, or collection literal.
276
class ChunkBlock {
277
  /// If this block is for a collection literal in an argument list, this will
278
  /// be the chunk preceding this literal argument.
279
  ///
280
  /// That chunk is owned by the argument list and if it splits, this collection
281
  /// may need extra expression-level indentation.
282
  final Chunk argument;
283
284
  /// The child chunks in this block.
285
  final List<Chunk> chunks = [];
286
2872
  ChunkBlock(this.argument);
288
}
289
290
/// Constants for the cost heuristics used to determine which set of splits is
291
/// most desirable.
292
class Cost {
293
  /// The cost of splitting after the `=>` in a lambda or arrow-bodied member.
294
  ///
295
  /// We make this zero because there is already a span around the entire body
296
  /// and we generally do prefer splitting after the `=>` over other places.
297
  static const arrow = 0;
298
299
  /// The default cost.
300
  ///
301
  /// This isn't zero because we want to ensure all splitting has *some* cost,
302
  /// otherwise, the formatter won't try to keep things on one line at all.
303
  /// Most splits and spans use this. Greater costs tend to come from a greater
304
  /// number of nested spans.
305
  static const normal = 1;
306
307
  /// Splitting after a "=".
308
  static const assign = 1;
309
310
  /// Splitting after a "=" when the right-hand side is a collection or cascade.
311
  static const assignBlock = 2;
312
313
  /// Splitting before the first argument when it happens to be a function
314
  /// expression with a block body.
315
  static const firstBlockArgument = 2;
316
317
  /// The series of positional arguments.
318
  static const positionalArguments = 2;
319
320
  /// Splitting inside the brackets of a list with only one element.
321
  static const singleElementList = 2;
322
323
  /// Splitting the internals of block arguments.
324
  ///
325
  /// Used to prefer splitting at the argument boundary over splitting the block
326
  /// contents.
327
  static const splitBlocks = 2;
328
329
  /// Splitting on the "." in a named constructor.
330
  static const constructorName = 4;
331
332
  /// Splitting a `[...]` index operator.
333
  static const index = 4;
334
335
  /// Splitting before a type argument or type parameter.
336
  static const typeArgument = 4;
337
338
  /// Split between a formal parameter name and its type.
339
  static const parameterType = 4;
340
}
341
342
/// The in-progress state for a [Span] that has been started but has not yet
343
/// been completed.
344
class OpenSpan {
345
  /// Index of the first chunk contained in this span.
3466
  int get start => _start;
347
  int _start;
348
349
  /// The cost applied when the span is split across multiple lines or `null`
350
  /// if the span is for a multisplit.
351
  final int cost;
352
3533
  OpenSpan(this._start, this.cost);
354
3550
  String toString() => "OpenSpan($start, \$$cost)";
356
}
357
358
/// Delimits a range of chunks that must end up on the same line to avoid an
359
/// additional cost.
360
///
361
/// These are used to encourage the line splitter to try to keep things
362
/// together, like parameter lists and binary operator expressions.
363
///
364
/// This is a wrapper around the cost so that spans have unique identities.
365
/// This way we can correctly avoid paying the cost multiple times if the same
366
/// span is split by multiple chunks.
367
class Span extends FastHash {
368
  /// The cost applied when the span is split across multiple lines or `null`
369
  /// if the span is for a multisplit.
370
  final int cost;
371
3723
  Span(this.cost);
373
3740
  String toString() => "$id\$$cost";
375
}
376
377
/// A comment in the source, with a bit of information about the surrounding
378
/// whitespace.
379
class SourceComment extends Selection {
380
  /// The text of the comment, including `//`, `/*`, and `*/`.
381
  final String text;
382
383
  /// The number of newlines between the comment or token preceding this comment
384
  /// and the beginning of this one.
385
  ///
386
  /// Will be zero if the comment is a trailing one.
387
  int linesBefore;
388
389
  /// Whether this comment is a line comment.
390
  final bool isLineComment;
391
392
  /// Whether this comment starts at column one in the source.
393
  ///
394
  /// Comments that start at the start of the line will not be indented in the
395
  /// output. This way, commented out chunks of code do not get erroneously
396
  /// re-indented.
397
  final bool flushLeft;
398
399
  /// Whether this comment is an inline block comment.
4008
  bool get isInline => linesBefore == 0 && !isLineComment;
401
4022
  SourceComment(this.text, this.linesBefore,
403
      {this.isLineComment, this.flushLeft});
404
}