| 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 |