OLD | NEW |
| (Empty) |
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 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 int get selectionEnd => _selectionEnd; | |
25 int _selectionEnd; | |
26 | |
27 /// Sets [selectionStart] to be [start] characters into [text]. | |
28 void startSelection(int start) { | |
29 _selectionStart = start; | |
30 } | |
31 | |
32 /// Sets [selectionStart] to be [fromEnd] characters from the end of [text]. | |
33 void startSelectionFromEnd(int fromEnd) { | |
34 _selectionStart = text.length - fromEnd; | |
35 } | |
36 | |
37 /// Sets [selectionEnd] to be [end] characters into [text]. | |
38 void endSelection(int end) { | |
39 _selectionEnd = end; | |
40 } | |
41 | |
42 /// Sets [selectionEnd] to be [fromEnd] characters from the end of [text]. | |
43 void endSelectionFromEnd(int fromEnd) { | |
44 _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 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 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 NestingLevel get nesting => _nesting; | |
93 NestingLevel _nesting; | |
94 | |
95 /// If this chunk marks the beginning of a block, these are the chunks | |
96 /// contained in the block. | |
97 final blockChunks = <Chunk>[]; | |
98 | |
99 /// Whether it's valid to add more text to this chunk or not. | |
100 /// | |
101 /// Chunks are built up by adding text and then "capped off" by having their | |
102 /// split information set by calling [handleSplit]. Once the latter has been | |
103 /// called, no more text should be added to the chunk since it would appear | |
104 /// *before* the split. | |
105 bool get canAddText => _rule == null; | |
106 | |
107 /// The [Rule] that controls when a split should occur after this chunk. | |
108 /// | |
109 /// Multiple splits may share a [Rule]. | |
110 Rule get rule => _rule; | |
111 Rule _rule; | |
112 | |
113 /// Whether this chunk is always followed by a newline or whether the line | |
114 /// splitter may choose to keep the next chunk on the same line. | |
115 bool get isHardSplit => _rule is HardSplitRule; | |
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 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 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 bool get flushLeftAfter { | |
139 if (blockChunks.isEmpty) return _flushLeft; | |
140 | |
141 return blockChunks.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 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 bool get canDivide { | |
153 // Have to call markDivide() before accessing this. | |
154 assert(_canDivide != null); | |
155 return _canDivide; | |
156 } | |
157 | |
158 bool _canDivide; | |
159 | |
160 /// The number of characters in this chunk when unsplit. | |
161 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 int get unsplitBlockLength { | |
168 var length = 0; | |
169 for (var chunk in blockChunks) { | |
170 length += chunk.length + chunk.unsplitBlockLength; | |
171 } | |
172 | |
173 return length; | |
174 } | |
175 | |
176 /// The [Span]s that contain this chunk. | |
177 final spans = <Span>[]; | |
178 | |
179 /// Creates a new chunk starting with [_text]. | |
180 Chunk(this._text); | |
181 | |
182 /// Discard the split for the chunk and put it back into the state where more | |
183 /// text can be appended. | |
184 void allowText() { | |
185 _rule = null; | |
186 } | |
187 | |
188 /// Append [text] to the end of the split's text. | |
189 void appendText(String text) { | |
190 assert(canAddText); | |
191 _text += text; | |
192 } | |
193 | |
194 /// Forces this soft split to become a hard split. | |
195 /// | |
196 /// This is called on the soft splits owned by a rule that decides to harden | |
197 /// when it finds out another hard split occurs within its chunks. | |
198 void harden() { | |
199 _rule = new HardSplitRule(); | |
200 spans.clear(); | |
201 } | |
202 | |
203 /// Finishes off this chunk with the given [rule] and split information. | |
204 /// | |
205 /// This may be called multiple times on the same split since the splits | |
206 /// produced by walking the source and the splits coming from comments and | |
207 /// preserved whitespace often overlap. When that happens, this has logic to | |
208 /// combine that information into a single split. | |
209 void applySplit(Rule rule, int indent, NestingLevel nesting, | |
210 {bool flushLeft, bool spaceWhenUnsplit, bool isDouble}) { | |
211 if (flushLeft == null) flushLeft = false; | |
212 if (spaceWhenUnsplit == null) spaceWhenUnsplit = false; | |
213 if (isHardSplit || rule is HardSplitRule) { | |
214 // A hard split always wins. | |
215 _rule = rule; | |
216 } else if (_rule == null) { | |
217 // If the chunk hasn't been initialized yet, just inherit the rule. | |
218 _rule = rule; | |
219 } | |
220 | |
221 // Last split settings win. | |
222 _flushLeft = flushLeft; | |
223 _nesting = nesting; | |
224 _indent = indent; | |
225 | |
226 _spaceWhenUnsplit = spaceWhenUnsplit; | |
227 | |
228 // Pin down the double state, if given and we haven't already. | |
229 if (_isDouble == null) _isDouble = isDouble; | |
230 } | |
231 | |
232 // Mark whether this chunk can divide the range of chunks. | |
233 void markDivide(canDivide) { | |
234 // Should only do this once. | |
235 assert(_canDivide == null); | |
236 | |
237 _canDivide = canDivide; | |
238 } | |
239 | |
240 String toString() { | |
241 var parts = []; | |
242 | |
243 if (text.isNotEmpty) parts.add(text); | |
244 | |
245 if (_indent != null) parts.add("indent:$_indent"); | |
246 if (spaceWhenUnsplit) parts.add("space"); | |
247 if (_isDouble) parts.add("double"); | |
248 if (_flushLeft) parts.add("flush"); | |
249 | |
250 if (_rule == null) { | |
251 parts.add("(no split)"); | |
252 } else if (isHardSplit) { | |
253 parts.add("hard"); | |
254 } else { | |
255 parts.add(rule.toString()); | |
256 | |
257 if (_rule.outerRules.isNotEmpty) { | |
258 parts.add("-> ${_rule.outerRules.join(' ')}"); | |
259 } | |
260 } | |
261 | |
262 return parts.join(" "); | |
263 } | |
264 } | |
265 | |
266 /// Constants for the cost heuristics used to determine which set of splits is | |
267 /// most desirable. | |
268 class Cost { | |
269 /// The cost of splitting after the `=>` in a lambda or arrow-bodied member. | |
270 /// | |
271 /// We make this zero because there is already a span around the entire body | |
272 /// and we generally do prefer splitting after the `=>` over other places. | |
273 static const arrow = 0; | |
274 | |
275 /// The default cost. | |
276 /// | |
277 /// This isn't zero because we want to ensure all splitting has *some* cost, | |
278 /// otherwise, the formatter won't try to keep things on one line at all. | |
279 /// Most splits and spans use this. Greater costs tend to come from a greater | |
280 /// number of nested spans. | |
281 static const normal = 1; | |
282 | |
283 /// Splitting after a "=" both for assignment and initialization. | |
284 static const assignment = 2; | |
285 | |
286 /// Splitting before the first argument when it happens to be a function | |
287 /// expression with a block body. | |
288 static const firstBlockArgument = 2; | |
289 | |
290 /// The series of positional arguments. | |
291 static const positionalArguments = 2; | |
292 | |
293 /// Splitting inside the brackets of a list with only one element. | |
294 static const singleElementList = 2; | |
295 | |
296 /// Splitting the internals of collection literal arguments. | |
297 /// | |
298 /// Used to prefer splitting at the argument boundary over splitting the | |
299 /// collection contents. | |
300 static const splitCollections = 2; | |
301 | |
302 /// Splitting before a type argument or type parameter. | |
303 static const typeArgument = 4; | |
304 } | |
305 | |
306 /// The in-progress state for a [Span] that has been started but has not yet | |
307 /// been completed. | |
308 class OpenSpan { | |
309 /// Index of the first chunk contained in this span. | |
310 int get start => _start; | |
311 int _start; | |
312 | |
313 /// The cost applied when the span is split across multiple lines or `null` | |
314 /// if the span is for a multisplit. | |
315 final int cost; | |
316 | |
317 OpenSpan(this._start, this.cost); | |
318 | |
319 String toString() => "OpenSpan($start, \$$cost)"; | |
320 } | |
321 | |
322 /// Delimits a range of chunks that must end up on the same line to avoid an | |
323 /// additional cost. | |
324 /// | |
325 /// These are used to encourage the line splitter to try to keep things | |
326 /// together, like parameter lists and binary operator expressions. | |
327 /// | |
328 /// This is a wrapper around the cost so that spans have unique identities. | |
329 /// This way we can correctly avoid paying the cost multiple times if the same | |
330 /// span is split by multiple chunks. | |
331 class Span extends FastHash { | |
332 /// The cost applied when the span is split across multiple lines or `null` | |
333 /// if the span is for a multisplit. | |
334 final int cost; | |
335 | |
336 Span(this.cost); | |
337 | |
338 String toString() => "$id\$$cost"; | |
339 } | |
340 | |
341 /// A comment in the source, with a bit of information about the surrounding | |
342 /// whitespace. | |
343 class SourceComment extends Selection { | |
344 /// The text of the comment, including `//`, `/*`, and `*/`. | |
345 final String text; | |
346 | |
347 /// The number of newlines between the comment or token preceding this comment | |
348 /// and the beginning of this one. | |
349 /// | |
350 /// Will be zero if the comment is a trailing one. | |
351 int linesBefore; | |
352 | |
353 /// Whether this comment is a line comment. | |
354 final bool isLineComment; | |
355 | |
356 /// Whether this comment starts at column one in the source. | |
357 /// | |
358 /// Comments that start at the start of the line will not be indented in the | |
359 /// output. This way, commented out chunks of code do not get erroneously | |
360 /// re-indented. | |
361 final bool flushLeft; | |
362 | |
363 /// Whether this comment is an inline block comment. | |
364 bool get isInline => linesBefore == 0 && !isLineComment; | |
365 | |
366 SourceComment(this.text, this.linesBefore, | |
367 {this.isLineComment, this.flushLeft}); | |
368 } | |
OLD | NEW |