| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 formatter_impl; | 5 library formatter_impl; |
| 6 | 6 |
| 7 import 'dart:math'; | 7 import 'dart:math'; |
| 8 | 8 |
| 9 import 'package:analyzer_experimental/analyzer.dart'; | 9 import 'package:analyzer_experimental/analyzer.dart'; |
| 10 import 'package:analyzer_experimental/src/generated/parser.dart'; | 10 import 'package:analyzer_experimental/src/generated/parser.dart'; |
| (...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 146 | 146 |
| 147 /// Cached line info for calculating blank lines. | 147 /// Cached line info for calculating blank lines. |
| 148 LineInfo lineInfo; | 148 LineInfo lineInfo; |
| 149 | 149 |
| 150 /// Cached previous token for calculating preceding whitespace. | 150 /// Cached previous token for calculating preceding whitespace. |
| 151 Token previousToken; | 151 Token previousToken; |
| 152 | 152 |
| 153 /// A flag to indicate that a newline should be emitted before the next token. | 153 /// A flag to indicate that a newline should be emitted before the next token. |
| 154 bool needsNewline = false; | 154 bool needsNewline = false; |
| 155 | 155 |
| 156 /// A flag to indicate that user introduced newlines should be emitted before | 156 /// Used for matching EOL comments |
| 157 /// the next token. | 157 final twoSlashes = new RegExp(r'//[^/]'); |
| 158 bool preservePrecedingNewlines = false; | 158 |
| 159 | |
| 160 /// Initialize a newly created visitor to write source code representing | 159 /// Initialize a newly created visitor to write source code representing |
| 161 /// the visited nodes to the given [writer]. | 160 /// the visited nodes to the given [writer]. |
| 162 SourceVisitor(FormatterOptions options, this.lineInfo) : | 161 SourceVisitor(FormatterOptions options, this.lineInfo) : |
| 163 writer = new SourceWriter(indentCount: options.initialIndentationLevel, | 162 writer = new SourceWriter(indentCount: options.initialIndentationLevel, |
| 164 lineSeparator: options.lineSeparator); | 163 lineSeparator: options.lineSeparator); |
| 165 | 164 |
| 166 visitAdjacentStrings(AdjacentStrings node) { | 165 visitAdjacentStrings(AdjacentStrings node) { |
| 167 visitNodes(node.strings, separatedBy: space); | 166 visitNodes(node.strings, separatedBy: space); |
| 168 } | 167 } |
| 169 | 168 |
| (...skipping 145 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 315 // Cache EOF for leading whitespace calculation | 314 // Cache EOF for leading whitespace calculation |
| 316 var start = node.beginToken.previous; | 315 var start = node.beginToken.previous; |
| 317 if (start != null && start.type is TokenType_EOF) { | 316 if (start != null && start.type is TokenType_EOF) { |
| 318 previousToken = start; | 317 previousToken = start; |
| 319 } | 318 } |
| 320 | 319 |
| 321 var scriptTag = node.scriptTag; | 320 var scriptTag = node.scriptTag; |
| 322 var directives = node.directives; | 321 var directives = node.directives; |
| 323 visit(scriptTag); | 322 visit(scriptTag); |
| 324 | 323 |
| 325 preservePrecedingNewlines = true; | |
| 326 visitNodes(directives, separatedBy: newlines, followedBy: newlines); | 324 visitNodes(directives, separatedBy: newlines, followedBy: newlines); |
| 327 | 325 |
| 328 preservePrecedingNewlines = true; | |
| 329 visitNodes(node.declarations, separatedBy: newlines); | 326 visitNodes(node.declarations, separatedBy: newlines); |
| 330 | 327 |
| 331 // Handle trailing whitespace | 328 // Handle trailing whitespace |
| 332 preservePrecedingNewlines = true; | |
| 333 token(node.endToken /* EOF */); | 329 token(node.endToken /* EOF */); |
| 334 } | 330 } |
| 335 | 331 |
| 336 visitConditionalExpression(ConditionalExpression node) { | 332 visitConditionalExpression(ConditionalExpression node) { |
| 337 visit(node.condition); | 333 visit(node.condition); |
| 338 space(); | 334 space(); |
| 339 token(node.question); | 335 token(node.question); |
| 340 space(); | 336 space(); |
| 341 visit(node.thenExpression); | 337 visit(node.thenExpression); |
| 342 space(); | 338 space(); |
| (...skipping 683 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1026 /// Emit the given [modifier] if it's non null, followed by non-breaking | 1022 /// Emit the given [modifier] if it's non null, followed by non-breaking |
| 1027 /// whitespace. | 1023 /// whitespace. |
| 1028 modifier(Token modifier) { | 1024 modifier(Token modifier) { |
| 1029 token(modifier, followedBy: space); | 1025 token(modifier, followedBy: space); |
| 1030 } | 1026 } |
| 1031 | 1027 |
| 1032 | 1028 |
| 1033 /// Indicate that at least one newline should be emitted and possibly more | 1029 /// Indicate that at least one newline should be emitted and possibly more |
| 1034 /// if the source has them. | 1030 /// if the source has them. |
| 1035 newlines() { | 1031 newlines() { |
| 1036 preservePrecedingNewlines = true; | |
| 1037 needsNewline = true; | 1032 needsNewline = true; |
| 1038 } | 1033 } |
| 1039 | 1034 |
| 1040 token(Token token, {precededBy(), followedBy(), int minNewlines: 0}) { | 1035 token(Token token, {precededBy(), followedBy(), int minNewlines: 0}) { |
| 1041 if (token != null) { | 1036 if (token != null) { |
| 1042 if (needsNewline) { | 1037 if (needsNewline) { |
| 1043 minNewlines = max(1, minNewlines); | 1038 minNewlines = max(1, minNewlines); |
| 1044 } | 1039 } |
| 1045 if (preservePrecedingNewlines || minNewlines > 0) { | 1040 var emitted = emitPrecedingCommentsAndNewlines(token, min: minNewlines); |
| 1046 var emitted = emitPrecedingNewlines(token, min: minNewlines); | 1041 if (emitted > 0) { |
| 1047 preservePrecedingNewlines = false; | 1042 needsNewline = false; |
| 1048 if (emitted > 0) { | |
| 1049 needsNewline = false; | |
| 1050 } | |
| 1051 } | 1043 } |
| 1052 if (precededBy !=null) { | 1044 if (precededBy !=null) { |
| 1053 precededBy(); | 1045 precededBy(); |
| 1054 } | 1046 } |
| 1055 emitBlockComments(token); | |
| 1056 append(token.lexeme); | 1047 append(token.lexeme); |
| 1057 if (followedBy != null) { | 1048 if (followedBy != null) { |
| 1058 followedBy(); | 1049 followedBy(); |
| 1059 } | 1050 } |
| 1060 previousToken = token; | 1051 previousToken = token; |
| 1061 } | 1052 } |
| 1062 } | 1053 } |
| 1063 | 1054 |
| 1064 commaSeperator() { | 1055 commaSeperator() { |
| 1065 comma(); | 1056 comma(); |
| (...skipping 25 matching lines...) Expand all Loading... |
| 1091 /// Indent. | 1082 /// Indent. |
| 1092 indent() { | 1083 indent() { |
| 1093 writer.indent(); | 1084 writer.indent(); |
| 1094 } | 1085 } |
| 1095 | 1086 |
| 1096 /// Unindent | 1087 /// Unindent |
| 1097 unindent() { | 1088 unindent() { |
| 1098 writer.unindent(); | 1089 writer.unindent(); |
| 1099 } | 1090 } |
| 1100 | 1091 |
| 1101 emitBlockComments(Token token) { | 1092 |
| 1102 var comment = token.precedingComments; | 1093 /// Emit any detected comments and newlines or a minimum as specified |
| 1103 while (comment != null) { | 1094 /// by [min]. |
| 1104 if (isBlock(comment)) { | 1095 int emitPrecedingCommentsAndNewlines(Token token, {min: 0}) { |
| 1105 append(comment.toString().trim()); | 1096 |
| 1106 if (linesBetween(comment.end, token.offset) >= 1) { | |
| 1107 writer.newline(); | |
| 1108 } else { | |
| 1109 space(); | |
| 1110 } | |
| 1111 } | |
| 1112 comment = comment.next; | |
| 1113 } | |
| 1114 } | |
| 1115 | |
| 1116 /// Emit any detected newlines or a minimum as specified by [min]. | |
| 1117 int emitPrecedingNewlines(Token token, {min: 0}) { | |
| 1118 var comment = token.precedingComments; | 1097 var comment = token.precedingComments; |
| 1119 var currentToken = comment != null ? comment : token; | 1098 var currentToken = comment != null ? comment : token; |
| 1099 |
| 1100 //Handle EOLs before newlines |
| 1101 if (isAtEOL(comment)) { |
| 1102 emitComment(comment, previousToken); |
| 1103 comment = comment.next; |
| 1104 currentToken = comment != null ? comment : token; |
| 1105 } |
| 1106 |
| 1120 var lines = max(min, countNewlinesBetween(previousToken, currentToken)); | 1107 var lines = max(min, countNewlinesBetween(previousToken, currentToken)); |
| 1121 writer.newlines(lines); | 1108 writer.newlines(lines); |
| 1109 |
| 1110 var previousToken = currentToken.previous; |
| 1111 |
| 1112 while (comment != null) { |
| 1122 | 1113 |
| 1123 while (comment != null) { | 1114 emitComment(comment, previousToken); |
| 1124 if (!isBlock(comment)) { | 1115 |
| 1125 append(comment.toString().trim()); | 1116 var nextToken = comment.next != null ? comment.next : token; |
| 1126 var nextToken = comment.next != null ? comment.next : token; | 1117 var newlines = calculateNewlinesBetweenComments(comment, nextToken); |
| 1127 var postCommentNewlines = | 1118 if (newlines > 0) { |
| 1128 max(1, countNewlinesBetween(comment, nextToken)); | 1119 writer.newlines(newlines); |
| 1129 writer.newlines(postCommentNewlines); | 1120 lines += newlines; |
| 1130 lines += postCommentNewlines; | 1121 } else if (!isEOF(token)) { |
| 1122 space(); |
| 1131 } | 1123 } |
| 1124 |
| 1125 previousToken = comment; |
| 1132 comment = comment.next; | 1126 comment = comment.next; |
| 1133 } | 1127 } |
| 1134 | 1128 |
| 1135 previousToken = token; | 1129 previousToken = token; |
| 1136 return lines; | 1130 return lines; |
| 1137 } | 1131 } |
| 1138 | 1132 |
| 1133 /// Test if this [comment] is at the end of a line. |
| 1134 bool isAtEOL(Token comment) => |
| 1135 comment != null && comment.toString().trim().startsWith(twoSlashes) && |
| 1136 sameLine(comment, previousToken); |
| 1137 |
| 1138 /// Emit this [comment], inserting leading whitespace if appropriate. |
| 1139 emitComment(Token comment, Token previousToken) { |
| 1140 if (!writer.currentLine.isWhitespace() && !isBlock(comment)) { |
| 1141 var ws = countSpacesBetween(previousToken, comment); |
| 1142 // Preserve one space but no more |
| 1143 if (ws > 0) { |
| 1144 space(); |
| 1145 } |
| 1146 } |
| 1147 |
| 1148 append(comment.toString().trim()); |
| 1149 } |
| 1150 |
| 1151 /// Test if this token is an EOF token. |
| 1152 bool isEOF(Token token) => token.type == TokenType.EOF; |
| 1153 |
| 1154 /// Count spaces between these tokens. Tokens on different lines return 0. |
| 1155 int countSpacesBetween(Token last, Token current) => isEOF(last) || |
| 1156 countNewlinesBetween(last, current) > 0 ? 0 : current.offset - last.end; |
| 1157 |
| 1139 /// Count the blanks between these two nodes. | 1158 /// Count the blanks between these two nodes. |
| 1140 int countBlankLinesBetween(ASTNode lastNode, ASTNode currentNode) => | 1159 int countBlankLinesBetween(ASTNode lastNode, ASTNode currentNode) => |
| 1141 countNewlinesBetween(lastNode.endToken, currentNode.beginToken); | 1160 countNewlinesBetween(lastNode.endToken, currentNode.beginToken); |
| 1142 | 1161 |
| 1143 /// Count newlines preceeding this [node]. | 1162 /// Count newlines preceeding this [node]. |
| 1144 int countPrecedingNewlines(ASTNode node) => | 1163 int countPrecedingNewlines(ASTNode node) => |
| 1145 countNewlinesBetween(node.beginToken.previous, node.beginToken); | 1164 countNewlinesBetween(node.beginToken.previous, node.beginToken); |
| 1146 | 1165 |
| 1147 /// Count newlines succeeding this [node]. | 1166 /// Count newlines succeeding this [node]. |
| 1148 int countSucceedingNewlines(ASTNode node) => node == null ? 0 : | 1167 int countSucceedingNewlines(ASTNode node) => node == null ? 0 : |
| 1149 countNewlinesBetween(node.endToken, node.endToken.next); | 1168 countNewlinesBetween(node.endToken, node.endToken.next); |
| 1150 | 1169 |
| 1151 /// Count the blanks between these two nodes. | 1170 /// Count the blanks between these two tokens. |
| 1152 int countNewlinesBetween(Token last, Token current) { | 1171 int countNewlinesBetween(Token last, Token current) { |
| 1153 if (last == null || current == null) { | 1172 if (last == null || current == null) { |
| 1154 return 0; | 1173 return 0; |
| 1155 } | 1174 } |
| 1156 | 1175 |
| 1157 return linesBetween(last.end - 1, current.offset); | 1176 return linesBetween(last.end - 1, current.offset); |
| 1158 } | 1177 } |
| 1178 |
| 1179 /// Calculate the newlines that should separate these comments. |
| 1180 int calculateNewlinesBetweenComments(Token last, Token current) { |
| 1181 // Insist on a newline after doc comments or single line comments |
| 1182 // (NOTE that EOL comments have already been processed). |
| 1183 if (isOldSingleLineDocComment(last) || isSingleLineComment(last)) { |
| 1184 return max(1, countNewlinesBetween(last, current)); |
| 1185 } else { |
| 1186 return countNewlinesBetween(last, current); |
| 1187 } |
| 1188 } |
| 1189 |
| 1190 /// Single line multi-line comments (e.g., '/** like this */'). |
| 1191 bool isOldSingleLineDocComment(Token comment) => |
| 1192 comment.lexeme.startsWith(r'/**') && singleLine(comment); |
| 1193 |
| 1194 /// Test if this [token] spans just one line. |
| 1195 bool singleLine(Token token) => linesBetween(token.offset, token.end) < 1; |
| 1159 | 1196 |
| 1160 /// Test if this [comment] is a block comment. | 1197 /// Test if token [first] is on the same line as [second]. |
| 1198 bool sameLine(Token first, Token second) => |
| 1199 countNewlinesBetween(first, second) == 0; |
| 1200 |
| 1201 /// Test if this is a multi-line [comment] (e.g., '/* ...' or '/** ...') |
| 1202 bool isMultiLineComment(Token comment) => |
| 1203 comment.type == TokenType.MULTI_LINE_COMMENT; |
| 1204 |
| 1205 /// Test if this is a single-line [comment] (e.g., '// ...') |
| 1206 bool isSingleLineComment(Token comment) => |
| 1207 comment.type == TokenType.SINGLE_LINE_COMMENT; |
| 1208 |
| 1209 /// Test if this [comment] is a block comment (e.g., '/* like this */').. |
| 1161 bool isBlock(Token comment) => | 1210 bool isBlock(Token comment) => |
| 1162 comment.type != TokenType.SINGLE_LINE_COMMENT && | 1211 isMultiLineComment(comment) && singleLine(comment); |
| 1163 linesBetween(comment.offset, comment.end) < 1; | |
| 1164 | 1212 |
| 1165 /// Count the lines between two offsets. | 1213 /// Count the lines between two offsets. |
| 1166 int linesBetween(int lastOffset, int currentOffset) { | 1214 int linesBetween(int lastOffset, int currentOffset) { |
| 1167 var lastLine = | 1215 var lastLine = |
| 1168 lineInfo.getLocation(lastOffset).lineNumber; | 1216 lineInfo.getLocation(lastOffset).lineNumber; |
| 1169 var currentLine = | 1217 var currentLine = |
| 1170 lineInfo.getLocation(currentOffset).lineNumber; | 1218 lineInfo.getLocation(currentOffset).lineNumber; |
| 1171 return currentLine - lastLine; | 1219 return currentLine - lastLine; |
| 1172 } | 1220 } |
| 1173 | 1221 |
| 1174 String toString() => writer.toString(); | 1222 String toString() => writer.toString(); |
| 1175 | 1223 |
| 1176 } | 1224 } |
| OLD | NEW |