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