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

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

Issue 2990843002: Removed fixed dependencies (Closed)
Patch Set: Created 3 years, 4 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 | « packages/dart_style/lib/src/chunk.dart ('k') | packages/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
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library dart_style.src.chunk_builder; 5 library dart_style.src.chunk_builder;
6 6
7 import 'chunk.dart'; 7 import 'chunk.dart';
8 import 'dart_formatter.dart'; 8 import 'dart_formatter.dart';
9 import 'debug.dart' as debug; 9 import 'debug.dart' as debug;
10 import 'line_writer.dart'; 10 import 'line_writer.dart';
11 import 'nesting_level.dart'; 11 import 'nesting_level.dart';
12 import 'nesting_builder.dart'; 12 import 'nesting_builder.dart';
13 import 'rule/rule.dart'; 13 import 'rule/rule.dart';
14 import 'source_code.dart'; 14 import 'source_code.dart';
15 import 'whitespace.dart'; 15 import 'whitespace.dart';
16 16
17 /// Matches if the last character of a string is an identifier character.
18 final _trailingIdentifierChar = new RegExp(r"[a-zA-Z0-9_]$");
19
17 /// Takes the incremental serialized output of [SourceVisitor]--the source text 20 /// Takes the incremental serialized output of [SourceVisitor]--the source text
18 /// along with any comments and preserved whitespace--and produces a coherent 21 /// along with any comments and preserved whitespace--and produces a coherent
19 /// tree of [Chunk]s which can then be split into physical lines. 22 /// tree of [Chunk]s which can then be split into physical lines.
20 /// 23 ///
21 /// Keeps track of leading indentation, expression nesting, and all of the hairy 24 /// Keeps track of leading indentation, expression nesting, and all of the hairy
22 /// code required to seamlessly integrate existing comments into the pure 25 /// code required to seamlessly integrate existing comments into the pure
23 /// output produced by [SourceVisitor]. 26 /// output produced by [SourceVisitor].
24 class ChunkBuilder { 27 class ChunkBuilder {
25 final DartFormatter _formatter; 28 final DartFormatter _formatter;
26 29
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
91 bool _firstFlushLeft = false; 94 bool _firstFlushLeft = false;
92 95
93 /// Whether there is pending whitespace that depends on the number of 96 /// Whether there is pending whitespace that depends on the number of
94 /// newlines in the source. 97 /// newlines in the source.
95 /// 98 ///
96 /// This is used to avoid calculating the newlines between tokens unless 99 /// This is used to avoid calculating the newlines between tokens unless
97 /// actually needed since doing so is slow when done between every single 100 /// actually needed since doing so is slow when done between every single
98 /// token pair. 101 /// token pair.
99 bool get needsToPreserveNewlines => 102 bool get needsToPreserveNewlines =>
100 _pendingWhitespace == Whitespace.oneOrTwoNewlines || 103 _pendingWhitespace == Whitespace.oneOrTwoNewlines ||
101 _pendingWhitespace == Whitespace.spaceOrNewline ||
102 _pendingWhitespace == Whitespace.splitOrNewline; 104 _pendingWhitespace == Whitespace.splitOrNewline;
103 105
104 /// The number of characters of code that can fit in a single line. 106 /// The number of characters of code that can fit in a single line.
105 int get pageWidth => _formatter.pageWidth; 107 int get pageWidth => _formatter.pageWidth;
106 108
107 /// The current innermost rule. 109 /// The current innermost rule.
108 Rule get rule => _rules.last; 110 Rule get rule => _rules.last;
109 111
110 ChunkBuilder(this._formatter, this._source) 112 ChunkBuilder(this._formatter, this._source)
111 : _parent = null, 113 : _parent = null,
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
164 /// Outputs the series of [comments] and associated whitespace that appear 166 /// Outputs the series of [comments] and associated whitespace that appear
165 /// before [token] (which is not written by this). 167 /// before [token] (which is not written by this).
166 /// 168 ///
167 /// The list contains each comment as it appeared in the source between the 169 /// The list contains each comment as it appeared in the source between the
168 /// last token written and the next one that's about to be written. 170 /// last token written and the next one that's about to be written.
169 /// 171 ///
170 /// [linesBeforeToken] is the number of lines between the last comment (or 172 /// [linesBeforeToken] is the number of lines between the last comment (or
171 /// previous token if there are no comments) and the next token. 173 /// previous token if there are no comments) and the next token.
172 void writeComments( 174 void writeComments(
173 List<SourceComment> comments, int linesBeforeToken, String token) { 175 List<SourceComment> comments, int linesBeforeToken, String token) {
174 // Corner case: if we require a blank line, but there exists one between 176 // Edge case: if we require a blank line, but there exists one between
175 // some of the comments, or after the last one, then we don't need to 177 // some of the comments, or after the last one, then we don't need to
176 // enforce one before the first comment. Example: 178 // enforce one before the first comment. Example:
177 // 179 //
178 // library foo; 180 // library foo;
179 // // comment 181 // // comment
180 // 182 //
181 // class Bar {} 183 // class Bar {}
182 // 184 //
183 // Normally, a blank line is required after `library`, but since there is 185 // Normally, a blank line is required after `library`, but since there is
184 // one after the comment, we don't need one before it. This is mainly so 186 // one after the comment, we don't need one before it. This is mainly so
185 // that commented out directives stick with their preceding group. 187 // that commented out directives stick with their preceding group.
186 if (_pendingWhitespace == Whitespace.twoNewlines && 188 if (_pendingWhitespace == Whitespace.twoNewlines &&
187 comments.first.linesBefore < 2) { 189 comments.first.linesBefore < 2) {
188 if (linesBeforeToken > 1) { 190 if (linesBeforeToken > 1) {
189 _pendingWhitespace = Whitespace.newline; 191 _pendingWhitespace = Whitespace.newline;
190 } else { 192 } else {
191 for (var i = 1; i < comments.length; i++) { 193 for (var i = 1; i < comments.length; i++) {
192 if (comments[i].linesBefore > 1) { 194 if (comments[i].linesBefore > 1) {
193 _pendingWhitespace = Whitespace.newline; 195 _pendingWhitespace = Whitespace.newline;
194 break; 196 break;
195 } 197 }
196 } 198 }
197 } 199 }
198 } 200 }
199 201
200 // Corner case: if the comments are completely inline (i.e. just a series 202 // Edge case: if the comments are completely inline (i.e. just a series of
201 // of block comments with no newlines before, after, or between them), then 203 // block comments with no newlines before, after, or between them), then
202 // they will eat any pending newlines. Make sure that doesn't happen by 204 // they will eat any pending newlines. Make sure that doesn't happen by
203 // putting the pending whitespace before the first comment and moving them 205 // putting the pending whitespace before the first comment and moving them
204 // to their own line. Turns this: 206 // to their own line. Turns this:
205 // 207 //
206 // library foo; /* a */ /* b */ import 'a.dart'; 208 // library foo; /* a */ /* b */ import 'a.dart';
207 // 209 //
208 // into: 210 // into:
209 // 211 //
210 // library foo; 212 // library foo;
211 // 213 //
(...skipping 22 matching lines...) Expand all
234 236
235 if (comment.linesBefore == 0) { 237 if (comment.linesBefore == 0) {
236 // If we're sitting on a split, move the comment before it to adhere it 238 // If we're sitting on a split, move the comment before it to adhere it
237 // to the preceding text. 239 // to the preceding text.
238 if (_shouldMoveCommentBeforeSplit(comment.text)) { 240 if (_shouldMoveCommentBeforeSplit(comment.text)) {
239 _chunks.last.allowText(); 241 _chunks.last.allowText();
240 } 242 }
241 243
242 // The comment follows other text, so we need to decide if it gets a 244 // The comment follows other text, so we need to decide if it gets a
243 // space before it or not. 245 // space before it or not.
244 if (_needsSpaceBeforeComment(isLineComment: comment.isLineComment)) { 246 if (_needsSpaceBeforeComment(comment)) _writeText(" ");
245 _writeText(" ");
246 }
247 } else { 247 } else {
248 // The comment starts a line, so make sure it stays on its own line. 248 // The comment starts a line, so make sure it stays on its own line.
249 _writeHardSplit( 249 _writeHardSplit(
250 flushLeft: comment.flushLeft, 250 flushLeft: comment.flushLeft,
251 isDouble: comment.linesBefore > 1, 251 isDouble: comment.linesBefore > 1,
252 nest: true); 252 nest: true);
253 } 253 }
254 254
255 _writeText(comment.text); 255 _writeText(comment.text);
256 256
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
293 preserveNewlines(linesBeforeToken); 293 preserveNewlines(linesBeforeToken);
294 } 294 }
295 295
296 /// If the current pending whitespace allows some source discretion, pins 296 /// If the current pending whitespace allows some source discretion, pins
297 /// that down given that the source contains [numLines] newlines at that 297 /// that down given that the source contains [numLines] newlines at that
298 /// point. 298 /// point.
299 void preserveNewlines(int numLines) { 299 void preserveNewlines(int numLines) {
300 // If we didn't know how many newlines the user authored between the last 300 // If we didn't know how many newlines the user authored between the last
301 // token and this one, now we do. 301 // token and this one, now we do.
302 switch (_pendingWhitespace) { 302 switch (_pendingWhitespace) {
303 case Whitespace.spaceOrNewline:
304 if (numLines > 0) {
305 _pendingWhitespace = Whitespace.nestedNewline;
306 } else {
307 _pendingWhitespace = Whitespace.space;
308 }
309 break;
310
311 case Whitespace.splitOrNewline: 303 case Whitespace.splitOrNewline:
312 if (numLines > 0) { 304 if (numLines > 0) {
313 _pendingWhitespace = Whitespace.nestedNewline; 305 _pendingWhitespace = Whitespace.nestedNewline;
314 } else { 306 } else {
315 _pendingWhitespace = Whitespace.none; 307 _pendingWhitespace = Whitespace.none;
316 split(space: true); 308 split(space: true);
317 } 309 }
318 break; 310 break;
319 311
320 case Whitespace.oneOrTwoNewlines: 312 case Whitespace.oneOrTwoNewlines:
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
362 } 354 }
363 } 355 }
364 356
365 /// Starts a new [Rule]. 357 /// Starts a new [Rule].
366 /// 358 ///
367 /// If omitted, defaults to a new [Rule]. 359 /// If omitted, defaults to a new [Rule].
368 void startRule([Rule rule]) { 360 void startRule([Rule rule]) {
369 if (rule == null) rule = new Rule(); 361 if (rule == null) rule = new Rule();
370 362
371 // See if any of the rules that contain this one care if it splits. 363 // See if any of the rules that contain this one care if it splits.
372 _rules.forEach((outer) => outer.contain(rule)); 364 _rules.forEach((outer) {
365 if (!outer.splitsOnInnerRules) return;
366 rule.imply(outer);
367 });
373 _rules.add(rule); 368 _rules.add(rule);
374 } 369 }
375 370
376 /// Starts a new [Rule] that comes into play *after* the next whitespace 371 /// Starts a new [Rule] that comes into play *after* the next whitespace
377 /// (including comments) is written. 372 /// (including comments) is written.
378 /// 373 ///
379 /// This is used for binary operators who want to start a rule before the 374 /// This is used for operators who want to start a rule before the first
380 /// first operand but not get forced to split if a comment appears before the 375 /// operand but not get forced to split if a comment appears before the
381 /// entire expression. 376 /// entire expression.
382 /// 377 ///
383 /// If [rule] is omitted, defaults to a new [Rule]. 378 /// If [rule] is omitted, defaults to a new [Rule].
384 void startLazyRule([Rule rule]) { 379 void startLazyRule([Rule rule]) {
385 if (rule == null) rule = new Rule(); 380 if (rule == null) rule = new Rule();
386 381
387 _lazyRules.add(rule); 382 _lazyRules.add(rule);
388 } 383 }
389 384
390 /// Ends the innermost rule. 385 /// Ends the innermost rule.
391 void endRule() { 386 void endRule() {
392 _rules.removeLast(); 387 if (_lazyRules.isNotEmpty) {
388 _lazyRules.removeLast();
389 } else {
390 _rules.removeLast();
391 }
393 } 392 }
394 393
395 /// Pre-emptively forces all of the current rules to become hard splits. 394 /// Pre-emptively forces all of the current rules to become hard splits.
396 /// 395 ///
397 /// This is called by [SourceVisitor] when it can determine that a rule will 396 /// This is called by [SourceVisitor] when it can determine that a rule will
398 /// will always be split. Turning it (and the surrounding rules) into hard 397 /// will always be split. Turning it (and the surrounding rules) into hard
399 /// splits lets the writer break the output into smaller pieces for the line 398 /// splits lets the writer break the output into smaller pieces for the line
400 /// splitter, which helps performance and avoids failing on very large input. 399 /// splitter, which helps performance and avoids failing on very large input.
401 /// 400 ///
402 /// In particular, it's easy for the visitor to know that collections with a 401 /// In particular, it's easy for the visitor to know that collections with a
(...skipping 185 matching lines...) Expand 10 before | Expand all | Expand 10 after
588 break; 587 break;
589 588
590 case Whitespace.newlineFlushLeft: 589 case Whitespace.newlineFlushLeft:
591 _writeHardSplit(flushLeft: true, nest: true); 590 _writeHardSplit(flushLeft: true, nest: true);
592 break; 591 break;
593 592
594 case Whitespace.twoNewlines: 593 case Whitespace.twoNewlines:
595 _writeHardSplit(isDouble: true); 594 _writeHardSplit(isDouble: true);
596 break; 595 break;
597 596
598 case Whitespace.spaceOrNewline:
599 case Whitespace.splitOrNewline: 597 case Whitespace.splitOrNewline:
600 case Whitespace.oneOrTwoNewlines: 598 case Whitespace.oneOrTwoNewlines:
601 // We should have pinned these down before getting here. 599 // We should have pinned these down before getting here.
602 assert(false); 600 assert(false);
603 break; 601 break;
604 } 602 }
605 603
606 _pendingWhitespace = Whitespace.none; 604 _pendingWhitespace = Whitespace.none;
607 } 605 }
608 606
609 /// Returns `true` if the last chunk is a split that should be moved after the 607 /// Returns `true` if the last chunk is a split that should be moved after the
610 /// comment that is about to be written. 608 /// comment that is about to be written.
611 bool _shouldMoveCommentBeforeSplit(String comment) { 609 bool _shouldMoveCommentBeforeSplit(String comment) {
612 // Not if there is nothing before it. 610 // Not if there is nothing before it.
613 if (_chunks.isEmpty) return false; 611 if (_chunks.isEmpty) return false;
614 612
615 // Multi-line comments are always pushed to the next line. 613 // Multi-line comments are always pushed to the next line.
616 if (comment.contains("\n")) return false; 614 if (comment.contains("\n")) return false;
617 615
618 // If the text before the split is an open grouping character, we don't
619 // want to adhere the comment to that.
620 var text = _chunks.last.text; 616 var text = _chunks.last.text;
617
618 // A block comment following a comma probably refers to the following item.
619 if (text.endsWith(",") && comment.startsWith("/*")) return false;
620
621 // If the text before the split is an open grouping character, it looks
622 // better to keep it with the elements than with the bracket itself.
621 return !text.endsWith("(") && !text.endsWith("[") && !text.endsWith("{"); 623 return !text.endsWith("(") && !text.endsWith("[") && !text.endsWith("{");
622 } 624 }
623 625
626 /// Returns `true` if [comment] appears to be a magic generic method comment.
627 ///
628 /// Those get spaced a little differently to look more like real syntax:
629 ///
630 /// int f/*<S, T>*/(int x) => 3;
631 bool _isGenericMethodComment(SourceComment comment) {
632 return comment.text.startsWith("/*<") || comment.text.startsWith("/*=");
633 }
634
624 /// Returns `true` if a space should be output between the end of the current 635 /// Returns `true` if a space should be output between the end of the current
625 /// output and the subsequent comment which is about to be written. 636 /// output and the subsequent comment which is about to be written.
626 /// 637 ///
627 /// This is only called if the comment is trailing text in the unformatted 638 /// This is only called if the comment is trailing text in the unformatted
628 /// source. In most cases, a space will be output to separate the comment 639 /// source. In most cases, a space will be output to separate the comment
629 /// from what precedes it. This returns false if: 640 /// from what precedes it. This returns false if:
630 /// 641 ///
631 /// * This comment does begin the line in the output even if it didn't in 642 /// * This comment does begin the line in the output even if it didn't in
632 /// the source. 643 /// the source.
633 /// * The comment is a block comment immediately following a grouping 644 /// * The comment is a block comment immediately following a grouping
634 /// character (`(`, `[`, or `{`). This is to allow `foo(/* comment */)`, 645 /// character (`(`, `[`, or `{`). This is to allow `foo(/* comment */)`,
635 /// et. al. 646 /// et. al.
636 bool _needsSpaceBeforeComment({bool isLineComment}) { 647 bool _needsSpaceBeforeComment(SourceComment comment) {
637 // Not at the start of the file. 648 // Not at the start of the file.
638 if (_chunks.isEmpty) return false; 649 if (_chunks.isEmpty) return false;
639 650
640 // Not at the start of a line. 651 // Not at the start of a line.
641 if (!_chunks.last.canAddText) return false; 652 if (!_chunks.last.canAddText) return false;
642 653
643 var text = _chunks.last.text; 654 var text = _chunks.last.text;
644 if (text.endsWith("\n")) return false; 655 if (text.endsWith("\n")) return false;
645 656
646 // Always put a space before line comments. 657 // Always put a space before line comments.
647 if (isLineComment) return true; 658 if (comment.isLineComment) return true;
659
660 // Magic generic method comments like "Foo/*<T>*/" don't get spaces.
661 if (_isGenericMethodComment(comment) &&
662 _trailingIdentifierChar.hasMatch(text)) {
663 return false;
664 }
648 665
649 // Block comments do not get a space if following a grouping character. 666 // Block comments do not get a space if following a grouping character.
650 return !text.endsWith("(") && !text.endsWith("[") && !text.endsWith("{"); 667 return !text.endsWith("(") && !text.endsWith("[") && !text.endsWith("{");
651 } 668 }
652 669
653 /// Returns `true` if a space should be output after the last comment which 670 /// Returns `true` if a space should be output after the last comment which
654 /// was just written and the token that will be written. 671 /// was just written and the token that will be written.
655 bool _needsSpaceAfterLastComment(List<SourceComment> comments, String token) { 672 bool _needsSpaceAfterLastComment(List<SourceComment> comments, String token) {
656 // Not if there are no comments. 673 // Not if there are no comments.
657 if (comments.isEmpty) return false; 674 if (comments.isEmpty) return false;
658 675
659 // Not at the beginning of a line. 676 // Not at the beginning of a line.
660 if (!_chunks.last.canAddText) return false; 677 if (!_chunks.last.canAddText) return false;
661 678
679 // Magic generic method comments like "Foo/*<T>*/" don't get spaces.
680 if (_isGenericMethodComment(comments.last) && token == "(") {
681 return false;
682 }
683
662 // Otherwise, it gets a space if the following token is not a delimiter or 684 // Otherwise, it gets a space if the following token is not a delimiter or
663 // the empty string, for EOF. 685 // the empty string, for EOF.
664 return token != ")" && 686 return token != ")" &&
665 token != "]" && 687 token != "]" &&
666 token != "}" && 688 token != "}" &&
667 token != "," && 689 token != "," &&
668 token != ";" && 690 token != ";" &&
669 token != ""; 691 token != "";
670 } 692 }
671 693
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after
788 810
789 // Discard spans in hardened chunks since we know for certain they will 811 // Discard spans in hardened chunks since we know for certain they will
790 // split anyway. 812 // split anyway.
791 for (var chunk in _chunks) { 813 for (var chunk in _chunks) {
792 if (chunk.rule != null && chunk.rule.isHardened) { 814 if (chunk.rule != null && chunk.rule.isHardened) {
793 chunk.spans.clear(); 815 chunk.spans.clear();
794 } 816 }
795 } 817 }
796 } 818 }
797 } 819 }
OLDNEW
« no previous file with comments | « packages/dart_style/lib/src/chunk.dart ('k') | packages/dart_style/lib/src/dart_formatter.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698