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.source_visitor; | 5 library dart_style.src.source_visitor; |
6 | 6 |
7 import 'package:analyzer/analyzer.dart'; | 7 import 'package:analyzer/analyzer.dart'; |
8 import 'package:analyzer/src/generated/scanner.dart'; | 8 import 'package:analyzer/src/generated/scanner.dart'; |
9 import 'package:analyzer/src/generated/source.dart'; | 9 import 'package:analyzer/src/generated/source.dart'; |
10 | 10 |
11 import '../dart_style.dart'; | 11 import '../dart_style.dart'; |
12 import 'chunk.dart'; | 12 import 'chunk.dart'; |
13 import 'line_writer.dart'; | 13 import 'line_writer.dart'; |
14 import 'whitespace.dart'; | 14 import 'whitespace.dart'; |
15 | 15 |
16 /// An AST visitor that drives formatting heuristics. | 16 /// An AST visitor that drives formatting heuristics. |
17 class SourceVisitor implements AstVisitor { | 17 class SourceVisitor implements AstVisitor { |
18 /// The writer to which the output lines are written. | 18 /// The writer to which the output lines are written. |
19 final LineWriter _writer; | 19 final LineWriter _writer; |
20 | 20 |
21 /// Cached line info for calculating blank lines. | 21 /// Cached line info for calculating blank lines. |
22 LineInfo _lineInfo; | 22 LineInfo _lineInfo; |
23 | 23 |
24 /// The source being formatted (used in interpolation handling) | 24 /// The source being formatted. |
25 final String _source; | 25 final SourceCode _source; |
| 26 |
| 27 /// `true` if the visitor has written past the beginning of the selection in |
| 28 /// the original source text. |
| 29 bool _passedSelectionStart = false; |
| 30 |
| 31 /// `true` if the visitor has written past the end of the selection in the |
| 32 /// original source text. |
| 33 bool _passedSelectionEnd = false; |
26 | 34 |
27 /// Initialize a newly created visitor to write source code representing | 35 /// Initialize a newly created visitor to write source code representing |
28 /// the visited nodes to the given [writer]. | 36 /// the visited nodes to the given [writer]. |
29 SourceVisitor(DartFormatter formatter, this._lineInfo, this._source, | 37 SourceVisitor(DartFormatter formatter, this._lineInfo, SourceCode source) |
30 StringBuffer outputBuffer) | 38 : _source = source, |
31 : _writer = new LineWriter(formatter, outputBuffer); | 39 _writer = new LineWriter(formatter, source); |
32 | 40 |
33 /// Run the visitor on [node], writing all of the formatted output to the | 41 /// Runs the visitor on [node], formatting its contents. |
34 /// output buffer. | 42 /// |
| 43 /// Returns a [SourceCode] containing the resulting formatted source and |
| 44 /// updated selection, if any. |
35 /// | 45 /// |
36 /// This is the only method that should be called externally. Everything else | 46 /// This is the only method that should be called externally. Everything else |
37 /// is effectively private. | 47 /// is effectively private. |
38 void run(AstNode node) { | 48 SourceCode run(AstNode node) { |
39 visit(node); | 49 visit(node); |
40 | 50 |
41 // Output trailing comments. | 51 // Output trailing comments. |
42 writePrecedingCommentsAndNewlines(node.endToken.next); | 52 writePrecedingCommentsAndNewlines(node.endToken.next); |
43 | 53 |
44 // Finish off the last line. | 54 // Finish writing and return the complete result. |
45 _writer.end(); | 55 return _writer.end(); |
46 } | 56 } |
47 | 57 |
48 visitAdjacentStrings(AdjacentStrings node) { | 58 visitAdjacentStrings(AdjacentStrings node) { |
49 visitNodes(node.strings, between: spaceOrNewline); | 59 visitNodes(node.strings, between: spaceOrNewline); |
50 } | 60 } |
51 | 61 |
52 visitAnnotation(Annotation node) { | 62 visitAnnotation(Annotation node) { |
53 token(node.atSign); | 63 token(node.atSign); |
54 visit(node.name); | 64 visit(node.name); |
55 token(node.period); | 65 token(node.period); |
(...skipping 1010 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1066 visit(node.expression); | 1076 visit(node.expression); |
1067 } | 1077 } |
1068 }); | 1078 }); |
1069 } | 1079 } |
1070 | 1080 |
1071 visitScriptTag(ScriptTag node) { | 1081 visitScriptTag(ScriptTag node) { |
1072 // The lexeme includes the trailing newline. Strip it off since the | 1082 // The lexeme includes the trailing newline. Strip it off since the |
1073 // formatter ensures it gets a newline after it. Since the script tag must | 1083 // formatter ensures it gets a newline after it. Since the script tag must |
1074 // come at the top of the file, we don't have to worry about preceding | 1084 // come at the top of the file, we don't have to worry about preceding |
1075 // comments or whitespace. | 1085 // comments or whitespace. |
1076 _writer.write(node.scriptTag.lexeme.trim()); | 1086 _writeText(node.scriptTag.lexeme.trim(), node.offset); |
| 1087 |
1077 oneOrTwoNewlines(); | 1088 oneOrTwoNewlines(); |
1078 } | 1089 } |
1079 | 1090 |
1080 visitShowCombinator(ShowCombinator node) { | 1091 visitShowCombinator(ShowCombinator node) { |
1081 _visitCombinator(node.keyword, node.shownNames); | 1092 _visitCombinator(node.keyword, node.shownNames); |
1082 } | 1093 } |
1083 | 1094 |
1084 visitSimpleFormalParameter(SimpleFormalParameter node) { | 1095 visitSimpleFormalParameter(SimpleFormalParameter node) { |
1085 visitParameterMetadata(node.metadata); | 1096 visitParameterMetadata(node.metadata); |
1086 modifier(node.keyword); | 1097 modifier(node.keyword); |
1087 visit(node.type, after: space); | 1098 visit(node.type, after: space); |
1088 visit(node.identifier); | 1099 visit(node.identifier); |
1089 } | 1100 } |
1090 | 1101 |
1091 visitSimpleIdentifier(SimpleIdentifier node) { | 1102 visitSimpleIdentifier(SimpleIdentifier node) { |
1092 token(node.token); | 1103 token(node.token); |
1093 } | 1104 } |
1094 | 1105 |
1095 visitSimpleStringLiteral(SimpleStringLiteral node) { | 1106 visitSimpleStringLiteral(SimpleStringLiteral node) { |
1096 // Since we output the string literal manually, ensure any preceding | 1107 // Since we output the string literal manually, ensure any preceding |
1097 // comments are written first. | 1108 // comments are written first. |
1098 writePrecedingCommentsAndNewlines(node.literal); | 1109 writePrecedingCommentsAndNewlines(node.literal); |
1099 | 1110 |
1100 _writeStringLiteral(node.literal.lexeme); | 1111 _writeStringLiteral(node.literal.lexeme, node.offset); |
1101 } | 1112 } |
1102 | 1113 |
1103 visitStringInterpolation(StringInterpolation node) { | 1114 visitStringInterpolation(StringInterpolation node) { |
1104 // Since we output the interpolated text manually, ensure we include any | 1115 // Since we output the interpolated text manually, ensure we include any |
1105 // preceding stuff first. | 1116 // preceding stuff first. |
1106 writePrecedingCommentsAndNewlines(node.beginToken); | 1117 writePrecedingCommentsAndNewlines(node.beginToken); |
1107 | 1118 |
1108 // Right now, the formatter does not try to do any reformatting of the | 1119 // Right now, the formatter does not try to do any reformatting of the |
1109 // contents of interpolated strings. Instead, it treats the entire thing as | 1120 // contents of interpolated strings. Instead, it treats the entire thing as |
1110 // a single (possibly multi-line) chunk of text. | 1121 // a single (possibly multi-line) chunk of text. |
1111 _writeStringLiteral( | 1122 _writeStringLiteral( |
1112 _source.substring(node.beginToken.offset, node.endToken.end)); | 1123 _source.text.substring(node.beginToken.offset, node.endToken.end), |
| 1124 node.offset); |
1113 } | 1125 } |
1114 | 1126 |
1115 visitSuperConstructorInvocation(SuperConstructorInvocation node) { | 1127 visitSuperConstructorInvocation(SuperConstructorInvocation node) { |
1116 _writer.startSpan(); | 1128 _writer.startSpan(); |
1117 | 1129 |
1118 token(node.keyword); | 1130 token(node.keyword); |
1119 token(node.period); | 1131 token(node.period); |
1120 visit(node.constructorName); | 1132 visit(node.constructorName); |
1121 visit(node.argumentList); | 1133 visit(node.argumentList); |
1122 | 1134 |
(...skipping 422 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1545 /// Returns `true` if [node] is immediately contained within an anonymous | 1557 /// Returns `true` if [node] is immediately contained within an anonymous |
1546 /// [FunctionExpression]. | 1558 /// [FunctionExpression]. |
1547 bool _isLambda(AstNode node) => | 1559 bool _isLambda(AstNode node) => |
1548 node.parent is FunctionExpression && | 1560 node.parent is FunctionExpression && |
1549 node.parent.parent is! FunctionDeclaration; | 1561 node.parent.parent is! FunctionDeclaration; |
1550 | 1562 |
1551 /// Writes the string literal [string] to the output. | 1563 /// Writes the string literal [string] to the output. |
1552 /// | 1564 /// |
1553 /// Splits multiline strings into separate chunks so that the line splitter | 1565 /// Splits multiline strings into separate chunks so that the line splitter |
1554 /// can handle them correctly. | 1566 /// can handle them correctly. |
1555 void _writeStringLiteral(String string) { | 1567 void _writeStringLiteral(String string, int offset) { |
1556 // Split each line of a multiline string into separate chunks. | 1568 // Split each line of a multiline string into separate chunks. |
1557 var lines = string.split("\n"); | 1569 var lines = string.split("\n"); |
1558 | 1570 |
1559 _writer.write(lines.first); | 1571 _writeText(lines.first, offset); |
| 1572 offset += lines.first.length; |
1560 | 1573 |
1561 for (var line in lines.skip(1)) { | 1574 for (var line in lines.skip(1)) { |
1562 _writer.writeWhitespace(Whitespace.newlineFlushLeft); | 1575 _writer.writeWhitespace(Whitespace.newlineFlushLeft); |
1563 _writer.write(line); | 1576 offset++; |
| 1577 _writeText(line, offset); |
| 1578 offset += line.length; |
1564 } | 1579 } |
1565 } | 1580 } |
1566 | 1581 |
1567 /// Emit the given [modifier] if it's non null, followed by non-breaking | 1582 /// Emit the given [modifier] if it's non null, followed by non-breaking |
1568 /// whitespace. | 1583 /// whitespace. |
1569 void modifier(Token modifier) { | 1584 void modifier(Token modifier) { |
1570 token(modifier, after: space); | 1585 token(modifier, after: space); |
1571 } | 1586 } |
1572 | 1587 |
1573 /// Emit a non-breaking space. | 1588 /// Emit a non-breaking space. |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1616 /// Does nothing if [token] is `null`. If [before] is given, it will be | 1631 /// Does nothing if [token] is `null`. If [before] is given, it will be |
1617 /// executed before the token is outout. Likewise, [after] will be called | 1632 /// executed before the token is outout. Likewise, [after] will be called |
1618 /// after the token is output. | 1633 /// after the token is output. |
1619 void token(Token token, {before(), after()}) { | 1634 void token(Token token, {before(), after()}) { |
1620 if (token == null) return; | 1635 if (token == null) return; |
1621 | 1636 |
1622 writePrecedingCommentsAndNewlines(token); | 1637 writePrecedingCommentsAndNewlines(token); |
1623 | 1638 |
1624 if (before != null) before(); | 1639 if (before != null) before(); |
1625 | 1640 |
1626 _writer.write(token.lexeme); | 1641 _writeText(token.lexeme, token.offset); |
1627 | 1642 |
1628 if (after != null) after(); | 1643 if (after != null) after(); |
1629 } | 1644 } |
1630 | 1645 |
1631 /// Writes all formatted whitespace and comments that appear before [token]. | 1646 /// Writes all formatted whitespace and comments that appear before [token]. |
1632 void writePrecedingCommentsAndNewlines(Token token) { | 1647 void writePrecedingCommentsAndNewlines(Token token) { |
1633 var comment = token.precedingComments; | 1648 var comment = token.precedingComments; |
1634 | 1649 |
1635 // For performance, avoid calculating newlines between tokens unless | 1650 // For performance, avoid calculating newlines between tokens unless |
1636 // actually needed. | 1651 // actually needed. |
(...skipping 17 matching lines...) Expand all Loading... |
1654 var comments = []; | 1669 var comments = []; |
1655 while (comment != null) { | 1670 while (comment != null) { |
1656 var commentLine = _startLine(comment); | 1671 var commentLine = _startLine(comment); |
1657 | 1672 |
1658 // Don't preserve newlines at the top of the file. | 1673 // Don't preserve newlines at the top of the file. |
1659 if (comment == token.precedingComments && | 1674 if (comment == token.precedingComments && |
1660 token.previous.type == TokenType.EOF) { | 1675 token.previous.type == TokenType.EOF) { |
1661 previousLine = commentLine; | 1676 previousLine = commentLine; |
1662 } | 1677 } |
1663 | 1678 |
1664 comments.add(new SourceComment(comment.toString().trim(), | 1679 var sourceComment = new SourceComment(comment.toString().trim(), |
1665 commentLine - previousLine, | 1680 commentLine - previousLine, |
1666 isLineComment: comment.type == TokenType.SINGLE_LINE_COMMENT, | 1681 isLineComment: comment.type == TokenType.SINGLE_LINE_COMMENT, |
1667 isStartOfLine: _startColumn(comment) == 1)); | 1682 isStartOfLine: _startColumn(comment) == 1); |
| 1683 |
| 1684 // If this comment contains either of the selection endpoints, mark them |
| 1685 // in the comment. |
| 1686 var start = _getSelectionStartWithin(comment.offset, comment.length); |
| 1687 if (start != null) sourceComment.startSelection(start); |
| 1688 |
| 1689 var end = _getSelectionEndWithin(comment.offset, comment.length); |
| 1690 if (end != null) sourceComment.endSelection(end); |
| 1691 |
| 1692 comments.add(sourceComment); |
1668 | 1693 |
1669 previousLine = _endLine(comment); | 1694 previousLine = _endLine(comment); |
1670 comment = comment.next; | 1695 comment = comment.next; |
1671 } | 1696 } |
1672 | 1697 |
1673 _writer.writeComments(comments, tokenLine - previousLine, token.lexeme); | 1698 _writer.writeComments(comments, tokenLine - previousLine, token.lexeme); |
1674 } | 1699 } |
1675 | 1700 |
| 1701 /// Write [text] to the current chunk, given that it starts at [offset] in |
| 1702 /// the original source. |
| 1703 /// |
| 1704 /// Also outputs the selection endpoints if needed. |
| 1705 void _writeText(String text, int offset) { |
| 1706 _writer.write(text); |
| 1707 |
| 1708 // If this text contains either of the selection endpoints, mark them in |
| 1709 // the chunk. |
| 1710 var start = _getSelectionStartWithin(offset, text.length); |
| 1711 if (start != null) { |
| 1712 _writer.startSelectionFromEnd(text.length - start); |
| 1713 } |
| 1714 |
| 1715 var end = _getSelectionEndWithin(offset, text.length); |
| 1716 if (end != null) { |
| 1717 _writer.endSelectionFromEnd(text.length - end); |
| 1718 } |
| 1719 } |
| 1720 |
| 1721 /// Returns the number of characters past [offset] in the source where the |
| 1722 /// selection start appears if it appears before `offset + length`. |
| 1723 /// |
| 1724 /// Returns `null` if the selection start has already been processed or is |
| 1725 /// not within that range. |
| 1726 int _getSelectionStartWithin(int offset, int length) { |
| 1727 // If there is no selection, do nothing. |
| 1728 if (_source.selectionStart == null) return null; |
| 1729 |
| 1730 // If we've already passed it, don't consider it again. |
| 1731 if (_passedSelectionStart) return null; |
| 1732 |
| 1733 var start = _source.selectionStart - offset; |
| 1734 |
| 1735 // If it started in whitespace before this text, push it forward to the |
| 1736 // beginning of the non-whitespace text. |
| 1737 if (start < 0) start = 0; |
| 1738 |
| 1739 // If we haven't reached it yet, don't consider it. |
| 1740 if (start >= length) return null; |
| 1741 |
| 1742 // We found it. |
| 1743 _passedSelectionStart = true; |
| 1744 |
| 1745 return start; |
| 1746 } |
| 1747 |
| 1748 /// Returns the number of characters past [offset] in the source where the |
| 1749 /// selection endpoint appears if it appears before `offset + length`. |
| 1750 /// |
| 1751 /// Returns `null` if the selection endpoint has already been processed or is |
| 1752 /// not within that range. |
| 1753 int _getSelectionEndWithin(int offset, int length) { |
| 1754 // If there is no selection, do nothing. |
| 1755 if (_source.selectionLength == null) return null; |
| 1756 |
| 1757 // If we've already passed it, don't consider it again. |
| 1758 if (_passedSelectionEnd) return null; |
| 1759 |
| 1760 var end = _source.selectionStart + _source.selectionLength - offset; |
| 1761 |
| 1762 // If it started in whitespace before this text, push it forward to the |
| 1763 // beginning of the non-whitespace text. |
| 1764 if (end < 0) end = 0; |
| 1765 |
| 1766 // If we haven't reached it yet, don't consider it. |
| 1767 if (end >= length) return null; |
| 1768 |
| 1769 // We found it. |
| 1770 _passedSelectionEnd = true; |
| 1771 |
| 1772 return end; |
| 1773 } |
| 1774 |
1676 /// Gets the 1-based line number that the beginning of [token] lies on. | 1775 /// Gets the 1-based line number that the beginning of [token] lies on. |
1677 int _startLine(Token token) => _lineInfo.getLocation(token.offset).lineNumber; | 1776 int _startLine(Token token) => _lineInfo.getLocation(token.offset).lineNumber; |
1678 | 1777 |
1679 /// Gets the 1-based line number that the end of [token] lies on. | 1778 /// Gets the 1-based line number that the end of [token] lies on. |
1680 int _endLine(Token token) => _lineInfo.getLocation(token.end).lineNumber; | 1779 int _endLine(Token token) => _lineInfo.getLocation(token.end).lineNumber; |
1681 | 1780 |
1682 /// Gets the 1-based column number that the beginning of [token] lies on. | 1781 /// Gets the 1-based column number that the beginning of [token] lies on. |
1683 int _startColumn(Token token) => | 1782 int _startColumn(Token token) => |
1684 _lineInfo.getLocation(token.offset).columnNumber; | 1783 _lineInfo.getLocation(token.offset).columnNumber; |
1685 } | 1784 } |
OLD | NEW |