| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library formatter_impl; | |
| 6 | |
| 7 import 'dart:math'; | |
| 8 | |
| 9 import 'package:analyzer/analyzer.dart'; | |
| 10 import 'package:analyzer/src/generated/parser.dart'; | |
| 11 import 'package:analyzer/src/generated/scanner.dart'; | |
| 12 import 'package:analyzer/src/generated/source.dart'; | |
| 13 import 'package:analyzer/src/services/writer.dart'; | |
| 14 | |
| 15 /// Formatter options. | |
| 16 class FormatterOptions { | |
| 17 | |
| 18 /// Create formatter options with defaults derived (where defined) from | |
| 19 /// the style guide: <http://www.dartlang.org/articles/style-guide/>. | |
| 20 const FormatterOptions({this.initialIndentationLevel: 0, | |
| 21 this.spacesPerIndent: 2, this.lineSeparator: NEW_LINE, this.pageWidth: 80, | |
| 22 this.tabsForIndent: false, this.tabSize: 2, this.codeTransforms: false}); | |
| 23 | |
| 24 final String lineSeparator; | |
| 25 final int initialIndentationLevel; | |
| 26 final int spacesPerIndent; | |
| 27 final int tabSize; | |
| 28 final bool tabsForIndent; | |
| 29 final int pageWidth; | |
| 30 final bool codeTransforms; | |
| 31 } | |
| 32 | |
| 33 /// Thrown when an error occurs in formatting. | |
| 34 class FormatterException implements Exception { | |
| 35 | |
| 36 /// A message describing the error. | |
| 37 final String message; | |
| 38 | |
| 39 /// Creates a new FormatterException with an optional error [message]. | |
| 40 const FormatterException([this.message = 'FormatterException']); | |
| 41 | |
| 42 FormatterException.forError(List<AnalysisError> errors, [LineInfo line]) | |
| 43 : message = _createMessage(errors); | |
| 44 | |
| 45 static String _createMessage(errors) { | |
| 46 //TODO(pquitslund): consider a verbosity flag to add/suppress details | |
| 47 var errorCode = errors[0].errorCode; | |
| 48 var phase = errorCode is ParserErrorCode ? 'parsing' : 'scanning'; | |
| 49 return 'An error occured while $phase (${errorCode.name}).'; | |
| 50 } | |
| 51 | |
| 52 String toString() => '$message'; | |
| 53 } | |
| 54 | |
| 55 /// Specifies the kind of code snippet to format. | |
| 56 class CodeKind { | |
| 57 final int ordinal; | |
| 58 | |
| 59 const CodeKind._(this.ordinal); | |
| 60 | |
| 61 /// A compilation unit snippet. | |
| 62 static const COMPILATION_UNIT = const CodeKind._(0); | |
| 63 | |
| 64 /// A statement snippet. | |
| 65 static const STATEMENT = const CodeKind._(1); | |
| 66 } | |
| 67 | |
| 68 /// Dart source code formatter. | |
| 69 abstract class CodeFormatter { | |
| 70 factory CodeFormatter( | |
| 71 [FormatterOptions options = const FormatterOptions()]) => | |
| 72 new CodeFormatterImpl(options); | |
| 73 | |
| 74 /// Format the specified portion (from [offset] with [length]) of the given | |
| 75 /// [source] string, optionally providing an [indentationLevel]. | |
| 76 FormattedSource format(CodeKind kind, String source, {int offset, int end, | |
| 77 int indentationLevel: 0, Selection selection: null}); | |
| 78 } | |
| 79 | |
| 80 /// Source selection state information. | |
| 81 class Selection { | |
| 82 | |
| 83 /// The offset of the source selection. | |
| 84 final int offset; | |
| 85 | |
| 86 /// The length of the selection. | |
| 87 final int length; | |
| 88 | |
| 89 Selection(this.offset, this.length); | |
| 90 | |
| 91 String toString() => 'Selection (offset: $offset, length: $length)'; | |
| 92 } | |
| 93 | |
| 94 /// Formatted source. | |
| 95 class FormattedSource { | |
| 96 | |
| 97 /// Selection state or null if unspecified. | |
| 98 Selection selection; | |
| 99 | |
| 100 /// Formatted source string. | |
| 101 final String source; | |
| 102 | |
| 103 /// Create a formatted [source] result, with optional [selection] information. | |
| 104 FormattedSource(this.source, [this.selection = null]); | |
| 105 } | |
| 106 | |
| 107 class CodeFormatterImpl implements CodeFormatter, AnalysisErrorListener { | |
| 108 final FormatterOptions options; | |
| 109 final errors = <AnalysisError>[]; | |
| 110 final whitespace = new RegExp(r'[\s]+'); | |
| 111 | |
| 112 LineInfo lineInfo; | |
| 113 | |
| 114 CodeFormatterImpl(this.options); | |
| 115 | |
| 116 FormattedSource format(CodeKind kind, String source, {int offset, int end, | |
| 117 int indentationLevel: 0, Selection selection: null}) { | |
| 118 var startToken = tokenize(source); | |
| 119 checkForErrors(); | |
| 120 | |
| 121 var node = parse(kind, startToken); | |
| 122 checkForErrors(); | |
| 123 | |
| 124 var formatter = new SourceVisitor(options, lineInfo, source, selection); | |
| 125 node.accept(formatter); | |
| 126 | |
| 127 var formattedSource = formatter.writer.toString(); | |
| 128 | |
| 129 checkTokenStreams(startToken, tokenize(formattedSource), | |
| 130 allowTransforms: options.codeTransforms); | |
| 131 | |
| 132 return new FormattedSource(formattedSource, formatter.selection); | |
| 133 } | |
| 134 | |
| 135 checkTokenStreams(Token t1, Token t2, {allowTransforms: false}) => | |
| 136 new TokenStreamComparator(lineInfo, t1, t2, transforms: allowTransforms) | |
| 137 .verifyEquals(); | |
| 138 | |
| 139 AstNode parse(CodeKind kind, Token start) { | |
| 140 var parser = new Parser(null, this); | |
| 141 | |
| 142 switch (kind) { | |
| 143 case CodeKind.COMPILATION_UNIT: | |
| 144 return parser.parseCompilationUnit(start); | |
| 145 case CodeKind.STATEMENT: | |
| 146 return parser.parseStatement(start); | |
| 147 } | |
| 148 | |
| 149 throw new FormatterException('Unsupported format kind: $kind'); | |
| 150 } | |
| 151 | |
| 152 checkForErrors() { | |
| 153 if (errors.length > 0) { | |
| 154 throw new FormatterException.forError(errors); | |
| 155 } | |
| 156 } | |
| 157 | |
| 158 onError(AnalysisError error) { | |
| 159 errors.add(error); | |
| 160 } | |
| 161 | |
| 162 Token tokenize(String source) { | |
| 163 var reader = new CharSequenceReader(source); | |
| 164 var scanner = new Scanner(null, reader, this); | |
| 165 var token = scanner.tokenize(); | |
| 166 lineInfo = new LineInfo(scanner.lineStarts); | |
| 167 return token; | |
| 168 } | |
| 169 } | |
| 170 | |
| 171 // Compares two token streams. Used for sanity checking formatted results. | |
| 172 class TokenStreamComparator { | |
| 173 final LineInfo lineInfo; | |
| 174 Token token1, token2; | |
| 175 bool allowTransforms; | |
| 176 | |
| 177 TokenStreamComparator(this.lineInfo, this.token1, this.token2, | |
| 178 {transforms: false}) | |
| 179 : this.allowTransforms = transforms; | |
| 180 | |
| 181 /// Verify that these two token streams are equal. | |
| 182 verifyEquals() { | |
| 183 while (!isEOF(token1)) { | |
| 184 checkPrecedingComments(); | |
| 185 if (!checkTokens()) { | |
| 186 throwNotEqualException(token1, token2); | |
| 187 } | |
| 188 advance(); | |
| 189 } | |
| 190 // TODO(pquitslund): consider a better way to notice trailing synthetics | |
| 191 if (!isEOF(token2) && | |
| 192 !(isCLOSE_CURLY_BRACKET(token2) && isEOF(token2.next))) { | |
| 193 throw new FormatterException('Expected "EOF" but got "$token2".'); | |
| 194 } | |
| 195 } | |
| 196 | |
| 197 checkPrecedingComments() { | |
| 198 var comment1 = token1.precedingComments; | |
| 199 var comment2 = token2.precedingComments; | |
| 200 while (comment1 != null) { | |
| 201 if (comment2 == null) { | |
| 202 throw new FormatterException( | |
| 203 'Expected comment, "$comment1", at ${describeLocation(token1)}, ' | |
| 204 'but got none.'); | |
| 205 } | |
| 206 if (!equivalentComments(comment1, comment2)) { | |
| 207 throwNotEqualException(comment1, comment2); | |
| 208 } | |
| 209 comment1 = comment1.next; | |
| 210 comment2 = comment2.next; | |
| 211 } | |
| 212 if (comment2 != null) { | |
| 213 throw new FormatterException( | |
| 214 'Unexpected comment, "$comment2", at ${describeLocation(token2)}.'); | |
| 215 } | |
| 216 } | |
| 217 | |
| 218 bool equivalentComments(Token comment1, Token comment2) => | |
| 219 comment1.lexeme.trim() == comment2.lexeme.trim(); | |
| 220 | |
| 221 throwNotEqualException(t1, t2) { | |
| 222 throw new FormatterException( | |
| 223 'Expected "$t1" but got "$t2", at ${describeLocation(t1)}.'); | |
| 224 } | |
| 225 | |
| 226 String describeLocation(Token token) => lineInfo == null | |
| 227 ? '<unknown>' | |
| 228 : 'Line: ${lineInfo.getLocation(token.offset).lineNumber}, ' | |
| 229 'Column: ${lineInfo.getLocation(token.offset).columnNumber}'; | |
| 230 | |
| 231 advance() { | |
| 232 token1 = token1.next; | |
| 233 token2 = token2.next; | |
| 234 } | |
| 235 | |
| 236 bool checkTokens() { | |
| 237 if (token1 == null || token2 == null) { | |
| 238 return false; | |
| 239 } | |
| 240 if (token1 == token2 || token1.lexeme == token2.lexeme) { | |
| 241 return true; | |
| 242 } | |
| 243 | |
| 244 // '[' ']' => '[]' | |
| 245 if (isOPEN_SQ_BRACKET(token1) && isCLOSE_SQUARE_BRACKET(token1.next)) { | |
| 246 if (isINDEX(token2)) { | |
| 247 token1 = token1.next; | |
| 248 return true; | |
| 249 } | |
| 250 } | |
| 251 // '>' '>' => '>>' | |
| 252 if (isGT(token1) && isGT(token1.next)) { | |
| 253 if (isGT_GT(token2)) { | |
| 254 token1 = token1.next; | |
| 255 return true; | |
| 256 } | |
| 257 } | |
| 258 // Cons(){} => Cons(); | |
| 259 if (isOPEN_CURLY_BRACKET(token1) && isCLOSE_CURLY_BRACKET(token1.next)) { | |
| 260 if (isSEMICOLON(token2)) { | |
| 261 token1 = token1.next; | |
| 262 advance(); | |
| 263 return true; | |
| 264 } | |
| 265 } | |
| 266 | |
| 267 // Transform-related special casing | |
| 268 if (allowTransforms) { | |
| 269 | |
| 270 // Advance past empty statements | |
| 271 if (isSEMICOLON(token1)) { | |
| 272 // TODO whitelist | |
| 273 token1 = token1.next; | |
| 274 return checkTokens(); | |
| 275 } | |
| 276 | |
| 277 // Advance past synthetic { } tokens | |
| 278 if (isOPEN_CURLY_BRACKET(token2) || isCLOSE_CURLY_BRACKET(token2)) { | |
| 279 token2 = token2.next; | |
| 280 return checkTokens(); | |
| 281 } | |
| 282 } | |
| 283 | |
| 284 return false; | |
| 285 } | |
| 286 } | |
| 287 | |
| 288 /// Test for token type. | |
| 289 bool tokenIs(Token token, TokenType type) => | |
| 290 token != null && token.type == type; | |
| 291 | |
| 292 /// Test if this token is an EOF token. | |
| 293 bool isEOF(Token token) => tokenIs(token, TokenType.EOF); | |
| 294 | |
| 295 /// Test if this token is a GT token. | |
| 296 bool isGT(Token token) => tokenIs(token, TokenType.GT); | |
| 297 | |
| 298 /// Test if this token is a GT_GT token. | |
| 299 bool isGT_GT(Token token) => tokenIs(token, TokenType.GT_GT); | |
| 300 | |
| 301 /// Test if this token is an INDEX token. | |
| 302 bool isINDEX(Token token) => tokenIs(token, TokenType.INDEX); | |
| 303 | |
| 304 /// Test if this token is a OPEN_CURLY_BRACKET token. | |
| 305 bool isOPEN_CURLY_BRACKET(Token token) => | |
| 306 tokenIs(token, TokenType.OPEN_CURLY_BRACKET); | |
| 307 | |
| 308 /// Test if this token is a CLOSE_CURLY_BRACKET token. | |
| 309 bool isCLOSE_CURLY_BRACKET(Token token) => | |
| 310 tokenIs(token, TokenType.CLOSE_CURLY_BRACKET); | |
| 311 | |
| 312 /// Test if this token is a OPEN_SQUARE_BRACKET token. | |
| 313 bool isOPEN_SQ_BRACKET(Token token) => | |
| 314 tokenIs(token, TokenType.OPEN_SQUARE_BRACKET); | |
| 315 | |
| 316 /// Test if this token is a CLOSE_SQUARE_BRACKET token. | |
| 317 bool isCLOSE_SQUARE_BRACKET(Token token) => | |
| 318 tokenIs(token, TokenType.CLOSE_SQUARE_BRACKET); | |
| 319 | |
| 320 /// Test if this token is a SEMICOLON token. | |
| 321 bool isSEMICOLON(Token token) => tokenIs(token, TokenType.SEMICOLON); | |
| 322 | |
| 323 /// An AST visitor that drives formatting heuristics. | |
| 324 class SourceVisitor implements AstVisitor { | |
| 325 static final OPEN_CURLY = syntheticToken(TokenType.OPEN_CURLY_BRACKET, '{'); | |
| 326 static final CLOSE_CURLY = syntheticToken(TokenType.CLOSE_CURLY_BRACKET, '}'); | |
| 327 static final SEMI_COLON = syntheticToken(TokenType.SEMICOLON, ';'); | |
| 328 | |
| 329 static const SYNTH_OFFSET = -13; | |
| 330 | |
| 331 static StringToken syntheticToken(TokenType type, String value) => | |
| 332 new StringToken(type, value, SYNTH_OFFSET); | |
| 333 | |
| 334 static bool isSynthetic(Token token) => token.offset == SYNTH_OFFSET; | |
| 335 | |
| 336 /// The writer to which the source is to be written. | |
| 337 final SourceWriter writer; | |
| 338 | |
| 339 /// Cached line info for calculating blank lines. | |
| 340 LineInfo lineInfo; | |
| 341 | |
| 342 /// Cached previous token for calculating preceding whitespace. | |
| 343 Token previousToken; | |
| 344 | |
| 345 /// A flag to indicate that a newline should be emitted before the next token. | |
| 346 bool needsNewline = false; | |
| 347 | |
| 348 /// A flag to indicate that user introduced newlines should be emitted before | |
| 349 /// the next token. | |
| 350 bool preserveNewlines = false; | |
| 351 | |
| 352 /// A counter for spaces that should be emitted preceding the next token. | |
| 353 int leadingSpaces = 0; | |
| 354 | |
| 355 /// A flag to specify whether line-leading spaces should be preserved (and | |
| 356 /// addded to the indent level). | |
| 357 bool allowLineLeadingSpaces; | |
| 358 | |
| 359 /// A flag to specify whether zero-length spaces should be emmitted. | |
| 360 bool emitEmptySpaces = false; | |
| 361 | |
| 362 /// Used for matching EOL comments | |
| 363 final twoSlashes = new RegExp(r'//[^/]'); | |
| 364 | |
| 365 /// A weight for potential breakpoints. | |
| 366 int currentBreakWeight = DEFAULT_SPACE_WEIGHT; | |
| 367 | |
| 368 /// The last issued space weight. | |
| 369 int lastSpaceWeight = 0; | |
| 370 | |
| 371 /// Original pre-format selection information (may be null). | |
| 372 final Selection preSelection; | |
| 373 | |
| 374 final bool codeTransforms; | |
| 375 | |
| 376 /// The source being formatted (used in interpolation handling) | |
| 377 final String source; | |
| 378 | |
| 379 /// Post format selection information. | |
| 380 Selection selection; | |
| 381 | |
| 382 /// Initialize a newly created visitor to write source code representing | |
| 383 /// the visited nodes to the given [writer]. | |
| 384 SourceVisitor( | |
| 385 FormatterOptions options, this.lineInfo, this.source, this.preSelection) | |
| 386 : writer = new SourceWriter( | |
| 387 indentCount: options.initialIndentationLevel, | |
| 388 lineSeparator: options.lineSeparator, | |
| 389 maxLineLength: options.pageWidth, | |
| 390 useTabs: options.tabsForIndent, | |
| 391 spacesPerIndent: options.spacesPerIndent), | |
| 392 codeTransforms = options.codeTransforms; | |
| 393 | |
| 394 visitAdjacentStrings(AdjacentStrings node) { | |
| 395 visitNodes(node.strings, separatedBy: space); | |
| 396 } | |
| 397 | |
| 398 visitAnnotation(Annotation node) { | |
| 399 token(node.atSign); | |
| 400 visit(node.name); | |
| 401 token(node.period); | |
| 402 visit(node.constructorName); | |
| 403 visit(node.arguments); | |
| 404 } | |
| 405 | |
| 406 visitArgumentList(ArgumentList node) { | |
| 407 token(node.leftParenthesis); | |
| 408 if (node.arguments.isNotEmpty) { | |
| 409 int weight = lastSpaceWeight++; | |
| 410 levelSpace(weight, 0); | |
| 411 visitCommaSeparatedNodes(node.arguments, | |
| 412 followedBy: () => levelSpace(weight)); | |
| 413 } | |
| 414 token(node.rightParenthesis); | |
| 415 } | |
| 416 | |
| 417 visitAsExpression(AsExpression node) { | |
| 418 visit(node.expression); | |
| 419 space(); | |
| 420 token(node.asOperator); | |
| 421 space(); | |
| 422 visit(node.type); | |
| 423 } | |
| 424 | |
| 425 visitAssertStatement(AssertStatement node) { | |
| 426 token(node.assertKeyword); | |
| 427 token(node.leftParenthesis); | |
| 428 visit(node.condition); | |
| 429 token(node.rightParenthesis); | |
| 430 token(node.semicolon); | |
| 431 } | |
| 432 | |
| 433 visitAssignmentExpression(AssignmentExpression node) { | |
| 434 visit(node.leftHandSide); | |
| 435 space(); | |
| 436 token(node.operator); | |
| 437 allowContinuedLines(() { | |
| 438 levelSpace(SINGLE_SPACE_WEIGHT); | |
| 439 visit(node.rightHandSide); | |
| 440 }); | |
| 441 } | |
| 442 | |
| 443 @override | |
| 444 visitAwaitExpression(AwaitExpression node) { | |
| 445 token(node.awaitKeyword); | |
| 446 space(); | |
| 447 visit(node.expression); | |
| 448 } | |
| 449 | |
| 450 visitBinaryExpression(BinaryExpression node) { | |
| 451 Token operator = node.operator; | |
| 452 TokenType operatorType = operator.type; | |
| 453 int addOperands(List<Expression> operands, Expression e, int i) { | |
| 454 if (e is BinaryExpression && e.operator.type == operatorType) { | |
| 455 i = addOperands(operands, e.leftOperand, i); | |
| 456 i = addOperands(operands, e.rightOperand, i); | |
| 457 } else { | |
| 458 operands.insert(i++, e); | |
| 459 } | |
| 460 return i; | |
| 461 } | |
| 462 List<Expression> operands = []; | |
| 463 addOperands(operands, node.leftOperand, 0); | |
| 464 addOperands(operands, node.rightOperand, operands.length); | |
| 465 int weight = lastSpaceWeight++; | |
| 466 for (int i = 0; i < operands.length; i++) { | |
| 467 if (i != 0) { | |
| 468 space(); | |
| 469 token(operator); | |
| 470 levelSpace(weight); | |
| 471 } | |
| 472 visit(operands[i]); | |
| 473 } | |
| 474 } | |
| 475 | |
| 476 visitBlock(Block node) { | |
| 477 token(node.leftBracket); | |
| 478 indent(); | |
| 479 if (!node.statements.isEmpty) { | |
| 480 visitNodes(node.statements, precededBy: newlines, separatedBy: newlines); | |
| 481 newlines(); | |
| 482 } else { | |
| 483 preserveLeadingNewlines(); | |
| 484 } | |
| 485 token(node.rightBracket, precededBy: unindent); | |
| 486 } | |
| 487 | |
| 488 visitBlockFunctionBody(BlockFunctionBody node) { | |
| 489 // sync[*] or async[*] | |
| 490 token(node.keyword); | |
| 491 token(node.star); | |
| 492 if (node.keyword != null) { | |
| 493 nonBreakingSpace(); | |
| 494 } | |
| 495 | |
| 496 visit(node.block); | |
| 497 } | |
| 498 | |
| 499 visitBooleanLiteral(BooleanLiteral node) { | |
| 500 token(node.literal); | |
| 501 } | |
| 502 | |
| 503 visitBreakStatement(BreakStatement node) { | |
| 504 token(node.breakKeyword); | |
| 505 visitNode(node.label, precededBy: space); | |
| 506 token(node.semicolon); | |
| 507 } | |
| 508 | |
| 509 visitCascadeExpression(CascadeExpression node) { | |
| 510 visit(node.target); | |
| 511 indent(2); | |
| 512 // Single cascades do not force a linebreak (dartbug.com/16384) | |
| 513 if (node.cascadeSections.length > 1) { | |
| 514 newlines(); | |
| 515 } | |
| 516 visitNodes(node.cascadeSections, separatedBy: newlines); | |
| 517 unindent(2); | |
| 518 } | |
| 519 | |
| 520 visitCatchClause(CatchClause node) { | |
| 521 token(node.onKeyword, followedBy: space); | |
| 522 visit(node.exceptionType); | |
| 523 | |
| 524 if (node.catchKeyword != null) { | |
| 525 if (node.exceptionType != null) { | |
| 526 space(); | |
| 527 } | |
| 528 token(node.catchKeyword); | |
| 529 space(); | |
| 530 token(node.leftParenthesis); | |
| 531 visit(node.exceptionParameter); | |
| 532 token(node.comma, followedBy: space); | |
| 533 visit(node.stackTraceParameter); | |
| 534 token(node.rightParenthesis); | |
| 535 space(); | |
| 536 } else { | |
| 537 space(); | |
| 538 } | |
| 539 visit(node.body); | |
| 540 } | |
| 541 | |
| 542 visitClassDeclaration(ClassDeclaration node) { | |
| 543 preserveLeadingNewlines(); | |
| 544 visitMemberMetadata(node.metadata); | |
| 545 modifier(node.abstractKeyword); | |
| 546 token(node.classKeyword); | |
| 547 space(); | |
| 548 visit(node.name); | |
| 549 allowContinuedLines(() { | |
| 550 visit(node.typeParameters); | |
| 551 visitNode(node.extendsClause, precededBy: space); | |
| 552 visitNode(node.withClause, precededBy: space); | |
| 553 visitNode(node.implementsClause, precededBy: space); | |
| 554 visitNode(node.nativeClause, precededBy: space); | |
| 555 space(); | |
| 556 }); | |
| 557 token(node.leftBracket); | |
| 558 indent(); | |
| 559 if (!node.members.isEmpty) { | |
| 560 visitNodes(node.members, precededBy: newlines, separatedBy: newlines); | |
| 561 newlines(); | |
| 562 } else { | |
| 563 preserveLeadingNewlines(); | |
| 564 } | |
| 565 token(node.rightBracket, precededBy: unindent); | |
| 566 } | |
| 567 | |
| 568 visitClassTypeAlias(ClassTypeAlias node) { | |
| 569 preserveLeadingNewlines(); | |
| 570 visitMemberMetadata(node.metadata); | |
| 571 modifier(node.abstractKeyword); | |
| 572 token(node.typedefKeyword); | |
| 573 space(); | |
| 574 visit(node.name); | |
| 575 visit(node.typeParameters); | |
| 576 space(); | |
| 577 token(node.equals); | |
| 578 space(); | |
| 579 visit(node.superclass); | |
| 580 visitNode(node.withClause, precededBy: space); | |
| 581 visitNode(node.implementsClause, precededBy: space); | |
| 582 token(node.semicolon); | |
| 583 } | |
| 584 | |
| 585 visitComment(Comment node) => null; | |
| 586 | |
| 587 visitCommentReference(CommentReference node) => null; | |
| 588 | |
| 589 visitCompilationUnit(CompilationUnit node) { | |
| 590 | |
| 591 // Cache EOF for leading whitespace calculation | |
| 592 var start = node.beginToken.previous; | |
| 593 if (start != null && start.type is TokenType_EOF) { | |
| 594 previousToken = start; | |
| 595 } | |
| 596 | |
| 597 var scriptTag = node.scriptTag; | |
| 598 var directives = node.directives; | |
| 599 visit(scriptTag); | |
| 600 | |
| 601 visitNodes(directives, separatedBy: newlines, followedBy: newlines); | |
| 602 | |
| 603 visitNodes(node.declarations, separatedBy: newlines); | |
| 604 | |
| 605 preserveLeadingNewlines(); | |
| 606 | |
| 607 // Handle trailing whitespace | |
| 608 token(node.endToken /* EOF */); | |
| 609 | |
| 610 // Be a good citizen, end with a NL | |
| 611 ensureTrailingNewline(); | |
| 612 } | |
| 613 | |
| 614 visitConditionalExpression(ConditionalExpression node) { | |
| 615 int weight = lastSpaceWeight++; | |
| 616 visit(node.condition); | |
| 617 space(); | |
| 618 token(node.question); | |
| 619 allowContinuedLines(() { | |
| 620 levelSpace(weight); | |
| 621 visit(node.thenExpression); | |
| 622 space(); | |
| 623 token(node.colon); | |
| 624 levelSpace(weight); | |
| 625 visit(node.elseExpression); | |
| 626 }); | |
| 627 } | |
| 628 | |
| 629 visitConstructorDeclaration(ConstructorDeclaration node) { | |
| 630 visitMemberMetadata(node.metadata); | |
| 631 modifier(node.externalKeyword); | |
| 632 modifier(node.constKeyword); | |
| 633 modifier(node.factoryKeyword); | |
| 634 visit(node.returnType); | |
| 635 token(node.period); | |
| 636 visit(node.name); | |
| 637 visit(node.parameters); | |
| 638 | |
| 639 // Check for redirects or initializer lists | |
| 640 if (node.separator != null) { | |
| 641 if (node.redirectedConstructor != null) { | |
| 642 visitConstructorRedirects(node); | |
| 643 } else { | |
| 644 visitConstructorInitializers(node); | |
| 645 } | |
| 646 } | |
| 647 | |
| 648 var body = node.body; | |
| 649 if (codeTransforms && body is BlockFunctionBody) { | |
| 650 if (body.block.statements.isEmpty) { | |
| 651 token(SEMI_COLON); | |
| 652 newlines(); | |
| 653 return; | |
| 654 } | |
| 655 } | |
| 656 | |
| 657 visitPrefixedBody(space, body); | |
| 658 } | |
| 659 | |
| 660 visitConstructorInitializers(ConstructorDeclaration node) { | |
| 661 if (node.initializers.length > 1) { | |
| 662 newlines(); | |
| 663 } else { | |
| 664 preserveLeadingNewlines(); | |
| 665 levelSpace(lastSpaceWeight++); | |
| 666 } | |
| 667 indent(2); | |
| 668 token(node.separator /* : */); | |
| 669 space(); | |
| 670 for (var i = 0; i < node.initializers.length; i++) { | |
| 671 if (i > 0) { | |
| 672 // preceding comma | |
| 673 token(node.initializers[i].beginToken.previous); | |
| 674 newlines(); | |
| 675 space(n: 2, allowLineLeading: true); | |
| 676 } | |
| 677 node.initializers[i].accept(this); | |
| 678 } | |
| 679 unindent(2); | |
| 680 } | |
| 681 | |
| 682 visitConstructorRedirects(ConstructorDeclaration node) { | |
| 683 token(node.separator /* = */, precededBy: space, followedBy: space); | |
| 684 visitCommaSeparatedNodes(node.initializers); | |
| 685 visit(node.redirectedConstructor); | |
| 686 } | |
| 687 | |
| 688 visitConstructorFieldInitializer(ConstructorFieldInitializer node) { | |
| 689 token(node.thisKeyword); | |
| 690 token(node.period); | |
| 691 visit(node.fieldName); | |
| 692 space(); | |
| 693 token(node.equals); | |
| 694 space(); | |
| 695 visit(node.expression); | |
| 696 } | |
| 697 | |
| 698 visitConstructorName(ConstructorName node) { | |
| 699 visit(node.type); | |
| 700 token(node.period); | |
| 701 visit(node.name); | |
| 702 } | |
| 703 | |
| 704 visitContinueStatement(ContinueStatement node) { | |
| 705 token(node.continueKeyword); | |
| 706 visitNode(node.label, precededBy: space); | |
| 707 token(node.semicolon); | |
| 708 } | |
| 709 | |
| 710 visitDeclaredIdentifier(DeclaredIdentifier node) { | |
| 711 modifier(node.keyword); | |
| 712 visitNode(node.type, followedBy: space); | |
| 713 visit(node.identifier); | |
| 714 } | |
| 715 | |
| 716 visitDefaultFormalParameter(DefaultFormalParameter node) { | |
| 717 visit(node.parameter); | |
| 718 if (node.separator != null) { | |
| 719 // The '=' separator is preceded by a space | |
| 720 if (node.separator.type == TokenType.EQ) { | |
| 721 space(); | |
| 722 } | |
| 723 token(node.separator); | |
| 724 visitNode(node.defaultValue, precededBy: space); | |
| 725 } | |
| 726 } | |
| 727 | |
| 728 visitDoStatement(DoStatement node) { | |
| 729 token(node.doKeyword); | |
| 730 space(); | |
| 731 visit(node.body); | |
| 732 space(); | |
| 733 token(node.whileKeyword); | |
| 734 space(); | |
| 735 token(node.leftParenthesis); | |
| 736 allowContinuedLines(() { | |
| 737 visit(node.condition); | |
| 738 token(node.rightParenthesis); | |
| 739 }); | |
| 740 token(node.semicolon); | |
| 741 } | |
| 742 | |
| 743 visitDoubleLiteral(DoubleLiteral node) { | |
| 744 token(node.literal); | |
| 745 } | |
| 746 | |
| 747 visitEmptyFunctionBody(EmptyFunctionBody node) { | |
| 748 token(node.semicolon); | |
| 749 } | |
| 750 | |
| 751 visitEmptyStatement(EmptyStatement node) { | |
| 752 if (!codeTransforms || node.parent is! Block) { | |
| 753 token(node.semicolon); | |
| 754 } | |
| 755 } | |
| 756 | |
| 757 visitEnumConstantDeclaration(EnumConstantDeclaration node) { | |
| 758 visit(node.name); | |
| 759 } | |
| 760 | |
| 761 visitEnumDeclaration(EnumDeclaration node) { | |
| 762 visitMemberMetadata(node.metadata); | |
| 763 token(node.enumKeyword); | |
| 764 space(); | |
| 765 visit(node.name); | |
| 766 space(); | |
| 767 token(node.leftBracket); | |
| 768 newlines(); | |
| 769 indent(); | |
| 770 visitCommaSeparatedNodes(node.constants); | |
| 771 newlines(); | |
| 772 token(node.rightBracket, precededBy: unindent); | |
| 773 } | |
| 774 | |
| 775 visitExportDirective(ExportDirective node) { | |
| 776 visitDirectiveMetadata(node.metadata); | |
| 777 token(node.keyword); | |
| 778 space(); | |
| 779 visit(node.uri); | |
| 780 allowContinuedLines(() { | |
| 781 visitNodes(node.combinators, precededBy: space, separatedBy: space); | |
| 782 }); | |
| 783 token(node.semicolon); | |
| 784 } | |
| 785 | |
| 786 visitExpressionFunctionBody(ExpressionFunctionBody node) { | |
| 787 int weight = lastSpaceWeight++; | |
| 788 token(node.keyword, followedBy: nonBreakingSpace); | |
| 789 token(node.functionDefinition); | |
| 790 levelSpace(weight); | |
| 791 visit(node.expression); | |
| 792 token(node.semicolon); | |
| 793 } | |
| 794 | |
| 795 visitExpressionStatement(ExpressionStatement node) { | |
| 796 visit(node.expression); | |
| 797 token(node.semicolon); | |
| 798 } | |
| 799 | |
| 800 visitExtendsClause(ExtendsClause node) { | |
| 801 token(node.extendsKeyword); | |
| 802 space(); | |
| 803 visit(node.superclass); | |
| 804 } | |
| 805 | |
| 806 visitFieldDeclaration(FieldDeclaration node) { | |
| 807 visitMemberMetadata(node.metadata); | |
| 808 modifier(node.staticKeyword); | |
| 809 visit(node.fields); | |
| 810 token(node.semicolon); | |
| 811 } | |
| 812 | |
| 813 visitFieldFormalParameter(FieldFormalParameter node) { | |
| 814 token(node.keyword, followedBy: space); | |
| 815 visitNode(node.type, followedBy: space); | |
| 816 token(node.thisKeyword); | |
| 817 token(node.period); | |
| 818 visit(node.identifier); | |
| 819 visit(node.parameters); | |
| 820 } | |
| 821 | |
| 822 visitForEachStatement(ForEachStatement node) { | |
| 823 token(node.awaitKeyword, followedBy: nonBreakingSpace); | |
| 824 token(node.forKeyword); | |
| 825 space(); | |
| 826 token(node.leftParenthesis); | |
| 827 if (node.loopVariable != null) { | |
| 828 visit(node.loopVariable); | |
| 829 } else { | |
| 830 visit(node.identifier); | |
| 831 } | |
| 832 space(); | |
| 833 token(node.inKeyword); | |
| 834 space(); | |
| 835 visit(node.iterable); | |
| 836 token(node.rightParenthesis); | |
| 837 space(); | |
| 838 visit(node.body); | |
| 839 } | |
| 840 | |
| 841 visitFormalParameterList(FormalParameterList node) { | |
| 842 var groupEnd = null; | |
| 843 token(node.leftParenthesis); | |
| 844 var parameters = node.parameters; | |
| 845 var size = parameters.length; | |
| 846 for (var i = 0; i < size; i++) { | |
| 847 var parameter = parameters[i]; | |
| 848 if (i > 0) { | |
| 849 append(','); | |
| 850 space(); | |
| 851 } | |
| 852 if (groupEnd == null && parameter is DefaultFormalParameter) { | |
| 853 if (identical(parameter.kind, ParameterKind.NAMED)) { | |
| 854 groupEnd = '}'; | |
| 855 append('{'); | |
| 856 } else { | |
| 857 groupEnd = ']'; | |
| 858 append('['); | |
| 859 } | |
| 860 } | |
| 861 parameter.accept(this); | |
| 862 } | |
| 863 if (groupEnd != null) { | |
| 864 append(groupEnd); | |
| 865 } | |
| 866 token(node.rightParenthesis); | |
| 867 } | |
| 868 | |
| 869 visitForStatement(ForStatement node) { | |
| 870 token(node.forKeyword); | |
| 871 space(); | |
| 872 token(node.leftParenthesis); | |
| 873 if (node.initialization != null) { | |
| 874 visit(node.initialization); | |
| 875 } else { | |
| 876 if (node.variables == null) { | |
| 877 space(); | |
| 878 } else { | |
| 879 visit(node.variables); | |
| 880 } | |
| 881 } | |
| 882 token(node.leftSeparator); | |
| 883 space(); | |
| 884 visit(node.condition); | |
| 885 token(node.rightSeparator); | |
| 886 if (node.updaters != null) { | |
| 887 space(); | |
| 888 visitCommaSeparatedNodes(node.updaters); | |
| 889 } | |
| 890 token(node.rightParenthesis); | |
| 891 if (node.body is! EmptyStatement) { | |
| 892 space(); | |
| 893 } | |
| 894 visit(node.body); | |
| 895 } | |
| 896 | |
| 897 visitFunctionDeclaration(FunctionDeclaration node) { | |
| 898 preserveLeadingNewlines(); | |
| 899 visitMemberMetadata(node.metadata); | |
| 900 modifier(node.externalKeyword); | |
| 901 visitNode(node.returnType, followedBy: space); | |
| 902 modifier(node.propertyKeyword); | |
| 903 visit(node.name); | |
| 904 visit(node.functionExpression); | |
| 905 } | |
| 906 | |
| 907 visitFunctionDeclarationStatement(FunctionDeclarationStatement node) { | |
| 908 visit(node.functionDeclaration); | |
| 909 } | |
| 910 | |
| 911 visitFunctionExpression(FunctionExpression node) { | |
| 912 visit(node.parameters); | |
| 913 if (node.body is! EmptyFunctionBody) { | |
| 914 space(); | |
| 915 } | |
| 916 visit(node.body); | |
| 917 } | |
| 918 | |
| 919 visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { | |
| 920 visit(node.function); | |
| 921 visit(node.argumentList); | |
| 922 } | |
| 923 | |
| 924 visitFunctionTypeAlias(FunctionTypeAlias node) { | |
| 925 visitMemberMetadata(node.metadata); | |
| 926 token(node.typedefKeyword); | |
| 927 space(); | |
| 928 visitNode(node.returnType, followedBy: space); | |
| 929 visit(node.name); | |
| 930 visit(node.typeParameters); | |
| 931 visit(node.parameters); | |
| 932 token(node.semicolon); | |
| 933 } | |
| 934 | |
| 935 visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) { | |
| 936 visitNode(node.returnType, followedBy: space); | |
| 937 visit(node.identifier); | |
| 938 visit(node.parameters); | |
| 939 } | |
| 940 | |
| 941 visitHideCombinator(HideCombinator node) { | |
| 942 token(node.keyword); | |
| 943 space(); | |
| 944 visitCommaSeparatedNodes(node.hiddenNames); | |
| 945 } | |
| 946 | |
| 947 visitIfStatement(IfStatement node) { | |
| 948 var hasElse = node.elseStatement != null; | |
| 949 token(node.ifKeyword); | |
| 950 allowContinuedLines(() { | |
| 951 space(); | |
| 952 token(node.leftParenthesis); | |
| 953 visit(node.condition); | |
| 954 token(node.rightParenthesis); | |
| 955 }); | |
| 956 space(); | |
| 957 if (hasElse) { | |
| 958 printAsBlock(node.thenStatement); | |
| 959 space(); | |
| 960 token(node.elseKeyword); | |
| 961 space(); | |
| 962 if (node.elseStatement is IfStatement) { | |
| 963 visit(node.elseStatement); | |
| 964 } else { | |
| 965 printAsBlock(node.elseStatement); | |
| 966 } | |
| 967 } else { | |
| 968 visit(node.thenStatement); | |
| 969 } | |
| 970 } | |
| 971 | |
| 972 visitImplementsClause(ImplementsClause node) { | |
| 973 token(node.implementsKeyword); | |
| 974 space(); | |
| 975 visitCommaSeparatedNodes(node.interfaces); | |
| 976 } | |
| 977 | |
| 978 visitImportDirective(ImportDirective node) { | |
| 979 visitDirectiveMetadata(node.metadata); | |
| 980 token(node.keyword); | |
| 981 nonBreakingSpace(); | |
| 982 visit(node.uri); | |
| 983 token(node.deferredKeyword, precededBy: space); | |
| 984 token(node.asKeyword, precededBy: space, followedBy: space); | |
| 985 allowContinuedLines(() { | |
| 986 visit(node.prefix); | |
| 987 visitNodes(node.combinators, precededBy: space, separatedBy: space); | |
| 988 }); | |
| 989 token(node.semicolon); | |
| 990 } | |
| 991 | |
| 992 visitIndexExpression(IndexExpression node) { | |
| 993 if (node.isCascaded) { | |
| 994 token(node.period); | |
| 995 } else { | |
| 996 visit(node.target); | |
| 997 } | |
| 998 token(node.leftBracket); | |
| 999 visit(node.index); | |
| 1000 token(node.rightBracket); | |
| 1001 } | |
| 1002 | |
| 1003 visitInstanceCreationExpression(InstanceCreationExpression node) { | |
| 1004 token(node.keyword); | |
| 1005 nonBreakingSpace(); | |
| 1006 visit(node.constructorName); | |
| 1007 visit(node.argumentList); | |
| 1008 } | |
| 1009 | |
| 1010 visitIntegerLiteral(IntegerLiteral node) { | |
| 1011 token(node.literal); | |
| 1012 } | |
| 1013 | |
| 1014 visitInterpolationExpression(InterpolationExpression node) { | |
| 1015 if (node.rightBracket != null) { | |
| 1016 token(node.leftBracket); | |
| 1017 visit(node.expression); | |
| 1018 token(node.rightBracket); | |
| 1019 } else { | |
| 1020 token(node.leftBracket); | |
| 1021 visit(node.expression); | |
| 1022 } | |
| 1023 } | |
| 1024 | |
| 1025 visitInterpolationString(InterpolationString node) { | |
| 1026 token(node.contents); | |
| 1027 } | |
| 1028 | |
| 1029 visitIsExpression(IsExpression node) { | |
| 1030 visit(node.expression); | |
| 1031 space(); | |
| 1032 token(node.isOperator); | |
| 1033 token(node.notOperator); | |
| 1034 space(); | |
| 1035 visit(node.type); | |
| 1036 } | |
| 1037 | |
| 1038 visitLabel(Label node) { | |
| 1039 visit(node.label); | |
| 1040 token(node.colon); | |
| 1041 } | |
| 1042 | |
| 1043 visitLabeledStatement(LabeledStatement node) { | |
| 1044 visitNodes(node.labels, separatedBy: space, followedBy: space); | |
| 1045 visit(node.statement); | |
| 1046 } | |
| 1047 | |
| 1048 visitLibraryDirective(LibraryDirective node) { | |
| 1049 visitDirectiveMetadata(node.metadata); | |
| 1050 token(node.keyword); | |
| 1051 space(); | |
| 1052 visit(node.name); | |
| 1053 token(node.semicolon); | |
| 1054 } | |
| 1055 | |
| 1056 visitLibraryIdentifier(LibraryIdentifier node) { | |
| 1057 append(node.name); | |
| 1058 } | |
| 1059 | |
| 1060 visitListLiteral(ListLiteral node) { | |
| 1061 int weight = lastSpaceWeight++; | |
| 1062 modifier(node.constKeyword); | |
| 1063 visit(node.typeArguments); | |
| 1064 token(node.leftBracket); | |
| 1065 indent(); | |
| 1066 levelSpace(weight, 0); | |
| 1067 visitCommaSeparatedNodes(node.elements, | |
| 1068 followedBy: () => levelSpace(weight)); | |
| 1069 optionalTrailingComma(node.rightBracket); | |
| 1070 token(node.rightBracket, precededBy: unindent); | |
| 1071 } | |
| 1072 | |
| 1073 visitMapLiteral(MapLiteral node) { | |
| 1074 modifier(node.constKeyword); | |
| 1075 visitNode(node.typeArguments); | |
| 1076 token(node.leftBracket); | |
| 1077 if (!node.entries.isEmpty) { | |
| 1078 newlines(); | |
| 1079 indent(); | |
| 1080 visitCommaSeparatedNodes(node.entries, followedBy: newlines); | |
| 1081 optionalTrailingComma(node.rightBracket); | |
| 1082 unindent(); | |
| 1083 newlines(); | |
| 1084 } | |
| 1085 token(node.rightBracket); | |
| 1086 } | |
| 1087 | |
| 1088 visitMapLiteralEntry(MapLiteralEntry node) { | |
| 1089 visit(node.key); | |
| 1090 token(node.separator); | |
| 1091 space(); | |
| 1092 visit(node.value); | |
| 1093 } | |
| 1094 | |
| 1095 visitMethodDeclaration(MethodDeclaration node) { | |
| 1096 visitMemberMetadata(node.metadata); | |
| 1097 modifier(node.externalKeyword); | |
| 1098 modifier(node.modifierKeyword); | |
| 1099 visitNode(node.returnType, followedBy: space); | |
| 1100 modifier(node.propertyKeyword); | |
| 1101 modifier(node.operatorKeyword); | |
| 1102 visit(node.name); | |
| 1103 if (!node.isGetter) { | |
| 1104 visit(node.parameters); | |
| 1105 } | |
| 1106 visitPrefixedBody(nonBreakingSpace, node.body); | |
| 1107 } | |
| 1108 | |
| 1109 visitMethodInvocation(MethodInvocation node) { | |
| 1110 visit(node.target); | |
| 1111 token(node.period); | |
| 1112 visit(node.methodName); | |
| 1113 visit(node.argumentList); | |
| 1114 } | |
| 1115 | |
| 1116 visitNamedExpression(NamedExpression node) { | |
| 1117 visit(node.name); | |
| 1118 visitNode(node.expression, precededBy: space); | |
| 1119 } | |
| 1120 | |
| 1121 visitNativeClause(NativeClause node) { | |
| 1122 token(node.nativeKeyword); | |
| 1123 space(); | |
| 1124 visit(node.name); | |
| 1125 } | |
| 1126 | |
| 1127 visitNativeFunctionBody(NativeFunctionBody node) { | |
| 1128 token(node.nativeKeyword); | |
| 1129 space(); | |
| 1130 visit(node.stringLiteral); | |
| 1131 token(node.semicolon); | |
| 1132 } | |
| 1133 | |
| 1134 visitNullLiteral(NullLiteral node) { | |
| 1135 token(node.literal); | |
| 1136 } | |
| 1137 | |
| 1138 visitParenthesizedExpression(ParenthesizedExpression node) { | |
| 1139 token(node.leftParenthesis); | |
| 1140 visit(node.expression); | |
| 1141 token(node.rightParenthesis); | |
| 1142 } | |
| 1143 | |
| 1144 visitPartDirective(PartDirective node) { | |
| 1145 token(node.keyword); | |
| 1146 space(); | |
| 1147 visit(node.uri); | |
| 1148 token(node.semicolon); | |
| 1149 } | |
| 1150 | |
| 1151 visitPartOfDirective(PartOfDirective node) { | |
| 1152 token(node.keyword); | |
| 1153 space(); | |
| 1154 token(node.ofKeyword); | |
| 1155 space(); | |
| 1156 visit(node.libraryName); | |
| 1157 token(node.semicolon); | |
| 1158 } | |
| 1159 | |
| 1160 visitPostfixExpression(PostfixExpression node) { | |
| 1161 visit(node.operand); | |
| 1162 token(node.operator); | |
| 1163 } | |
| 1164 | |
| 1165 visitPrefixedIdentifier(PrefixedIdentifier node) { | |
| 1166 visit(node.prefix); | |
| 1167 token(node.period); | |
| 1168 visit(node.identifier); | |
| 1169 } | |
| 1170 | |
| 1171 visitPrefixExpression(PrefixExpression node) { | |
| 1172 token(node.operator); | |
| 1173 visit(node.operand); | |
| 1174 } | |
| 1175 | |
| 1176 visitPropertyAccess(PropertyAccess node) { | |
| 1177 if (node.isCascaded) { | |
| 1178 token(node.operator); | |
| 1179 } else { | |
| 1180 visit(node.target); | |
| 1181 token(node.operator); | |
| 1182 } | |
| 1183 visit(node.propertyName); | |
| 1184 } | |
| 1185 | |
| 1186 visitRedirectingConstructorInvocation(RedirectingConstructorInvocation node) { | |
| 1187 token(node.thisKeyword); | |
| 1188 token(node.period); | |
| 1189 visit(node.constructorName); | |
| 1190 visit(node.argumentList); | |
| 1191 } | |
| 1192 | |
| 1193 visitRethrowExpression(RethrowExpression node) { | |
| 1194 token(node.rethrowKeyword); | |
| 1195 } | |
| 1196 | |
| 1197 visitReturnStatement(ReturnStatement node) { | |
| 1198 var expression = node.expression; | |
| 1199 if (expression == null) { | |
| 1200 token(node.returnKeyword); | |
| 1201 token(node.semicolon); | |
| 1202 } else { | |
| 1203 token(node.returnKeyword); | |
| 1204 allowContinuedLines(() { | |
| 1205 space(); | |
| 1206 expression.accept(this); | |
| 1207 token(node.semicolon); | |
| 1208 }); | |
| 1209 } | |
| 1210 } | |
| 1211 | |
| 1212 visitScriptTag(ScriptTag node) { | |
| 1213 token(node.scriptTag); | |
| 1214 } | |
| 1215 | |
| 1216 visitShowCombinator(ShowCombinator node) { | |
| 1217 token(node.keyword); | |
| 1218 space(); | |
| 1219 visitCommaSeparatedNodes(node.shownNames); | |
| 1220 } | |
| 1221 | |
| 1222 visitSimpleFormalParameter(SimpleFormalParameter node) { | |
| 1223 visitMemberMetadata(node.metadata); | |
| 1224 modifier(node.keyword); | |
| 1225 visitNode(node.type, followedBy: nonBreakingSpace); | |
| 1226 visit(node.identifier); | |
| 1227 } | |
| 1228 | |
| 1229 visitSimpleIdentifier(SimpleIdentifier node) { | |
| 1230 token(node.token); | |
| 1231 } | |
| 1232 | |
| 1233 visitSimpleStringLiteral(SimpleStringLiteral node) { | |
| 1234 token(node.literal); | |
| 1235 } | |
| 1236 | |
| 1237 visitStringInterpolation(StringInterpolation node) { | |
| 1238 // Ensure that interpolated strings don't get broken up by treating them as | |
| 1239 // a single String token | |
| 1240 // Process token (for comments etc. but don't print the lexeme) | |
| 1241 token(node.beginToken, printToken: (tok) => null); | |
| 1242 var start = node.beginToken.offset; | |
| 1243 var end = node.endToken.end; | |
| 1244 String string = source.substring(start, end); | |
| 1245 append(string); | |
| 1246 //visitNodes(node.elements); | |
| 1247 } | |
| 1248 | |
| 1249 visitSuperConstructorInvocation(SuperConstructorInvocation node) { | |
| 1250 token(node.superKeyword); | |
| 1251 token(node.period); | |
| 1252 visit(node.constructorName); | |
| 1253 visit(node.argumentList); | |
| 1254 } | |
| 1255 | |
| 1256 visitSuperExpression(SuperExpression node) { | |
| 1257 token(node.superKeyword); | |
| 1258 } | |
| 1259 | |
| 1260 visitSwitchCase(SwitchCase node) { | |
| 1261 visitNodes(node.labels, separatedBy: space, followedBy: space); | |
| 1262 token(node.keyword); | |
| 1263 space(); | |
| 1264 visit(node.expression); | |
| 1265 token(node.colon); | |
| 1266 newlines(); | |
| 1267 indent(); | |
| 1268 visitNodes(node.statements, separatedBy: newlines); | |
| 1269 unindent(); | |
| 1270 } | |
| 1271 | |
| 1272 visitSwitchDefault(SwitchDefault node) { | |
| 1273 visitNodes(node.labels, separatedBy: space, followedBy: space); | |
| 1274 token(node.keyword); | |
| 1275 token(node.colon); | |
| 1276 newlines(); | |
| 1277 indent(); | |
| 1278 visitNodes(node.statements, separatedBy: newlines); | |
| 1279 unindent(); | |
| 1280 } | |
| 1281 | |
| 1282 visitSwitchStatement(SwitchStatement node) { | |
| 1283 token(node.switchKeyword); | |
| 1284 space(); | |
| 1285 token(node.leftParenthesis); | |
| 1286 visit(node.expression); | |
| 1287 token(node.rightParenthesis); | |
| 1288 space(); | |
| 1289 token(node.leftBracket); | |
| 1290 indent(); | |
| 1291 newlines(); | |
| 1292 visitNodes(node.members, separatedBy: newlines, followedBy: newlines); | |
| 1293 token(node.rightBracket, precededBy: unindent); | |
| 1294 } | |
| 1295 | |
| 1296 visitSymbolLiteral(SymbolLiteral node) { | |
| 1297 token(node.poundSign); | |
| 1298 var components = node.components; | |
| 1299 for (var component in components) { | |
| 1300 // The '.' separator | |
| 1301 if (component.previous.lexeme == '.') { | |
| 1302 token(component.previous); | |
| 1303 } | |
| 1304 token(component); | |
| 1305 } | |
| 1306 } | |
| 1307 | |
| 1308 visitThisExpression(ThisExpression node) { | |
| 1309 token(node.thisKeyword); | |
| 1310 } | |
| 1311 | |
| 1312 visitThrowExpression(ThrowExpression node) { | |
| 1313 token(node.throwKeyword); | |
| 1314 space(); | |
| 1315 visit(node.expression); | |
| 1316 } | |
| 1317 | |
| 1318 visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { | |
| 1319 visit(node.variables); | |
| 1320 token(node.semicolon); | |
| 1321 } | |
| 1322 | |
| 1323 visitTryStatement(TryStatement node) { | |
| 1324 token(node.tryKeyword); | |
| 1325 space(); | |
| 1326 visit(node.body); | |
| 1327 visitNodes(node.catchClauses, precededBy: space, separatedBy: space); | |
| 1328 token(node.finallyKeyword, precededBy: space, followedBy: space); | |
| 1329 visit(node.finallyBlock); | |
| 1330 } | |
| 1331 | |
| 1332 visitTypeArgumentList(TypeArgumentList node) { | |
| 1333 token(node.leftBracket); | |
| 1334 visitCommaSeparatedNodes(node.arguments); | |
| 1335 token(node.rightBracket); | |
| 1336 } | |
| 1337 | |
| 1338 visitTypeName(TypeName node) { | |
| 1339 visit(node.name); | |
| 1340 visit(node.typeArguments); | |
| 1341 } | |
| 1342 | |
| 1343 visitTypeParameter(TypeParameter node) { | |
| 1344 visitMemberMetadata(node.metadata); | |
| 1345 visit(node.name); | |
| 1346 token(node.extendsKeyword, precededBy: space, followedBy: space); | |
| 1347 visit(node.bound); | |
| 1348 } | |
| 1349 | |
| 1350 visitTypeParameterList(TypeParameterList node) { | |
| 1351 token(node.leftBracket); | |
| 1352 visitCommaSeparatedNodes(node.typeParameters); | |
| 1353 token(node.rightBracket); | |
| 1354 } | |
| 1355 | |
| 1356 visitVariableDeclaration(VariableDeclaration node) { | |
| 1357 visit(node.name); | |
| 1358 if (node.initializer != null) { | |
| 1359 space(); | |
| 1360 token(node.equals); | |
| 1361 var initializer = node.initializer; | |
| 1362 if (initializer is ListLiteral || initializer is MapLiteral) { | |
| 1363 space(); | |
| 1364 visit(initializer); | |
| 1365 } else if (initializer is BinaryExpression) { | |
| 1366 allowContinuedLines(() { | |
| 1367 levelSpace(lastSpaceWeight); | |
| 1368 visit(initializer); | |
| 1369 }); | |
| 1370 } else { | |
| 1371 allowContinuedLines(() { | |
| 1372 levelSpace(SINGLE_SPACE_WEIGHT); | |
| 1373 visit(initializer); | |
| 1374 }); | |
| 1375 } | |
| 1376 } | |
| 1377 } | |
| 1378 | |
| 1379 visitVariableDeclarationList(VariableDeclarationList node) { | |
| 1380 visitMemberMetadata(node.metadata); | |
| 1381 modifier(node.keyword); | |
| 1382 visitNode(node.type, followedBy: space); | |
| 1383 | |
| 1384 var variables = node.variables; | |
| 1385 // Decls with initializers get their own lines (dartbug.com/16849) | |
| 1386 if (variables.any((v) => (v.initializer != null))) { | |
| 1387 var size = variables.length; | |
| 1388 if (size > 0) { | |
| 1389 var variable; | |
| 1390 for (var i = 0; i < size; i++) { | |
| 1391 variable = variables[i]; | |
| 1392 if (i > 0) { | |
| 1393 var comma = variable.beginToken.previous; | |
| 1394 token(comma); | |
| 1395 newlines(); | |
| 1396 } | |
| 1397 if (i == 1) { | |
| 1398 indent(2); | |
| 1399 } | |
| 1400 variable.accept(this); | |
| 1401 } | |
| 1402 if (size > 1) { | |
| 1403 unindent(2); | |
| 1404 } | |
| 1405 } | |
| 1406 } else { | |
| 1407 visitCommaSeparatedNodes(node.variables); | |
| 1408 } | |
| 1409 } | |
| 1410 | |
| 1411 visitVariableDeclarationStatement(VariableDeclarationStatement node) { | |
| 1412 visit(node.variables); | |
| 1413 token(node.semicolon); | |
| 1414 } | |
| 1415 | |
| 1416 visitWhileStatement(WhileStatement node) { | |
| 1417 token(node.whileKeyword); | |
| 1418 space(); | |
| 1419 token(node.leftParenthesis); | |
| 1420 allowContinuedLines(() { | |
| 1421 visit(node.condition); | |
| 1422 token(node.rightParenthesis); | |
| 1423 }); | |
| 1424 if (node.body is! EmptyStatement) { | |
| 1425 space(); | |
| 1426 } | |
| 1427 visit(node.body); | |
| 1428 } | |
| 1429 | |
| 1430 visitWithClause(WithClause node) { | |
| 1431 token(node.withKeyword); | |
| 1432 space(); | |
| 1433 visitCommaSeparatedNodes(node.mixinTypes); | |
| 1434 } | |
| 1435 | |
| 1436 @override | |
| 1437 visitYieldStatement(YieldStatement node) { | |
| 1438 token(node.yieldKeyword); | |
| 1439 token(node.star); | |
| 1440 space(); | |
| 1441 visit(node.expression); | |
| 1442 token(node.semicolon); | |
| 1443 } | |
| 1444 | |
| 1445 /// Safely visit the given [node]. | |
| 1446 visit(AstNode node) { | |
| 1447 if (node != null) { | |
| 1448 node.accept(this); | |
| 1449 } | |
| 1450 } | |
| 1451 | |
| 1452 /// Visit member metadata | |
| 1453 visitMemberMetadata(NodeList<Annotation> metadata) { | |
| 1454 visitNodes(metadata, separatedBy: () { | |
| 1455 space(); | |
| 1456 preserveLeadingNewlines(); | |
| 1457 }, followedBy: space); | |
| 1458 if (metadata != null && metadata.length > 0) { | |
| 1459 preserveLeadingNewlines(); | |
| 1460 } | |
| 1461 } | |
| 1462 | |
| 1463 /// Visit member metadata | |
| 1464 visitDirectiveMetadata(NodeList<Annotation> metadata) { | |
| 1465 visitNodes(metadata, separatedBy: newlines, followedBy: newlines); | |
| 1466 } | |
| 1467 | |
| 1468 /// Visit the given function [body], printing the [prefix] before if given | |
| 1469 /// body is not empty. | |
| 1470 visitPrefixedBody(prefix(), FunctionBody body) { | |
| 1471 if (body is! EmptyFunctionBody) { | |
| 1472 prefix(); | |
| 1473 } | |
| 1474 visit(body); | |
| 1475 } | |
| 1476 | |
| 1477 /// Visit a list of [nodes] if not null, optionally separated and/or preceded | |
| 1478 /// and followed by the given functions. | |
| 1479 visitNodes(NodeList<AstNode> nodes, | |
| 1480 {precededBy(): null, separatedBy(): null, followedBy(): null}) { | |
| 1481 if (nodes != null) { | |
| 1482 var size = nodes.length; | |
| 1483 if (size > 0) { | |
| 1484 if (precededBy != null) { | |
| 1485 precededBy(); | |
| 1486 } | |
| 1487 for (var i = 0; i < size; i++) { | |
| 1488 if (i > 0 && separatedBy != null) { | |
| 1489 separatedBy(); | |
| 1490 } | |
| 1491 nodes[i].accept(this); | |
| 1492 } | |
| 1493 if (followedBy != null) { | |
| 1494 followedBy(); | |
| 1495 } | |
| 1496 } | |
| 1497 } | |
| 1498 } | |
| 1499 | |
| 1500 /// Visit a comma-separated list of [nodes] if not null. | |
| 1501 visitCommaSeparatedNodes(NodeList<AstNode> nodes, {followedBy(): null}) { | |
| 1502 //TODO(pquitslund): handle this more neatly | |
| 1503 if (followedBy == null) { | |
| 1504 followedBy = space; | |
| 1505 } | |
| 1506 if (nodes != null) { | |
| 1507 var size = nodes.length; | |
| 1508 if (size > 0) { | |
| 1509 var node; | |
| 1510 for (var i = 0; i < size; i++) { | |
| 1511 node = nodes[i]; | |
| 1512 if (i > 0) { | |
| 1513 var comma = node.beginToken.previous; | |
| 1514 token(comma); | |
| 1515 followedBy(); | |
| 1516 } | |
| 1517 node.accept(this); | |
| 1518 } | |
| 1519 } | |
| 1520 } | |
| 1521 } | |
| 1522 | |
| 1523 /// Visit a [node], and if not null, optionally preceded or followed by the | |
| 1524 /// specified functions. | |
| 1525 visitNode(AstNode node, {precededBy(): null, followedBy(): null}) { | |
| 1526 if (node != null) { | |
| 1527 if (precededBy != null) { | |
| 1528 precededBy(); | |
| 1529 } | |
| 1530 node.accept(this); | |
| 1531 if (followedBy != null) { | |
| 1532 followedBy(); | |
| 1533 } | |
| 1534 } | |
| 1535 } | |
| 1536 | |
| 1537 /// Allow [code] to be continued across lines. | |
| 1538 allowContinuedLines(code()) { | |
| 1539 //TODO(pquitslund): add before | |
| 1540 code(); | |
| 1541 //TODO(pquitslund): add after | |
| 1542 } | |
| 1543 | |
| 1544 /// Emit the given [modifier] if it's non null, followed by non-breaking | |
| 1545 /// whitespace. | |
| 1546 modifier(Token modifier) { | |
| 1547 token(modifier, followedBy: space); | |
| 1548 } | |
| 1549 | |
| 1550 /// Indicate that at least one newline should be emitted and possibly more | |
| 1551 /// if the source has them. | |
| 1552 newlines() { | |
| 1553 needsNewline = true; | |
| 1554 } | |
| 1555 | |
| 1556 /// Optionally emit a trailing comma. | |
| 1557 optionalTrailingComma(Token rightBracket) { | |
| 1558 if (rightBracket.previous.lexeme == ',') { | |
| 1559 token(rightBracket.previous); | |
| 1560 } | |
| 1561 } | |
| 1562 | |
| 1563 /// Indicate that user introduced newlines should be emitted before the next | |
| 1564 /// token. | |
| 1565 preserveLeadingNewlines() { | |
| 1566 preserveNewlines = true; | |
| 1567 } | |
| 1568 | |
| 1569 token(Token token, | |
| 1570 {precededBy(), followedBy(), printToken(tok), int minNewlines: 0}) { | |
| 1571 if (token != null) { | |
| 1572 if (needsNewline) { | |
| 1573 minNewlines = max(1, minNewlines); | |
| 1574 } | |
| 1575 var emitted = emitPrecedingCommentsAndNewlines(token, min: minNewlines); | |
| 1576 if (emitted > 0) { | |
| 1577 needsNewline = false; | |
| 1578 } | |
| 1579 if (precededBy != null) { | |
| 1580 precededBy(); | |
| 1581 } | |
| 1582 checkForSelectionUpdate(token); | |
| 1583 if (printToken == null) { | |
| 1584 append(token.lexeme); | |
| 1585 } else { | |
| 1586 printToken(token); | |
| 1587 } | |
| 1588 if (followedBy != null) { | |
| 1589 followedBy(); | |
| 1590 } | |
| 1591 previousToken = token; | |
| 1592 } | |
| 1593 } | |
| 1594 | |
| 1595 emitSpaces() { | |
| 1596 if (leadingSpaces > 0 || emitEmptySpaces) { | |
| 1597 if (allowLineLeadingSpaces || !writer.currentLine.isWhitespace()) { | |
| 1598 writer.spaces(leadingSpaces, breakWeight: currentBreakWeight); | |
| 1599 } | |
| 1600 leadingSpaces = 0; | |
| 1601 allowLineLeadingSpaces = false; | |
| 1602 emitEmptySpaces = false; | |
| 1603 currentBreakWeight = DEFAULT_SPACE_WEIGHT; | |
| 1604 } | |
| 1605 } | |
| 1606 | |
| 1607 checkForSelectionUpdate(Token token) { | |
| 1608 // Cache the first token on or AFTER the selection offset | |
| 1609 if (preSelection != null && selection == null) { | |
| 1610 // Check for overshots | |
| 1611 var overshot = token.offset - preSelection.offset; | |
| 1612 if (overshot >= 0) { | |
| 1613 //TODO(pquitslund): update length (may need truncating) | |
| 1614 selection = new Selection(writer.toString().length + | |
| 1615 leadingSpaces - | |
| 1616 overshot, preSelection.length); | |
| 1617 } | |
| 1618 } | |
| 1619 } | |
| 1620 | |
| 1621 /// Emit a breakable 'non' (zero-length) space | |
| 1622 breakableNonSpace() { | |
| 1623 space(n: 0); | |
| 1624 emitEmptySpaces = true; | |
| 1625 } | |
| 1626 | |
| 1627 /// Emit level spaces, even if empty (works as a break point). | |
| 1628 levelSpace(int weight, [int n = 1]) { | |
| 1629 space(n: n, breakWeight: weight); | |
| 1630 emitEmptySpaces = true; | |
| 1631 } | |
| 1632 | |
| 1633 /// Emit a non-breakable space. | |
| 1634 nonBreakingSpace() { | |
| 1635 space(breakWeight: UNBREAKABLE_SPACE_WEIGHT); | |
| 1636 } | |
| 1637 | |
| 1638 /// Emit a space. If [allowLineLeading] is specified, spaces | |
| 1639 /// will be preserved at the start of a line (in addition to the | |
| 1640 /// indent-level), otherwise line-leading spaces will be ignored. | |
| 1641 space({n: 1, allowLineLeading: false, breakWeight: DEFAULT_SPACE_WEIGHT}) { | |
| 1642 //TODO(pquitslund): replace with a proper space token | |
| 1643 leadingSpaces += n; | |
| 1644 allowLineLeadingSpaces = allowLineLeading; | |
| 1645 currentBreakWeight = breakWeight; | |
| 1646 } | |
| 1647 | |
| 1648 /// Append the given [string] to the source writer if it's non-null. | |
| 1649 append(String string) { | |
| 1650 if (string != null && !string.isEmpty) { | |
| 1651 emitSpaces(); | |
| 1652 writer.write(string); | |
| 1653 } | |
| 1654 } | |
| 1655 | |
| 1656 /// Indent. | |
| 1657 indent([n = 1]) { | |
| 1658 while (n-- > 0) { | |
| 1659 writer.indent(); | |
| 1660 } | |
| 1661 } | |
| 1662 | |
| 1663 /// Unindent | |
| 1664 unindent([n = 1]) { | |
| 1665 while (n-- > 0) { | |
| 1666 writer.unindent(); | |
| 1667 } | |
| 1668 } | |
| 1669 | |
| 1670 /// Print this statement as if it were a block (e.g., surrounded by braces). | |
| 1671 printAsBlock(Statement statement) { | |
| 1672 if (codeTransforms && statement is! Block) { | |
| 1673 token(OPEN_CURLY); | |
| 1674 indent(); | |
| 1675 newlines(); | |
| 1676 visit(statement); | |
| 1677 newlines(); | |
| 1678 token(CLOSE_CURLY, precededBy: unindent); | |
| 1679 } else { | |
| 1680 visit(statement); | |
| 1681 } | |
| 1682 } | |
| 1683 | |
| 1684 /// Emit any detected comments and newlines or a minimum as specified | |
| 1685 /// by [min]. | |
| 1686 int emitPrecedingCommentsAndNewlines(Token token, {min: 0}) { | |
| 1687 var comment = token.precedingComments; | |
| 1688 var currentToken = comment != null ? comment : token; | |
| 1689 | |
| 1690 //Handle EOLs before newlines | |
| 1691 if (isAtEOL(comment)) { | |
| 1692 emitComment(comment, previousToken); | |
| 1693 comment = comment.next; | |
| 1694 currentToken = comment != null ? comment : token; | |
| 1695 // Ensure EOL comments force a linebreak | |
| 1696 needsNewline = true; | |
| 1697 } | |
| 1698 | |
| 1699 var lines = 0; | |
| 1700 if (needsNewline || preserveNewlines) { | |
| 1701 lines = max(min, countNewlinesBetween(previousToken, currentToken)); | |
| 1702 preserveNewlines = false; | |
| 1703 } | |
| 1704 | |
| 1705 emitNewlines(lines); | |
| 1706 | |
| 1707 previousToken = | |
| 1708 currentToken.previous != null ? currentToken.previous : token.previous; | |
| 1709 | |
| 1710 while (comment != null) { | |
| 1711 emitComment(comment, previousToken); | |
| 1712 | |
| 1713 var nextToken = comment.next != null ? comment.next : token; | |
| 1714 var newlines = calculateNewlinesBetweenComments(comment, nextToken); | |
| 1715 if (newlines > 0) { | |
| 1716 emitNewlines(newlines); | |
| 1717 lines += newlines; | |
| 1718 } else { | |
| 1719 var spaces = countSpacesBetween(comment, nextToken); | |
| 1720 if (spaces > 0) { | |
| 1721 space(); | |
| 1722 } | |
| 1723 } | |
| 1724 | |
| 1725 previousToken = comment; | |
| 1726 comment = comment.next; | |
| 1727 } | |
| 1728 | |
| 1729 previousToken = token; | |
| 1730 return lines; | |
| 1731 } | |
| 1732 | |
| 1733 void emitNewlines(lines) { | |
| 1734 writer.newlines(lines); | |
| 1735 } | |
| 1736 | |
| 1737 ensureTrailingNewline() { | |
| 1738 if (writer.lastToken is! NewlineToken) { | |
| 1739 writer.newline(); | |
| 1740 } | |
| 1741 } | |
| 1742 | |
| 1743 /// Test if this EOL [comment] is at the beginning of a line. | |
| 1744 bool isAtBOL(Token comment) => | |
| 1745 lineInfo.getLocation(comment.offset).columnNumber == 1; | |
| 1746 | |
| 1747 /// Test if this [comment] is at the end of a line. | |
| 1748 bool isAtEOL(Token comment) => comment != null && | |
| 1749 comment.toString().trim().startsWith(twoSlashes) && | |
| 1750 sameLine(comment, previousToken); | |
| 1751 | |
| 1752 /// Emit this [comment], inserting leading whitespace if appropriate. | |
| 1753 emitComment(Token comment, Token previousToken) { | |
| 1754 if (!writer.currentLine.isWhitespace() && previousToken != null) { | |
| 1755 var ws = countSpacesBetween(previousToken, comment); | |
| 1756 // Preserve one space but no more | |
| 1757 if (ws > 0 && leadingSpaces == 0) { | |
| 1758 space(); | |
| 1759 } | |
| 1760 } | |
| 1761 | |
| 1762 // Don't indent commented-out lines | |
| 1763 if (isAtBOL(comment)) { | |
| 1764 writer.currentLine.clear(); | |
| 1765 } | |
| 1766 | |
| 1767 append(comment.toString().trim()); | |
| 1768 } | |
| 1769 | |
| 1770 /// Count spaces between these tokens. Tokens on different lines return 0. | |
| 1771 int countSpacesBetween(Token last, Token current) => isEOF(last) || | |
| 1772 countNewlinesBetween(last, current) > 0 ? 0 : current.offset - last.end; | |
| 1773 | |
| 1774 /// Count the blanks between these two nodes. | |
| 1775 int countBlankLinesBetween(AstNode lastNode, AstNode currentNode) => | |
| 1776 countNewlinesBetween(lastNode.endToken, currentNode.beginToken); | |
| 1777 | |
| 1778 /// Count newlines preceeding this [node]. | |
| 1779 int countPrecedingNewlines(AstNode node) => | |
| 1780 countNewlinesBetween(node.beginToken.previous, node.beginToken); | |
| 1781 | |
| 1782 /// Count newlines succeeding this [node]. | |
| 1783 int countSucceedingNewlines(AstNode node) => node == null | |
| 1784 ? 0 | |
| 1785 : countNewlinesBetween(node.endToken, node.endToken.next); | |
| 1786 | |
| 1787 /// Count the blanks between these two tokens. | |
| 1788 int countNewlinesBetween(Token last, Token current) { | |
| 1789 if (last == null || current == null || isSynthetic(last)) { | |
| 1790 return 0; | |
| 1791 } | |
| 1792 | |
| 1793 return linesBetween(last.end - 1, current.offset); | |
| 1794 } | |
| 1795 | |
| 1796 /// Calculate the newlines that should separate these comments. | |
| 1797 int calculateNewlinesBetweenComments(Token last, Token current) { | |
| 1798 // Insist on a newline after doc comments or single line comments | |
| 1799 // (NOTE that EOL comments have already been processed). | |
| 1800 if (isOldSingleLineDocComment(last) || isSingleLineComment(last)) { | |
| 1801 return max(1, countNewlinesBetween(last, current)); | |
| 1802 } else { | |
| 1803 return countNewlinesBetween(last, current); | |
| 1804 } | |
| 1805 } | |
| 1806 | |
| 1807 /// Single line multi-line comments (e.g., '/** like this */'). | |
| 1808 bool isOldSingleLineDocComment(Token comment) => | |
| 1809 comment.lexeme.startsWith(r'/**') && singleLine(comment); | |
| 1810 | |
| 1811 /// Test if this [token] spans just one line. | |
| 1812 bool singleLine(Token token) => linesBetween(token.offset, token.end) < 1; | |
| 1813 | |
| 1814 /// Test if token [first] is on the same line as [second]. | |
| 1815 bool sameLine(Token first, Token second) => | |
| 1816 countNewlinesBetween(first, second) == 0; | |
| 1817 | |
| 1818 /// Test if this is a multi-line [comment] (e.g., '/* ...' or '/** ...') | |
| 1819 bool isMultiLineComment(Token comment) => | |
| 1820 comment.type == TokenType.MULTI_LINE_COMMENT; | |
| 1821 | |
| 1822 /// Test if this is a single-line [comment] (e.g., '// ...') | |
| 1823 bool isSingleLineComment(Token comment) => | |
| 1824 comment.type == TokenType.SINGLE_LINE_COMMENT; | |
| 1825 | |
| 1826 /// Test if this [comment] is a block comment (e.g., '/* like this */').. | |
| 1827 bool isBlock(Token comment) => | |
| 1828 isMultiLineComment(comment) && singleLine(comment); | |
| 1829 | |
| 1830 /// Count the lines between two offsets. | |
| 1831 int linesBetween(int lastOffset, int currentOffset) { | |
| 1832 var lastLine = lineInfo.getLocation(lastOffset).lineNumber; | |
| 1833 var currentLine = lineInfo.getLocation(currentOffset).lineNumber; | |
| 1834 return currentLine - lastLine; | |
| 1835 } | |
| 1836 | |
| 1837 String toString() => writer.toString(); | |
| 1838 } | |
| OLD | NEW |