Chromium Code Reviews| 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'; |
| 11 import 'package:analyzer_experimental/src/generated/scanner.dart'; | 11 import 'package:analyzer_experimental/src/generated/scanner.dart'; |
| 12 import 'package:analyzer_experimental/src/generated/source.dart'; | 12 import 'package:analyzer_experimental/src/generated/source.dart'; |
| 13 import 'package:analyzer_experimental/src/services/writer.dart'; | 13 import 'package:analyzer_experimental/src/services/writer.dart'; |
| 14 | 14 |
| 15 /// OS line separator. --- TODO(pquitslund): may not be necessary | |
| 16 const NEW_LINE = '\n' ; //Platform.pathSeparator; | |
| 17 | |
| 18 /// Formatter options. | 15 /// Formatter options. |
| 19 class FormatterOptions { | 16 class FormatterOptions { |
| 20 | 17 |
| 21 /// Create formatter options with defaults derived (where defined) from | 18 /// Create formatter options with defaults derived (where defined) from |
| 22 /// the style guide: <http://www.dartlang.org/articles/style-guide/>. | 19 /// the style guide: <http://www.dartlang.org/articles/style-guide/>. |
| 23 const FormatterOptions({this.initialIndentationLevel: 0, | 20 const FormatterOptions({this.initialIndentationLevel: 0, |
| 24 this.spacesPerIndent: 2, | 21 this.spacesPerIndent: 2, |
| 25 this.lineSeparator: NEW_LINE, | 22 this.lineSeparator: NEW_LINE, |
| 26 this.pageWidth: 80, | 23 this.pageWidth: 80, |
| 27 this.tabsForIndent: false, | 24 this.tabsForIndent: false, |
| 28 this.tabSize: 2}); | 25 this.tabSize: 2}); |
| 29 | 26 |
| 30 final String lineSeparator; | 27 final String lineSeparator; |
| 31 final int initialIndentationLevel; | 28 final int initialIndentationLevel; |
| 32 final int spacesPerIndent; | 29 final int spacesPerIndent; |
| 33 final int tabSize; | 30 final int tabSize; |
| 34 final bool tabsForIndent; | 31 final bool tabsForIndent; |
| 35 final int pageWidth; | 32 final int pageWidth; |
| 36 } | 33 } |
| 37 | 34 |
| 38 | 35 |
| 39 /// Thrown when an error occurs in formatting. | 36 /// Thrown when an error occurs in formatting. |
| 40 class FormatterException implements Exception { | 37 class FormatterException implements Exception { |
| 41 | 38 |
| 42 /// A message describing the error. | 39 /// A message describing the error. |
| 43 final String message; | 40 final String message; |
| 44 | 41 |
| 45 /// Creates a new FormatterException with an optional error [message]. | 42 /// Creates a new FormatterException with an optional error [message]. |
| 46 const FormatterException([this.message = '']); | 43 const FormatterException([this.message = 'FormatterException']); |
| 47 | 44 |
| 48 FormatterException.forError(List<AnalysisError> errors) : | 45 FormatterException.forError(List<AnalysisError> errors, [LineInfo line]) : |
| 49 // TODO(pquitslund): add descriptive message based on errors | 46 message = _createMessage(errors); |
| 50 message = 'an analysis error occured during format'; | |
| 51 | 47 |
| 52 String toString() => 'FormatterException: $message'; | 48 static String _createMessage(errors) { |
| 49 //TODO(pquitslund): consider a verbosity flag to add/suppress details | |
| 50 var errorCode = errors[0].errorCode; | |
| 51 var phase = errorCode is ParserErrorCode ? 'parsing' : 'scanning'; | |
| 52 return 'An error occured while ${phase} (${errorCode.name}).'; | |
| 53 } | |
| 54 | |
| 55 String toString() => '$message'; | |
| 53 } | 56 } |
| 54 | 57 |
| 55 /// Specifies the kind of code snippet to format. | 58 /// Specifies the kind of code snippet to format. |
| 56 class CodeKind { | 59 class CodeKind { |
| 57 | 60 |
| 58 final int _index; | 61 final int _index; |
| 59 | 62 |
| 60 const CodeKind._(this._index); | 63 const CodeKind._(this._index); |
| 61 | 64 |
| 62 /// A compilation unit snippet. | 65 /// A compilation unit snippet. |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 77 /// [source] string, optionally providing an [indentationLevel]. | 80 /// [source] string, optionally providing an [indentationLevel]. |
| 78 String format(CodeKind kind, String source, {int offset, int end, | 81 String format(CodeKind kind, String source, {int offset, int end, |
| 79 int indentationLevel: 0}); | 82 int indentationLevel: 0}); |
| 80 | 83 |
| 81 } | 84 } |
| 82 | 85 |
| 83 class CodeFormatterImpl implements CodeFormatter, AnalysisErrorListener { | 86 class CodeFormatterImpl implements CodeFormatter, AnalysisErrorListener { |
| 84 | 87 |
| 85 final FormatterOptions options; | 88 final FormatterOptions options; |
| 86 final errors = <AnalysisError>[]; | 89 final errors = <AnalysisError>[]; |
| 90 final whitespace = new RegExp(r'[\s]+'); | |
| 87 | 91 |
| 88 LineInfo lineInfo; | 92 LineInfo lineInfo; |
| 89 | 93 |
| 90 CodeFormatterImpl(this.options); | 94 CodeFormatterImpl(this.options); |
| 91 | 95 |
| 92 String format(CodeKind kind, String source, {int offset, int end, | 96 String format(CodeKind kind, String source, {int offset, int end, |
| 93 int indentationLevel: 0}) { | 97 int indentationLevel: 0}) { |
| 94 | 98 |
| 95 var start = tokenize(source); | 99 var start = tokenize(source); |
| 96 checkForErrors(); | 100 checkForErrors(); |
| 97 | 101 |
| 98 var node = parse(kind, start); | 102 var node = parse(kind, start); |
| 99 checkForErrors(); | 103 checkForErrors(); |
| 100 | 104 |
| 101 var formatter = new SourceVisitor(options, lineInfo); | 105 var formatter = new SourceVisitor(options, lineInfo); |
| 102 node.accept(formatter); | 106 node.accept(formatter); |
| 103 | 107 |
| 108 checkTokenStreams(start, tokenize(source)); | |
|
Brian Wilkerson
2013/09/09 22:21:47
It looks like you're testing to see whether the sc
pquitslund
2013/09/10 18:24:13
Hilarious. Looks like I did one undo too many! T
| |
| 109 | |
| 104 return formatter.writer.toString(); | 110 return formatter.writer.toString(); |
| 105 } | 111 } |
| 106 | 112 |
| 113 checkTokenStreams(Token t1, Token t2) => | |
| 114 new TokenStreamComparator(lineInfo, t1, t2).verifyEquals(); | |
| 115 | |
| 107 ASTNode parse(CodeKind kind, Token start) { | 116 ASTNode parse(CodeKind kind, Token start) { |
| 108 | 117 |
| 109 var parser = new Parser(null, this); | 118 var parser = new Parser(null, this); |
| 110 | 119 |
| 111 switch (kind) { | 120 switch (kind) { |
| 112 case CodeKind.COMPILATION_UNIT: | 121 case CodeKind.COMPILATION_UNIT: |
| 113 return parser.parseCompilationUnit(start); | 122 return parser.parseCompilationUnit(start); |
| 114 case CodeKind.STATEMENT: | 123 case CodeKind.STATEMENT: |
| 115 return parser.parseStatement(start); | 124 return parser.parseStatement(start); |
| 116 } | 125 } |
| 117 | 126 |
| 118 throw new FormatterException('Unsupported format kind: $kind'); | 127 throw new FormatterException('Unsupported format kind: $kind'); |
| 119 } | 128 } |
| 120 | 129 |
| 121 void checkForErrors() { | 130 checkForErrors() { |
| 122 if (errors.length > 0) { | 131 if (errors.length > 0) { |
| 123 throw new FormatterException.forError(errors); | 132 throw new FormatterException.forError(errors); |
| 124 } | 133 } |
| 125 } | 134 } |
| 126 | 135 |
| 127 void onError(AnalysisError error) { | 136 onError(AnalysisError error) { |
| 128 errors.add(error); | 137 errors.add(error); |
| 129 } | 138 } |
| 130 | 139 |
| 131 Token tokenize(String source) { | 140 Token tokenize(String source) { |
| 132 var scanner = new StringScanner(null, source, this); | 141 var scanner = new StringScanner(null, source, this); |
| 133 var token = scanner.tokenize(); | 142 var token = scanner.tokenize(); |
| 134 lineInfo = new LineInfo(scanner.lineStarts); | 143 lineInfo = new LineInfo(scanner.lineStarts); |
| 135 return token; | 144 return token; |
| 136 } | 145 } |
| 137 | 146 |
| 138 } | 147 } |
| 139 | 148 |
| 140 | 149 |
| 150 // Compares two token streams. Used for sanity checking formatted results. | |
| 151 class TokenStreamComparator { | |
| 152 | |
| 153 final LineInfo lineInfo; | |
| 154 Token token1, token2; | |
| 155 | |
| 156 TokenStreamComparator(this.lineInfo, this.token1, this.token2); | |
| 157 | |
| 158 /// Verify that these two token streams are equal. | |
| 159 verifyEquals() { | |
| 160 while (!isEOF(token1)) { | |
| 161 checkPrecedingComments(); | |
| 162 if (!checkTokens()) { | |
| 163 throwNotEqualException(token1, token2); | |
| 164 } | |
| 165 advance(); | |
| 166 | |
| 167 } | |
| 168 if (!isEOF(token2)) { | |
| 169 throw new FormatterException( | |
| 170 'Expected "EOF" but got "${token2}".'); | |
| 171 } | |
| 172 } | |
| 173 | |
| 174 checkPrecedingComments() { | |
| 175 var comment1 = token1.precedingComments; | |
| 176 var comment2 = token2.precedingComments; | |
| 177 while (comment1 != null) { | |
| 178 if (comment2 == null) { | |
| 179 throw new FormatterException( | |
| 180 'Expected comment, "${comment1}", at ${describeLocation(token1)}, ' | |
| 181 'but got none.'); | |
| 182 } | |
| 183 if (!equivalentComments(comment1, comment2)) { | |
| 184 throwNotEqualException(comment1, comment2); | |
| 185 } | |
| 186 comment1 = comment1.next; | |
| 187 comment2 = comment2.next; | |
| 188 } | |
| 189 if (comment2 != null) { | |
| 190 throw new FormatterException( | |
| 191 'Unexpected comment, "${comment2}", at ${describeLocation(token2)}.'); | |
| 192 } | |
| 193 } | |
| 194 | |
| 195 bool equivalentComments(Token comment1, Token comment2) => | |
| 196 comment1.lexeme.trim() == comment2.lexeme.trim(); | |
| 197 | |
| 198 throwNotEqualException(t1, t2) { | |
| 199 throw new FormatterException( | |
| 200 'Expected "${t1}" but got "${t2}", at ${describeLocation(t1)}.'); | |
| 201 } | |
| 202 | |
| 203 String describeLocation(Token token) => lineInfo == null ? '<unknown>' : | |
| 204 'Line: ${lineInfo.getLocation(token.offset).lineNumber}, ' | |
| 205 'Column: ${lineInfo.getLocation(token.offset).columnNumber}'; | |
| 206 | |
| 207 advance() { | |
| 208 token1 = token1.next; | |
| 209 token2 = token2.next; | |
| 210 } | |
| 211 | |
| 212 bool checkTokens() { | |
| 213 if (token1 == null || token2 == null) { | |
| 214 return false; | |
| 215 } | |
| 216 if (token1 == token2 || token1.lexeme == token2.lexeme) { | |
| 217 return true; | |
| 218 } | |
| 219 | |
| 220 // '[' ']' => '[]' | |
| 221 if (isOPEN_SQ_BRACKET(token1) && isCLOSE_SQUARE_BRACKET(token1.next)) { | |
| 222 if (isINDEX(token2)) { | |
| 223 token1 = token1.next; | |
| 224 return true; | |
| 225 } | |
| 226 } | |
| 227 // '>' '>' => '>>' | |
| 228 if (isGT(token1) && isGT(token1.next)) { | |
| 229 if (isGT_GT(token2)) { | |
| 230 token1 = token1.next; | |
| 231 return true; | |
| 232 } | |
| 233 } | |
| 234 | |
| 235 return false; | |
| 236 } | |
| 237 | |
| 238 } | |
| 239 | |
| 240 /// Test if this token is an EOF token. | |
| 241 bool isEOF(Token token) => token != null && token.type == TokenType.EOF; | |
| 242 | |
| 243 /// Test if this token is a GT token. | |
| 244 bool isGT(Token token) => token != null && token.type == TokenType.GT; | |
| 245 | |
| 246 /// Test if this token is a GT_GT token. | |
| 247 bool isGT_GT(Token token) => token != null && token.type == TokenType.GT_GT; | |
| 248 | |
| 249 /// Test if this token is an INDEX token. | |
| 250 bool isINDEX(Token token) => token != null && token.type == TokenType.INDEX; | |
| 251 | |
| 252 /// Test if this token is a OPEN_SQUARE_BRACKET token. | |
| 253 bool isOPEN_SQ_BRACKET(Token token) => | |
| 254 token != null && token.type == TokenType.OPEN_SQUARE_BRACKET; | |
| 255 | |
| 256 /// Test if this token is a CLOSE_SQUARE_BRACKET token. | |
| 257 bool isCLOSE_SQUARE_BRACKET(Token token) => | |
| 258 token != null && token.type == TokenType.CLOSE_SQUARE_BRACKET; | |
| 259 | |
|
Brian Wilkerson
2013/09/09 22:21:47
The parser uses a similar method, but passes the e
pquitslund
2013/09/10 18:24:13
Done.
| |
| 141 /// An AST visitor that drives formatting heuristics. | 260 /// An AST visitor that drives formatting heuristics. |
| 142 class SourceVisitor implements ASTVisitor { | 261 class SourceVisitor implements ASTVisitor { |
| 143 | 262 |
| 144 /// The writer to which the source is to be written. | 263 /// The writer to which the source is to be written. |
| 145 final SourceWriter writer; | 264 final SourceWriter writer; |
| 146 | 265 |
| 147 /// Cached line info for calculating blank lines. | 266 /// Cached line info for calculating blank lines. |
| 148 LineInfo lineInfo; | 267 LineInfo lineInfo; |
| 149 | 268 |
| 150 /// Cached previous token for calculating preceding whitespace. | 269 /// Cached previous token for calculating preceding whitespace. |
| 151 Token previousToken; | 270 Token previousToken; |
| 152 | 271 |
| 153 /// A flag to indicate that a newline should be emitted before the next token. | 272 /// A flag to indicate that a newline should be emitted before the next token. |
| 154 bool needsNewline = false; | 273 bool needsNewline = false; |
| 155 | 274 |
| 156 /// Used for matching EOL comments | 275 /// Used for matching EOL comments |
| 157 final twoSlashes = new RegExp(r'//[^/]'); | 276 final twoSlashes = new RegExp(r'//[^/]'); |
| 158 | 277 |
| 159 /// Initialize a newly created visitor to write source code representing | 278 /// Initialize a newly created visitor to write source code representing |
| 160 /// the visited nodes to the given [writer]. | 279 /// the visited nodes to the given [writer]. |
| 161 SourceVisitor(FormatterOptions options, this.lineInfo) : | 280 SourceVisitor(FormatterOptions options, this.lineInfo) : |
| 162 writer = new SourceWriter(indentCount: options.initialIndentationLevel, | 281 writer = new SourceWriter(indentCount: options.initialIndentationLevel, |
| 163 lineSeparator: options.lineSeparator); | 282 lineSeparator: options.lineSeparator); |
| 164 | 283 |
| 165 visitAdjacentStrings(AdjacentStrings node) { | 284 visitAdjacentStrings(AdjacentStrings node) { |
| 166 visitNodes(node.strings, separatedBy: space); | 285 visitNodes(node.strings, separatedBy: space); |
| 167 } | 286 } |
| 168 | 287 |
| (...skipping 913 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1082 /// Indent. | 1201 /// Indent. |
| 1083 indent() { | 1202 indent() { |
| 1084 writer.indent(); | 1203 writer.indent(); |
| 1085 } | 1204 } |
| 1086 | 1205 |
| 1087 /// Unindent | 1206 /// Unindent |
| 1088 unindent() { | 1207 unindent() { |
| 1089 writer.unindent(); | 1208 writer.unindent(); |
| 1090 } | 1209 } |
| 1091 | 1210 |
| 1092 | 1211 |
| 1093 /// Emit any detected comments and newlines or a minimum as specified | 1212 /// Emit any detected comments and newlines or a minimum as specified |
| 1094 /// by [min]. | 1213 /// by [min]. |
| 1095 int emitPrecedingCommentsAndNewlines(Token token, {min: 0}) { | 1214 int emitPrecedingCommentsAndNewlines(Token token, {min: 0}) { |
| 1096 | 1215 |
| 1097 var comment = token.precedingComments; | 1216 var comment = token.precedingComments; |
| 1098 var currentToken = comment != null ? comment : token; | 1217 var currentToken = comment != null ? comment : token; |
| 1099 | 1218 |
| 1100 //Handle EOLs before newlines | 1219 //Handle EOLs before newlines |
| 1101 if (isAtEOL(comment)) { | 1220 if (isAtEOL(comment)) { |
| 1102 emitComment(comment, previousToken); | 1221 emitComment(comment, previousToken); |
| 1103 comment = comment.next; | 1222 comment = comment.next; |
| 1104 currentToken = comment != null ? comment : token; | 1223 currentToken = comment != null ? comment : token; |
| 1105 } | 1224 } |
| 1106 | 1225 |
| 1107 var lines = max(min, countNewlinesBetween(previousToken, currentToken)); | 1226 var lines = max(min, countNewlinesBetween(previousToken, currentToken)); |
| 1108 writer.newlines(lines); | 1227 writer.newlines(lines); |
| 1109 | 1228 |
| 1110 var previousToken = currentToken.previous; | 1229 var previousToken = currentToken.previous; |
| 1111 | 1230 |
| 1112 while (comment != null) { | 1231 while (comment != null) { |
| 1113 | 1232 |
| 1114 emitComment(comment, previousToken); | 1233 emitComment(comment, previousToken); |
| 1115 | 1234 |
| 1116 var nextToken = comment.next != null ? comment.next : token; | 1235 var nextToken = comment.next != null ? comment.next : token; |
| 1117 var newlines = calculateNewlinesBetweenComments(comment, nextToken); | 1236 var newlines = calculateNewlinesBetweenComments(comment, nextToken); |
| 1118 if (newlines > 0) { | 1237 if (newlines > 0) { |
| 1119 writer.newlines(newlines); | 1238 writer.newlines(newlines); |
| 1120 lines += newlines; | 1239 lines += newlines; |
| 1121 } else if (!isEOF(token)) { | 1240 } else if (!isEOF(token)) { |
| 1122 space(); | 1241 space(); |
| 1123 } | 1242 } |
| 1124 | 1243 |
| 1125 previousToken = comment; | 1244 previousToken = comment; |
| 1126 comment = comment.next; | 1245 comment = comment.next; |
| 1127 } | 1246 } |
| 1128 | 1247 |
| 1129 previousToken = token; | 1248 previousToken = token; |
| 1130 return lines; | 1249 return lines; |
| 1131 } | 1250 } |
| 1132 | 1251 |
| 1133 /// Test if this [comment] is at the end of a line. | 1252 /// Test if this [comment] is at the end of a line. |
| 1134 bool isAtEOL(Token comment) => | 1253 bool isAtEOL(Token comment) => |
| 1135 comment != null && comment.toString().trim().startsWith(twoSlashes) && | 1254 comment != null && comment.toString().trim().startsWith(twoSlashes) && |
| 1136 sameLine(comment, previousToken); | 1255 sameLine(comment, previousToken); |
| 1137 | 1256 |
| 1138 /// Emit this [comment], inserting leading whitespace if appropriate. | 1257 /// Emit this [comment], inserting leading whitespace if appropriate. |
| 1139 emitComment(Token comment, Token previousToken) { | 1258 emitComment(Token comment, Token previousToken) { |
| 1140 if (!writer.currentLine.isWhitespace() && !isBlock(comment)) { | 1259 if (!writer.currentLine.isWhitespace() && !isBlock(comment)) { |
| 1141 var ws = countSpacesBetween(previousToken, comment); | 1260 var ws = countSpacesBetween(previousToken, comment); |
| 1142 // Preserve one space but no more | 1261 // Preserve one space but no more |
| 1143 if (ws > 0) { | 1262 if (ws > 0) { |
| 1144 space(); | 1263 space(); |
| 1145 } | 1264 } |
| 1146 } | 1265 } |
| 1147 | 1266 |
| 1148 append(comment.toString().trim()); | 1267 append(comment.toString().trim()); |
| 1149 } | 1268 } |
| 1150 | 1269 |
| 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. | 1270 /// Count spaces between these tokens. Tokens on different lines return 0. |
| 1155 int countSpacesBetween(Token last, Token current) => isEOF(last) || | 1271 int countSpacesBetween(Token last, Token current) => isEOF(last) || |
| 1156 countNewlinesBetween(last, current) > 0 ? 0 : current.offset - last.end; | 1272 countNewlinesBetween(last, current) > 0 ? 0 : current.offset - last.end; |
| 1157 | 1273 |
| 1158 /// Count the blanks between these two nodes. | 1274 /// Count the blanks between these two nodes. |
| 1159 int countBlankLinesBetween(ASTNode lastNode, ASTNode currentNode) => | 1275 int countBlankLinesBetween(ASTNode lastNode, ASTNode currentNode) => |
| 1160 countNewlinesBetween(lastNode.endToken, currentNode.beginToken); | 1276 countNewlinesBetween(lastNode.endToken, currentNode.beginToken); |
| 1161 | 1277 |
| 1162 /// Count newlines preceeding this [node]. | 1278 /// Count newlines preceeding this [node]. |
| 1163 int countPrecedingNewlines(ASTNode node) => | 1279 int countPrecedingNewlines(ASTNode node) => |
| 1164 countNewlinesBetween(node.beginToken.previous, node.beginToken); | 1280 countNewlinesBetween(node.beginToken.previous, node.beginToken); |
| 1165 | 1281 |
| 1166 /// Count newlines succeeding this [node]. | 1282 /// Count newlines succeeding this [node]. |
| 1167 int countSucceedingNewlines(ASTNode node) => node == null ? 0 : | 1283 int countSucceedingNewlines(ASTNode node) => node == null ? 0 : |
| 1168 countNewlinesBetween(node.endToken, node.endToken.next); | 1284 countNewlinesBetween(node.endToken, node.endToken.next); |
| 1169 | 1285 |
| 1170 /// Count the blanks between these two tokens. | 1286 /// Count the blanks between these two tokens. |
| 1171 int countNewlinesBetween(Token last, Token current) { | 1287 int countNewlinesBetween(Token last, Token current) { |
| 1172 if (last == null || current == null) { | 1288 if (last == null || current == null) { |
| 1173 return 0; | 1289 return 0; |
| 1174 } | 1290 } |
| 1175 | 1291 |
| 1176 return linesBetween(last.end - 1, current.offset); | 1292 return linesBetween(last.end - 1, current.offset); |
| 1177 } | 1293 } |
| 1178 | 1294 |
| 1179 /// Calculate the newlines that should separate these comments. | 1295 /// Calculate the newlines that should separate these comments. |
| 1180 int calculateNewlinesBetweenComments(Token last, Token current) { | 1296 int calculateNewlinesBetweenComments(Token last, Token current) { |
| 1181 // Insist on a newline after doc comments or single line comments | 1297 // Insist on a newline after doc comments or single line comments |
| 1182 // (NOTE that EOL comments have already been processed). | 1298 // (NOTE that EOL comments have already been processed). |
| 1183 if (isOldSingleLineDocComment(last) || isSingleLineComment(last)) { | 1299 if (isOldSingleLineDocComment(last) || isSingleLineComment(last)) { |
| 1184 return max(1, countNewlinesBetween(last, current)); | 1300 return max(1, countNewlinesBetween(last, current)); |
| 1185 } else { | 1301 } else { |
| 1186 return countNewlinesBetween(last, current); | 1302 return countNewlinesBetween(last, current); |
| 1187 } | 1303 } |
| 1188 } | 1304 } |
| 1189 | 1305 |
| 1190 /// Single line multi-line comments (e.g., '/** like this */'). | 1306 /// Single line multi-line comments (e.g., '/** like this */'). |
| 1191 bool isOldSingleLineDocComment(Token comment) => | 1307 bool isOldSingleLineDocComment(Token comment) => |
| 1192 comment.lexeme.startsWith(r'/**') && singleLine(comment); | 1308 comment.lexeme.startsWith(r'/**') && singleLine(comment); |
| 1193 | 1309 |
| 1194 /// Test if this [token] spans just one line. | 1310 /// Test if this [token] spans just one line. |
| 1195 bool singleLine(Token token) => linesBetween(token.offset, token.end) < 1; | 1311 bool singleLine(Token token) => linesBetween(token.offset, token.end) < 1; |
| 1196 | 1312 |
| 1197 /// Test if token [first] is on the same line as [second]. | 1313 /// Test if token [first] is on the same line as [second]. |
| 1198 bool sameLine(Token first, Token second) => | 1314 bool sameLine(Token first, Token second) => |
| 1199 countNewlinesBetween(first, second) == 0; | 1315 countNewlinesBetween(first, second) == 0; |
| 1200 | 1316 |
| 1201 /// Test if this is a multi-line [comment] (e.g., '/* ...' or '/** ...') | 1317 /// Test if this is a multi-line [comment] (e.g., '/* ...' or '/** ...') |
| 1202 bool isMultiLineComment(Token comment) => | 1318 bool isMultiLineComment(Token comment) => |
| 1203 comment.type == TokenType.MULTI_LINE_COMMENT; | 1319 comment.type == TokenType.MULTI_LINE_COMMENT; |
| 1204 | 1320 |
| 1205 /// Test if this is a single-line [comment] (e.g., '// ...') | 1321 /// Test if this is a single-line [comment] (e.g., '// ...') |
| 1206 bool isSingleLineComment(Token comment) => | 1322 bool isSingleLineComment(Token comment) => |
| 1207 comment.type == TokenType.SINGLE_LINE_COMMENT; | 1323 comment.type == TokenType.SINGLE_LINE_COMMENT; |
| 1208 | 1324 |
| 1209 /// Test if this [comment] is a block comment (e.g., '/* like this */').. | 1325 /// Test if this [comment] is a block comment (e.g., '/* like this */').. |
| 1210 bool isBlock(Token comment) => | 1326 bool isBlock(Token comment) => |
| 1211 isMultiLineComment(comment) && singleLine(comment); | 1327 isMultiLineComment(comment) && singleLine(comment); |
| 1212 | 1328 |
| 1213 /// Count the lines between two offsets. | 1329 /// Count the lines between two offsets. |
| 1214 int linesBetween(int lastOffset, int currentOffset) { | 1330 int linesBetween(int lastOffset, int currentOffset) { |
| 1215 var lastLine = | 1331 var lastLine = |
| 1216 lineInfo.getLocation(lastOffset).lineNumber; | 1332 lineInfo.getLocation(lastOffset).lineNumber; |
| 1217 var currentLine = | 1333 var currentLine = |
| 1218 lineInfo.getLocation(currentOffset).lineNumber; | 1334 lineInfo.getLocation(currentOffset).lineNumber; |
| 1219 return currentLine - lastLine; | 1335 return currentLine - lastLine; |
| 1220 } | 1336 } |
| 1221 | 1337 |
| 1222 String toString() => writer.toString(); | 1338 String toString() => writer.toString(); |
| 1223 | 1339 |
| 1224 } | 1340 } |
| OLD | NEW |