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