| 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_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_level.dart'; | |
| 12 import 'nesting_builder.dart'; | |
| 13 import 'rule/rule.dart'; | |
| 14 import 'source_code.dart'; | |
| 15 import 'whitespace.dart'; | |
| 16 | |
| 17 /// Takes the incremental serialized output of [SourceVisitor]--the source text | |
| 18 /// along with any comments and preserved whitespace--and produces a coherent | |
| 19 /// tree of [Chunk]s which can then be split into physical lines. | |
| 20 /// | |
| 21 /// Keeps track of leading indentation, expression nesting, and all of the hairy | |
| 22 /// code required to seamlessly integrate existing comments into the pure | |
| 23 /// output produced by [SourceVisitor]. | |
| 24 class ChunkBuilder { | |
| 25 final DartFormatter _formatter; | |
| 26 | |
| 27 /// The builder for the code surrounding the block that this writer is for, or | |
| 28 /// `null` if this is writing the top-level code. | |
| 29 final ChunkBuilder _parent; | |
| 30 | |
| 31 final SourceCode _source; | |
| 32 | |
| 33 final List<Chunk> _chunks; | |
| 34 | |
| 35 /// The whitespace that should be written to [_chunks] before the next | |
| 36 /// non-whitespace token. | |
| 37 /// | |
| 38 /// This ensures that changes to indentation and nesting also apply to the | |
| 39 /// most recent split, even if the visitor "creates" the split before changing | |
| 40 /// indentation or nesting. | |
| 41 Whitespace _pendingWhitespace = Whitespace.none; | |
| 42 | |
| 43 /// The nested stack of rules that are currently in use. | |
| 44 /// | |
| 45 /// New chunks are implicitly split by the innermost rule when the chunk is | |
| 46 /// ended. | |
| 47 final _rules = <Rule>[]; | |
| 48 | |
| 49 /// The set of rules known to contain hard splits that will in turn force | |
| 50 /// these rules to harden. | |
| 51 /// | |
| 52 /// This is accumulated lazily while chunks are being built. Then, once they | |
| 53 /// are all done, the rules are all hardened. We do this later because some | |
| 54 /// rules may not have all of their constraints fully wired up until after | |
| 55 /// the hard split appears. For example, a hard split in a positional | |
| 56 /// argument list needs to force the named arguments to split too, but we | |
| 57 /// don't create that rule until after the positional arguments are done. | |
| 58 final _hardSplitRules = new Set<Rule>(); | |
| 59 | |
| 60 /// The list of rules that are waiting until the next whitespace has been | |
| 61 /// written before they start. | |
| 62 final _lazyRules = <Rule>[]; | |
| 63 | |
| 64 /// The indexes of the chunks owned by each rule (except for hard splits). | |
| 65 final _ruleChunks = <Rule, List<int>>{}; | |
| 66 | |
| 67 /// The nested stack of spans that are currently being written. | |
| 68 final _openSpans = <OpenSpan>[]; | |
| 69 | |
| 70 /// The current state. | |
| 71 final _nesting = new NestingBuilder(); | |
| 72 | |
| 73 /// The stack of nesting levels where block arguments may start. | |
| 74 /// | |
| 75 /// A block argument's contents will nest at the last level in this stack. | |
| 76 final _blockArgumentNesting = <NestingLevel>[]; | |
| 77 | |
| 78 /// The index of the "current" chunk being written. | |
| 79 /// | |
| 80 /// If the last chunk is still being appended to, this is its index. | |
| 81 /// Otherwise, it is the index of the next chunk which will be created. | |
| 82 int get _currentChunkIndex { | |
| 83 if (_chunks.isEmpty) return 0; | |
| 84 if (_chunks.last.canAddText) return _chunks.length - 1; | |
| 85 return _chunks.length; | |
| 86 } | |
| 87 | |
| 88 /// Whether or not there was a leading comment that was flush left before any | |
| 89 /// other content was written. | |
| 90 /// | |
| 91 /// This is used when writing child blocks to make the parent chunk have the | |
| 92 /// right flush left value when a comment appears immediately inside the | |
| 93 /// block. | |
| 94 bool _firstFlushLeft = false; | |
| 95 | |
| 96 /// Whether there is pending whitespace that depends on the number of | |
| 97 /// newlines in the source. | |
| 98 /// | |
| 99 /// This is used to avoid calculating the newlines between tokens unless | |
| 100 /// actually needed since doing so is slow when done between every single | |
| 101 /// token pair. | |
| 102 bool get needsToPreserveNewlines => | |
| 103 _pendingWhitespace == Whitespace.oneOrTwoNewlines || | |
| 104 _pendingWhitespace == Whitespace.spaceOrNewline || | |
| 105 _pendingWhitespace == Whitespace.splitOrNewline; | |
| 106 | |
| 107 /// The number of characters of code that can fit in a single line. | |
| 108 int get pageWidth => _formatter.pageWidth; | |
| 109 | |
| 110 /// The current innermost rule. | |
| 111 Rule get rule => _rules.last; | |
| 112 | |
| 113 ChunkBuilder(this._formatter, this._source) | |
| 114 : _parent = null, | |
| 115 _chunks = [] { | |
| 116 indent(_formatter.indent); | |
| 117 startBlockArgumentNesting(); | |
| 118 } | |
| 119 | |
| 120 ChunkBuilder._(this._parent, this._formatter, this._source, this._chunks) { | |
| 121 startBlockArgumentNesting(); | |
| 122 } | |
| 123 | |
| 124 /// Writes [string], the text for a single token, to the output. | |
| 125 /// | |
| 126 /// By default, this also implicitly adds one level of nesting if we aren't | |
| 127 /// currently nested at all. We do this here so that if a comment appears | |
| 128 /// after any token within a statement or top-level form and that comment | |
| 129 /// leads to splitting, we correctly nest. Even pathological cases like: | |
| 130 /// | |
| 131 /// | |
| 132 /// import // comment | |
| 133 /// "this_gets_nested.dart"; | |
| 134 /// | |
| 135 /// If we didn't do this here, we'd have to call [nestExpression] after the | |
| 136 /// first token of practically every grammar production. | |
| 137 void write(String string) { | |
| 138 _emitPendingWhitespace(); | |
| 139 _writeText(string); | |
| 140 | |
| 141 _lazyRules.forEach(startRule); | |
| 142 _lazyRules.clear(); | |
| 143 | |
| 144 _nesting.commitNesting(); | |
| 145 } | |
| 146 | |
| 147 /// Writes a [WhitespaceChunk] of [type]. | |
| 148 void writeWhitespace(Whitespace type) { | |
| 149 _pendingWhitespace = type; | |
| 150 } | |
| 151 | |
| 152 /// Write a split owned by the current innermost rule. | |
| 153 /// | |
| 154 /// If [nesting] is given, uses that. Otherwise, uses the current nesting | |
| 155 /// level. If unsplit, it expands to a space if [space] is `true`. | |
| 156 /// | |
| 157 /// If [flushLeft] is `true`, then forces the next line to start at column | |
| 158 /// one regardless of any indentation or nesting. | |
| 159 /// | |
| 160 /// If [isDouble] is passed, forces the split to either be a single or double | |
| 161 /// newline. Otherwise, leaves it indeterminate. | |
| 162 Chunk split({bool space, bool isDouble, bool flushLeft}) => | |
| 163 _writeSplit(_rules.last, null, | |
| 164 flushLeft: flushLeft, isDouble: isDouble, spaceWhenUnsplit: space); | |
| 165 | |
| 166 /// Write a split owned by the current innermost rule. | |
| 167 /// | |
| 168 /// Unlike [split()], this ignores any current expression nesting. It always | |
| 169 /// indents the next line at the statement level. | |
| 170 Chunk blockSplit({bool space, bool isDouble}) => | |
| 171 _writeSplit(_rules.last, _nesting.blockNesting, | |
| 172 isDouble: isDouble, spaceWhenUnsplit: space); | |
| 173 | |
| 174 /// Outputs the series of [comments] and associated whitespace that appear | |
| 175 /// before [token] (which is not written by this). | |
| 176 /// | |
| 177 /// The list contains each comment as it appeared in the source between the | |
| 178 /// last token written and the next one that's about to be written. | |
| 179 /// | |
| 180 /// [linesBeforeToken] is the number of lines between the last comment (or | |
| 181 /// previous token if there are no comments) and the next token. | |
| 182 void writeComments( | |
| 183 List<SourceComment> comments, int linesBeforeToken, String token) { | |
| 184 // Corner case: if we require a blank line, but there exists one between | |
| 185 // some of the comments, or after the last one, then we don't need to | |
| 186 // enforce one before the first comment. Example: | |
| 187 // | |
| 188 // library foo; | |
| 189 // // comment | |
| 190 // | |
| 191 // class Bar {} | |
| 192 // | |
| 193 // Normally, a blank line is required after `library`, but since there is | |
| 194 // one after the comment, we don't need one before it. This is mainly so | |
| 195 // that commented out directives stick with their preceding group. | |
| 196 if (_pendingWhitespace == Whitespace.twoNewlines && | |
| 197 comments.first.linesBefore < 2) { | |
| 198 if (linesBeforeToken > 1) { | |
| 199 _pendingWhitespace = Whitespace.newline; | |
| 200 } else { | |
| 201 for (var i = 1; i < comments.length; i++) { | |
| 202 if (comments[i].linesBefore > 1) { | |
| 203 _pendingWhitespace = Whitespace.newline; | |
| 204 break; | |
| 205 } | |
| 206 } | |
| 207 } | |
| 208 } | |
| 209 | |
| 210 // Corner case: if the comments are completely inline (i.e. just a series | |
| 211 // of block comments with no newlines before, after, or between them), then | |
| 212 // they will eat any pending newlines. Make sure that doesn't happen by | |
| 213 // putting the pending whitespace before the first comment and moving them | |
| 214 // to their own line. Turns this: | |
| 215 // | |
| 216 // library foo; /* a */ /* b */ import 'a.dart'; | |
| 217 // | |
| 218 // into: | |
| 219 // | |
| 220 // library foo; | |
| 221 // | |
| 222 // /* a */ /* b */ | |
| 223 // import 'a.dart'; | |
| 224 if (linesBeforeToken == 0 && | |
| 225 comments.every((comment) => comment.isInline)) { | |
| 226 if (_pendingWhitespace.minimumLines > 0) { | |
| 227 comments.first.linesBefore = _pendingWhitespace.minimumLines; | |
| 228 linesBeforeToken = 1; | |
| 229 } | |
| 230 } | |
| 231 | |
| 232 // Write each comment and the whitespace between them. | |
| 233 for (var i = 0; i < comments.length; i++) { | |
| 234 var comment = comments[i]; | |
| 235 | |
| 236 preserveNewlines(comment.linesBefore); | |
| 237 | |
| 238 // Don't emit a space because we'll handle it below. If we emit it here, | |
| 239 // we may get a trailing space if the comment needs a line before it. | |
| 240 if (_pendingWhitespace == Whitespace.space) { | |
| 241 _pendingWhitespace = Whitespace.none; | |
| 242 } | |
| 243 _emitPendingWhitespace(); | |
| 244 | |
| 245 if (comment.linesBefore == 0) { | |
| 246 // If we're sitting on a split, move the comment before it to adhere it | |
| 247 // to the preceding text. | |
| 248 if (_shouldMoveCommentBeforeSplit(comment.text)) { | |
| 249 _chunks.last.allowText(); | |
| 250 } | |
| 251 | |
| 252 // The comment follows other text, so we need to decide if it gets a | |
| 253 // space before it or not. | |
| 254 if (_needsSpaceBeforeComment(isLineComment: comment.isLineComment)) { | |
| 255 _writeText(" "); | |
| 256 } | |
| 257 } else { | |
| 258 // The comment starts a line, so make sure it stays on its own line. | |
| 259 _writeHardSplit( | |
| 260 nest: true, | |
| 261 flushLeft: comment.flushLeft, | |
| 262 double: comment.linesBefore > 1); | |
| 263 } | |
| 264 | |
| 265 _writeText(comment.text); | |
| 266 | |
| 267 if (comment.selectionStart != null) { | |
| 268 startSelectionFromEnd(comment.text.length - comment.selectionStart); | |
| 269 } | |
| 270 | |
| 271 if (comment.selectionEnd != null) { | |
| 272 endSelectionFromEnd(comment.text.length - comment.selectionEnd); | |
| 273 } | |
| 274 | |
| 275 // Make sure there is at least one newline after a line comment and allow | |
| 276 // one or two after a block comment that has nothing after it. | |
| 277 var linesAfter; | |
| 278 if (i < comments.length - 1) { | |
| 279 linesAfter = comments[i + 1].linesBefore; | |
| 280 } else { | |
| 281 linesAfter = linesBeforeToken; | |
| 282 | |
| 283 // Always force a newline after multi-line block comments. Prevents | |
| 284 // mistakes like: | |
| 285 // | |
| 286 // /** | |
| 287 // * Some doc comment. | |
| 288 // */ someFunction() { ... } | |
| 289 if (linesAfter == 0 && comments.last.text.contains("\n")) { | |
| 290 linesAfter = 1; | |
| 291 } | |
| 292 } | |
| 293 | |
| 294 if (linesAfter > 0) _writeHardSplit(nest: true, double: linesAfter > 1); | |
| 295 } | |
| 296 | |
| 297 // If the comment has text following it (aside from a grouping character), | |
| 298 // it needs a trailing space. | |
| 299 if (_needsSpaceAfterLastComment(comments, token)) { | |
| 300 _pendingWhitespace = Whitespace.space; | |
| 301 } | |
| 302 | |
| 303 preserveNewlines(linesBeforeToken); | |
| 304 } | |
| 305 | |
| 306 /// If the current pending whitespace allows some source discretion, pins | |
| 307 /// that down given that the source contains [numLines] newlines at that | |
| 308 /// point. | |
| 309 void preserveNewlines(int numLines) { | |
| 310 // If we didn't know how many newlines the user authored between the last | |
| 311 // token and this one, now we do. | |
| 312 switch (_pendingWhitespace) { | |
| 313 case Whitespace.spaceOrNewline: | |
| 314 if (numLines > 0) { | |
| 315 _pendingWhitespace = Whitespace.nestedNewline; | |
| 316 } else { | |
| 317 _pendingWhitespace = Whitespace.space; | |
| 318 } | |
| 319 break; | |
| 320 | |
| 321 case Whitespace.splitOrNewline: | |
| 322 if (numLines > 0) { | |
| 323 _pendingWhitespace = Whitespace.nestedNewline; | |
| 324 } else { | |
| 325 _pendingWhitespace = Whitespace.none; | |
| 326 split(space: true); | |
| 327 } | |
| 328 break; | |
| 329 | |
| 330 case Whitespace.oneOrTwoNewlines: | |
| 331 if (numLines > 1) { | |
| 332 _pendingWhitespace = Whitespace.twoNewlines; | |
| 333 } else { | |
| 334 _pendingWhitespace = Whitespace.newline; | |
| 335 } | |
| 336 break; | |
| 337 } | |
| 338 } | |
| 339 | |
| 340 /// Creates a new indentation level [spaces] deeper than the current one. | |
| 341 /// | |
| 342 /// If omitted, [spaces] defaults to [Indent.block]. | |
| 343 void indent([int spaces]) { | |
| 344 _nesting.indent(spaces); | |
| 345 } | |
| 346 | |
| 347 /// Discards the most recent indentation level. | |
| 348 void unindent() { | |
| 349 _nesting.unindent(); | |
| 350 } | |
| 351 | |
| 352 /// Starts a new span with [cost]. | |
| 353 /// | |
| 354 /// Each call to this needs a later matching call to [endSpan]. | |
| 355 void startSpan([int cost = Cost.normal]) { | |
| 356 _openSpans.add(new OpenSpan(_currentChunkIndex, cost)); | |
| 357 } | |
| 358 | |
| 359 /// Ends the innermost span. | |
| 360 void endSpan() { | |
| 361 var openSpan = _openSpans.removeLast(); | |
| 362 | |
| 363 // A span that just covers a single chunk can't be split anyway. | |
| 364 var end = _currentChunkIndex; | |
| 365 if (openSpan.start == end) return; | |
| 366 | |
| 367 // Add the span to every chunk that can split it. | |
| 368 var span = new Span(openSpan.cost); | |
| 369 for (var i = openSpan.start; i < end; i++) { | |
| 370 var chunk = _chunks[i]; | |
| 371 if (!chunk.isHardSplit) chunk.spans.add(span); | |
| 372 } | |
| 373 } | |
| 374 | |
| 375 /// Starts a new [Rule]. | |
| 376 /// | |
| 377 /// If omitted, defaults to a new [SimpleRule]. | |
| 378 void startRule([Rule rule]) { | |
| 379 if (rule == null) rule = new SimpleRule(); | |
| 380 | |
| 381 // See if any of the rules that contain this one care if it splits. | |
| 382 _rules.forEach((outer) => outer.contain(rule)); | |
| 383 _rules.add(rule); | |
| 384 } | |
| 385 | |
| 386 /// Starts a new [Rule] that comes into play *after* the next whitespace | |
| 387 /// (including comments) is written. | |
| 388 /// | |
| 389 /// This is used for binary operators who want to start a rule before the | |
| 390 /// first operand but not get forced to split if a comment appears before the | |
| 391 /// entire expression. | |
| 392 /// | |
| 393 /// If [rule] is omitted, defaults to a new [SimpleRule]. | |
| 394 void startLazyRule([Rule rule]) { | |
| 395 if (rule == null) rule = new SimpleRule(); | |
| 396 | |
| 397 _lazyRules.add(rule); | |
| 398 } | |
| 399 | |
| 400 /// Ends the innermost rule. | |
| 401 void endRule() { | |
| 402 _rules.removeLast(); | |
| 403 } | |
| 404 | |
| 405 /// Pre-emptively forces all of the current rules to become hard splits. | |
| 406 /// | |
| 407 /// This is called by [SourceVisitor] when it can determine that a rule will | |
| 408 /// will always be split. Turning it (and the surrounding rules) into hard | |
| 409 /// splits lets the writer break the output into smaller pieces for the line | |
| 410 /// splitter, which helps performance and avoids failing on very large input. | |
| 411 /// | |
| 412 /// In particular, it's easy for the visitor to know that collections with a | |
| 413 /// large number of items must split. Doing that early avoids crashing the | |
| 414 /// splitter when it tries to recurse on huge collection literals. | |
| 415 void forceRules() => _handleHardSplit(); | |
| 416 | |
| 417 /// Begins a new expression nesting level [indent] spaces deeper than the | |
| 418 /// current one if it splits. | |
| 419 /// | |
| 420 /// If [indent] is omitted, defaults to [Indent.expression]. If [now] is | |
| 421 /// `true`, commits the nesting change immediately instead of waiting until | |
| 422 /// after the next chunk of text is written. | |
| 423 void nestExpression({int indent, bool now}) { | |
| 424 if (now == null) now = false; | |
| 425 | |
| 426 _nesting.nest(indent); | |
| 427 if (now) _nesting.commitNesting(); | |
| 428 } | |
| 429 | |
| 430 /// Discards the most recent level of expression nesting. | |
| 431 /// | |
| 432 /// Expressions that are more nested will get increased indentation when split | |
| 433 /// if the previous line has a lower level of nesting. | |
| 434 void unnest() { | |
| 435 _nesting.unnest(); | |
| 436 } | |
| 437 | |
| 438 /// Marks the selection starting point as occurring [fromEnd] characters to | |
| 439 /// the left of the end of what's currently been written. | |
| 440 /// | |
| 441 /// It counts backwards from the end because this is called *after* the chunk | |
| 442 /// of text containing the selection has been output. | |
| 443 void startSelectionFromEnd(int fromEnd) { | |
| 444 assert(_chunks.isNotEmpty); | |
| 445 _chunks.last.startSelectionFromEnd(fromEnd); | |
| 446 } | |
| 447 | |
| 448 /// Marks the selection ending point as occurring [fromEnd] characters to the | |
| 449 /// left of the end of what's currently been written. | |
| 450 /// | |
| 451 /// It counts backwards from the end because this is called *after* the chunk | |
| 452 /// of text containing the selection has been output. | |
| 453 void endSelectionFromEnd(int fromEnd) { | |
| 454 assert(_chunks.isNotEmpty); | |
| 455 _chunks.last.endSelectionFromEnd(fromEnd); | |
| 456 } | |
| 457 | |
| 458 /// Captures the current nesting level as marking where subsequent block | |
| 459 /// arguments should start. | |
| 460 void startBlockArgumentNesting() { | |
| 461 _blockArgumentNesting.add(_nesting.currentNesting); | |
| 462 } | |
| 463 | |
| 464 /// Releases the last nesting level captured by [startBlockArgumentNesting]. | |
| 465 void endBlockArgumentNesting() { | |
| 466 _blockArgumentNesting.removeLast(); | |
| 467 } | |
| 468 | |
| 469 /// Starts a new block as a child of the current chunk. | |
| 470 /// | |
| 471 /// Nested blocks are handled using their own independent [LineWriter]. | |
| 472 ChunkBuilder startBlock() { | |
| 473 var builder = | |
| 474 new ChunkBuilder._(this, _formatter, _source, _chunks.last.blockChunks); | |
| 475 | |
| 476 // A block always starts off indented one level. | |
| 477 builder.indent(); | |
| 478 | |
| 479 return builder; | |
| 480 } | |
| 481 | |
| 482 /// Ends this [ChunkBuilder], which must have been created by [startBlock()]. | |
| 483 /// | |
| 484 /// Forces the chunk that owns the block to split if it can tell that the | |
| 485 /// block contents will always split. It does that by looking for hard splits | |
| 486 /// in the block. If [ignoredSplit] is given, that rule will be ignored | |
| 487 /// when determining if a block contains a hard split. If [forceSplit] is | |
| 488 /// `true`, the block is considered to always split. | |
| 489 /// | |
| 490 /// Returns the previous writer for the surrounding block. | |
| 491 ChunkBuilder endBlock(HardSplitRule ignoredSplit, {bool forceSplit}) { | |
| 492 _divideChunks(); | |
| 493 | |
| 494 // If we don't already know if the block is going to split, see if it | |
| 495 // contains any hard splits or is longer than a page. | |
| 496 if (!forceSplit) { | |
| 497 var length = 0; | |
| 498 for (var chunk in _chunks) { | |
| 499 length += chunk.length + chunk.unsplitBlockLength; | |
| 500 if (length > _formatter.pageWidth) { | |
| 501 forceSplit = true; | |
| 502 break; | |
| 503 } | |
| 504 | |
| 505 if (chunk.isHardSplit && chunk.rule != ignoredSplit) { | |
| 506 forceSplit = true; | |
| 507 break; | |
| 508 } | |
| 509 } | |
| 510 } | |
| 511 | |
| 512 // If there is a hard newline within the block, force the surrounding rule | |
| 513 // for it so that we apply that constraint. | |
| 514 if (forceSplit) _parent.forceRules(); | |
| 515 | |
| 516 // Write the split for the block contents themselves. | |
| 517 _parent._writeSplit(_parent._rules.last, _parent._blockArgumentNesting.last, | |
| 518 flushLeft: _firstFlushLeft); | |
| 519 return _parent; | |
| 520 } | |
| 521 | |
| 522 /// Finishes writing and returns a [SourceCode] containing the final output | |
| 523 /// and updated selection, if any. | |
| 524 SourceCode end() { | |
| 525 _writeHardSplit(); | |
| 526 _divideChunks(); | |
| 527 | |
| 528 if (debug.traceChunkBuilder) { | |
| 529 debug.log(debug.green("\nBuilt:")); | |
| 530 debug.dumpChunks(0, _chunks); | |
| 531 debug.log(); | |
| 532 } | |
| 533 | |
| 534 var writer = new LineWriter(_formatter, _chunks); | |
| 535 var result = writer.writeLines(_formatter.indent, | |
| 536 isCompilationUnit: _source.isCompilationUnit); | |
| 537 | |
| 538 var selectionStart; | |
| 539 var selectionLength; | |
| 540 if (_source.selectionStart != null) { | |
| 541 selectionStart = result.selectionStart; | |
| 542 var selectionEnd = result.selectionEnd; | |
| 543 | |
| 544 // If we haven't hit the beginning and/or end of the selection yet, they | |
| 545 // must be at the very end of the code. | |
| 546 if (selectionStart == null) selectionStart = writer.length; | |
| 547 if (selectionEnd == null) selectionEnd = writer.length; | |
| 548 | |
| 549 selectionLength = selectionEnd - selectionStart; | |
| 550 } | |
| 551 | |
| 552 return new SourceCode(result.text, | |
| 553 uri: _source.uri, | |
| 554 isCompilationUnit: _source.isCompilationUnit, | |
| 555 selectionStart: selectionStart, | |
| 556 selectionLength: selectionLength); | |
| 557 } | |
| 558 | |
| 559 /// Writes the current pending [Whitespace] to the output, if any. | |
| 560 /// | |
| 561 /// This should only be called after source lines have been preserved to turn | |
| 562 /// any ambiguous whitespace into a concrete choice. | |
| 563 void _emitPendingWhitespace() { | |
| 564 // Output any pending whitespace first now that we know it won't be | |
| 565 // trailing. | |
| 566 switch (_pendingWhitespace) { | |
| 567 case Whitespace.space: | |
| 568 _writeText(" "); | |
| 569 break; | |
| 570 | |
| 571 case Whitespace.newline: | |
| 572 _writeHardSplit(); | |
| 573 break; | |
| 574 | |
| 575 case Whitespace.nestedNewline: | |
| 576 _writeHardSplit(nest: true); | |
| 577 break; | |
| 578 | |
| 579 case Whitespace.newlineFlushLeft: | |
| 580 _writeHardSplit(nest: true, flushLeft: true); | |
| 581 break; | |
| 582 | |
| 583 case Whitespace.twoNewlines: | |
| 584 _writeHardSplit(double: true); | |
| 585 break; | |
| 586 | |
| 587 case Whitespace.spaceOrNewline: | |
| 588 case Whitespace.splitOrNewline: | |
| 589 case Whitespace.oneOrTwoNewlines: | |
| 590 // We should have pinned these down before getting here. | |
| 591 assert(false); | |
| 592 break; | |
| 593 } | |
| 594 | |
| 595 _pendingWhitespace = Whitespace.none; | |
| 596 } | |
| 597 | |
| 598 /// Returns `true` if the last chunk is a split that should be moved after the | |
| 599 /// comment that is about to be written. | |
| 600 bool _shouldMoveCommentBeforeSplit(String comment) { | |
| 601 // Not if there is nothing before it. | |
| 602 if (_chunks.isEmpty) return false; | |
| 603 | |
| 604 // Multi-line comments are always pushed to the next line. | |
| 605 if (comment.contains("\n")) return false; | |
| 606 | |
| 607 // If the text before the split is an open grouping character, we don't | |
| 608 // want to adhere the comment to that. | |
| 609 var text = _chunks.last.text; | |
| 610 return !text.endsWith("(") && !text.endsWith("[") && !text.endsWith("{"); | |
| 611 } | |
| 612 | |
| 613 /// Returns `true` if a space should be output between the end of the current | |
| 614 /// output and the subsequent comment which is about to be written. | |
| 615 /// | |
| 616 /// This is only called if the comment is trailing text in the unformatted | |
| 617 /// source. In most cases, a space will be output to separate the comment | |
| 618 /// from what precedes it. This returns false if: | |
| 619 /// | |
| 620 /// * This comment does begin the line in the output even if it didn't in | |
| 621 /// the source. | |
| 622 /// * The comment is a block comment immediately following a grouping | |
| 623 /// character (`(`, `[`, or `{`). This is to allow `foo(/* comment */)`, | |
| 624 /// et. al. | |
| 625 bool _needsSpaceBeforeComment({bool isLineComment}) { | |
| 626 // Not at the start of the file. | |
| 627 if (_chunks.isEmpty) return false; | |
| 628 | |
| 629 // Not at the start of a line. | |
| 630 if (!_chunks.last.canAddText) return false; | |
| 631 | |
| 632 var text = _chunks.last.text; | |
| 633 if (text.endsWith("\n")) return false; | |
| 634 | |
| 635 // Always put a space before line comments. | |
| 636 if (isLineComment) return true; | |
| 637 | |
| 638 // Block comments do not get a space if following a grouping character. | |
| 639 return !text.endsWith("(") && !text.endsWith("[") && !text.endsWith("{"); | |
| 640 } | |
| 641 | |
| 642 /// Returns `true` if a space should be output after the last comment which | |
| 643 /// was just written and the token that will be written. | |
| 644 bool _needsSpaceAfterLastComment(List<SourceComment> comments, String token) { | |
| 645 // Not if there are no comments. | |
| 646 if (comments.isEmpty) return false; | |
| 647 | |
| 648 // Not at the beginning of a line. | |
| 649 if (!_chunks.last.canAddText) return false; | |
| 650 | |
| 651 // Otherwise, it gets a space if the following token is not a delimiter or | |
| 652 // the empty string, for EOF. | |
| 653 return token != ")" && | |
| 654 token != "]" && | |
| 655 token != "}" && | |
| 656 token != "," && | |
| 657 token != ";" && | |
| 658 token != ""; | |
| 659 } | |
| 660 | |
| 661 /// Appends a hard split with the current indentation and nesting (the latter | |
| 662 /// only if [nest] is `true`). | |
| 663 /// | |
| 664 /// If [double] is `true` or `false`, forces a since or double line to be | |
| 665 /// output. Otherwise, it is left indeterminate. | |
| 666 /// | |
| 667 /// If [flushLeft] is `true`, then the split will always cause the next line | |
| 668 /// to be at column zero. Otherwise, it uses the normal indentation and | |
| 669 /// nesting behavior. | |
| 670 void _writeHardSplit({bool nest: false, bool double, bool flushLeft}) { | |
| 671 // A hard split overrides any other whitespace. | |
| 672 _pendingWhitespace = null; | |
| 673 _writeSplit(new HardSplitRule(), nest ? null : _nesting.blockNesting, | |
| 674 flushLeft: flushLeft, isDouble: double); | |
| 675 } | |
| 676 | |
| 677 /// Ends the current chunk (if any) with the given split information. | |
| 678 /// | |
| 679 /// Returns the chunk. | |
| 680 Chunk _writeSplit(Rule rule, NestingLevel nesting, | |
| 681 {bool flushLeft, bool isDouble, bool spaceWhenUnsplit}) { | |
| 682 if (_chunks.isEmpty) { | |
| 683 if (flushLeft != null) _firstFlushLeft = flushLeft; | |
| 684 | |
| 685 return null; | |
| 686 } | |
| 687 | |
| 688 if (nesting == null) nesting = _nesting.nesting; | |
| 689 | |
| 690 var chunk = _chunks.last; | |
| 691 chunk.applySplit(rule, _nesting.indentation, nesting, | |
| 692 flushLeft: flushLeft, | |
| 693 isDouble: isDouble, | |
| 694 spaceWhenUnsplit: spaceWhenUnsplit); | |
| 695 | |
| 696 // Keep track of which chunks are owned by the rule. | |
| 697 if (rule is! HardSplitRule) { | |
| 698 _ruleChunks.putIfAbsent(rule, () => []).add(_chunks.length - 1); | |
| 699 } | |
| 700 | |
| 701 if (chunk.isHardSplit) _handleHardSplit(); | |
| 702 | |
| 703 return chunk; | |
| 704 } | |
| 705 | |
| 706 /// Writes [text] to either the current chunk or a new one if the current | |
| 707 /// chunk is complete. | |
| 708 void _writeText(String text) { | |
| 709 if (_chunks.isNotEmpty && _chunks.last.canAddText) { | |
| 710 _chunks.last.appendText(text); | |
| 711 } else { | |
| 712 _chunks.add(new Chunk(text)); | |
| 713 } | |
| 714 } | |
| 715 | |
| 716 /// Returns true if we can divide the chunks at [index] and line split the | |
| 717 /// ones before and after that separately. | |
| 718 bool _canDivideAt(int i) { | |
| 719 var chunk = _chunks[i]; | |
| 720 if (!chunk.isHardSplit) return false; | |
| 721 if (chunk.nesting.isNested) return false; | |
| 722 if (chunk.blockChunks.isNotEmpty) return false; | |
| 723 | |
| 724 // Make sure we don't split the line in the middle of a rule. | |
| 725 var chunks = _ruleChunks[chunk.rule]; | |
| 726 if (chunks != null && chunks.any((other) => other > i)) return false; | |
| 727 | |
| 728 return true; | |
| 729 } | |
| 730 | |
| 731 /// Pre-processes the chunks after they are done being written by the visitor | |
| 732 /// but before they are run through the line splitter. | |
| 733 /// | |
| 734 /// Marks ranges of chunks that can be line split independently to keep the | |
| 735 /// batches we send to [LineSplitter] small. | |
| 736 void _divideChunks() { | |
| 737 // Harden all of the rules that we know get forced by containing hard | |
| 738 // splits, along with all of the other rules they constrain. | |
| 739 _hardenRules(); | |
| 740 | |
| 741 // Now that we know where all of the divided chunk sections are, mark the | |
| 742 // chunks. | |
| 743 for (var i = 0; i < _chunks.length; i++) { | |
| 744 _chunks[i].markDivide(_canDivideAt(i)); | |
| 745 } | |
| 746 } | |
| 747 | |
| 748 /// Hardens the active rules when a hard split occurs within them. | |
| 749 void _handleHardSplit() { | |
| 750 if (_rules.isEmpty) return; | |
| 751 | |
| 752 // If the current rule doesn't care, it will "eat" the hard split and no | |
| 753 // others will care either. | |
| 754 if (!_rules.last.splitsOnInnerRules) return; | |
| 755 | |
| 756 // Start with the innermost rule. This will traverse the other rules it | |
| 757 // constrains. | |
| 758 _hardSplitRules.add(_rules.last); | |
| 759 } | |
| 760 | |
| 761 /// Replaces all of the previously hardened rules with hard splits, along | |
| 762 /// with every rule that those constrain to also split. | |
| 763 /// | |
| 764 /// This should only be called after all chunks have been written. | |
| 765 void _hardenRules() { | |
| 766 if (_hardSplitRules.isEmpty) return; | |
| 767 | |
| 768 // Harden all of the rules that are constrained by [rules] as well. | |
| 769 var hardenedRules = new Set(); | |
| 770 walkConstraints(rule) { | |
| 771 if (hardenedRules.contains(rule)) return; | |
| 772 hardenedRules.add(rule); | |
| 773 | |
| 774 // Follow this rule's constraints, recursively. | |
| 775 for (var other in _ruleChunks.keys) { | |
| 776 if (other == rule) continue; | |
| 777 | |
| 778 if (rule.constrain(rule.fullySplitValue, other) == | |
| 779 other.fullySplitValue) { | |
| 780 walkConstraints(other); | |
| 781 } | |
| 782 } | |
| 783 } | |
| 784 | |
| 785 for (var rule in _hardSplitRules) { | |
| 786 walkConstraints(rule); | |
| 787 } | |
| 788 | |
| 789 // Harden every chunk that uses one of these rules. | |
| 790 for (var chunk in _chunks) { | |
| 791 if (hardenedRules.contains(chunk.rule)) { | |
| 792 chunk.harden(); | |
| 793 } | |
| 794 } | |
| 795 } | |
| 796 } | |
| OLD | NEW |