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

Side by Side Diff: lib/src/source_visitor.dart

Issue 822273004: Add API to provide a selection range, and return the updated selection after formatting. (Closed) Base URL: https://github.com/dart-lang/dart_style.git@master
Patch Set: Remove redundant argument check. Created 5 years, 11 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 | « lib/src/line_writer.dart ('k') | test/formatter_test.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.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
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
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
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
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 }
OLDNEW
« no previous file with comments | « lib/src/line_writer.dart ('k') | test/formatter_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698