| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2014, 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 dart_style.src.source_visitor; | |
| 6 | |
| 7 import 'package:analyzer/analyzer.dart'; | |
| 8 import 'package:analyzer/src/generated/scanner.dart'; | |
| 9 import 'package:analyzer/src/generated/source.dart'; | |
| 10 | |
| 11 import 'argument_list_visitor.dart'; | |
| 12 import 'call_chain_visitor.dart'; | |
| 13 import 'chunk.dart'; | |
| 14 import 'chunk_builder.dart'; | |
| 15 import 'dart_formatter.dart'; | |
| 16 import 'rule/argument.dart'; | |
| 17 import 'rule/combinator.dart'; | |
| 18 import 'rule/rule.dart'; | |
| 19 import 'rule/type_argument.dart'; | |
| 20 import 'source_code.dart'; | |
| 21 import 'whitespace.dart'; | |
| 22 | |
| 23 /// Visits every token of the AST and passes all of the relevant bits to a | |
| 24 /// [ChunkBuilder]. | |
| 25 class SourceVisitor implements AstVisitor { | |
| 26 /// The builder for the block that is currently being visited. | |
| 27 ChunkBuilder builder; | |
| 28 | |
| 29 final DartFormatter _formatter; | |
| 30 | |
| 31 /// Cached line info for calculating blank lines. | |
| 32 LineInfo _lineInfo; | |
| 33 | |
| 34 /// The source being formatted. | |
| 35 final SourceCode _source; | |
| 36 | |
| 37 /// `true` if the visitor has written past the beginning of the selection in | |
| 38 /// the original source text. | |
| 39 bool _passedSelectionStart = false; | |
| 40 | |
| 41 /// `true` if the visitor has written past the end of the selection in the | |
| 42 /// original source text. | |
| 43 bool _passedSelectionEnd = false; | |
| 44 | |
| 45 /// The character offset of the end of the selection, if there is a selection. | |
| 46 /// | |
| 47 /// This is calculated and cached by [_findSelectionEnd]. | |
| 48 int _selectionEnd; | |
| 49 | |
| 50 /// The rule that should be used for the contents of a literal body that are | |
| 51 /// about to be written. | |
| 52 /// | |
| 53 /// This is set by [visitArgumentList] to ensure that all block arguments | |
| 54 /// share a rule. | |
| 55 /// | |
| 56 /// If `null`, a literal body creates its own rule. | |
| 57 Rule _nextLiteralBodyRule; | |
| 58 | |
| 59 /// A stack that tracks forcing nested collections to split. | |
| 60 /// | |
| 61 /// Each entry corresponds to a collection currently being visited and the | |
| 62 /// value is whether or not it should be forced to split. Every time a | |
| 63 /// collection is entered, it sets all of the existing elements to `true` | |
| 64 /// then it pushes `false` for itself. | |
| 65 /// | |
| 66 /// When done visiting the elements, it removes its value. If it was set to | |
| 67 /// `true`, we know we visited a nested collection so we force this one to | |
| 68 /// split. | |
| 69 final List<bool> _collectionSplits = []; | |
| 70 | |
| 71 /// Initialize a newly created visitor to write source code representing | |
| 72 /// the visited nodes to the given [writer]. | |
| 73 SourceVisitor(this._formatter, this._lineInfo, this._source) { | |
| 74 builder = new ChunkBuilder(_formatter, _source); | |
| 75 } | |
| 76 | |
| 77 /// Runs the visitor on [node], formatting its contents. | |
| 78 /// | |
| 79 /// Returns a [SourceCode] containing the resulting formatted source and | |
| 80 /// updated selection, if any. | |
| 81 /// | |
| 82 /// This is the only method that should be called externally. Everything else | |
| 83 /// is effectively private. | |
| 84 SourceCode run(AstNode node) { | |
| 85 visit(node); | |
| 86 | |
| 87 // Output trailing comments. | |
| 88 writePrecedingCommentsAndNewlines(node.endToken.next); | |
| 89 | |
| 90 // Finish writing and return the complete result. | |
| 91 return builder.end(); | |
| 92 } | |
| 93 | |
| 94 visitAdjacentStrings(AdjacentStrings node) { | |
| 95 builder.startSpan(); | |
| 96 builder.startRule(); | |
| 97 visitNodes(node.strings, between: splitOrNewline); | |
| 98 builder.endRule(); | |
| 99 builder.endSpan(); | |
| 100 } | |
| 101 | |
| 102 visitAnnotation(Annotation node) { | |
| 103 token(node.atSign); | |
| 104 visit(node.name); | |
| 105 token(node.period); | |
| 106 visit(node.constructorName); | |
| 107 visit(node.arguments); | |
| 108 } | |
| 109 | |
| 110 /// Visits an argument list. | |
| 111 /// | |
| 112 /// This is a bit complex to handle the rules for formatting positional and | |
| 113 /// named arguments. The goals, in rough order of descending priority are: | |
| 114 /// | |
| 115 /// 1. Keep everything on the first line. | |
| 116 /// 2. Keep the named arguments together on the next line. | |
| 117 /// 3. Keep everything together on the second line. | |
| 118 /// 4. Split between one or more positional arguments, trying to keep as many | |
| 119 /// on earlier lines as possible. | |
| 120 /// 5. Split the named arguments each onto their own line. | |
| 121 visitArgumentList(ArgumentList node) { | |
| 122 // Corner case: handle empty argument lists. | |
| 123 if (node.arguments.isEmpty) { | |
| 124 token(node.leftParenthesis); | |
| 125 | |
| 126 // If there is a comment inside the parens, do allow splitting before it. | |
| 127 if (node.rightParenthesis.precedingComments != null) soloZeroSplit(); | |
| 128 | |
| 129 token(node.rightParenthesis); | |
| 130 return; | |
| 131 } | |
| 132 | |
| 133 new ArgumentListVisitor(this, node).visit(); | |
| 134 } | |
| 135 | |
| 136 visitAsExpression(AsExpression node) { | |
| 137 builder.startSpan(); | |
| 138 visit(node.expression); | |
| 139 soloSplit(); | |
| 140 token(node.asOperator); | |
| 141 space(); | |
| 142 visit(node.type); | |
| 143 builder.endSpan(); | |
| 144 } | |
| 145 | |
| 146 visitAssertStatement(AssertStatement node) { | |
| 147 _simpleStatement(node, () { | |
| 148 token(node.assertKeyword); | |
| 149 token(node.leftParenthesis); | |
| 150 soloZeroSplit(); | |
| 151 visit(node.condition); | |
| 152 token(node.rightParenthesis); | |
| 153 }); | |
| 154 } | |
| 155 | |
| 156 visitAssignmentExpression(AssignmentExpression node) { | |
| 157 builder.nestExpression(); | |
| 158 | |
| 159 visit(node.leftHandSide); | |
| 160 _visitAssignment(node.operator, node.rightHandSide); | |
| 161 | |
| 162 builder.unnest(); | |
| 163 } | |
| 164 | |
| 165 visitAwaitExpression(AwaitExpression node) { | |
| 166 token(node.awaitKeyword); | |
| 167 space(); | |
| 168 visit(node.expression); | |
| 169 } | |
| 170 | |
| 171 visitBinaryExpression(BinaryExpression node) { | |
| 172 builder.startSpan(); | |
| 173 builder.nestExpression(); | |
| 174 | |
| 175 // Start lazily so we don't force the operator to split if a line comment | |
| 176 // appears before the first operand. | |
| 177 builder.startLazyRule(); | |
| 178 | |
| 179 // Flatten out a tree/chain of the same precedence. If we split on this | |
| 180 // precedence level, we will break all of them. | |
| 181 var precedence = node.operator.type.precedence; | |
| 182 | |
| 183 traverse(Expression e) { | |
| 184 if (e is BinaryExpression && e.operator.type.precedence == precedence) { | |
| 185 traverse(e.leftOperand); | |
| 186 | |
| 187 space(); | |
| 188 token(e.operator); | |
| 189 | |
| 190 split(); | |
| 191 traverse(e.rightOperand); | |
| 192 } else { | |
| 193 visit(e); | |
| 194 } | |
| 195 } | |
| 196 | |
| 197 // Blocks as operands to infix operators should always nest like regular | |
| 198 // operands. (Granted, this case is exceedingly rare in real code.) | |
| 199 builder.startBlockArgumentNesting(); | |
| 200 | |
| 201 traverse(node); | |
| 202 | |
| 203 builder.endBlockArgumentNesting(); | |
| 204 | |
| 205 builder.unnest(); | |
| 206 builder.endSpan(); | |
| 207 builder.endRule(); | |
| 208 } | |
| 209 | |
| 210 visitBlock(Block node) { | |
| 211 // For a block that is not a function body, just bump the indentation and | |
| 212 // keep it in the current block. | |
| 213 if (node.parent is! BlockFunctionBody) { | |
| 214 _writeBody(node.leftBracket, node.rightBracket, body: () { | |
| 215 visitNodes(node.statements, between: oneOrTwoNewlines, after: newline); | |
| 216 }); | |
| 217 return; | |
| 218 } | |
| 219 | |
| 220 _startLiteralBody(node.leftBracket); | |
| 221 visitNodes(node.statements, between: oneOrTwoNewlines, after: newline); | |
| 222 _endLiteralBody(node.rightBracket, forceSplit: node.statements.isNotEmpty); | |
| 223 } | |
| 224 | |
| 225 visitBlockFunctionBody(BlockFunctionBody node) { | |
| 226 // Space after the parameter list. | |
| 227 space(); | |
| 228 | |
| 229 // The "async" or "sync" keyword. | |
| 230 token(node.keyword); | |
| 231 | |
| 232 // The "*" in "async*" or "sync*". | |
| 233 token(node.star); | |
| 234 if (node.keyword != null) space(); | |
| 235 | |
| 236 visit(node.block); | |
| 237 } | |
| 238 | |
| 239 visitBooleanLiteral(BooleanLiteral node) { | |
| 240 token(node.literal); | |
| 241 } | |
| 242 | |
| 243 visitBreakStatement(BreakStatement node) { | |
| 244 _simpleStatement(node, () { | |
| 245 token(node.breakKeyword); | |
| 246 visit(node.label, before: space); | |
| 247 }); | |
| 248 } | |
| 249 | |
| 250 visitCascadeExpression(CascadeExpression node) { | |
| 251 // If the target of the cascade is a method call (or chain of them), we | |
| 252 // treat the nesting specially. Normally, you would end up with: | |
| 253 // | |
| 254 // receiver | |
| 255 // .method() | |
| 256 // .method() | |
| 257 // ..cascade() | |
| 258 // ..cascade(); | |
| 259 // | |
| 260 // This is logical, since the method chain is an operand of the cascade | |
| 261 // expression, so it's more deeply nested. But it looks wrong, so we leave | |
| 262 // the method chain's nesting active until after the cascade sections to | |
| 263 // force the *cascades* to be deeper because it looks better: | |
| 264 // | |
| 265 // receiver | |
| 266 // .method() | |
| 267 // .method() | |
| 268 // ..cascade() | |
| 269 // ..cascade(); | |
| 270 if (node.target is MethodInvocation) { | |
| 271 new CallChainVisitor(this, node.target).visit(unnest: false); | |
| 272 } else { | |
| 273 visit(node.target); | |
| 274 } | |
| 275 | |
| 276 builder.nestExpression(indent: Indent.cascade, now: true); | |
| 277 builder.startBlockArgumentNesting(); | |
| 278 | |
| 279 // If the cascade sections have consistent names they can be broken | |
| 280 // normally otherwise they always get their own line. | |
| 281 if (_allowInlineCascade(node.cascadeSections)) { | |
| 282 builder.startRule(); | |
| 283 zeroSplit(); | |
| 284 visitNodes(node.cascadeSections, between: zeroSplit); | |
| 285 builder.endRule(); | |
| 286 } else { | |
| 287 builder.startRule(new HardSplitRule()); | |
| 288 zeroSplit(); | |
| 289 visitNodes(node.cascadeSections, between: zeroSplit); | |
| 290 builder.endRule(); | |
| 291 } | |
| 292 | |
| 293 builder.endBlockArgumentNesting(); | |
| 294 builder.unnest(); | |
| 295 | |
| 296 if (node.target is MethodInvocation) builder.unnest(); | |
| 297 } | |
| 298 | |
| 299 /// Whether a cascade should be allowed to be inline as opposed to one | |
| 300 /// expression per line. | |
| 301 bool _allowInlineCascade(List<Expression> sections) { | |
| 302 if (sections.length < 2) return true; | |
| 303 | |
| 304 var name; | |
| 305 // We could be more forgiving about what constitutes sections with | |
| 306 // consistent names but for now we require all sections to have the same | |
| 307 // method name. | |
| 308 for (var expression in sections) { | |
| 309 if (expression is! MethodInvocation) return false; | |
| 310 if (name == null) { | |
| 311 name = expression.methodName.name; | |
| 312 } else if (name != expression.methodName.name) { | |
| 313 return false; | |
| 314 } | |
| 315 } | |
| 316 return true; | |
| 317 } | |
| 318 | |
| 319 visitCatchClause(CatchClause node) { | |
| 320 token(node.onKeyword, after: space); | |
| 321 visit(node.exceptionType); | |
| 322 | |
| 323 if (node.catchKeyword != null) { | |
| 324 if (node.exceptionType != null) { | |
| 325 space(); | |
| 326 } | |
| 327 token(node.catchKeyword); | |
| 328 space(); | |
| 329 token(node.leftParenthesis); | |
| 330 visit(node.exceptionParameter); | |
| 331 token(node.comma, after: space); | |
| 332 visit(node.stackTraceParameter); | |
| 333 token(node.rightParenthesis); | |
| 334 space(); | |
| 335 } else { | |
| 336 space(); | |
| 337 } | |
| 338 visit(node.body); | |
| 339 } | |
| 340 | |
| 341 visitClassDeclaration(ClassDeclaration node) { | |
| 342 visitDeclarationMetadata(node.metadata); | |
| 343 | |
| 344 builder.nestExpression(); | |
| 345 modifier(node.abstractKeyword); | |
| 346 token(node.classKeyword); | |
| 347 space(); | |
| 348 visit(node.name); | |
| 349 visit(node.typeParameters); | |
| 350 visit(node.extendsClause); | |
| 351 | |
| 352 builder.startRule(new CombinatorRule()); | |
| 353 visit(node.withClause); | |
| 354 visit(node.implementsClause); | |
| 355 builder.endRule(); | |
| 356 | |
| 357 visit(node.nativeClause, before: space); | |
| 358 space(); | |
| 359 | |
| 360 builder.unnest(); | |
| 361 _writeBody(node.leftBracket, node.rightBracket, body: () { | |
| 362 if (node.members.isNotEmpty) { | |
| 363 for (var member in node.members) { | |
| 364 visit(member); | |
| 365 | |
| 366 if (member == node.members.last) { | |
| 367 newline(); | |
| 368 break; | |
| 369 } | |
| 370 | |
| 371 var needsDouble = false; | |
| 372 if (member is ClassDeclaration) { | |
| 373 // Add a blank line after classes. | |
| 374 twoNewlines(); | |
| 375 } else if (member is MethodDeclaration) { | |
| 376 // Add a blank line after non-empty block methods. | |
| 377 var method = member as MethodDeclaration; | |
| 378 if (method.body is BlockFunctionBody) { | |
| 379 var body = method.body as BlockFunctionBody; | |
| 380 needsDouble = body.block.statements.isNotEmpty; | |
| 381 } | |
| 382 } | |
| 383 | |
| 384 if (needsDouble) { | |
| 385 twoNewlines(); | |
| 386 } else { | |
| 387 // Variables and arrow-bodied members can be more tightly packed if | |
| 388 // the user wants to group things together. | |
| 389 oneOrTwoNewlines(); | |
| 390 } | |
| 391 } | |
| 392 } | |
| 393 }); | |
| 394 } | |
| 395 | |
| 396 visitClassTypeAlias(ClassTypeAlias node) { | |
| 397 visitDeclarationMetadata(node.metadata); | |
| 398 | |
| 399 _simpleStatement(node, () { | |
| 400 modifier(node.abstractKeyword); | |
| 401 token(node.typedefKeyword); | |
| 402 space(); | |
| 403 visit(node.name); | |
| 404 visit(node.typeParameters); | |
| 405 space(); | |
| 406 token(node.equals); | |
| 407 space(); | |
| 408 | |
| 409 visit(node.superclass); | |
| 410 | |
| 411 builder.startRule(new CombinatorRule()); | |
| 412 visit(node.withClause); | |
| 413 visit(node.implementsClause); | |
| 414 builder.endRule(); | |
| 415 }); | |
| 416 } | |
| 417 | |
| 418 visitComment(Comment node) => null; | |
| 419 | |
| 420 visitCommentReference(CommentReference node) => null; | |
| 421 | |
| 422 visitCompilationUnit(CompilationUnit node) { | |
| 423 visit(node.scriptTag); | |
| 424 | |
| 425 // Put a blank line between the library tag and the other directives. | |
| 426 var directives = node.directives; | |
| 427 if (directives.isNotEmpty && directives.first is LibraryDirective) { | |
| 428 visit(directives.first); | |
| 429 twoNewlines(); | |
| 430 | |
| 431 directives = directives.skip(1); | |
| 432 } | |
| 433 | |
| 434 visitNodes(directives, between: oneOrTwoNewlines); | |
| 435 | |
| 436 if (node.declarations.isNotEmpty) { | |
| 437 var needsDouble = true; | |
| 438 | |
| 439 for (var declaration in node.declarations) { | |
| 440 // Add a blank line before classes. | |
| 441 if (declaration is ClassDeclaration) needsDouble = true; | |
| 442 | |
| 443 if (needsDouble) { | |
| 444 twoNewlines(); | |
| 445 } else { | |
| 446 // Variables and arrow-bodied members can be more tightly packed if | |
| 447 // the user wants to group things together. | |
| 448 oneOrTwoNewlines(); | |
| 449 } | |
| 450 | |
| 451 visit(declaration); | |
| 452 | |
| 453 needsDouble = false; | |
| 454 if (declaration is ClassDeclaration) { | |
| 455 // Add a blank line after classes. | |
| 456 needsDouble = true; | |
| 457 } else if (declaration is FunctionDeclaration) { | |
| 458 // Add a blank line after non-empty block functions. | |
| 459 var function = declaration as FunctionDeclaration; | |
| 460 if (function.functionExpression.body is BlockFunctionBody) { | |
| 461 var body = function.functionExpression.body as BlockFunctionBody; | |
| 462 needsDouble = body.block.statements.isNotEmpty; | |
| 463 } | |
| 464 } | |
| 465 } | |
| 466 } | |
| 467 } | |
| 468 | |
| 469 visitConditionalExpression(ConditionalExpression node) { | |
| 470 builder.nestExpression(); | |
| 471 | |
| 472 // Push any block arguments all the way past the leading "?" and ":". | |
| 473 builder.nestExpression(indent: Indent.block, now: true); | |
| 474 builder.startBlockArgumentNesting(); | |
| 475 builder.unnest(); | |
| 476 | |
| 477 visit(node.condition); | |
| 478 | |
| 479 builder.startSpan(); | |
| 480 | |
| 481 // If we split after one clause in a conditional, always split after both. | |
| 482 builder.startRule(); | |
| 483 split(); | |
| 484 token(node.question); | |
| 485 space(); | |
| 486 | |
| 487 builder.nestExpression(); | |
| 488 visit(node.thenExpression); | |
| 489 builder.unnest(); | |
| 490 | |
| 491 split(); | |
| 492 token(node.colon); | |
| 493 space(); | |
| 494 | |
| 495 visit(node.elseExpression); | |
| 496 | |
| 497 builder.endRule(); | |
| 498 builder.endSpan(); | |
| 499 builder.endBlockArgumentNesting(); | |
| 500 builder.unnest(); | |
| 501 } | |
| 502 | |
| 503 visitConstructorDeclaration(ConstructorDeclaration node) { | |
| 504 visitMemberMetadata(node.metadata); | |
| 505 | |
| 506 modifier(node.externalKeyword); | |
| 507 modifier(node.constKeyword); | |
| 508 modifier(node.factoryKeyword); | |
| 509 visit(node.returnType); | |
| 510 token(node.period); | |
| 511 visit(node.name); | |
| 512 | |
| 513 // Make the rule for the ":" span both the preceding parameter list and | |
| 514 // the entire initialization list. This ensures that we split before the | |
| 515 // ":" if the parameters and initialization list don't all fit on one line. | |
| 516 builder.startRule(); | |
| 517 | |
| 518 _visitBody(node.parameters, node.body, () { | |
| 519 // Check for redirects or initializer lists. | |
| 520 if (node.redirectedConstructor != null) { | |
| 521 _visitConstructorRedirects(node); | |
| 522 } else if (node.initializers.isNotEmpty) { | |
| 523 _visitConstructorInitializers(node); | |
| 524 } | |
| 525 }); | |
| 526 } | |
| 527 | |
| 528 void _visitConstructorRedirects(ConstructorDeclaration node) { | |
| 529 token(node.separator /* = */, before: space, after: space); | |
| 530 visitCommaSeparatedNodes(node.initializers); | |
| 531 visit(node.redirectedConstructor); | |
| 532 } | |
| 533 | |
| 534 void _visitConstructorInitializers(ConstructorDeclaration node) { | |
| 535 // Shift the ":" forward. | |
| 536 builder.indent(Indent.constructorInitializer); | |
| 537 | |
| 538 split(); | |
| 539 token(node.separator); // ":". | |
| 540 space(); | |
| 541 | |
| 542 // Shift everything past the ":". | |
| 543 builder.indent(); | |
| 544 | |
| 545 for (var i = 0; i < node.initializers.length; i++) { | |
| 546 if (i > 0) { | |
| 547 // Preceding comma. | |
| 548 token(node.initializers[i].beginToken.previous); | |
| 549 newline(); | |
| 550 } | |
| 551 | |
| 552 node.initializers[i].accept(this); | |
| 553 } | |
| 554 | |
| 555 builder.unindent(); | |
| 556 builder.unindent(); | |
| 557 | |
| 558 // End the rule for ":" after all of the initializers. | |
| 559 builder.endRule(); | |
| 560 } | |
| 561 | |
| 562 visitConstructorFieldInitializer(ConstructorFieldInitializer node) { | |
| 563 builder.nestExpression(); | |
| 564 | |
| 565 token(node.thisKeyword); | |
| 566 token(node.period); | |
| 567 visit(node.fieldName); | |
| 568 | |
| 569 _visitAssignment(node.equals, node.expression); | |
| 570 | |
| 571 builder.unnest(); | |
| 572 } | |
| 573 | |
| 574 visitConstructorName(ConstructorName node) { | |
| 575 visit(node.type); | |
| 576 token(node.period); | |
| 577 visit(node.name); | |
| 578 } | |
| 579 | |
| 580 visitContinueStatement(ContinueStatement node) { | |
| 581 _simpleStatement(node, () { | |
| 582 token(node.continueKeyword); | |
| 583 visit(node.label, before: space); | |
| 584 }); | |
| 585 } | |
| 586 | |
| 587 visitDeclaredIdentifier(DeclaredIdentifier node) { | |
| 588 modifier(node.keyword); | |
| 589 visit(node.type, after: space); | |
| 590 visit(node.identifier); | |
| 591 } | |
| 592 | |
| 593 visitDefaultFormalParameter(DefaultFormalParameter node) { | |
| 594 visit(node.parameter); | |
| 595 if (node.separator != null) { | |
| 596 builder.startSpan(); | |
| 597 builder.nestExpression(); | |
| 598 | |
| 599 // The '=' separator is preceded by a space, ":" is not. | |
| 600 if (node.separator.type == TokenType.EQ) space(); | |
| 601 token(node.separator); | |
| 602 | |
| 603 soloSplit(Cost.assignment); | |
| 604 visit(node.defaultValue); | |
| 605 | |
| 606 builder.unnest(); | |
| 607 builder.endSpan(); | |
| 608 } | |
| 609 } | |
| 610 | |
| 611 visitDoStatement(DoStatement node) { | |
| 612 _simpleStatement(node, () { | |
| 613 token(node.doKeyword); | |
| 614 space(); | |
| 615 visit(node.body); | |
| 616 space(); | |
| 617 token(node.whileKeyword); | |
| 618 space(); | |
| 619 token(node.leftParenthesis); | |
| 620 soloZeroSplit(); | |
| 621 visit(node.condition); | |
| 622 token(node.rightParenthesis); | |
| 623 }); | |
| 624 } | |
| 625 | |
| 626 visitDoubleLiteral(DoubleLiteral node) { | |
| 627 token(node.literal); | |
| 628 } | |
| 629 | |
| 630 visitEmptyFunctionBody(EmptyFunctionBody node) { | |
| 631 token(node.semicolon); | |
| 632 } | |
| 633 | |
| 634 visitEmptyStatement(EmptyStatement node) { | |
| 635 token(node.semicolon); | |
| 636 } | |
| 637 | |
| 638 visitEnumConstantDeclaration(EnumConstantDeclaration node) { | |
| 639 visit(node.name); | |
| 640 } | |
| 641 | |
| 642 visitEnumDeclaration(EnumDeclaration node) { | |
| 643 visitDeclarationMetadata(node.metadata); | |
| 644 | |
| 645 token(node.enumKeyword); | |
| 646 space(); | |
| 647 visit(node.name); | |
| 648 space(); | |
| 649 | |
| 650 _writeBody(node.leftBracket, node.rightBracket, space: true, body: () { | |
| 651 visitCommaSeparatedNodes(node.constants, between: split); | |
| 652 }); | |
| 653 } | |
| 654 | |
| 655 visitExportDirective(ExportDirective node) { | |
| 656 visitDeclarationMetadata(node.metadata); | |
| 657 | |
| 658 _simpleStatement(node, () { | |
| 659 token(node.keyword); | |
| 660 space(); | |
| 661 visit(node.uri); | |
| 662 | |
| 663 builder.startRule(new CombinatorRule()); | |
| 664 visitNodes(node.combinators); | |
| 665 builder.endRule(); | |
| 666 }); | |
| 667 } | |
| 668 | |
| 669 visitExpressionFunctionBody(ExpressionFunctionBody node) { | |
| 670 // Space after the parameter list. | |
| 671 space(); | |
| 672 | |
| 673 // The "async" or "sync" keyword. | |
| 674 token(node.keyword, after: space); | |
| 675 | |
| 676 // Try to keep the "(...) => " with the start of the body for anonymous | |
| 677 // functions. | |
| 678 if (_isInLambda(node)) builder.startSpan(); | |
| 679 | |
| 680 token(node.functionDefinition); // "=>". | |
| 681 | |
| 682 // Split after the "=>", using the rule created before the parameters | |
| 683 // by _visitBody(). | |
| 684 split(); | |
| 685 builder.endRule(); | |
| 686 | |
| 687 if (_isInLambda(node)) builder.endSpan(); | |
| 688 | |
| 689 builder.startBlockArgumentNesting(); | |
| 690 builder.startSpan(); | |
| 691 visit(node.expression); | |
| 692 builder.endSpan(); | |
| 693 builder.endBlockArgumentNesting(); | |
| 694 | |
| 695 token(node.semicolon); | |
| 696 } | |
| 697 | |
| 698 visitExpressionStatement(ExpressionStatement node) { | |
| 699 _simpleStatement(node, () { | |
| 700 visit(node.expression); | |
| 701 }); | |
| 702 } | |
| 703 | |
| 704 visitExtendsClause(ExtendsClause node) { | |
| 705 soloSplit(); | |
| 706 token(node.extendsKeyword); | |
| 707 space(); | |
| 708 visit(node.superclass); | |
| 709 } | |
| 710 | |
| 711 visitFieldDeclaration(FieldDeclaration node) { | |
| 712 visitMemberMetadata(node.metadata); | |
| 713 | |
| 714 _simpleStatement(node, () { | |
| 715 modifier(node.staticKeyword); | |
| 716 visit(node.fields); | |
| 717 }); | |
| 718 } | |
| 719 | |
| 720 visitFieldFormalParameter(FieldFormalParameter node) { | |
| 721 visitParameterMetadata(node.metadata, () { | |
| 722 token(node.keyword, after: space); | |
| 723 visit(node.type, after: space); | |
| 724 token(node.thisKeyword); | |
| 725 token(node.period); | |
| 726 visit(node.identifier); | |
| 727 visit(node.parameters); | |
| 728 }); | |
| 729 } | |
| 730 | |
| 731 visitForEachStatement(ForEachStatement node) { | |
| 732 builder.nestExpression(); | |
| 733 token(node.awaitKeyword, after: space); | |
| 734 token(node.forKeyword); | |
| 735 space(); | |
| 736 token(node.leftParenthesis); | |
| 737 if (node.loopVariable != null) { | |
| 738 visit(node.loopVariable); | |
| 739 } else { | |
| 740 visit(node.identifier); | |
| 741 } | |
| 742 soloSplit(); | |
| 743 token(node.inKeyword); | |
| 744 space(); | |
| 745 visit(node.iterable); | |
| 746 token(node.rightParenthesis); | |
| 747 space(); | |
| 748 visit(node.body); | |
| 749 builder.unnest(); | |
| 750 } | |
| 751 | |
| 752 visitFormalParameterList(FormalParameterList node) { | |
| 753 // Corner case: empty parameter lists. | |
| 754 if (node.parameters.isEmpty) { | |
| 755 token(node.leftParenthesis); | |
| 756 | |
| 757 // If there is a comment, do allow splitting before it. | |
| 758 if (node.rightParenthesis.precedingComments != null) soloZeroSplit(); | |
| 759 | |
| 760 token(node.rightParenthesis); | |
| 761 return; | |
| 762 } | |
| 763 | |
| 764 var requiredParams = node.parameters | |
| 765 .where((param) => param is! DefaultFormalParameter) | |
| 766 .toList(); | |
| 767 var optionalParams = node.parameters | |
| 768 .where((param) => param is DefaultFormalParameter) | |
| 769 .toList(); | |
| 770 | |
| 771 builder.nestExpression(); | |
| 772 token(node.leftParenthesis); | |
| 773 | |
| 774 var rule; | |
| 775 if (requiredParams.isNotEmpty) { | |
| 776 if (requiredParams.length > 1) { | |
| 777 rule = new MultiplePositionalRule(null, 0, 0); | |
| 778 } else { | |
| 779 rule = new SinglePositionalRule(null); | |
| 780 } | |
| 781 | |
| 782 builder.startRule(rule); | |
| 783 if (_isInLambda(node)) { | |
| 784 // Don't allow splitting before the first argument (i.e. right after | |
| 785 // the bare "(" in a lambda. Instead, just stuff a null chunk in there | |
| 786 // to avoid confusing the arg rule. | |
| 787 rule.beforeArgument(null); | |
| 788 } else { | |
| 789 // Split before the first argument. | |
| 790 rule.beforeArgument(zeroSplit()); | |
| 791 } | |
| 792 | |
| 793 builder.startSpan(); | |
| 794 | |
| 795 for (var param in requiredParams) { | |
| 796 visit(param); | |
| 797 | |
| 798 // Write the trailing comma. | |
| 799 if (param != node.parameters.last) token(param.endToken.next); | |
| 800 | |
| 801 if (param != requiredParams.last) rule.beforeArgument(split()); | |
| 802 } | |
| 803 | |
| 804 builder.endSpan(); | |
| 805 builder.endRule(); | |
| 806 } | |
| 807 | |
| 808 if (optionalParams.isNotEmpty) { | |
| 809 var namedRule = new NamedRule(null); | |
| 810 if (rule != null) rule.setNamedArgsRule(namedRule); | |
| 811 | |
| 812 builder.startRule(namedRule); | |
| 813 | |
| 814 namedRule | |
| 815 .beforeArguments(builder.split(space: requiredParams.isNotEmpty)); | |
| 816 | |
| 817 // "[" or "{" for optional parameters. | |
| 818 token(node.leftDelimiter); | |
| 819 | |
| 820 for (var param in optionalParams) { | |
| 821 visit(param); | |
| 822 | |
| 823 // Write the trailing comma. | |
| 824 if (param != node.parameters.last) token(param.endToken.next); | |
| 825 if (param != optionalParams.last) split(); | |
| 826 } | |
| 827 | |
| 828 builder.endRule(); | |
| 829 | |
| 830 // "]" or "}" for optional parameters. | |
| 831 token(node.rightDelimiter); | |
| 832 } | |
| 833 | |
| 834 token(node.rightParenthesis); | |
| 835 builder.unnest(); | |
| 836 } | |
| 837 | |
| 838 visitForStatement(ForStatement node) { | |
| 839 builder.nestExpression(); | |
| 840 token(node.forKeyword); | |
| 841 space(); | |
| 842 token(node.leftParenthesis); | |
| 843 | |
| 844 builder.startRule(); | |
| 845 | |
| 846 // The initialization clause. | |
| 847 if (node.initialization != null) { | |
| 848 visit(node.initialization); | |
| 849 } else if (node.variables != null) { | |
| 850 // Indent split variables more so they aren't at the same level | |
| 851 // as the rest of the loop clauses. | |
| 852 builder.indent(Indent.loopVariable); | |
| 853 | |
| 854 // Allow the variables to stay unsplit even if the clauses split. | |
| 855 builder.startRule(); | |
| 856 | |
| 857 var declaration = node.variables; | |
| 858 visitDeclarationMetadata(declaration.metadata); | |
| 859 modifier(declaration.keyword); | |
| 860 visit(declaration.type, after: space); | |
| 861 | |
| 862 visitCommaSeparatedNodes(declaration.variables, between: () { | |
| 863 split(); | |
| 864 }); | |
| 865 | |
| 866 builder.endRule(); | |
| 867 builder.unindent(); | |
| 868 } | |
| 869 | |
| 870 token(node.leftSeparator); | |
| 871 | |
| 872 // The condition clause. | |
| 873 if (node.condition != null) split(); | |
| 874 visit(node.condition); | |
| 875 token(node.rightSeparator); | |
| 876 | |
| 877 // The update clause. | |
| 878 if (node.updaters.isNotEmpty) { | |
| 879 split(); | |
| 880 | |
| 881 // Allow the updates to stay unsplit even if the clauses split. | |
| 882 builder.startRule(); | |
| 883 | |
| 884 visitCommaSeparatedNodes(node.updaters, between: split); | |
| 885 | |
| 886 builder.endRule(); | |
| 887 } | |
| 888 | |
| 889 token(node.rightParenthesis); | |
| 890 builder.endRule(); | |
| 891 builder.unnest(); | |
| 892 | |
| 893 // The body. | |
| 894 if (node.body is! EmptyStatement) space(); | |
| 895 visit(node.body); | |
| 896 } | |
| 897 | |
| 898 visitFunctionDeclaration(FunctionDeclaration node) { | |
| 899 visitMemberMetadata(node.metadata); | |
| 900 | |
| 901 builder.nestExpression(); | |
| 902 modifier(node.externalKeyword); | |
| 903 visit(node.returnType, after: space); | |
| 904 modifier(node.propertyKeyword); | |
| 905 visit(node.name); | |
| 906 visit(node.functionExpression); | |
| 907 builder.unnest(); | |
| 908 } | |
| 909 | |
| 910 visitFunctionDeclarationStatement(FunctionDeclarationStatement node) { | |
| 911 visit(node.functionDeclaration); | |
| 912 } | |
| 913 | |
| 914 visitFunctionExpression(FunctionExpression node) { | |
| 915 _visitBody(node.parameters, node.body); | |
| 916 } | |
| 917 | |
| 918 visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { | |
| 919 visit(node.function); | |
| 920 visit(node.argumentList); | |
| 921 } | |
| 922 | |
| 923 visitFunctionTypeAlias(FunctionTypeAlias node) { | |
| 924 visitDeclarationMetadata(node.metadata); | |
| 925 | |
| 926 _simpleStatement(node, () { | |
| 927 token(node.typedefKeyword); | |
| 928 space(); | |
| 929 visit(node.returnType, after: space); | |
| 930 visit(node.name); | |
| 931 visit(node.typeParameters); | |
| 932 visit(node.parameters); | |
| 933 }); | |
| 934 } | |
| 935 | |
| 936 visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) { | |
| 937 visitParameterMetadata(node.metadata, () { | |
| 938 visit(node.returnType, after: space); | |
| 939 | |
| 940 // Try to keep the function's parameters with its name. | |
| 941 builder.startSpan(); | |
| 942 visit(node.identifier); | |
| 943 visit(node.parameters); | |
| 944 builder.endSpan(); | |
| 945 }); | |
| 946 } | |
| 947 | |
| 948 visitHideCombinator(HideCombinator node) { | |
| 949 _visitCombinator(node.keyword, node.hiddenNames); | |
| 950 } | |
| 951 | |
| 952 visitIfStatement(IfStatement node) { | |
| 953 builder.nestExpression(); | |
| 954 token(node.ifKeyword); | |
| 955 space(); | |
| 956 token(node.leftParenthesis); | |
| 957 visit(node.condition); | |
| 958 token(node.rightParenthesis); | |
| 959 | |
| 960 space(); | |
| 961 visit(node.thenStatement); | |
| 962 builder.unnest(); | |
| 963 | |
| 964 if (node.elseStatement != null) { | |
| 965 if (node.thenStatement is Block) { | |
| 966 space(); | |
| 967 } else { | |
| 968 // Corner case where an else follows a single-statement then clause. | |
| 969 // This is against the style guide, but we still need to handle it. If | |
| 970 // it happens, put the else on the next line. | |
| 971 newline(); | |
| 972 } | |
| 973 | |
| 974 token(node.elseKeyword); | |
| 975 space(); | |
| 976 visit(node.elseStatement); | |
| 977 } | |
| 978 } | |
| 979 | |
| 980 visitImplementsClause(ImplementsClause node) { | |
| 981 _visitCombinator(node.implementsKeyword, node.interfaces); | |
| 982 } | |
| 983 | |
| 984 visitImportDirective(ImportDirective node) { | |
| 985 visitDeclarationMetadata(node.metadata); | |
| 986 | |
| 987 _simpleStatement(node, () { | |
| 988 token(node.keyword); | |
| 989 space(); | |
| 990 visit(node.uri); | |
| 991 | |
| 992 if (node.asKeyword != null) { | |
| 993 soloSplit(); | |
| 994 token(node.deferredKeyword, after: space); | |
| 995 token(node.asKeyword); | |
| 996 space(); | |
| 997 visit(node.prefix); | |
| 998 } | |
| 999 | |
| 1000 builder.startRule(new CombinatorRule()); | |
| 1001 visitNodes(node.combinators); | |
| 1002 builder.endRule(); | |
| 1003 }); | |
| 1004 } | |
| 1005 | |
| 1006 visitIndexExpression(IndexExpression node) { | |
| 1007 builder.nestExpression(); | |
| 1008 | |
| 1009 if (node.isCascaded) { | |
| 1010 token(node.period); | |
| 1011 } else { | |
| 1012 visit(node.target); | |
| 1013 } | |
| 1014 | |
| 1015 if (node.target is IndexExpression) { | |
| 1016 // Corner case: On a chain of [] accesses, allow splitting between them. | |
| 1017 // Produces nicer output in cases like: | |
| 1018 // | |
| 1019 // someJson['property']['property']['property']['property']... | |
| 1020 soloZeroSplit(); | |
| 1021 } | |
| 1022 | |
| 1023 builder.startSpan(); | |
| 1024 token(node.leftBracket); | |
| 1025 soloZeroSplit(); | |
| 1026 visit(node.index); | |
| 1027 token(node.rightBracket); | |
| 1028 builder.endSpan(); | |
| 1029 builder.unnest(); | |
| 1030 } | |
| 1031 | |
| 1032 visitInstanceCreationExpression(InstanceCreationExpression node) { | |
| 1033 builder.startSpan(); | |
| 1034 token(node.keyword); | |
| 1035 space(); | |
| 1036 visit(node.constructorName); | |
| 1037 visit(node.argumentList); | |
| 1038 builder.endSpan(); | |
| 1039 } | |
| 1040 | |
| 1041 visitIntegerLiteral(IntegerLiteral node) { | |
| 1042 token(node.literal); | |
| 1043 } | |
| 1044 | |
| 1045 visitInterpolationExpression(InterpolationExpression node) { | |
| 1046 token(node.leftBracket); | |
| 1047 visit(node.expression); | |
| 1048 token(node.rightBracket); | |
| 1049 } | |
| 1050 | |
| 1051 visitInterpolationString(InterpolationString node) { | |
| 1052 token(node.contents); | |
| 1053 } | |
| 1054 | |
| 1055 visitIsExpression(IsExpression node) { | |
| 1056 builder.startSpan(); | |
| 1057 visit(node.expression); | |
| 1058 soloSplit(); | |
| 1059 token(node.isOperator); | |
| 1060 token(node.notOperator); | |
| 1061 space(); | |
| 1062 visit(node.type); | |
| 1063 builder.endSpan(); | |
| 1064 } | |
| 1065 | |
| 1066 visitLabel(Label node) { | |
| 1067 visit(node.label); | |
| 1068 token(node.colon); | |
| 1069 } | |
| 1070 | |
| 1071 visitLabeledStatement(LabeledStatement node) { | |
| 1072 visitNodes(node.labels, between: space, after: space); | |
| 1073 visit(node.statement); | |
| 1074 } | |
| 1075 | |
| 1076 visitLibraryDirective(LibraryDirective node) { | |
| 1077 visitDeclarationMetadata(node.metadata); | |
| 1078 | |
| 1079 _simpleStatement(node, () { | |
| 1080 token(node.keyword); | |
| 1081 space(); | |
| 1082 visit(node.name); | |
| 1083 }); | |
| 1084 } | |
| 1085 | |
| 1086 visitLibraryIdentifier(LibraryIdentifier node) { | |
| 1087 visit(node.components.first); | |
| 1088 for (var component in node.components.skip(1)) { | |
| 1089 token(component.beginToken.previous); // "." | |
| 1090 visit(component); | |
| 1091 } | |
| 1092 } | |
| 1093 | |
| 1094 visitListLiteral(ListLiteral node) { | |
| 1095 // Corner case: Splitting inside a list looks bad if there's only one | |
| 1096 // element, so make those more costly. | |
| 1097 var cost = node.elements.length <= 1 ? Cost.singleElementList : Cost.normal; | |
| 1098 _visitCollectionLiteral( | |
| 1099 node, node.leftBracket, node.elements, node.rightBracket, cost); | |
| 1100 } | |
| 1101 | |
| 1102 visitMapLiteral(MapLiteral node) { | |
| 1103 _visitCollectionLiteral( | |
| 1104 node, node.leftBracket, node.entries, node.rightBracket); | |
| 1105 } | |
| 1106 | |
| 1107 visitMapLiteralEntry(MapLiteralEntry node) { | |
| 1108 visit(node.key); | |
| 1109 token(node.separator); | |
| 1110 soloSplit(); | |
| 1111 visit(node.value); | |
| 1112 } | |
| 1113 | |
| 1114 visitMethodDeclaration(MethodDeclaration node) { | |
| 1115 visitMemberMetadata(node.metadata); | |
| 1116 | |
| 1117 modifier(node.externalKeyword); | |
| 1118 modifier(node.modifierKeyword); | |
| 1119 visit(node.returnType, after: space); | |
| 1120 modifier(node.propertyKeyword); | |
| 1121 modifier(node.operatorKeyword); | |
| 1122 visit(node.name); | |
| 1123 | |
| 1124 _visitBody(node.parameters, node.body); | |
| 1125 } | |
| 1126 | |
| 1127 visitMethodInvocation(MethodInvocation node) { | |
| 1128 // If there's no target, this is a "bare" function call like "foo(1, 2)", | |
| 1129 // or a section in a cascade. Handle this case specially. | |
| 1130 if (node.target == null) { | |
| 1131 // Try to keep the entire method invocation one line. | |
| 1132 builder.startSpan(); | |
| 1133 builder.nestExpression(); | |
| 1134 | |
| 1135 // This will be non-null for cascade sections. | |
| 1136 token(node.operator); | |
| 1137 token(node.methodName.token); | |
| 1138 visit(node.argumentList); | |
| 1139 | |
| 1140 builder.unnest(); | |
| 1141 builder.endSpan(); | |
| 1142 return; | |
| 1143 } | |
| 1144 | |
| 1145 new CallChainVisitor(this, node).visit(); | |
| 1146 } | |
| 1147 | |
| 1148 visitNamedExpression(NamedExpression node) { | |
| 1149 builder.nestExpression(); | |
| 1150 builder.startSpan(); | |
| 1151 visit(node.name); | |
| 1152 visit(node.expression, before: soloSplit); | |
| 1153 builder.endSpan(); | |
| 1154 builder.unnest(); | |
| 1155 } | |
| 1156 | |
| 1157 visitNativeClause(NativeClause node) { | |
| 1158 token(node.nativeKeyword); | |
| 1159 space(); | |
| 1160 visit(node.name); | |
| 1161 } | |
| 1162 | |
| 1163 visitNativeFunctionBody(NativeFunctionBody node) { | |
| 1164 _simpleStatement(node, () { | |
| 1165 builder.nestExpression(now: true); | |
| 1166 soloSplit(); | |
| 1167 token(node.nativeKeyword); | |
| 1168 space(); | |
| 1169 visit(node.stringLiteral); | |
| 1170 builder.unnest(); | |
| 1171 }); | |
| 1172 } | |
| 1173 | |
| 1174 visitNullLiteral(NullLiteral node) { | |
| 1175 token(node.literal); | |
| 1176 } | |
| 1177 | |
| 1178 visitParenthesizedExpression(ParenthesizedExpression node) { | |
| 1179 builder.nestExpression(); | |
| 1180 token(node.leftParenthesis); | |
| 1181 visit(node.expression); | |
| 1182 builder.unnest(); | |
| 1183 token(node.rightParenthesis); | |
| 1184 } | |
| 1185 | |
| 1186 visitPartDirective(PartDirective node) { | |
| 1187 _simpleStatement(node, () { | |
| 1188 token(node.keyword); | |
| 1189 space(); | |
| 1190 visit(node.uri); | |
| 1191 }); | |
| 1192 } | |
| 1193 | |
| 1194 visitPartOfDirective(PartOfDirective node) { | |
| 1195 _simpleStatement(node, () { | |
| 1196 token(node.keyword); | |
| 1197 space(); | |
| 1198 token(node.ofKeyword); | |
| 1199 space(); | |
| 1200 visit(node.libraryName); | |
| 1201 }); | |
| 1202 } | |
| 1203 | |
| 1204 visitPostfixExpression(PostfixExpression node) { | |
| 1205 visit(node.operand); | |
| 1206 token(node.operator); | |
| 1207 } | |
| 1208 | |
| 1209 visitPrefixedIdentifier(PrefixedIdentifier node) { | |
| 1210 visit(node.prefix); | |
| 1211 token(node.period); | |
| 1212 visit(node.identifier); | |
| 1213 } | |
| 1214 | |
| 1215 visitPrefixExpression(PrefixExpression node) { | |
| 1216 token(node.operator); | |
| 1217 | |
| 1218 // Corner case: put a space between successive "-" operators so we don't | |
| 1219 // inadvertently turn them into a "--" decrement operator. | |
| 1220 if (node.operand is PrefixExpression && | |
| 1221 (node.operand as PrefixExpression).operator.lexeme == "-") { | |
| 1222 space(); | |
| 1223 } | |
| 1224 | |
| 1225 visit(node.operand); | |
| 1226 } | |
| 1227 | |
| 1228 visitPropertyAccess(PropertyAccess node) { | |
| 1229 if (node.isCascaded) { | |
| 1230 token(node.operator); | |
| 1231 visit(node.propertyName); | |
| 1232 return; | |
| 1233 } | |
| 1234 | |
| 1235 new CallChainVisitor(this, node).visit(); | |
| 1236 } | |
| 1237 | |
| 1238 visitRedirectingConstructorInvocation(RedirectingConstructorInvocation node) { | |
| 1239 builder.startSpan(); | |
| 1240 | |
| 1241 token(node.thisKeyword); | |
| 1242 token(node.period); | |
| 1243 visit(node.constructorName); | |
| 1244 visit(node.argumentList); | |
| 1245 | |
| 1246 builder.endSpan(); | |
| 1247 } | |
| 1248 | |
| 1249 visitRethrowExpression(RethrowExpression node) { | |
| 1250 token(node.rethrowKeyword); | |
| 1251 } | |
| 1252 | |
| 1253 visitReturnStatement(ReturnStatement node) { | |
| 1254 _simpleStatement(node, () { | |
| 1255 token(node.returnKeyword); | |
| 1256 visit(node.expression, before: space); | |
| 1257 }); | |
| 1258 } | |
| 1259 | |
| 1260 visitScriptTag(ScriptTag node) { | |
| 1261 // The lexeme includes the trailing newline. Strip it off since the | |
| 1262 // formatter ensures it gets a newline after it. Since the script tag must | |
| 1263 // come at the top of the file, we don't have to worry about preceding | |
| 1264 // comments or whitespace. | |
| 1265 _writeText(node.scriptTag.lexeme.trim(), node.offset); | |
| 1266 | |
| 1267 oneOrTwoNewlines(); | |
| 1268 } | |
| 1269 | |
| 1270 visitShowCombinator(ShowCombinator node) { | |
| 1271 _visitCombinator(node.keyword, node.shownNames); | |
| 1272 } | |
| 1273 | |
| 1274 visitSimpleFormalParameter(SimpleFormalParameter node) { | |
| 1275 visitParameterMetadata(node.metadata, () { | |
| 1276 modifier(node.keyword); | |
| 1277 visit(node.type, after: space); | |
| 1278 visit(node.identifier); | |
| 1279 }); | |
| 1280 } | |
| 1281 | |
| 1282 visitSimpleIdentifier(SimpleIdentifier node) { | |
| 1283 token(node.token); | |
| 1284 } | |
| 1285 | |
| 1286 visitSimpleStringLiteral(SimpleStringLiteral node) { | |
| 1287 // Since we output the string literal manually, ensure any preceding | |
| 1288 // comments are written first. | |
| 1289 writePrecedingCommentsAndNewlines(node.literal); | |
| 1290 | |
| 1291 _writeStringLiteral(node.literal.lexeme, node.offset); | |
| 1292 } | |
| 1293 | |
| 1294 visitStringInterpolation(StringInterpolation node) { | |
| 1295 // Since we output the interpolated text manually, ensure we include any | |
| 1296 // preceding stuff first. | |
| 1297 writePrecedingCommentsAndNewlines(node.beginToken); | |
| 1298 | |
| 1299 // Right now, the formatter does not try to do any reformatting of the | |
| 1300 // contents of interpolated strings. Instead, it treats the entire thing as | |
| 1301 // a single (possibly multi-line) chunk of text. | |
| 1302 _writeStringLiteral( | |
| 1303 _source.text.substring(node.beginToken.offset, node.endToken.end), | |
| 1304 node.offset); | |
| 1305 } | |
| 1306 | |
| 1307 visitSuperConstructorInvocation(SuperConstructorInvocation node) { | |
| 1308 builder.startSpan(); | |
| 1309 | |
| 1310 token(node.superKeyword); | |
| 1311 token(node.period); | |
| 1312 visit(node.constructorName); | |
| 1313 visit(node.argumentList); | |
| 1314 | |
| 1315 builder.endSpan(); | |
| 1316 } | |
| 1317 | |
| 1318 visitSuperExpression(SuperExpression node) { | |
| 1319 token(node.superKeyword); | |
| 1320 } | |
| 1321 | |
| 1322 visitSwitchCase(SwitchCase node) { | |
| 1323 visitNodes(node.labels, between: space, after: space); | |
| 1324 token(node.keyword); | |
| 1325 space(); | |
| 1326 visit(node.expression); | |
| 1327 token(node.colon); | |
| 1328 | |
| 1329 builder.indent(); | |
| 1330 // TODO(rnystrom): Allow inline cases? | |
| 1331 newline(); | |
| 1332 | |
| 1333 visitNodes(node.statements, between: oneOrTwoNewlines); | |
| 1334 builder.unindent(); | |
| 1335 } | |
| 1336 | |
| 1337 visitSwitchDefault(SwitchDefault node) { | |
| 1338 visitNodes(node.labels, between: space, after: space); | |
| 1339 token(node.keyword); | |
| 1340 token(node.colon); | |
| 1341 | |
| 1342 builder.indent(); | |
| 1343 // TODO(rnystrom): Allow inline cases? | |
| 1344 newline(); | |
| 1345 | |
| 1346 visitNodes(node.statements, between: oneOrTwoNewlines); | |
| 1347 builder.unindent(); | |
| 1348 } | |
| 1349 | |
| 1350 visitSwitchStatement(SwitchStatement node) { | |
| 1351 builder.nestExpression(); | |
| 1352 token(node.switchKeyword); | |
| 1353 space(); | |
| 1354 token(node.leftParenthesis); | |
| 1355 soloZeroSplit(); | |
| 1356 visit(node.expression); | |
| 1357 token(node.rightParenthesis); | |
| 1358 space(); | |
| 1359 token(node.leftBracket); | |
| 1360 builder.indent(); | |
| 1361 newline(); | |
| 1362 | |
| 1363 visitNodes(node.members, between: oneOrTwoNewlines, after: newline); | |
| 1364 token(node.rightBracket, before: () { | |
| 1365 builder.unindent(); | |
| 1366 newline(); | |
| 1367 }); | |
| 1368 builder.unnest(); | |
| 1369 } | |
| 1370 | |
| 1371 visitSymbolLiteral(SymbolLiteral node) { | |
| 1372 token(node.poundSign); | |
| 1373 var components = node.components; | |
| 1374 for (var component in components) { | |
| 1375 // The '.' separator | |
| 1376 if (component.previous.lexeme == '.') { | |
| 1377 token(component.previous); | |
| 1378 } | |
| 1379 token(component); | |
| 1380 } | |
| 1381 } | |
| 1382 | |
| 1383 visitThisExpression(ThisExpression node) { | |
| 1384 token(node.thisKeyword); | |
| 1385 } | |
| 1386 | |
| 1387 visitThrowExpression(ThrowExpression node) { | |
| 1388 token(node.throwKeyword); | |
| 1389 space(); | |
| 1390 visit(node.expression); | |
| 1391 } | |
| 1392 | |
| 1393 visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { | |
| 1394 visitDeclarationMetadata(node.metadata); | |
| 1395 | |
| 1396 _simpleStatement(node, () { | |
| 1397 visit(node.variables); | |
| 1398 }); | |
| 1399 } | |
| 1400 | |
| 1401 visitTryStatement(TryStatement node) { | |
| 1402 token(node.tryKeyword); | |
| 1403 space(); | |
| 1404 visit(node.body); | |
| 1405 visitNodes(node.catchClauses, before: space, between: space); | |
| 1406 token(node.finallyKeyword, before: space, after: space); | |
| 1407 visit(node.finallyBlock); | |
| 1408 } | |
| 1409 | |
| 1410 visitTypeArgumentList(TypeArgumentList node) { | |
| 1411 _visitGenericList(node.leftBracket, node.rightBracket, node.arguments); | |
| 1412 } | |
| 1413 | |
| 1414 visitTypeName(TypeName node) { | |
| 1415 visit(node.name); | |
| 1416 visit(node.typeArguments); | |
| 1417 } | |
| 1418 | |
| 1419 visitTypeParameter(TypeParameter node) { | |
| 1420 visitParameterMetadata(node.metadata, () { | |
| 1421 visit(node.name); | |
| 1422 token(node.extendsKeyword, before: space, after: space); | |
| 1423 visit(node.bound); | |
| 1424 }); | |
| 1425 } | |
| 1426 | |
| 1427 visitTypeParameterList(TypeParameterList node) { | |
| 1428 _visitGenericList(node.leftBracket, node.rightBracket, node.typeParameters); | |
| 1429 } | |
| 1430 | |
| 1431 visitVariableDeclaration(VariableDeclaration node) { | |
| 1432 visit(node.name); | |
| 1433 if (node.initializer == null) return; | |
| 1434 | |
| 1435 _visitAssignment(node.equals, node.initializer); | |
| 1436 } | |
| 1437 | |
| 1438 visitVariableDeclarationList(VariableDeclarationList node) { | |
| 1439 visitDeclarationMetadata(node.metadata); | |
| 1440 modifier(node.keyword); | |
| 1441 visit(node.type, after: space); | |
| 1442 | |
| 1443 // Use a single rule for all of the variables. If there are multiple | |
| 1444 // declarations, we will try to keep them all on one line. If that isn't | |
| 1445 // possible, we split after *every* declaration so that each is on its own | |
| 1446 // line. | |
| 1447 builder.startRule(); | |
| 1448 visitCommaSeparatedNodes(node.variables, between: split); | |
| 1449 builder.endRule(); | |
| 1450 } | |
| 1451 | |
| 1452 visitVariableDeclarationStatement(VariableDeclarationStatement node) { | |
| 1453 _simpleStatement(node, () { | |
| 1454 visit(node.variables); | |
| 1455 }); | |
| 1456 } | |
| 1457 | |
| 1458 visitWhileStatement(WhileStatement node) { | |
| 1459 builder.nestExpression(); | |
| 1460 token(node.whileKeyword); | |
| 1461 space(); | |
| 1462 token(node.leftParenthesis); | |
| 1463 soloZeroSplit(); | |
| 1464 visit(node.condition); | |
| 1465 token(node.rightParenthesis); | |
| 1466 if (node.body is! EmptyStatement) space(); | |
| 1467 visit(node.body); | |
| 1468 builder.unnest(); | |
| 1469 } | |
| 1470 | |
| 1471 visitWithClause(WithClause node) { | |
| 1472 _visitCombinator(node.withKeyword, node.mixinTypes); | |
| 1473 } | |
| 1474 | |
| 1475 visitYieldStatement(YieldStatement node) { | |
| 1476 _simpleStatement(node, () { | |
| 1477 token(node.yieldKeyword); | |
| 1478 token(node.star); | |
| 1479 space(); | |
| 1480 visit(node.expression); | |
| 1481 }); | |
| 1482 } | |
| 1483 | |
| 1484 /// Visit a [node], and if not null, optionally preceded or followed by the | |
| 1485 /// specified functions. | |
| 1486 void visit(AstNode node, {void before(), void after()}) { | |
| 1487 if (node == null) return; | |
| 1488 | |
| 1489 if (before != null) before(); | |
| 1490 | |
| 1491 node.accept(this); | |
| 1492 | |
| 1493 if (after != null) after(); | |
| 1494 } | |
| 1495 | |
| 1496 /// Visit metadata annotations on directives and declarations. | |
| 1497 /// | |
| 1498 /// These always force the annotations to be on the previous line. | |
| 1499 void visitDeclarationMetadata(NodeList<Annotation> metadata) { | |
| 1500 // If there are multiple annotations, they are always on their own lines, | |
| 1501 // even the last. | |
| 1502 if (metadata.length > 1) { | |
| 1503 visitNodes(metadata, between: newline, after: newline); | |
| 1504 } else { | |
| 1505 visitNodes(metadata, between: space, after: newline); | |
| 1506 } | |
| 1507 } | |
| 1508 | |
| 1509 /// Visit metadata annotations on members. | |
| 1510 /// | |
| 1511 /// These may be on the same line as the member, or on the previous. | |
| 1512 void visitMemberMetadata(NodeList<Annotation> metadata) { | |
| 1513 // If there are multiple annotations, they are always on their own lines, | |
| 1514 // even the last. | |
| 1515 if (metadata.length > 1) { | |
| 1516 visitNodes(metadata, between: newline, after: newline); | |
| 1517 } else { | |
| 1518 visitNodes(metadata, between: space, after: spaceOrNewline); | |
| 1519 } | |
| 1520 } | |
| 1521 | |
| 1522 /// Visits metadata annotations on parameters and type parameters. | |
| 1523 /// | |
| 1524 /// These are always on the same line as the parameter. | |
| 1525 void visitParameterMetadata( | |
| 1526 NodeList<Annotation> metadata, void visitParameter()) { | |
| 1527 // Split before all of the annotations or none. | |
| 1528 builder.startRule(); | |
| 1529 visitNodes(metadata, between: split, after: split); | |
| 1530 visitParameter(); | |
| 1531 | |
| 1532 // Wrap the rule around the parameter too. If it splits, we want to force | |
| 1533 // the annotations to split as well. | |
| 1534 builder.endRule(); | |
| 1535 } | |
| 1536 | |
| 1537 /// Visits the `=` and the following expression in any place where an `=` | |
| 1538 /// appears: | |
| 1539 /// | |
| 1540 /// * Assignment | |
| 1541 /// * Variable declaration | |
| 1542 /// * Constructor initialization | |
| 1543 void _visitAssignment(Token equalsOperator, Expression rightHandSide) { | |
| 1544 space(); | |
| 1545 token(equalsOperator); | |
| 1546 soloSplit(Cost.assignment); | |
| 1547 builder.startSpan(); | |
| 1548 visit(rightHandSide); | |
| 1549 builder.endSpan(); | |
| 1550 } | |
| 1551 | |
| 1552 /// Visits a type parameter or type argument list. | |
| 1553 void _visitGenericList( | |
| 1554 Token leftBracket, Token rightBracket, List<AstNode> nodes) { | |
| 1555 var rule = new TypeArgumentRule(); | |
| 1556 builder.startRule(rule); | |
| 1557 builder.startSpan(); | |
| 1558 builder.nestExpression(); | |
| 1559 | |
| 1560 token(leftBracket); | |
| 1561 rule.beforeArgument(zeroSplit()); | |
| 1562 | |
| 1563 for (var node in nodes) { | |
| 1564 visit(node); | |
| 1565 | |
| 1566 // Write the trailing comma. | |
| 1567 if (node != nodes.last) { | |
| 1568 token(node.endToken.next); | |
| 1569 rule.beforeArgument(split()); | |
| 1570 } | |
| 1571 } | |
| 1572 | |
| 1573 token(rightBracket); | |
| 1574 | |
| 1575 builder.unnest(); | |
| 1576 builder.endSpan(); | |
| 1577 builder.endRule(); | |
| 1578 } | |
| 1579 | |
| 1580 /// Visit the given function [parameters] followed by its [body], printing a | |
| 1581 /// space before it if it's not empty. | |
| 1582 /// | |
| 1583 /// If [afterParameters] is provided, it is invoked between the parameters | |
| 1584 /// and body. (It's used for constructor initialization lists.) | |
| 1585 void _visitBody(FormalParameterList parameters, FunctionBody body, | |
| 1586 [afterParameters()]) { | |
| 1587 // If the body is "=>", add an extra level of indentation around the | |
| 1588 // parameters and a rule that spans the parameters and the "=>". This | |
| 1589 // ensures that if the parameters wrap, they wrap more deeply than the "=>" | |
| 1590 // does, as in: | |
| 1591 // | |
| 1592 // someFunction(parameter, | |
| 1593 // parameter, parameter) => | |
| 1594 // "the body"; | |
| 1595 // | |
| 1596 // Also, it ensures that if the parameters wrap, we split at the "=>" too | |
| 1597 // to avoid: | |
| 1598 // | |
| 1599 // someFunction(parameter, | |
| 1600 // parameter) => function( | |
| 1601 // argument); | |
| 1602 // | |
| 1603 // This is confusing because it looks like those two lines are at the same | |
| 1604 // level when they are actually unrelated. Splitting at "=>" forces: | |
| 1605 // | |
| 1606 // someFunction(parameter, | |
| 1607 // parameter) => | |
| 1608 // function( | |
| 1609 // argument); | |
| 1610 if (body is ExpressionFunctionBody) { | |
| 1611 builder.nestExpression(); | |
| 1612 | |
| 1613 // This rule is ended by visitExpressionFunctionBody(). | |
| 1614 builder.startLazyRule(new SimpleRule(cost: Cost.arrow)); | |
| 1615 } | |
| 1616 | |
| 1617 if (parameters != null) { | |
| 1618 builder.nestExpression(); | |
| 1619 | |
| 1620 visit(parameters); | |
| 1621 if (afterParameters != null) afterParameters(); | |
| 1622 | |
| 1623 builder.unnest(); | |
| 1624 } | |
| 1625 | |
| 1626 visit(body); | |
| 1627 | |
| 1628 if (body is ExpressionFunctionBody) builder.unnest(); | |
| 1629 } | |
| 1630 | |
| 1631 /// Visit a list of [nodes] if not null, optionally separated and/or preceded | |
| 1632 /// and followed by the given functions. | |
| 1633 void visitNodes(Iterable<AstNode> nodes, {before(), between(), after()}) { | |
| 1634 if (nodes == null || nodes.isEmpty) return; | |
| 1635 | |
| 1636 if (before != null) before(); | |
| 1637 | |
| 1638 visit(nodes.first); | |
| 1639 for (var node in nodes.skip(1)) { | |
| 1640 if (between != null) between(); | |
| 1641 visit(node); | |
| 1642 } | |
| 1643 | |
| 1644 if (after != null) after(); | |
| 1645 } | |
| 1646 | |
| 1647 /// Visit a comma-separated list of [nodes] if not null. | |
| 1648 void visitCommaSeparatedNodes(Iterable<AstNode> nodes, {between()}) { | |
| 1649 if (nodes == null || nodes.isEmpty) return; | |
| 1650 | |
| 1651 if (between == null) between = space; | |
| 1652 | |
| 1653 var first = true; | |
| 1654 for (var node in nodes) { | |
| 1655 if (!first) between(); | |
| 1656 first = false; | |
| 1657 | |
| 1658 visit(node); | |
| 1659 | |
| 1660 // The comma after the node. | |
| 1661 if (node.endToken.next.lexeme == ",") token(node.endToken.next); | |
| 1662 } | |
| 1663 } | |
| 1664 | |
| 1665 /// Visits the collection literal [node] whose body starts with [leftBracket], | |
| 1666 /// ends with [rightBracket] and contains [elements]. | |
| 1667 void _visitCollectionLiteral(TypedLiteral node, Token leftBracket, | |
| 1668 Iterable<AstNode> elements, Token rightBracket, | |
| 1669 [int cost]) { | |
| 1670 modifier(node.constKeyword); | |
| 1671 visit(node.typeArguments); | |
| 1672 | |
| 1673 // Don't allow splitting in an empty collection. | |
| 1674 if (elements.isEmpty && rightBracket.precedingComments == null) { | |
| 1675 token(leftBracket); | |
| 1676 token(rightBracket); | |
| 1677 | |
| 1678 // Clear this out in case this empty collection is in an argument list. | |
| 1679 // We don't want this rule to bleed over to some other collection. | |
| 1680 _nextLiteralBodyRule = null; | |
| 1681 return; | |
| 1682 } | |
| 1683 | |
| 1684 // Force all of the surrounding collections to split. | |
| 1685 for (var i = 0; i < _collectionSplits.length; i++) { | |
| 1686 _collectionSplits[i] = true; | |
| 1687 } | |
| 1688 | |
| 1689 // Add this collection to the stack. | |
| 1690 _collectionSplits.add(false); | |
| 1691 | |
| 1692 _startLiteralBody(leftBracket); | |
| 1693 | |
| 1694 // Always use a hard rule to split the elements. The parent chunk of | |
| 1695 // the collection will handle the unsplit case, so this only comes | |
| 1696 // into play when the collection is split. | |
| 1697 var rule = new HardSplitRule(); | |
| 1698 builder.startRule(rule); | |
| 1699 | |
| 1700 // If a collection contains a line comment, we assume it's a big complex | |
| 1701 // blob of data with some documented structure. In that case, the user | |
| 1702 // probably broke the elements into lines deliberately, so preserve those. | |
| 1703 var preserveNewlines = _containsLineComments(elements, rightBracket); | |
| 1704 | |
| 1705 for (var element in elements) { | |
| 1706 if (element != elements.first) { | |
| 1707 if (preserveNewlines) { | |
| 1708 if (_endLine(element.beginToken.previous) != | |
| 1709 _startLine(element.beginToken)) { | |
| 1710 oneOrTwoNewlines(); | |
| 1711 } else { | |
| 1712 soloSplit(); | |
| 1713 } | |
| 1714 } else { | |
| 1715 builder.blockSplit(space: true); | |
| 1716 } | |
| 1717 } | |
| 1718 | |
| 1719 builder.nestExpression(); | |
| 1720 visit(element); | |
| 1721 | |
| 1722 // The comma after the element. | |
| 1723 if (element.endToken.next.lexeme == ",") token(element.endToken.next); | |
| 1724 | |
| 1725 builder.unnest(); | |
| 1726 } | |
| 1727 | |
| 1728 builder.endRule(); | |
| 1729 | |
| 1730 // If there is a collection inside this one, it forces this one to split. | |
| 1731 var force = _collectionSplits.removeLast(); | |
| 1732 | |
| 1733 _endLiteralBody(rightBracket, ignoredRule: rule, forceSplit: force); | |
| 1734 } | |
| 1735 | |
| 1736 /// Returns `true` if the collection withs [elements] delimited by | |
| 1737 /// [rightBracket] contains any line comments. | |
| 1738 /// | |
| 1739 /// This only looks for comments at the element boundary. Comments within an | |
| 1740 /// element are ignored. | |
| 1741 bool _containsLineComments(Iterable<AstNode> elements, Token rightBracket) { | |
| 1742 hasLineCommentBefore(token) { | |
| 1743 var comment = token.precedingComments; | |
| 1744 for (; comment != null; comment = comment.next) { | |
| 1745 if (comment.type == TokenType.SINGLE_LINE_COMMENT) return true; | |
| 1746 } | |
| 1747 | |
| 1748 return false; | |
| 1749 } | |
| 1750 | |
| 1751 // Look before each element. | |
| 1752 for (var element in elements) { | |
| 1753 if (hasLineCommentBefore(element.beginToken)) return true; | |
| 1754 } | |
| 1755 | |
| 1756 // Look before the closing bracket. | |
| 1757 return hasLineCommentBefore(rightBracket); | |
| 1758 } | |
| 1759 | |
| 1760 /// Begins writing a literal body: a collection or block-bodied function | |
| 1761 /// expression. | |
| 1762 /// | |
| 1763 /// Writes the delimiter and then creates the [Rule] that handles splitting | |
| 1764 /// the body. | |
| 1765 void _startLiteralBody(Token leftBracket) { | |
| 1766 token(leftBracket); | |
| 1767 | |
| 1768 // Split the literal. Use the explicitly given rule if we have one. | |
| 1769 // Otherwise, create a new rule. | |
| 1770 var rule = _nextLiteralBodyRule; | |
| 1771 _nextLiteralBodyRule = null; | |
| 1772 | |
| 1773 // Create a rule for whether or not to split the block contents. | |
| 1774 builder.startRule(rule); | |
| 1775 | |
| 1776 // Process the collection contents as a separate set of chunks. | |
| 1777 builder = builder.startBlock(); | |
| 1778 } | |
| 1779 | |
| 1780 /// Ends the literal body started by a call to [_startLiteralBody()]. | |
| 1781 /// | |
| 1782 /// If [forceSplit] is `true`, forces the body to split. If [ignoredRule] is | |
| 1783 /// given, ignores that rule inside the body when determining if it should | |
| 1784 /// split. | |
| 1785 void _endLiteralBody(Token rightBracket, | |
| 1786 {Rule ignoredRule, bool forceSplit}) { | |
| 1787 if (forceSplit == null) forceSplit = false; | |
| 1788 | |
| 1789 // Put comments before the closing delimiter inside the block. | |
| 1790 var hasLeadingNewline = writePrecedingCommentsAndNewlines(rightBracket); | |
| 1791 | |
| 1792 builder = builder.endBlock(ignoredRule, | |
| 1793 forceSplit: hasLeadingNewline || forceSplit); | |
| 1794 | |
| 1795 builder.endRule(); | |
| 1796 | |
| 1797 // Now write the delimiter itself. | |
| 1798 _writeText(rightBracket.lexeme, rightBracket.offset); | |
| 1799 } | |
| 1800 | |
| 1801 /// Visits a "combinator". | |
| 1802 /// | |
| 1803 /// This is a [keyword] followed by a list of [nodes], with specific line | |
| 1804 /// splitting rules. As the name implies, this is used for [HideCombinator] | |
| 1805 /// and [ShowCombinator], but it also used for "with" and "implements" | |
| 1806 /// clauses in class declarations, which are formatted the same way. | |
| 1807 /// | |
| 1808 /// This assumes the current rule is a [CombinatorRule]. | |
| 1809 void _visitCombinator(Token keyword, Iterable<AstNode> nodes) { | |
| 1810 // Allow splitting before the keyword. | |
| 1811 var rule = builder.rule as CombinatorRule; | |
| 1812 rule.addCombinator(split()); | |
| 1813 | |
| 1814 builder.nestExpression(); | |
| 1815 token(keyword); | |
| 1816 | |
| 1817 rule.addName(split()); | |
| 1818 visitCommaSeparatedNodes(nodes, between: () => rule.addName(split())); | |
| 1819 | |
| 1820 builder.unnest(); | |
| 1821 } | |
| 1822 | |
| 1823 /// Writes the simple statement or semicolon-delimited top-level declaration. | |
| 1824 /// | |
| 1825 /// Handles nesting if a line break occurs in the statement and writes the | |
| 1826 /// terminating semicolon. Invokes [body] which should write statement itself. | |
| 1827 void _simpleStatement(AstNode node, body()) { | |
| 1828 builder.nestExpression(); | |
| 1829 body(); | |
| 1830 | |
| 1831 // TODO(rnystrom): Can the analyzer move "semicolon" to some shared base | |
| 1832 // type? | |
| 1833 token((node as dynamic).semicolon); | |
| 1834 builder.unnest(); | |
| 1835 } | |
| 1836 | |
| 1837 /// Makes [rule] the rule that will be used for the contents of a collection | |
| 1838 /// or function literal body that are about to be visited. | |
| 1839 void setNextLiteralBodyRule(Rule rule) { | |
| 1840 _nextLiteralBodyRule = rule; | |
| 1841 } | |
| 1842 | |
| 1843 /// Writes an bracket-delimited body and handles indenting and starting the | |
| 1844 /// rule used to split the contents. | |
| 1845 /// | |
| 1846 /// If [space] is `true`, then the contents and delimiters will have a space | |
| 1847 /// between then when unsplit. | |
| 1848 void _writeBody(Token leftBracket, Token rightBracket, | |
| 1849 {bool space: false, body()}) { | |
| 1850 token(leftBracket); | |
| 1851 | |
| 1852 // Indent the body. | |
| 1853 builder.indent(); | |
| 1854 | |
| 1855 // Split after the bracket. | |
| 1856 builder.startRule(); | |
| 1857 builder.blockSplit(space: space, isDouble: false); | |
| 1858 | |
| 1859 body(); | |
| 1860 | |
| 1861 token(rightBracket, before: () { | |
| 1862 // Split before the closing bracket character. | |
| 1863 builder.unindent(); | |
| 1864 builder.blockSplit(space: space); | |
| 1865 }); | |
| 1866 | |
| 1867 builder.endRule(); | |
| 1868 } | |
| 1869 | |
| 1870 /// Returns `true` if [node] is immediately contained within an anonymous | |
| 1871 /// [FunctionExpression]. | |
| 1872 bool _isInLambda(AstNode node) => node.parent is FunctionExpression && | |
| 1873 node.parent.parent is! FunctionDeclaration; | |
| 1874 | |
| 1875 /// Writes the string literal [string] to the output. | |
| 1876 /// | |
| 1877 /// Splits multiline strings into separate chunks so that the line splitter | |
| 1878 /// can handle them correctly. | |
| 1879 void _writeStringLiteral(String string, int offset) { | |
| 1880 // Split each line of a multiline string into separate chunks. | |
| 1881 var lines = string.split(_formatter.lineEnding); | |
| 1882 | |
| 1883 _writeText(lines.first, offset); | |
| 1884 offset += lines.first.length; | |
| 1885 | |
| 1886 for (var line in lines.skip(1)) { | |
| 1887 builder.writeWhitespace(Whitespace.newlineFlushLeft); | |
| 1888 offset++; | |
| 1889 _writeText(line, offset); | |
| 1890 offset += line.length; | |
| 1891 } | |
| 1892 } | |
| 1893 | |
| 1894 /// Emit the given [modifier] if it's non null, followed by non-breaking | |
| 1895 /// whitespace. | |
| 1896 void modifier(Token modifier) { | |
| 1897 token(modifier, after: space); | |
| 1898 } | |
| 1899 | |
| 1900 /// Emit a non-breaking space. | |
| 1901 void space() { | |
| 1902 builder.writeWhitespace(Whitespace.space); | |
| 1903 } | |
| 1904 | |
| 1905 /// Emit a single mandatory newline. | |
| 1906 void newline() { | |
| 1907 builder.writeWhitespace(Whitespace.newline); | |
| 1908 } | |
| 1909 | |
| 1910 /// Emit a two mandatory newlines. | |
| 1911 void twoNewlines() { | |
| 1912 builder.writeWhitespace(Whitespace.twoNewlines); | |
| 1913 } | |
| 1914 | |
| 1915 /// Allow either a single space or newline to be emitted before the next | |
| 1916 /// non-whitespace token based on whether a newline exists in the source | |
| 1917 /// between the last token and the next one. | |
| 1918 void spaceOrNewline() { | |
| 1919 builder.writeWhitespace(Whitespace.spaceOrNewline); | |
| 1920 } | |
| 1921 | |
| 1922 /// Allow either a single split or newline to be emitted before the next | |
| 1923 /// non-whitespace token based on whether a newline exists in the source | |
| 1924 /// between the last token and the next one. | |
| 1925 void splitOrNewline() { | |
| 1926 builder.writeWhitespace(Whitespace.splitOrNewline); | |
| 1927 } | |
| 1928 | |
| 1929 /// Allow either one or two newlines to be emitted before the next | |
| 1930 /// non-whitespace token based on whether more than one newline exists in the | |
| 1931 /// source between the last token and the next one. | |
| 1932 void oneOrTwoNewlines() { | |
| 1933 builder.writeWhitespace(Whitespace.oneOrTwoNewlines); | |
| 1934 } | |
| 1935 | |
| 1936 /// Writes a single space split owned by the current rule. | |
| 1937 /// | |
| 1938 /// Returns the chunk the split was applied to. | |
| 1939 Chunk split() => builder.split(space: true); | |
| 1940 | |
| 1941 /// Writes a zero-space split owned by the current rule. | |
| 1942 /// | |
| 1943 /// Returns the chunk the split was applied to. | |
| 1944 Chunk zeroSplit() => builder.split(); | |
| 1945 | |
| 1946 /// Writes a single space split with its own rule. | |
| 1947 void soloSplit([int cost]) { | |
| 1948 builder.startRule(new SimpleRule(cost: cost)); | |
| 1949 split(); | |
| 1950 builder.endRule(); | |
| 1951 } | |
| 1952 | |
| 1953 /// Writes a zero-space split with its own rule. | |
| 1954 void soloZeroSplit() { | |
| 1955 builder.startRule(); | |
| 1956 builder.split(); | |
| 1957 builder.endRule(); | |
| 1958 } | |
| 1959 | |
| 1960 /// Emit [token], along with any comments and formatted whitespace that comes | |
| 1961 /// before it. | |
| 1962 /// | |
| 1963 /// Does nothing if [token] is `null`. If [before] is given, it will be | |
| 1964 /// executed before the token is outout. Likewise, [after] will be called | |
| 1965 /// after the token is output. | |
| 1966 void token(Token token, {before(), after()}) { | |
| 1967 if (token == null) return; | |
| 1968 | |
| 1969 writePrecedingCommentsAndNewlines(token); | |
| 1970 | |
| 1971 if (before != null) before(); | |
| 1972 | |
| 1973 _writeText(token.lexeme, token.offset); | |
| 1974 | |
| 1975 if (after != null) after(); | |
| 1976 } | |
| 1977 | |
| 1978 /// Writes all formatted whitespace and comments that appear before [token]. | |
| 1979 bool writePrecedingCommentsAndNewlines(Token token) { | |
| 1980 var comment = token.precedingComments; | |
| 1981 | |
| 1982 // For performance, avoid calculating newlines between tokens unless | |
| 1983 // actually needed. | |
| 1984 if (comment == null) { | |
| 1985 if (builder.needsToPreserveNewlines) { | |
| 1986 builder.preserveNewlines(_startLine(token) - _endLine(token.previous)); | |
| 1987 } | |
| 1988 | |
| 1989 return false; | |
| 1990 } | |
| 1991 | |
| 1992 var previousLine = _endLine(token.previous); | |
| 1993 | |
| 1994 // Corner case! The analyzer includes the "\n" in the script tag's lexeme, | |
| 1995 // which means it appears to be one line later than it is. That causes a | |
| 1996 // comment following it to appear to be on the same line. Fix that here by | |
| 1997 // correcting the script tag's line. | |
| 1998 if (token.previous.type == TokenType.SCRIPT_TAG) previousLine--; | |
| 1999 | |
| 2000 var tokenLine = _startLine(token); | |
| 2001 | |
| 2002 var comments = []; | |
| 2003 while (comment != null) { | |
| 2004 var commentLine = _startLine(comment); | |
| 2005 | |
| 2006 // Don't preserve newlines at the top of the file. | |
| 2007 if (comment == token.precedingComments && | |
| 2008 token.previous.type == TokenType.EOF) { | |
| 2009 previousLine = commentLine; | |
| 2010 } | |
| 2011 | |
| 2012 var text = comment.toString().trim(); | |
| 2013 var linesBefore = commentLine - previousLine; | |
| 2014 var flushLeft = _startColumn(comment) == 1; | |
| 2015 | |
| 2016 if (text.startsWith("///") && !text.startsWith("////")) { | |
| 2017 // Line doc comments are always indented even if they were flush left. | |
| 2018 flushLeft = false; | |
| 2019 | |
| 2020 // Always add a blank line (if possible) before a doc comment block. | |
| 2021 if (comment == token.precedingComments) linesBefore = 2; | |
| 2022 } | |
| 2023 | |
| 2024 var sourceComment = new SourceComment(text, linesBefore, | |
| 2025 isLineComment: comment.type == TokenType.SINGLE_LINE_COMMENT, | |
| 2026 flushLeft: flushLeft); | |
| 2027 | |
| 2028 // If this comment contains either of the selection endpoints, mark them | |
| 2029 // in the comment. | |
| 2030 var start = _getSelectionStartWithin(comment.offset, comment.length); | |
| 2031 if (start != null) sourceComment.startSelection(start); | |
| 2032 | |
| 2033 var end = _getSelectionEndWithin(comment.offset, comment.length); | |
| 2034 if (end != null) sourceComment.endSelection(end); | |
| 2035 | |
| 2036 comments.add(sourceComment); | |
| 2037 | |
| 2038 previousLine = _endLine(comment); | |
| 2039 comment = comment.next; | |
| 2040 } | |
| 2041 | |
| 2042 builder.writeComments(comments, tokenLine - previousLine, token.lexeme); | |
| 2043 | |
| 2044 // TODO(rnystrom): This is wrong. Consider: | |
| 2045 // | |
| 2046 // [/* inline comment */ | |
| 2047 // // line comment | |
| 2048 // element]; | |
| 2049 return comments.first.linesBefore > 0; | |
| 2050 } | |
| 2051 | |
| 2052 /// Write [text] to the current chunk, given that it starts at [offset] in | |
| 2053 /// the original source. | |
| 2054 /// | |
| 2055 /// Also outputs the selection endpoints if needed. | |
| 2056 void _writeText(String text, int offset) { | |
| 2057 builder.write(text); | |
| 2058 | |
| 2059 // If this text contains either of the selection endpoints, mark them in | |
| 2060 // the chunk. | |
| 2061 var start = _getSelectionStartWithin(offset, text.length); | |
| 2062 if (start != null) { | |
| 2063 builder.startSelectionFromEnd(text.length - start); | |
| 2064 } | |
| 2065 | |
| 2066 var end = _getSelectionEndWithin(offset, text.length); | |
| 2067 if (end != null) { | |
| 2068 builder.endSelectionFromEnd(text.length - end); | |
| 2069 } | |
| 2070 } | |
| 2071 | |
| 2072 /// Returns the number of characters past [offset] in the source where the | |
| 2073 /// selection start appears if it appears before `offset + length`. | |
| 2074 /// | |
| 2075 /// Returns `null` if the selection start has already been processed or is | |
| 2076 /// not within that range. | |
| 2077 int _getSelectionStartWithin(int offset, int length) { | |
| 2078 // If there is no selection, do nothing. | |
| 2079 if (_source.selectionStart == null) return null; | |
| 2080 | |
| 2081 // If we've already passed it, don't consider it again. | |
| 2082 if (_passedSelectionStart) return null; | |
| 2083 | |
| 2084 var start = _source.selectionStart - offset; | |
| 2085 | |
| 2086 // If it started in whitespace before this text, push it forward to the | |
| 2087 // beginning of the non-whitespace text. | |
| 2088 if (start < 0) start = 0; | |
| 2089 | |
| 2090 // If we haven't reached it yet, don't consider it. | |
| 2091 if (start >= length) return null; | |
| 2092 | |
| 2093 // We found it. | |
| 2094 _passedSelectionStart = true; | |
| 2095 | |
| 2096 return start; | |
| 2097 } | |
| 2098 | |
| 2099 /// Returns the number of characters past [offset] in the source where the | |
| 2100 /// selection endpoint appears if it appears before `offset + length`. | |
| 2101 /// | |
| 2102 /// Returns `null` if the selection endpoint has already been processed or is | |
| 2103 /// not within that range. | |
| 2104 int _getSelectionEndWithin(int offset, int length) { | |
| 2105 // If there is no selection, do nothing. | |
| 2106 if (_source.selectionLength == null) return null; | |
| 2107 | |
| 2108 // If we've already passed it, don't consider it again. | |
| 2109 if (_passedSelectionEnd) return null; | |
| 2110 | |
| 2111 var end = _findSelectionEnd() - offset; | |
| 2112 | |
| 2113 // If it started in whitespace before this text, push it forward to the | |
| 2114 // beginning of the non-whitespace text. | |
| 2115 if (end < 0) end = 0; | |
| 2116 | |
| 2117 // If we haven't reached it yet, don't consider it. | |
| 2118 if (end > length) return null; | |
| 2119 | |
| 2120 if (end == length && _findSelectionEnd() == _source.selectionStart) { | |
| 2121 return null; | |
| 2122 } | |
| 2123 | |
| 2124 // We found it. | |
| 2125 _passedSelectionEnd = true; | |
| 2126 | |
| 2127 return end; | |
| 2128 } | |
| 2129 | |
| 2130 /// Calculates the character offset in the source text of the end of the | |
| 2131 /// selection. | |
| 2132 /// | |
| 2133 /// Removes any trailing whitespace from the selection. | |
| 2134 int _findSelectionEnd() { | |
| 2135 if (_selectionEnd != null) return _selectionEnd; | |
| 2136 | |
| 2137 _selectionEnd = _source.selectionStart + _source.selectionLength; | |
| 2138 | |
| 2139 // If the selection bumps to the end of the source, pin it there. | |
| 2140 if (_selectionEnd == _source.text.length) return _selectionEnd; | |
| 2141 | |
| 2142 // Trim off any trailing whitespace. We want the selection to "rubberband" | |
| 2143 // around the selected non-whitespace tokens since the whitespace will | |
| 2144 // be munged by the formatter itself. | |
| 2145 while (_selectionEnd > _source.selectionStart) { | |
| 2146 // Stop if we hit anything other than space, tab, newline or carriage | |
| 2147 // return. | |
| 2148 var char = _source.text.codeUnitAt(_selectionEnd - 1); | |
| 2149 if (char != 0x20 && char != 0x09 && char != 0x0a && char != 0x0d) { | |
| 2150 break; | |
| 2151 } | |
| 2152 | |
| 2153 _selectionEnd--; | |
| 2154 } | |
| 2155 | |
| 2156 return _selectionEnd; | |
| 2157 } | |
| 2158 | |
| 2159 /// Gets the 1-based line number that the beginning of [token] lies on. | |
| 2160 int _startLine(Token token) => _lineInfo.getLocation(token.offset).lineNumber; | |
| 2161 | |
| 2162 /// Gets the 1-based line number that the end of [token] lies on. | |
| 2163 int _endLine(Token token) => _lineInfo.getLocation(token.end).lineNumber; | |
| 2164 | |
| 2165 /// Gets the 1-based column number that the beginning of [token] lies on. | |
| 2166 int _startColumn(Token token) => | |
| 2167 _lineInfo.getLocation(token.offset).columnNumber; | |
| 2168 } | |
| OLD | NEW |