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.
|
19 | 6 | 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.
|
24 | 6 | int get selectionEnd => _selectionEnd;
|
25 | | int _selectionEnd;
|
26 | |
|
27 | | /// Sets [selectionStart] to be [start] characters into [text].
|
28 | 1 | void startSelection(int start) {
|
29 | 1 | _selectionStart = start;
|
30 | | }
|
31 | |
|
32 | | /// Sets [selectionStart] to be [fromEnd] characters from the end of [text].
|
33 | 1 | void startSelectionFromEnd(int fromEnd) {
|
34 | 4 | _selectionStart = text.length - fromEnd;
|
35 | | }
|
36 | |
|
37 | | /// Sets [selectionEnd] to be [end] characters into [text].
|
38 | 1 | void endSelection(int end) {
|
39 | 1 | _selectionEnd = end;
|
40 | | }
|
41 | |
|
42 | | /// Sets [selectionEnd] to be [fromEnd] characters from the end of [text].
|
43 | 1 | void endSelectionFromEnd(int fromEnd) {
|
44 | 4 | _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.
|
71 | 6 | 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.
|
79 | 4 | 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));
|
92 | 4 | 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.
|
97 | 4 | ChunkBlock get block => _block;
|
98 | | ChunkBlock _block;
|
99 | |
|
100 | | /// Whether this chunk has a [block].
|
101 | 6 | 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.
|
109 | 6 | 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].
|
114 | 6 | 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.
|
126 | 6 | 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.
|
133 | 4 | 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.
|
138 | 2 | bool get flushLeftAfter {
|
139 | 4 | if (!isBlock) return _flushLeft;
|
140 | |
|
141 | 8 | 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 ",".
|
147 | 6 | 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.
|
152 | 3 | bool get canDivide {
|
153 | | // Have to call markDivide() before accessing this.
|
154 | 3 | assert(_canDivide != null);
|
155 | 3 | return _canDivide;
|
156 | | }
|
157 | |
|
158 | | bool _canDivide;
|
159 | |
|
160 | | /// The number of characters in this chunk when unsplit.
|
161 | 10 | 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).
|
167 | 3 | int get unsplitBlockLength {
|
168 | 3 | if (_block == null) return 0;
|
169 | |
|
170 | | var length = 0;
|
171 | 6 | for (var chunk in _block.chunks) {
|
172 | 8 | 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].
|
182 | 3 | 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.
|
186 | 2 | void allowText() {
|
187 | 2 | _rule = null;
|
188 | | }
|
189 | |
|
190 | | /// Append [text] to the end of the split's text.
|
191 | 3 | void appendText(String text) {
|
192 | 3 | assert(canAddText);
|
193 | 6 | _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.
|
202 | 3 | 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;
|
206 | 3 | if (rule.isHardened) {
|
207 | | // A hard split always wins.
|
208 | 3 | _rule = rule;
|
209 | 3 | } else if (_rule == null) {
|
210 | | // If the chunk hasn't been initialized yet, just inherit the rule.
|
211 | 3 | _rule = rule;
|
212 | | }
|
213 | |
|
214 | | // Last split settings win.
|
215 | 3 | _flushLeft = flushLeft;
|
216 | 3 | _nesting = nesting;
|
217 | 3 | _indent = indent;
|
218 | |
|
219 | 3 | _spaceWhenUnsplit = space;
|
220 | |
|
221 | | // Pin down the double state, if given and we haven't already.
|
222 | 6 | if (_isDouble == null) _isDouble = isDouble;
|
223 | | }
|
224 | |
|
225 | | /// Turns this chunk into one that can contain a block of child chunks.
|
226 | 2 | void makeBlock(Chunk blockArgument) {
|
227 | 2 | assert(_block == null);
|
228 | 4 | _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].
|
233 | 2 | bool indentBlock(int getValue(Rule rule)) {
|
234 | 2 | if (_block == null) return false;
|
235 | 4 | if (_block.argument == null) return false;
|
236 | |
|
237 | 3 | return _block.argument.rule
|
238 | 7 | .isSplit(getValue(_block.argument.rule), _block.argument);
|
239 | | }
|
240 | |
|
241 | | // Mark whether this chunk can divide the range of chunks.
|
242 | 3 | void markDivide(canDivide) {
|
243 | | // Should only do this once.
|
244 | 3 | assert(_canDivide == null);
|
245 | |
|
246 | 3 | _canDivide = canDivide;
|
247 | | }
|
248 | |
|
249 | 0 | String toString() {
|
250 | 0 | var parts = [];
|
251 | |
|
252 | 0 | if (text.isNotEmpty) parts.add(text);
|
253 | |
|
254 | 0 | if (_indent != null) parts.add("indent:$_indent");
|
255 | 0 | if (spaceWhenUnsplit == true) parts.add("space");
|
256 | 0 | if (_isDouble == true) parts.add("double");
|
257 | 0 | if (_flushLeft == true) parts.add("flush");
|
258 | |
|
259 | 0 | if (_rule == null) {
|
260 | 0 | parts.add("(no split)");
|
261 | | } else {
|
262 | 0 | parts.add(rule.toString());
|
263 | 0 | if (rule.isHardened) parts.add("(hard)");
|
264 | |
|
265 | 0 | if (_rule.constrainedRules.isNotEmpty) {
|
266 | 0 | parts.add("-> ${_rule.constrainedRules.join(' ')}");
|
267 | | }
|
268 | | }
|
269 | |
|
270 | 0 | 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 | |
|
287 | 2 | 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.
|
346 | 6 | 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 | |
|
353 | 3 | OpenSpan(this._start, this.cost);
|
354 | |
|
355 | 0 | 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 | |
|
372 | 3 | Span(this.cost);
|
373 | |
|
374 | 0 | 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.
|
400 | 8 | bool get isInline => linesBefore == 0 && !isLineComment;
|
401 | |
|
402 | 2 | SourceComment(this.text, this.linesBefore,
|
403 | | {this.isLineComment, this.flushLeft});
|
404 | | }
|