OLD | NEW |
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 206 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
233 | 236 |
234 if (comment.linesBefore == 0) { | 237 if (comment.linesBefore == 0) { |
235 // 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 |
236 // to the preceding text. | 239 // to the preceding text. |
237 if (_shouldMoveCommentBeforeSplit(comment.text)) { | 240 if (_shouldMoveCommentBeforeSplit(comment.text)) { |
238 _chunks.last.allowText(); | 241 _chunks.last.allowText(); |
239 } | 242 } |
240 | 243 |
241 // 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 |
242 // space before it or not. | 245 // space before it or not. |
243 if (_needsSpaceBeforeComment(isLineComment: comment.isLineComment)) { | 246 if (_needsSpaceBeforeComment(comment)) _writeText(" "); |
244 _writeText(" "); | |
245 } | |
246 } else { | 247 } else { |
247 // 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. |
248 _writeHardSplit( | 249 _writeHardSplit( |
249 flushLeft: comment.flushLeft, | 250 flushLeft: comment.flushLeft, |
250 isDouble: comment.linesBefore > 1, | 251 isDouble: comment.linesBefore > 1, |
251 nest: true); | 252 nest: true); |
252 } | 253 } |
253 | 254 |
254 _writeText(comment.text); | 255 _writeText(comment.text); |
255 | 256 |
(...skipping 351 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
607 | 608 |
608 // Multi-line comments are always pushed to the next line. | 609 // Multi-line comments are always pushed to the next line. |
609 if (comment.contains("\n")) return false; | 610 if (comment.contains("\n")) return false; |
610 | 611 |
611 // If the text before the split is an open grouping character, we don't | 612 // If the text before the split is an open grouping character, we don't |
612 // want to adhere the comment to that. | 613 // want to adhere the comment to that. |
613 var text = _chunks.last.text; | 614 var text = _chunks.last.text; |
614 return !text.endsWith("(") && !text.endsWith("[") && !text.endsWith("{"); | 615 return !text.endsWith("(") && !text.endsWith("[") && !text.endsWith("{"); |
615 } | 616 } |
616 | 617 |
| 618 /// Returns `true` if [comment] appears to be a magic generic method comment. |
| 619 /// |
| 620 /// Those get spaced a little differently to look more like real syntax: |
| 621 /// |
| 622 /// int f/*<S, T>*/(int x) => 3; |
| 623 bool _isGenericMethodComment(SourceComment comment) { |
| 624 return comment.text.startsWith("/*<") || comment.text.startsWith("/*="); |
| 625 } |
| 626 |
617 /// Returns `true` if a space should be output between the end of the current | 627 /// Returns `true` if a space should be output between the end of the current |
618 /// output and the subsequent comment which is about to be written. | 628 /// output and the subsequent comment which is about to be written. |
619 /// | 629 /// |
620 /// This is only called if the comment is trailing text in the unformatted | 630 /// This is only called if the comment is trailing text in the unformatted |
621 /// source. In most cases, a space will be output to separate the comment | 631 /// source. In most cases, a space will be output to separate the comment |
622 /// from what precedes it. This returns false if: | 632 /// from what precedes it. This returns false if: |
623 /// | 633 /// |
624 /// * This comment does begin the line in the output even if it didn't in | 634 /// * This comment does begin the line in the output even if it didn't in |
625 /// the source. | 635 /// the source. |
626 /// * The comment is a block comment immediately following a grouping | 636 /// * The comment is a block comment immediately following a grouping |
627 /// character (`(`, `[`, or `{`). This is to allow `foo(/* comment */)`, | 637 /// character (`(`, `[`, or `{`). This is to allow `foo(/* comment */)`, |
628 /// et. al. | 638 /// et. al. |
629 bool _needsSpaceBeforeComment({bool isLineComment}) { | 639 bool _needsSpaceBeforeComment(SourceComment comment) { |
630 // Not at the start of the file. | 640 // Not at the start of the file. |
631 if (_chunks.isEmpty) return false; | 641 if (_chunks.isEmpty) return false; |
632 | 642 |
633 // Not at the start of a line. | 643 // Not at the start of a line. |
634 if (!_chunks.last.canAddText) return false; | 644 if (!_chunks.last.canAddText) return false; |
635 | 645 |
636 var text = _chunks.last.text; | 646 var text = _chunks.last.text; |
637 if (text.endsWith("\n")) return false; | 647 if (text.endsWith("\n")) return false; |
638 | 648 |
639 // Always put a space before line comments. | 649 // Always put a space before line comments. |
640 if (isLineComment) return true; | 650 if (comment.isLineComment) return true; |
| 651 |
| 652 // Magic generic method comments like "Foo/*<T>*/" don't get spaces. |
| 653 if (_isGenericMethodComment(comment) && |
| 654 _trailingIdentifierChar.hasMatch(text)) { |
| 655 return false; |
| 656 } |
641 | 657 |
642 // Block comments do not get a space if following a grouping character. | 658 // Block comments do not get a space if following a grouping character. |
643 return !text.endsWith("(") && !text.endsWith("[") && !text.endsWith("{"); | 659 return !text.endsWith("(") && !text.endsWith("[") && !text.endsWith("{"); |
644 } | 660 } |
645 | 661 |
646 /// Returns `true` if a space should be output after the last comment which | 662 /// Returns `true` if a space should be output after the last comment which |
647 /// was just written and the token that will be written. | 663 /// was just written and the token that will be written. |
648 bool _needsSpaceAfterLastComment(List<SourceComment> comments, String token) { | 664 bool _needsSpaceAfterLastComment(List<SourceComment> comments, String token) { |
649 // Not if there are no comments. | 665 // Not if there are no comments. |
650 if (comments.isEmpty) return false; | 666 if (comments.isEmpty) return false; |
651 | 667 |
652 // Not at the beginning of a line. | 668 // Not at the beginning of a line. |
653 if (!_chunks.last.canAddText) return false; | 669 if (!_chunks.last.canAddText) return false; |
654 | 670 |
| 671 // Magic generic method comments like "Foo/*<T>*/" don't get spaces. |
| 672 if (_isGenericMethodComment(comments.last) && token == "(") { |
| 673 return false; |
| 674 } |
| 675 |
655 // Otherwise, it gets a space if the following token is not a delimiter or | 676 // Otherwise, it gets a space if the following token is not a delimiter or |
656 // the empty string, for EOF. | 677 // the empty string, for EOF. |
657 return token != ")" && | 678 return token != ")" && |
658 token != "]" && | 679 token != "]" && |
659 token != "}" && | 680 token != "}" && |
660 token != "," && | 681 token != "," && |
661 token != ";" && | 682 token != ";" && |
662 token != ""; | 683 token != ""; |
663 } | 684 } |
664 | 685 |
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
781 | 802 |
782 // Discard spans in hardened chunks since we know for certain they will | 803 // Discard spans in hardened chunks since we know for certain they will |
783 // split anyway. | 804 // split anyway. |
784 for (var chunk in _chunks) { | 805 for (var chunk in _chunks) { |
785 if (chunk.rule != null && chunk.rule.isHardened) { | 806 if (chunk.rule != null && chunk.rule.isHardened) { |
786 chunk.spans.clear(); | 807 chunk.spans.clear(); |
787 } | 808 } |
788 } | 809 } |
789 } | 810 } |
790 } | 811 } |
OLD | NEW |