| 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 |