Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(913)

Side by Side Diff: dart_style/lib/src/chunk_builder.dart

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « dart_style/lib/src/chunk.dart ('k') | dart_style/lib/src/dart_formatter.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « dart_style/lib/src/chunk.dart ('k') | dart_style/lib/src/dart_formatter.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698