1 /* <lambda>null2 * Copyright (c) Meta Platforms, Inc. and affiliates. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.facebook.ktfmt.format 18 19 import com.google.common.base.Throwables 20 import com.google.common.collect.ImmutableList 21 import com.google.googlejavaformat.Doc 22 import com.google.googlejavaformat.FormattingError 23 import com.google.googlejavaformat.Indent 24 import com.google.googlejavaformat.Indent.Const.ZERO 25 import com.google.googlejavaformat.OpsBuilder 26 import com.google.googlejavaformat.Output.BreakTag 27 import java.util.ArrayDeque 28 import java.util.Optional 29 import org.jetbrains.kotlin.com.intellij.psi.PsiComment 30 import org.jetbrains.kotlin.com.intellij.psi.PsiElement 31 import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace 32 import org.jetbrains.kotlin.com.intellij.psi.stubs.PsiFileStubImpl 33 import org.jetbrains.kotlin.lexer.KtModifierKeywordToken 34 import org.jetbrains.kotlin.lexer.KtTokens 35 import org.jetbrains.kotlin.psi.KtAnnotatedExpression 36 import org.jetbrains.kotlin.psi.KtAnnotation 37 import org.jetbrains.kotlin.psi.KtAnnotationEntry 38 import org.jetbrains.kotlin.psi.KtAnnotationUseSiteTarget 39 import org.jetbrains.kotlin.psi.KtArrayAccessExpression 40 import org.jetbrains.kotlin.psi.KtBinaryExpression 41 import org.jetbrains.kotlin.psi.KtBinaryExpressionWithTypeRHS 42 import org.jetbrains.kotlin.psi.KtBlockExpression 43 import org.jetbrains.kotlin.psi.KtBreakExpression 44 import org.jetbrains.kotlin.psi.KtCallExpression 45 import org.jetbrains.kotlin.psi.KtCallableReferenceExpression 46 import org.jetbrains.kotlin.psi.KtCatchClause 47 import org.jetbrains.kotlin.psi.KtClass 48 import org.jetbrains.kotlin.psi.KtClassBody 49 import org.jetbrains.kotlin.psi.KtClassInitializer 50 import org.jetbrains.kotlin.psi.KtClassLiteralExpression 51 import org.jetbrains.kotlin.psi.KtClassOrObject 52 import org.jetbrains.kotlin.psi.KtCollectionLiteralExpression 53 import org.jetbrains.kotlin.psi.KtConstantExpression 54 import org.jetbrains.kotlin.psi.KtConstructorDelegationCall 55 import org.jetbrains.kotlin.psi.KtContainerNode 56 import org.jetbrains.kotlin.psi.KtContextReceiverList 57 import org.jetbrains.kotlin.psi.KtContinueExpression 58 import org.jetbrains.kotlin.psi.KtDelegatedSuperTypeEntry 59 import org.jetbrains.kotlin.psi.KtDestructuringDeclaration 60 import org.jetbrains.kotlin.psi.KtDestructuringDeclarationEntry 61 import org.jetbrains.kotlin.psi.KtDoWhileExpression 62 import org.jetbrains.kotlin.psi.KtDotQualifiedExpression 63 import org.jetbrains.kotlin.psi.KtDynamicType 64 import org.jetbrains.kotlin.psi.KtElement 65 import org.jetbrains.kotlin.psi.KtEnumEntry 66 import org.jetbrains.kotlin.psi.KtExpression 67 import org.jetbrains.kotlin.psi.KtFile 68 import org.jetbrains.kotlin.psi.KtFileAnnotationList 69 import org.jetbrains.kotlin.psi.KtFinallySection 70 import org.jetbrains.kotlin.psi.KtForExpression 71 import org.jetbrains.kotlin.psi.KtFunctionType 72 import org.jetbrains.kotlin.psi.KtIfExpression 73 import org.jetbrains.kotlin.psi.KtImportDirective 74 import org.jetbrains.kotlin.psi.KtImportList 75 import org.jetbrains.kotlin.psi.KtIntersectionType 76 import org.jetbrains.kotlin.psi.KtIsExpression 77 import org.jetbrains.kotlin.psi.KtLabelReferenceExpression 78 import org.jetbrains.kotlin.psi.KtLabeledExpression 79 import org.jetbrains.kotlin.psi.KtLambdaArgument 80 import org.jetbrains.kotlin.psi.KtLambdaExpression 81 import org.jetbrains.kotlin.psi.KtModifierList 82 import org.jetbrains.kotlin.psi.KtNamedFunction 83 import org.jetbrains.kotlin.psi.KtNullableType 84 import org.jetbrains.kotlin.psi.KtPackageDirective 85 import org.jetbrains.kotlin.psi.KtParameter 86 import org.jetbrains.kotlin.psi.KtParameterList 87 import org.jetbrains.kotlin.psi.KtParenthesizedExpression 88 import org.jetbrains.kotlin.psi.KtPostfixExpression 89 import org.jetbrains.kotlin.psi.KtPrefixExpression 90 import org.jetbrains.kotlin.psi.KtPrimaryConstructor 91 import org.jetbrains.kotlin.psi.KtProjectionKind 92 import org.jetbrains.kotlin.psi.KtProperty 93 import org.jetbrains.kotlin.psi.KtPropertyAccessor 94 import org.jetbrains.kotlin.psi.KtPropertyDelegate 95 import org.jetbrains.kotlin.psi.KtQualifiedExpression 96 import org.jetbrains.kotlin.psi.KtReferenceExpression 97 import org.jetbrains.kotlin.psi.KtReturnExpression 98 import org.jetbrains.kotlin.psi.KtScript 99 import org.jetbrains.kotlin.psi.KtScriptInitializer 100 import org.jetbrains.kotlin.psi.KtSecondaryConstructor 101 import org.jetbrains.kotlin.psi.KtSimpleNameExpression 102 import org.jetbrains.kotlin.psi.KtStringTemplateExpression 103 import org.jetbrains.kotlin.psi.KtSuperExpression 104 import org.jetbrains.kotlin.psi.KtSuperTypeCallEntry 105 import org.jetbrains.kotlin.psi.KtSuperTypeList 106 import org.jetbrains.kotlin.psi.KtThisExpression 107 import org.jetbrains.kotlin.psi.KtThrowExpression 108 import org.jetbrains.kotlin.psi.KtTreeVisitorVoid 109 import org.jetbrains.kotlin.psi.KtTryExpression 110 import org.jetbrains.kotlin.psi.KtTypeAlias 111 import org.jetbrains.kotlin.psi.KtTypeArgumentList 112 import org.jetbrains.kotlin.psi.KtTypeConstraint 113 import org.jetbrains.kotlin.psi.KtTypeConstraintList 114 import org.jetbrains.kotlin.psi.KtTypeParameter 115 import org.jetbrains.kotlin.psi.KtTypeParameterList 116 import org.jetbrains.kotlin.psi.KtTypeProjection 117 import org.jetbrains.kotlin.psi.KtTypeReference 118 import org.jetbrains.kotlin.psi.KtUserType 119 import org.jetbrains.kotlin.psi.KtValueArgument 120 import org.jetbrains.kotlin.psi.KtValueArgumentList 121 import org.jetbrains.kotlin.psi.KtWhenConditionInRange 122 import org.jetbrains.kotlin.psi.KtWhenConditionIsPattern 123 import org.jetbrains.kotlin.psi.KtWhenConditionWithExpression 124 import org.jetbrains.kotlin.psi.KtWhenExpression 125 import org.jetbrains.kotlin.psi.KtWhileExpression 126 import org.jetbrains.kotlin.psi.psiUtil.children 127 import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespace 128 import org.jetbrains.kotlin.psi.psiUtil.startOffset 129 import org.jetbrains.kotlin.psi.psiUtil.startsWithComment 130 import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes 131 import org.jetbrains.kotlin.psi.stubs.impl.KotlinPlaceHolderStubImpl 132 133 /** An AST visitor that builds a stream of {@link Op}s to format. */ 134 class KotlinInputAstVisitor( 135 private val options: FormattingOptions, 136 private val builder: OpsBuilder 137 ) : KtTreeVisitorVoid() { 138 139 /** Standard indentation for a block */ 140 private val blockIndent: Indent.Const = Indent.Const.make(options.blockIndent, 1) 141 142 /** 143 * Standard indentation for a long expression or function call, it is different than block 144 * indentation on purpose 145 */ 146 private val expressionBreakIndent: Indent.Const = Indent.Const.make(options.continuationIndent, 1) 147 148 private val blockPlusExpressionBreakIndent: Indent.Const = 149 Indent.Const.make(options.blockIndent + options.continuationIndent, 1) 150 151 private val doubleExpressionBreakIndent: Indent.Const = 152 Indent.Const.make(options.continuationIndent, 2) 153 154 private val expressionBreakNegativeIndent: Indent.Const = 155 Indent.Const.make(-options.continuationIndent, 1) 156 157 /** A record of whether we have visited into an expression. */ 158 private val inExpression = ArrayDeque(ImmutableList.of(false)) 159 160 /** Tracks whether we are handling an import directive */ 161 private var inImport = false 162 163 /** Example: `fun foo(n: Int) { println(n) }` */ 164 override fun visitNamedFunction(function: KtNamedFunction) { 165 builder.sync(function) 166 builder.block(ZERO) { 167 visitFunctionLikeExpression( 168 contextReceiverList = 169 function.getStubOrPsiChild(KtStubElementTypes.CONTEXT_RECEIVER_LIST), 170 modifierList = function.modifierList, 171 keyword = "fun", 172 typeParameters = function.typeParameterList, 173 receiverTypeReference = function.receiverTypeReference, 174 name = function.nameIdentifier?.text, 175 parameterList = function.valueParameterList, 176 typeConstraintList = function.typeConstraintList, 177 bodyExpression = function.bodyBlockExpression ?: function.bodyExpression, 178 typeOrDelegationCall = function.typeReference, 179 ) 180 } 181 } 182 183 /** Example `Int`, `(String)` or `() -> Int` */ 184 override fun visitTypeReference(typeReference: KtTypeReference) { 185 builder.sync(typeReference) 186 // Normally we'd visit the children nodes through accessors on 'typeReference', and we wouldn't 187 // loop over children. 188 // But, in this case the modifier list can either be inside the parenthesis: 189 // ... (@Composable (x) -> Unit) 190 // or outside of them: 191 // ... @Composable ((x) -> Unit) 192 val modifierList = typeReference.modifierList 193 val typeElement = typeReference.typeElement 194 for (child in typeReference.node.children()) { 195 when { 196 child.psi == modifierList -> visit(modifierList) 197 child.psi == typeElement -> visit(typeElement) 198 child.elementType == KtTokens.LPAR -> builder.token("(") 199 child.elementType == KtTokens.RPAR -> builder.token(")") 200 } 201 } 202 } 203 204 override fun visitDynamicType(type: KtDynamicType) { 205 builder.token("dynamic") 206 } 207 208 /** Example: `String?` or `((Int) -> Unit)?` */ 209 override fun visitNullableType(nullableType: KtNullableType) { 210 builder.sync(nullableType) 211 212 // Normally we wouldn't loop over children, but there can be multiple layers of parens. 213 val modifierList = nullableType.modifierList 214 val innerType = nullableType.innerType 215 for (child in nullableType.node.children()) { 216 when { 217 child.psi == modifierList -> visit(modifierList) 218 child.psi == innerType -> visit(innerType) 219 child.elementType == KtTokens.LPAR -> builder.token("(") 220 child.elementType == KtTokens.RPAR -> builder.token(")") 221 } 222 } 223 builder.token("?") 224 } 225 226 /** Example: `String` or `List<Int>`, */ 227 override fun visitUserType(type: KtUserType) { 228 builder.sync(type) 229 230 if (type.qualifier != null) { 231 visit(type.qualifier) 232 builder.token(".") 233 } 234 visit(type.referenceExpression) 235 val typeArgumentList = type.typeArgumentList 236 if (typeArgumentList != null) { 237 builder.block(expressionBreakIndent) { visit(typeArgumentList) } 238 } 239 } 240 241 /** Example: `A & B`, */ 242 override fun visitIntersectionType(type: KtIntersectionType) { 243 builder.sync(type) 244 245 // TODO(strulovich): Should this have the same indentation behaviour as `x && y`? 246 visit(type.getLeftTypeRef()) 247 builder.space() 248 builder.token("&") 249 builder.space() 250 visit(type.getRightTypeRef()) 251 } 252 253 /** Example `<Int, String>` in `List<Int, String>` */ 254 override fun visitTypeArgumentList(typeArgumentList: KtTypeArgumentList) { 255 builder.sync(typeArgumentList) 256 visitEachCommaSeparated( 257 typeArgumentList.arguments, 258 typeArgumentList.trailingComma != null, 259 wrapInBlock = !options.manageTrailingCommas, 260 prefix = "<", 261 postfix = ">", 262 ) 263 } 264 265 override fun visitTypeProjection(typeProjection: KtTypeProjection) { 266 builder.sync(typeProjection) 267 val typeReference = typeProjection.typeReference 268 when (typeProjection.projectionKind) { 269 KtProjectionKind.IN -> { 270 builder.token("in") 271 builder.space() 272 visit(typeReference) 273 } 274 KtProjectionKind.OUT -> { 275 builder.token("out") 276 builder.space() 277 visit(typeReference) 278 } 279 KtProjectionKind.STAR -> builder.token("*") 280 KtProjectionKind.NONE -> visit(typeReference) 281 } 282 } 283 284 /** 285 * @param keyword e.g., "fun" or "class". 286 * @param typeOrDelegationCall for functions, the return typeOrDelegationCall; for classes, the 287 * list of supertypes. 288 */ 289 private fun visitFunctionLikeExpression( 290 contextReceiverList: KtContextReceiverList?, 291 modifierList: KtModifierList?, 292 keyword: String?, 293 typeParameters: KtTypeParameterList?, 294 receiverTypeReference: KtTypeReference?, 295 name: String?, 296 parameterList: KtParameterList?, 297 typeConstraintList: KtTypeConstraintList?, 298 bodyExpression: KtExpression?, 299 typeOrDelegationCall: KtElement?, 300 ) { 301 fun emitTypeOrDelegationCall(block: () -> Unit) { 302 if (typeOrDelegationCall != null) { 303 builder.block(ZERO) { 304 if (typeOrDelegationCall is KtConstructorDelegationCall) { 305 builder.space() 306 } 307 builder.token(":") 308 block() 309 } 310 } 311 } 312 313 val forceTrailingBreak = name != null 314 builder.block(ZERO, isEnabled = forceTrailingBreak) { 315 if (contextReceiverList != null) { 316 visitContextReceiverList(contextReceiverList) 317 } 318 if (modifierList != null) { 319 visitModifierList(modifierList) 320 } 321 if (keyword != null) { 322 builder.token(keyword) 323 } 324 if (typeParameters != null) { 325 builder.space() 326 builder.block(ZERO) { visit(typeParameters) } 327 } 328 329 if (name != null || receiverTypeReference != null) { 330 builder.space() 331 } 332 builder.block(ZERO) { 333 if (receiverTypeReference != null) { 334 visit(receiverTypeReference) 335 builder.breakOp(Doc.FillMode.INDEPENDENT, "", expressionBreakIndent) 336 builder.token(".") 337 } 338 if (name != null) { 339 builder.token(name) 340 } 341 } 342 343 if (parameterList != null && parameterList.hasEmptyParens()) { 344 builder.block(ZERO) { 345 builder.token("(") 346 builder.token(")") 347 emitTypeOrDelegationCall { 348 builder.breakOp(Doc.FillMode.INDEPENDENT, " ", expressionBreakIndent) 349 builder.block(expressionBreakIndent) { visit(typeOrDelegationCall) } 350 } 351 } 352 } else { 353 builder.block(expressionBreakIndent) { 354 if (parameterList != null) { 355 visitEachCommaSeparated( 356 list = parameterList.parameters, 357 hasTrailingComma = parameterList.trailingComma != null, 358 prefix = "(", 359 postfix = ")", 360 wrapInBlock = false, 361 breakBeforePostfix = true, 362 ) 363 } 364 emitTypeOrDelegationCall { 365 builder.space() 366 builder.block(expressionBreakNegativeIndent) { visit(typeOrDelegationCall) } 367 } 368 } 369 } 370 371 if (typeConstraintList != null) { 372 builder.space() 373 visit(typeConstraintList) 374 } 375 if (bodyExpression is KtBlockExpression) { 376 builder.space() 377 visit(bodyExpression) 378 } else if (bodyExpression != null) { 379 builder.space() 380 builder.block(ZERO) { 381 builder.token("=") 382 if (isLambdaOrScopingFunction(bodyExpression)) { 383 visitLambdaOrScopingFunction(bodyExpression) 384 } else { 385 builder.block(expressionBreakIndent) { 386 builder.breakOp(Doc.FillMode.INDEPENDENT, " ", ZERO) 387 builder.block(ZERO) { visit(bodyExpression) } 388 } 389 } 390 } 391 } 392 builder.guessToken(";") 393 } 394 if (forceTrailingBreak) { 395 builder.forcedBreak() 396 } 397 } 398 399 private fun genSym(): BreakTag { 400 return BreakTag() 401 } 402 403 private fun emitBracedBlock( 404 bodyBlockExpression: PsiElement, 405 emitChildren: (Array<PsiElement>) -> Unit, 406 ) { 407 builder.token("{", Doc.Token.RealOrImaginary.REAL, blockIndent, Optional.of(blockIndent)) 408 val statements = bodyBlockExpression.children 409 if (statements.isNotEmpty()) { 410 builder.block(blockIndent) { 411 builder.forcedBreak() 412 builder.blankLineWanted(OpsBuilder.BlankLineWanted.PRESERVE) 413 emitChildren(statements) 414 } 415 builder.forcedBreak() 416 builder.blankLineWanted(OpsBuilder.BlankLineWanted.NO) 417 } 418 builder.token("}", blockIndent) 419 } 420 421 private fun visitStatement(statement: PsiElement) { 422 builder.block(ZERO) { visit(statement) } 423 builder.guessToken(";") 424 } 425 426 private fun visitStatements(statements: Array<PsiElement>) { 427 var first = true 428 builder.guessToken(";") 429 for (statement in statements) { 430 builder.forcedBreak() 431 if (!first) { 432 builder.blankLineWanted(OpsBuilder.BlankLineWanted.PRESERVE) 433 } 434 first = false 435 visitStatement(statement) 436 } 437 } 438 439 override fun visitProperty(property: KtProperty) { 440 builder.sync(property) 441 builder.block(ZERO) { 442 declareOne( 443 kind = DeclarationKind.FIELD, 444 modifiers = property.modifierList, 445 valOrVarKeyword = property.valOrVarKeyword.text, 446 typeParameters = property.typeParameterList, 447 receiver = property.receiverTypeReference, 448 name = property.nameIdentifier?.text, 449 type = property.typeReference, 450 typeConstraintList = property.typeConstraintList, 451 delegate = property.delegate, 452 initializer = property.initializer, 453 accessors = property.accessors) 454 } 455 builder.guessToken(";") 456 if (property.parent !is KtWhenExpression) { 457 builder.forcedBreak() 458 } 459 } 460 461 /** 462 * Example: "com.facebook.bla.bla" in imports or "a.b.c.d" in expressions. 463 * 464 * There's a few cases that are different. We deal with imports by keeping them on the same line. 465 * For regular chained expressions we go the left most descendant so we can start indentation only 466 * before the first break (a `.` or `?.`), and keep the seem indentation for this chain of calls. 467 */ 468 override fun visitQualifiedExpression(expression: KtQualifiedExpression) { 469 builder.sync(expression) 470 val receiver = expression.receiverExpression 471 when { 472 inImport -> { 473 visit(receiver) 474 val selectorExpression = expression.selectorExpression 475 if (selectorExpression != null) { 476 builder.token(".") 477 visit(selectorExpression) 478 } 479 } 480 receiver is KtStringTemplateExpression -> { 481 builder.block(expressionBreakIndent) { 482 visit(receiver) 483 builder.breakOp(Doc.FillMode.UNIFIED, "", ZERO) 484 builder.token(expression.operationSign.value) 485 visit(expression.selectorExpression) 486 } 487 } 488 receiver is KtWhenExpression -> { 489 builder.block(ZERO) { 490 visit(receiver) 491 builder.token(expression.operationSign.value) 492 visit(expression.selectorExpression) 493 } 494 } 495 else -> { 496 emitQualifiedExpression(expression) 497 } 498 } 499 } 500 501 /** Extra data to help [emitQualifiedExpression] know when to open and close a group */ 502 private class GroupingInfo { 503 var groupOpenCount = 0 504 var shouldCloseGroup = false 505 } 506 507 /** 508 * Handles a chain of qualified expressions, i.e. `a[5].b!!.c()[4].f()` 509 * 510 * This is by far the most complicated part of this formatter. We start by breaking the expression 511 * to the steps it is executed in (which are in the opposite order of how the syntax tree is 512 * built). 513 * 514 * We then calculate information to know which parts need to be groups, and finally go part by 515 * part, emitting it to the [builder] while closing and opening groups. 516 */ 517 private fun emitQualifiedExpression(expression: KtExpression) { 518 val parts = breakIntoParts(expression) 519 // whether we want to make a lambda look like a block, this make Kotlin DSLs look as expected 520 val useBlockLikeLambdaStyle = parts.last().isLambda() && parts.count { it.isLambda() } == 1 521 val groupingInfos = computeGroupingInfo(parts, useBlockLikeLambdaStyle) 522 builder.block(expressionBreakIndent) { 523 val nameTag = genSym() // allows adjusting arguments indentation if a break will be made 524 for ((index, ktExpression) in parts.withIndex()) { 525 if (ktExpression is KtQualifiedExpression) { 526 builder.breakOp(Doc.FillMode.UNIFIED, "", ZERO, Optional.of(nameTag)) 527 } 528 repeat(groupingInfos[index].groupOpenCount) { builder.open(ZERO) } 529 when (ktExpression) { 530 is KtQualifiedExpression -> { 531 builder.token(ktExpression.operationSign.value) 532 val selectorExpression = ktExpression.selectorExpression 533 if (selectorExpression !is KtCallExpression) { 534 // selector is a simple field access 535 visit(selectorExpression) 536 if (groupingInfos[index].shouldCloseGroup) { 537 builder.close() 538 } 539 } else { 540 // selector is a function call, we may close a group after its name 541 // emit `doIt` from `doIt(1, 2) { it }` 542 visit(selectorExpression.calleeExpression) 543 // close groups according to instructions 544 if (groupingInfos[index].shouldCloseGroup) { 545 builder.close() 546 } 547 // close group due to last lambda to allow block-like style in `as.forEach { ... }` 548 val isTrailingLambda = useBlockLikeLambdaStyle && index == parts.size - 1 549 if (isTrailingLambda) { 550 builder.close() 551 } 552 val argsIndentElse = if (index == parts.size - 1) ZERO else expressionBreakIndent 553 val lambdaIndentElse = if (isTrailingLambda) expressionBreakNegativeIndent else ZERO 554 val negativeLambdaIndentElse = if (isTrailingLambda) expressionBreakIndent else ZERO 555 556 // emit `(1, 2) { it }` from `doIt(1, 2) { it }` 557 visitCallElement( 558 null, 559 selectorExpression.typeArgumentList, 560 selectorExpression.valueArgumentList, 561 selectorExpression.lambdaArguments, 562 argumentsIndent = Indent.If.make(nameTag, expressionBreakIndent, argsIndentElse), 563 lambdaIndent = Indent.If.make(nameTag, ZERO, lambdaIndentElse), 564 negativeLambdaIndent = Indent.If.make(nameTag, ZERO, negativeLambdaIndentElse), 565 ) 566 } 567 } 568 is KtArrayAccessExpression -> { 569 visitArrayAccessBrackets(ktExpression) 570 builder.close() 571 } 572 is KtPostfixExpression -> { 573 builder.token(ktExpression.operationReference.text) 574 builder.close() 575 } 576 else -> { 577 check(index == 0) 578 visit(ktExpression) 579 } 580 } 581 } 582 } 583 } 584 585 /** 586 * Decomposes a qualified expression into parts, so `rainbow.red.orange.yellow` becomes `[rainbow, 587 * rainbow.red, rainbow.red.orange, rainbow.orange.yellow]` 588 */ 589 private fun breakIntoParts(expression: KtExpression): List<KtExpression> { 590 val parts = ArrayDeque<KtExpression>() 591 592 // use an ArrayDeque and add elements to the beginning so the innermost expression comes first 593 // foo.bar.yay -> [yay, bar.yay, foo.bar.yay] 594 595 var node: KtExpression? = expression 596 while (node != null) { 597 parts.addFirst(node) 598 node = 599 when (node) { 600 is KtQualifiedExpression -> node.receiverExpression 601 is KtArrayAccessExpression -> node.arrayExpression 602 is KtPostfixExpression -> node.baseExpression 603 else -> null 604 } 605 } 606 607 return parts.toList() 608 } 609 610 /** 611 * Generates the [GroupingInfo] array to go with an array of [KtQualifiedExpression] parts 612 * 613 * For example, the expression `a.b[2].c.d()` is made of four expressions: 614 * 1. [KtQualifiedExpression] `a.b[2].c . d()` (this will be `parts[4]`) 615 * 1. [KtQualifiedExpression] `a.b[2] . c` (this will be `parts[3]`) 616 * 2. [KtArrayAccessExpression] `a.b [2]` (this will be `parts[2]`) 617 * 3. [KtQualifiedExpression] `a . b` (this will be `parts[1]`) 618 * 4. [KtSimpleNameExpression] `a` (this will be `parts[0]`) 619 * 620 * Once in parts, these are in the reverse order. To render the array correct we need to make sure 621 * `b` and [2] are in a group so we avoid splitting them. To do so we need to open a group for `b` 622 * (that will be done in part 2), and always close a group for an array. 623 * 624 * Here is the same expression, with justified braces marking the groupings it will get: 625 * ``` 626 * a . b [2] . c . d () 627 * {a . b} --> Grouping `a.b` because it can be a package name or simple field access so we add 1 628 * to the number of groups to open at groupingInfos[0], and mark to close a group at 629 * groupingInfos[1] 630 * {a . b [2]} --> Grouping `a.b` with `[2]`, since otherwise we may break inside the brackets 631 * instead of preferring breaks before dots. So we open a group at [0], but since 632 * we always close a group after brackets, we don't store that information. 633 * {c . d} --> another group to attach the first function name to the fields before it 634 * this time we don't start the group in the beginning, and use 635 * lastIndexToOpen to track the spot after the last time we stopped 636 * grouping. 637 * ``` 638 * 639 * The final expression with groupings: 640 * ``` 641 * {{a.b}[2]}.{c.d}() 642 * ``` 643 */ 644 private fun computeGroupingInfo( 645 parts: List<KtExpression>, 646 useBlockLikeLambdaStyle: Boolean 647 ): List<GroupingInfo> { 648 val groupingInfos = List(parts.size) { GroupingInfo() } 649 var lastIndexToOpen = 0 650 for ((index, part) in parts.withIndex()) { 651 when (part) { 652 is KtQualifiedExpression -> { 653 val receiverExpression = part.receiverExpression 654 val previous = 655 (receiverExpression as? KtQualifiedExpression)?.selectorExpression 656 ?: receiverExpression 657 val current = checkNotNull(part.selectorExpression) 658 if (lastIndexToOpen == 0 && 659 shouldGroupPartWithPrevious(parts, part, index, previous, current)) { 660 // this and the previous items should be grouped for better style 661 // we add another group to open in index 0 662 groupingInfos[0].groupOpenCount++ 663 // we don't always close a group when emitting this node, so we need this flag to 664 // mark if we need to close a group 665 groupingInfos[index].shouldCloseGroup = true 666 } else { 667 // use this index in to open future groups for arrays and postfixes 668 // we will also stop grouping field access to the beginning of the expression 669 lastIndexToOpen = index 670 } 671 } 672 is KtArrayAccessExpression, 673 is KtPostfixExpression -> { 674 // we group these with the last item with a name, and we always close them 675 groupingInfos[lastIndexToOpen].groupOpenCount++ 676 } 677 } 678 } 679 if (useBlockLikeLambdaStyle) { 680 // a trailing lambda adds a group that we stop before emitting the lambda 681 groupingInfos[0].groupOpenCount++ 682 } 683 return groupingInfos 684 } 685 686 /** Decide whether a [KtQualifiedExpression] part should be grouped with the previous part */ 687 private fun shouldGroupPartWithPrevious( 688 parts: List<KtExpression>, 689 part: KtExpression, 690 index: Int, 691 previous: KtExpression, 692 current: KtExpression 693 ): Boolean { 694 // this is the second, and the first is short, avoid `.` "hanging in air" 695 if (index == 1 && previous.text.length < options.continuationIndent) { 696 return true 697 } 698 // the previous part is `this` or `super` 699 if (previous is KtSuperExpression || previous is KtThisExpression) { 700 return true 701 } 702 // this and the previous part are a package name, type name, or property 703 if (previous is KtSimpleNameExpression && 704 current is KtSimpleNameExpression && 705 part is KtDotQualifiedExpression) { 706 return true 707 } 708 // this is `Foo` in `com.facebook.Foo`, so everything before it is a package name 709 if (current.text.first().isUpperCase() && 710 current is KtSimpleNameExpression && 711 part is KtDotQualifiedExpression) { 712 return true 713 } 714 // this is the `foo()` in `com.facebook.Foo.foo()` or in `Foo.foo()` 715 if (current is KtCallExpression && 716 (previous !is KtCallExpression) && 717 previous.text?.firstOrNull()?.isUpperCase() == true) { 718 return true 719 } 720 // this is an invocation and the last item, and the previous it not, i.e. `a.b.c()` 721 // keeping it grouped and splitting the arguments makes `a.b(...)` feel like `aab()` 722 return current is KtCallExpression && 723 previous !is KtCallExpression && 724 index == parts.indices.last 725 } 726 727 override fun visitCallExpression(callExpression: KtCallExpression) { 728 builder.sync(callExpression) 729 with(callExpression) { 730 visitCallElement( 731 calleeExpression, 732 typeArgumentList, 733 valueArgumentList, 734 lambdaArguments, 735 ) 736 } 737 } 738 739 /** 740 * Examples `foo<T>(a, b)`, `foo(a)`, `boo()`, `super(a)` 741 * 742 * @param lambdaIndent how to indent [lambdaArguments], if present 743 * @param negativeLambdaIndent the negative indentation of [lambdaIndent] 744 */ 745 private fun visitCallElement( 746 callee: KtExpression?, 747 typeArgumentList: KtTypeArgumentList?, 748 argumentList: KtValueArgumentList?, 749 lambdaArguments: List<KtLambdaArgument>, 750 argumentsIndent: Indent = expressionBreakIndent, 751 lambdaIndent: Indent = ZERO, 752 negativeLambdaIndent: Indent = ZERO, 753 ) { 754 // Apply the lambda indent to the callee, type args, value args, and the lambda. 755 // This is undone for the first three by the negative lambda indent. 756 // This way they're in one block, and breaks in the argument list cause a break in the lambda. 757 builder.block(lambdaIndent) { 758 759 // Used to keep track of whether or not we need to indent the lambda 760 // This is based on if there is a break in the argument list 761 var brokeBeforeBrace: BreakTag? = null 762 763 builder.block(negativeLambdaIndent) { 764 visit(callee) 765 builder.block(argumentsIndent) { 766 builder.block(ZERO) { visit(typeArgumentList) } 767 if (argumentList != null) { 768 brokeBeforeBrace = visitValueArgumentListInternal(argumentList) 769 } 770 } 771 } 772 when (lambdaArguments.size) { 773 0 -> {} 774 1 -> { 775 builder.space() 776 visitArgumentInternal( 777 lambdaArguments.single(), 778 wrapInBlock = false, 779 brokeBeforeBrace = brokeBeforeBrace, 780 ) 781 } 782 else -> throw ParseError("Maximum one trailing lambda is allowed", lambdaArguments[1]) 783 } 784 } 785 } 786 787 /** Example (`1, "hi"`) in a function call */ 788 override fun visitValueArgumentList(list: KtValueArgumentList) { 789 visitValueArgumentListInternal(list) 790 } 791 792 /** 793 * Example (`1, "hi"`) in a function call 794 * 795 * @return a [BreakTag] which can tell you if a break was taken, but only when the list doesn't 796 * terminate in a negative closing indent. See [visitEachCommaSeparated] for examples. 797 */ 798 private fun visitValueArgumentListInternal(list: KtValueArgumentList): BreakTag? { 799 builder.sync(list) 800 801 val arguments = list.arguments 802 val isSingleUnnamedLambda = 803 arguments.size == 1 && 804 arguments.first().getArgumentExpression() is KtLambdaExpression && 805 arguments.first().getArgumentName() == null 806 val hasTrailingComma = list.trailingComma != null 807 val hasEmptyParens = list.hasEmptyParens() 808 809 val wrapInBlock: Boolean 810 val breakBeforePostfix: Boolean 811 val leadingBreak: Boolean 812 val breakAfterPrefix: Boolean 813 if (isSingleUnnamedLambda) { 814 wrapInBlock = true 815 breakBeforePostfix = false 816 leadingBreak = !hasEmptyParens && hasTrailingComma 817 breakAfterPrefix = false 818 } else { 819 wrapInBlock = !options.manageTrailingCommas 820 breakBeforePostfix = options.manageTrailingCommas && !hasEmptyParens 821 leadingBreak = !hasEmptyParens 822 breakAfterPrefix = !hasEmptyParens 823 } 824 825 return visitEachCommaSeparated( 826 arguments, 827 hasTrailingComma, 828 wrapInBlock = wrapInBlock, 829 breakBeforePostfix = breakBeforePostfix, 830 leadingBreak = leadingBreak, 831 prefix = "(", 832 postfix = ")", 833 breakAfterPrefix = breakAfterPrefix, 834 ) 835 } 836 837 /** Example `{ 1 + 1 }` (as lambda) or `{ (x, y) -> x + y }` */ 838 override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) { 839 visitLambdaExpressionInternal(lambdaExpression, brokeBeforeBrace = null) 840 } 841 842 /** 843 * The internal version of [visitLambdaExpression]. 844 * 845 * @param brokeBeforeBrace used for tracking if a break was taken right before the lambda 846 * expression. Useful for scoping functions where we want good looking indentation. For example, 847 * here we have correct indentation before `bar()` and `car()` because we can detect the break 848 * after the equals: 849 * ``` 850 * fun foo() = 851 * coroutineScope { x -> 852 * bar() 853 * car() 854 * } 855 * ``` 856 */ 857 private fun visitLambdaExpressionInternal( 858 lambdaExpression: KtLambdaExpression, 859 brokeBeforeBrace: BreakTag?, 860 ) { 861 builder.sync(lambdaExpression) 862 863 val valueParams = lambdaExpression.valueParameters 864 val hasParams = valueParams.isNotEmpty() 865 val bodyExpression = lambdaExpression.bodyExpression ?: fail() 866 val expressionStatements = bodyExpression.children 867 val hasStatements = expressionStatements.isNotEmpty() 868 val hasComments = bodyExpression.children().any { it is PsiComment } 869 val hasArrow = lambdaExpression.functionLiteral.arrow != null 870 871 fun ifBrokeBeforeBrace(onTrue: Indent, onFalse: Indent): Indent { 872 if (brokeBeforeBrace == null) return onFalse 873 return Indent.If.make(brokeBeforeBrace, onTrue, onFalse) 874 } 875 876 /** 877 * Enable correct formatting of the `fun foo() = scope {` syntax. 878 * 879 * We can't denote the lambda (+ scope function) as a block, since (for multiline lambdas) the 880 * rectangle rule would force the entire lambda onto a lower line. Instead, we conditionally 881 * indent all the interior levels of the lambda based on whether we had to break before the 882 * opening brace (or scope function). This mimics the look of a block when the break is taken. 883 * 884 * These conditional indents should not be used inside interior blocks, since that would apply 885 * the condition twice. 886 */ 887 val bracePlusBlockIndent = ifBrokeBeforeBrace(blockPlusExpressionBreakIndent, blockIndent) 888 val bracePlusExpressionIndent = 889 ifBrokeBeforeBrace(doubleExpressionBreakIndent, expressionBreakIndent) 890 val bracePlusZeroIndent = ifBrokeBeforeBrace(expressionBreakIndent, ZERO) 891 892 builder.token("{") 893 894 if (hasParams || hasArrow) { 895 builder.space() 896 builder.block(bracePlusExpressionIndent) { visitEachCommaSeparated(valueParams) } 897 builder.block(bracePlusBlockIndent) { 898 if (lambdaExpression.functionLiteral.valueParameterList?.trailingComma != null) { 899 builder.token(",") 900 builder.forcedBreak() 901 } else if (hasParams) { 902 builder.breakOp(Doc.FillMode.INDEPENDENT, " ", ZERO) 903 } 904 builder.token("->") 905 } 906 } 907 908 if (hasParams || hasArrow || hasStatements || hasComments) { 909 builder.breakOp(Doc.FillMode.UNIFIED, " ", bracePlusZeroIndent) 910 } 911 912 if (hasStatements) { 913 builder.breakOp(Doc.FillMode.UNIFIED, "", bracePlusBlockIndent) 914 builder.block(bracePlusBlockIndent) { 915 builder.blankLineWanted(OpsBuilder.BlankLineWanted.NO) 916 if (expressionStatements.size == 1 && 917 expressionStatements.first() !is KtReturnExpression && 918 !bodyExpression.startsWithComment()) { 919 visitStatement(expressionStatements[0]) 920 } else { 921 visitStatements(expressionStatements) 922 } 923 builder.breakOp(Doc.FillMode.UNIFIED, " ", bracePlusZeroIndent) 924 } 925 } 926 927 if (hasParams || hasArrow || hasStatements) { 928 // If we had to break in the body, ensure there is a break before the closing brace 929 builder.breakOp(Doc.FillMode.UNIFIED, "", bracePlusZeroIndent) 930 } 931 builder.block(bracePlusZeroIndent) { 932 builder.fenceComments() 933 builder.token("}", blockIndent) 934 } 935 } 936 937 /** Example `this` or `this@Foo` */ 938 override fun visitThisExpression(expression: KtThisExpression) { 939 builder.sync(expression) 940 builder.token("this") 941 visit(expression.getTargetLabel()) 942 } 943 944 /** Example `Foo` or `@Foo` */ 945 override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { 946 builder.sync(expression) 947 when (expression) { 948 is KtLabelReferenceExpression -> { 949 if (expression.text[0] == '@') { 950 builder.token("@") 951 builder.token(expression.getIdentifier()?.text ?: fail()) 952 } else { 953 builder.token(expression.getIdentifier()?.text ?: fail()) 954 builder.token("@") 955 } 956 } 957 else -> { 958 if (expression.text.isNotEmpty()) { 959 builder.token(expression.text) 960 } 961 } 962 } 963 } 964 965 /** e.g., `a: Int, b: Int, c: Int` in `fun foo(a: Int, b: Int, c: Int) { ... }`. */ 966 override fun visitParameterList(list: KtParameterList) { 967 visitEachCommaSeparated(list.parameters, list.trailingComma != null, wrapInBlock = false) 968 } 969 970 /** 971 * Visit each element in [list], with comma (,) tokens in-between. 972 * 973 * Example: 974 * ``` 975 * a, b, c, 3, 4, 5 976 * ``` 977 * 978 * Either the entire list fits in one line, or each element is put on its own line: 979 * ``` 980 * a, 981 * b, 982 * c, 983 * 3, 984 * 4, 985 * 5 986 * ``` 987 * 988 * Optionally include a prefix and postfix: 989 * ``` 990 * ( 991 * a, 992 * b, 993 * c, 994 * ) 995 * ``` 996 * 997 * @param hasTrailingComma if true, each element is placed on its own line (even if they could've 998 * fit in a single line), and a trailing comma is emitted. 999 * 1000 * Example: 1001 * ``` 1002 * a, 1003 * b, 1004 * ``` 1005 * 1006 * @param wrapInBlock if true, place all the elements in a block. When there's no [leadingBreak], 1007 * this will be negatively indented. Note that the [prefix] and [postfix] aren't included in the 1008 * block. 1009 * @param leadingBreak if true, break before the first element. 1010 * @param prefix if provided, emit this before the first element. 1011 * @param postfix if provided, emit this after the last element (or trailing comma). 1012 * @param breakAfterPrefix if true, emit a break after [prefix], but before the start of the 1013 * block. 1014 * @param breakBeforePostfix if true, place a break after the last element. Redundant when 1015 * [hasTrailingComma] is true. 1016 * @return a [BreakTag] which can tell you if a break was taken, but only when the list doesn't 1017 * terminate in a negative closing indent. 1018 * 1019 * Example 1, this returns a BreakTag which tells you a break wasn't taken: 1020 * ``` 1021 * (arg1, arg2) 1022 * ``` 1023 * 1024 * Example 2, this returns a BreakTag which tells you a break WAS taken: 1025 * ``` 1026 * ( 1027 * arg1, 1028 * arg2) 1029 * ``` 1030 * 1031 * Example 3, this returns null: 1032 * ``` 1033 * ( 1034 * arg1, 1035 * arg2, 1036 * ) 1037 * ``` 1038 * 1039 * Example 4, this also returns null (similar to example 2, but Google style): 1040 * ``` 1041 * ( 1042 * arg1, 1043 * arg2 1044 * ) 1045 * ``` 1046 */ 1047 private fun visitEachCommaSeparated( 1048 list: Iterable<PsiElement>, 1049 hasTrailingComma: Boolean = false, 1050 wrapInBlock: Boolean = true, 1051 leadingBreak: Boolean = true, 1052 prefix: String? = null, 1053 postfix: String? = null, 1054 breakAfterPrefix: Boolean = true, 1055 breakBeforePostfix: Boolean = options.manageTrailingCommas, 1056 ): BreakTag? { 1057 val breakAfterLastElement = hasTrailingComma || (postfix != null && breakBeforePostfix) 1058 val nameTag = if (breakAfterLastElement) null else genSym() 1059 1060 if (prefix != null) { 1061 builder.token(prefix) 1062 if (breakAfterPrefix) { 1063 builder.breakOp(Doc.FillMode.UNIFIED, "", ZERO, Optional.ofNullable(nameTag)) 1064 } 1065 } 1066 1067 val breakType = if (hasTrailingComma) Doc.FillMode.FORCED else Doc.FillMode.UNIFIED 1068 fun emitComma() { 1069 builder.token(",") 1070 builder.breakOp(breakType, " ", ZERO) 1071 } 1072 1073 val indent = if (leadingBreak) ZERO else expressionBreakNegativeIndent 1074 builder.block(indent, isEnabled = wrapInBlock) { 1075 if (leadingBreak) { 1076 builder.breakOp(breakType, "", ZERO) 1077 } 1078 1079 var first = true 1080 for (value in list) { 1081 if (!first) emitComma() 1082 first = false 1083 visit(value) 1084 } 1085 1086 if (hasTrailingComma) { 1087 emitComma() 1088 } 1089 } 1090 1091 if (breakAfterLastElement) { 1092 // a negative closing indent places the postfix to the left of the elements 1093 // see examples 2 and 4 in the docstring 1094 builder.breakOp(breakType, "", expressionBreakNegativeIndent) 1095 } 1096 1097 if (postfix != null) { 1098 if (breakAfterLastElement) { 1099 builder.block(expressionBreakNegativeIndent) { 1100 builder.fenceComments() 1101 builder.token(postfix, expressionBreakIndent) 1102 } 1103 } else { 1104 builder.token(postfix) 1105 } 1106 } 1107 1108 return nameTag 1109 } 1110 1111 /** Example `a` in `foo(a)`, or `*a`, or `limit = 50` */ 1112 override fun visitArgument(argument: KtValueArgument) { 1113 visitArgumentInternal( 1114 argument, 1115 wrapInBlock = true, 1116 brokeBeforeBrace = null, 1117 ) 1118 } 1119 1120 /** 1121 * The internal version of [visitArgument]. 1122 * 1123 * @param wrapInBlock if true places the argument expression in a block. 1124 */ 1125 private fun visitArgumentInternal( 1126 argument: KtValueArgument, 1127 wrapInBlock: Boolean, 1128 brokeBeforeBrace: BreakTag?, 1129 ) { 1130 builder.sync(argument) 1131 val hasArgName = argument.getArgumentName() != null 1132 val isLambda = argument.getArgumentExpression() is KtLambdaExpression 1133 if (hasArgName) { 1134 visit(argument.getArgumentName()) 1135 builder.space() 1136 builder.token("=") 1137 if (isLambda) { 1138 builder.space() 1139 } 1140 } 1141 val indent = if (hasArgName && !isLambda) expressionBreakIndent else ZERO 1142 builder.block(indent, isEnabled = wrapInBlock) { 1143 if (hasArgName && !isLambda) { 1144 builder.breakOp(Doc.FillMode.INDEPENDENT, " ", ZERO) 1145 } 1146 if (argument.isSpread) { 1147 builder.token("*") 1148 } 1149 if (isLambda) { 1150 visitLambdaExpressionInternal( 1151 argument.getArgumentExpression() as KtLambdaExpression, 1152 brokeBeforeBrace = brokeBeforeBrace, 1153 ) 1154 } else { 1155 visit(argument.getArgumentExpression()) 1156 } 1157 } 1158 } 1159 1160 override fun visitReferenceExpression(expression: KtReferenceExpression) { 1161 builder.sync(expression) 1162 builder.token(expression.text) 1163 } 1164 1165 override fun visitReturnExpression(expression: KtReturnExpression) { 1166 builder.sync(expression) 1167 builder.token("return") 1168 visit(expression.getTargetLabel()) 1169 val returnedExpression = expression.returnedExpression 1170 if (returnedExpression != null) { 1171 builder.space() 1172 visit(returnedExpression) 1173 } 1174 builder.guessToken(";") 1175 } 1176 1177 /** 1178 * For example `a + b`, `a + b + c` or `a..b` 1179 * 1180 * The extra handling here drills to the left most expression and handles it for long chains of 1181 * binary expressions that are formatted not accordingly to the associative values That is, we 1182 * want to think of `a + b + c` as `(a + b) + c`, whereas the AST parses it as `a + (b + c)` 1183 */ 1184 override fun visitBinaryExpression(expression: KtBinaryExpression) { 1185 builder.sync(expression) 1186 val op = expression.operationToken 1187 1188 if (KtTokens.ALL_ASSIGNMENTS.contains(op) && isLambdaOrScopingFunction(expression.right)) { 1189 // Assignments are statements in Kotlin; we don't have to worry about compound assignment. 1190 visit(expression.left) 1191 builder.space() 1192 builder.token(expression.operationReference.text) 1193 visitLambdaOrScopingFunction(expression.right) 1194 return 1195 } 1196 1197 val parts = 1198 ArrayDeque<KtBinaryExpression>().apply { 1199 var current: KtExpression? = expression 1200 while (current is KtBinaryExpression && current.operationToken == op) { 1201 addFirst(current) 1202 current = current.left 1203 } 1204 } 1205 1206 val leftMostExpression = parts.first() 1207 visit(leftMostExpression.left) 1208 for (leftExpression in parts) { 1209 val isFirst = leftExpression === leftMostExpression 1210 1211 when (leftExpression.operationToken) { 1212 KtTokens.RANGE, 1213 KtTokens.RANGE_UNTIL -> { 1214 if (isFirst) { 1215 builder.open(expressionBreakIndent) 1216 } 1217 builder.token(leftExpression.operationReference.text) 1218 } 1219 KtTokens.ELVIS -> { 1220 if (isFirst) { 1221 builder.open(expressionBreakIndent) 1222 } 1223 builder.breakOp(Doc.FillMode.UNIFIED, " ", ZERO) 1224 builder.token(leftExpression.operationReference.text) 1225 builder.space() 1226 } 1227 else -> { 1228 builder.space() 1229 if (isFirst) { 1230 builder.open(expressionBreakIndent) 1231 } 1232 builder.token(leftExpression.operationReference.text) 1233 builder.breakOp(Doc.FillMode.UNIFIED, " ", ZERO) 1234 } 1235 } 1236 visit(leftExpression.right) 1237 } 1238 builder.close() 1239 } 1240 1241 override fun visitPostfixExpression(expression: KtPostfixExpression) { 1242 builder.sync(expression) 1243 builder.block(ZERO) { 1244 val baseExpression = expression.baseExpression 1245 val operator = expression.operationReference.text 1246 1247 visit(baseExpression) 1248 if (baseExpression is KtPostfixExpression && 1249 baseExpression.operationReference.text.last() == operator.first()) { 1250 builder.space() 1251 } 1252 builder.token(operator) 1253 } 1254 } 1255 1256 override fun visitPrefixExpression(expression: KtPrefixExpression) { 1257 builder.sync(expression) 1258 builder.block(ZERO) { 1259 val baseExpression = expression.baseExpression 1260 val operator = expression.operationReference.text 1261 1262 builder.token(operator) 1263 if (baseExpression is KtPrefixExpression && 1264 operator.last() == baseExpression.operationReference.text.first()) { 1265 builder.space() 1266 } 1267 visit(baseExpression) 1268 } 1269 } 1270 1271 override fun visitLabeledExpression(expression: KtLabeledExpression) { 1272 builder.sync(expression) 1273 visit(expression.labelQualifier) 1274 if (expression.baseExpression !is KtLambdaExpression) { 1275 builder.space() 1276 } 1277 visit(expression.baseExpression) 1278 } 1279 1280 internal enum class DeclarationKind { 1281 FIELD, 1282 PARAMETER 1283 } 1284 1285 /** 1286 * Declare one variable or variable-like thing. 1287 * 1288 * Examples: 1289 * - `var a: Int = 5` 1290 * - `a: Int` 1291 * - `private val b: 1292 */ 1293 private fun declareOne( 1294 kind: DeclarationKind, 1295 modifiers: KtModifierList?, 1296 valOrVarKeyword: String?, 1297 typeParameters: KtTypeParameterList? = null, 1298 receiver: KtTypeReference? = null, 1299 name: String?, 1300 type: KtTypeReference?, 1301 typeConstraintList: KtTypeConstraintList? = null, 1302 initializer: KtExpression?, 1303 delegate: KtPropertyDelegate? = null, 1304 accessors: List<KtPropertyAccessor>? = null 1305 ): Int { 1306 val verticalAnnotationBreak = genSym() 1307 1308 val isField = kind == DeclarationKind.FIELD 1309 1310 if (isField) { 1311 builder.blankLineWanted(OpsBuilder.BlankLineWanted.conditional(verticalAnnotationBreak)) 1312 } 1313 1314 visit(modifiers) 1315 builder.block(ZERO) { 1316 builder.block(ZERO) { 1317 if (valOrVarKeyword != null) { 1318 builder.token(valOrVarKeyword) 1319 builder.space() 1320 } 1321 1322 if (typeParameters != null) { 1323 visit(typeParameters) 1324 builder.space() 1325 } 1326 1327 // conditionally indent the name and initializer +4 if the type spans 1328 // multiple lines 1329 if (name != null) { 1330 if (receiver != null) { 1331 visit(receiver) 1332 builder.token(".") 1333 } 1334 builder.token(name) 1335 } 1336 } 1337 1338 builder.block(expressionBreakIndent, isEnabled = name != null) { 1339 // For example `: String` in `val thisIsALongName: String` or `fun f(): String` 1340 if (type != null) { 1341 if (name != null) { 1342 builder.token(":") 1343 builder.breakOp(Doc.FillMode.UNIFIED, " ", ZERO) 1344 } 1345 visit(type) 1346 } 1347 } 1348 1349 // For example `where T : Int` in a generic method 1350 if (typeConstraintList != null) { 1351 builder.space() 1352 visit(typeConstraintList) 1353 builder.space() 1354 } 1355 1356 // for example `by lazy { compute() }` 1357 if (delegate != null) { 1358 builder.space() 1359 builder.token("by") 1360 if (isLambdaOrScopingFunction(delegate.expression)) { 1361 builder.space() 1362 visit(delegate) 1363 } else { 1364 builder.breakOp(Doc.FillMode.UNIFIED, " ", expressionBreakIndent) 1365 builder.block(expressionBreakIndent) { 1366 builder.fenceComments() 1367 visit(delegate) 1368 } 1369 } 1370 } else if (initializer != null) { 1371 builder.space() 1372 builder.token("=") 1373 if (isLambdaOrScopingFunction(initializer)) { 1374 visitLambdaOrScopingFunction(initializer) 1375 } else { 1376 builder.breakOp(Doc.FillMode.UNIFIED, " ", expressionBreakIndent) 1377 builder.block(expressionBreakIndent) { 1378 builder.fenceComments() 1379 visit(initializer) 1380 } 1381 } 1382 } 1383 } 1384 // for example `private set` or `get = 2 * field` 1385 if (accessors?.isNotEmpty() == true) { 1386 builder.block(blockIndent) { 1387 for (accessor in accessors) { 1388 builder.forcedBreak() 1389 // The semicolon must come after the newline, or the output code will not parse. 1390 builder.guessToken(";") 1391 1392 builder.block(ZERO) { 1393 visitFunctionLikeExpression( 1394 contextReceiverList = null, 1395 modifierList = accessor.modifierList, 1396 keyword = accessor.namePlaceholder.text, 1397 typeParameters = null, 1398 receiverTypeReference = null, 1399 name = null, 1400 parameterList = getParameterListWithBugFixes(accessor), 1401 typeConstraintList = null, 1402 bodyExpression = accessor.bodyBlockExpression ?: accessor.bodyExpression, 1403 typeOrDelegationCall = accessor.returnTypeReference, 1404 ) 1405 } 1406 } 1407 } 1408 } 1409 1410 builder.guessToken(";") 1411 1412 if (isField) { 1413 builder.blankLineWanted(OpsBuilder.BlankLineWanted.conditional(verticalAnnotationBreak)) 1414 } 1415 1416 return 0 1417 } 1418 1419 // Bug in Kotlin 1.9.10: KtPropertyAccessor is the direct parent of the left and right paren 1420 // elements. Also parameterList is always null for getters. As a workaround, we create our own 1421 // fake KtParameterList. 1422 // TODO: won't need this after https://youtrack.jetbrains.com/issue/KT-70922 1423 private fun getParameterListWithBugFixes(accessor: KtPropertyAccessor): KtParameterList? { 1424 if (accessor.bodyExpression == null && accessor.bodyBlockExpression == null) return null 1425 1426 val stub = accessor.stub ?: PsiFileStubImpl(accessor.containingFile) 1427 1428 return object : 1429 KtParameterList(KotlinPlaceHolderStubImpl(stub, KtStubElementTypes.VALUE_PARAMETER_LIST)) { 1430 override fun getParameters(): List<KtParameter> { 1431 return accessor.valueParameters 1432 } 1433 1434 override fun getTrailingComma(): PsiElement? { 1435 return accessor.parameterList?.trailingComma 1436 } 1437 1438 override fun getLeftParenthesis(): PsiElement? { 1439 return accessor.leftParenthesis 1440 } 1441 1442 override fun getRightParenthesis(): PsiElement? { 1443 return accessor.rightParenthesis 1444 } 1445 } 1446 } 1447 1448 /** 1449 * Returns whether an expression is a lambda or initializer expression in which case we will want 1450 * to avoid indenting the lambda block 1451 * 1452 * Examples: 1453 * 1. '... = { ... }' is a lambda expression 1454 * 2. '... = Runnable { ... }' is considered a scoping function 1455 * 3. '... = scope { ... }' '... = apply { ... }' is a scoping function 1456 * 1457 * but not: 1458 * 1. '... = foo() { ... }' due to the empty parenthesis 1459 * 2. '... = Runnable @Annotation { ... }' due to the annotation 1460 */ 1461 private fun isLambdaOrScopingFunction(expression: KtExpression?): Boolean { 1462 if (expression == null) return false 1463 if (expression.getPrevSiblingIgnoringWhitespace() is PsiComment) { 1464 return false // Leading comments cause weird indentation. 1465 } 1466 1467 var carry = expression 1468 if (carry is KtCallExpression) { 1469 if (carry.valueArgumentList?.leftParenthesis == null && 1470 carry.lambdaArguments.isNotEmpty() && 1471 carry.typeArgumentList?.arguments.isNullOrEmpty()) { 1472 carry = carry.lambdaArguments[0].getArgumentExpression() 1473 } else { 1474 return false 1475 } 1476 } 1477 if (carry is KtLabeledExpression) { 1478 carry = carry.baseExpression 1479 } 1480 if (carry is KtLambdaExpression) { 1481 return true 1482 } 1483 1484 return false 1485 } 1486 1487 /** See [isLambdaOrScopingFunction] for examples. */ 1488 private fun visitLambdaOrScopingFunction(expr: PsiElement?) { 1489 val breakToExpr = genSym() 1490 builder.breakOp(Doc.FillMode.INDEPENDENT, " ", expressionBreakIndent, Optional.of(breakToExpr)) 1491 1492 var carry = expr 1493 if (carry is KtCallExpression) { 1494 visit(carry.calleeExpression) 1495 builder.space() 1496 carry = carry.lambdaArguments[0].getArgumentExpression() 1497 } 1498 if (carry is KtLabeledExpression) { 1499 visit(carry.labelQualifier) 1500 carry = carry.baseExpression ?: fail() 1501 } 1502 if (carry is KtLambdaExpression) { 1503 visitLambdaExpressionInternal(carry, brokeBeforeBrace = breakToExpr) 1504 return 1505 } 1506 1507 throw AssertionError(carry) 1508 } 1509 1510 override fun visitClassOrObject(classOrObject: KtClassOrObject) { 1511 builder.sync(classOrObject) 1512 val contextReceiverList = 1513 classOrObject.getStubOrPsiChild(KtStubElementTypes.CONTEXT_RECEIVER_LIST) 1514 val modifierList = classOrObject.modifierList 1515 builder.block(ZERO) { 1516 if (contextReceiverList != null) { 1517 visitContextReceiverList(contextReceiverList) 1518 } 1519 if (modifierList != null) { 1520 visitModifierList(modifierList) 1521 } 1522 val declarationKeyword = classOrObject.getDeclarationKeyword() 1523 if (declarationKeyword != null) { 1524 builder.token(declarationKeyword.text ?: fail()) 1525 } 1526 val name = classOrObject.nameIdentifier 1527 if (name != null) { 1528 builder.space() 1529 builder.token(name.text) 1530 visit(classOrObject.typeParameterList) 1531 } 1532 visit(classOrObject.primaryConstructor) 1533 val superTypes = classOrObject.getSuperTypeList() 1534 if (superTypes != null) { 1535 builder.space() 1536 builder.block(ZERO) { 1537 builder.token(":") 1538 builder.breakOp(Doc.FillMode.UNIFIED, " ", expressionBreakIndent) 1539 visit(superTypes) 1540 } 1541 } 1542 builder.space() 1543 val typeConstraintList = classOrObject.typeConstraintList 1544 if (typeConstraintList != null) { 1545 if (superTypes?.entries?.lastOrNull() is KtDelegatedSuperTypeEntry) { 1546 builder.forcedBreak() 1547 } 1548 visit(typeConstraintList) 1549 builder.space() 1550 } 1551 visit(classOrObject.body) 1552 } 1553 if (classOrObject.nameIdentifier != null) { 1554 builder.forcedBreak() 1555 } 1556 } 1557 1558 override fun visitPrimaryConstructor(constructor: KtPrimaryConstructor) { 1559 builder.sync(constructor) 1560 builder.block(ZERO) { 1561 if (constructor.hasConstructorKeyword()) { 1562 builder.breakOp(Doc.FillMode.UNIFIED, " ", ZERO) 1563 } 1564 visitFunctionLikeExpression( 1565 contextReceiverList = null, 1566 modifierList = constructor.modifierList, 1567 keyword = if (constructor.hasConstructorKeyword()) "constructor" else null, 1568 typeParameters = null, 1569 receiverTypeReference = null, 1570 name = null, 1571 parameterList = constructor.valueParameterList, 1572 typeConstraintList = null, 1573 bodyExpression = constructor.bodyExpression, 1574 typeOrDelegationCall = null, 1575 ) 1576 } 1577 } 1578 1579 /** Example `private constructor(n: Int) : this(4, 5) { ... }` inside a class's body */ 1580 override fun visitSecondaryConstructor(constructor: KtSecondaryConstructor) { 1581 builder.sync(constructor) 1582 builder.block(ZERO) { 1583 val delegationCall = constructor.getDelegationCall() 1584 visitFunctionLikeExpression( 1585 contextReceiverList = 1586 constructor.getStubOrPsiChild(KtStubElementTypes.CONTEXT_RECEIVER_LIST), 1587 modifierList = constructor.modifierList, 1588 keyword = "constructor", 1589 typeParameters = null, 1590 receiverTypeReference = null, 1591 name = null, 1592 parameterList = constructor.valueParameterList, 1593 typeConstraintList = null, 1594 bodyExpression = constructor.bodyExpression, 1595 typeOrDelegationCall = if (!delegationCall.isImplicit) delegationCall else null, 1596 ) 1597 } 1598 } 1599 1600 override fun visitConstructorDelegationCall(call: KtConstructorDelegationCall) { 1601 // Work around a misfeature in kotlin-compiler: call.calleeExpression.accept doesn't call 1602 // visitReferenceExpression, but calls visitElement instead. 1603 builder.block(ZERO) { 1604 builder.token(if (call.isCallToThis) "this" else "super") 1605 visitCallElement( 1606 null, 1607 call.typeArgumentList, 1608 call.valueArgumentList, 1609 call.lambdaArguments, 1610 ) 1611 } 1612 } 1613 1614 override fun visitClassInitializer(initializer: KtClassInitializer) { 1615 builder.sync(initializer) 1616 builder.token("init") 1617 builder.space() 1618 visit(initializer.body) 1619 } 1620 1621 override fun visitConstantExpression(expression: KtConstantExpression) { 1622 builder.sync(expression) 1623 builder.token(expression.text) 1624 } 1625 1626 /** Example `(1 + 1)` */ 1627 override fun visitParenthesizedExpression(expression: KtParenthesizedExpression) { 1628 builder.sync(expression) 1629 builder.token("(") 1630 visit(expression.expression) 1631 builder.token(")") 1632 } 1633 1634 override fun visitPackageDirective(directive: KtPackageDirective) { 1635 builder.sync(directive) 1636 if (directive.packageKeyword == null) { 1637 return 1638 } 1639 builder.token("package") 1640 builder.space() 1641 var first = true 1642 for (packageName in directive.packageNames) { 1643 if (first) { 1644 first = false 1645 } else { 1646 builder.token(".") 1647 } 1648 builder.token(packageName.getIdentifier()?.text ?: packageName.getReferencedName()) 1649 } 1650 1651 builder.guessToken(";") 1652 builder.forcedBreak() 1653 } 1654 1655 /** Example `import com.foo.A; import com.bar.B` */ 1656 override fun visitImportList(importList: KtImportList) { 1657 builder.sync(importList) 1658 importList.imports.forEach { visit(it) } 1659 } 1660 1661 /** Example `import com.foo.A` */ 1662 override fun visitImportDirective(directive: KtImportDirective) { 1663 builder.sync(directive) 1664 builder.token("import") 1665 builder.space() 1666 1667 val importedReference = directive.importedReference 1668 if (importedReference != null) { 1669 inImport = true 1670 visit(importedReference) 1671 inImport = false 1672 } 1673 if (directive.isAllUnder) { 1674 builder.token(".") 1675 builder.token("*") 1676 } 1677 1678 // Possible alias. 1679 val alias = directive.alias?.nameIdentifier 1680 if (alias != null) { 1681 builder.space() 1682 builder.token("as") 1683 builder.space() 1684 builder.token(alias.text ?: fail()) 1685 } 1686 1687 // Force a newline afterwards. 1688 builder.guessToken(";") 1689 builder.forcedBreak() 1690 } 1691 1692 /** Example `context(Logger, Raise<Error>)` */ 1693 override fun visitContextReceiverList(contextReceiverList: KtContextReceiverList) { 1694 builder.sync(contextReceiverList) 1695 builder.token("context") 1696 visitEachCommaSeparated( 1697 contextReceiverList.contextReceivers(), 1698 prefix = "(", 1699 postfix = ")", 1700 breakAfterPrefix = false, 1701 breakBeforePostfix = false, 1702 ) 1703 builder.forcedBreak() 1704 } 1705 1706 /** For example `@Magic private final` */ 1707 override fun visitModifierList(list: KtModifierList) { 1708 builder.sync(list) 1709 var onlyAnnotationsSoFar = true 1710 1711 for (child in list.node.children()) { 1712 val psi = child.psi 1713 if (psi is PsiWhiteSpace) { 1714 continue 1715 } 1716 1717 if (child.elementType is KtModifierKeywordToken) { 1718 onlyAnnotationsSoFar = false 1719 builder.token(child.text) 1720 } else { 1721 visit(psi) 1722 } 1723 1724 if (onlyAnnotationsSoFar) { 1725 builder.breakOp(Doc.FillMode.UNIFIED, " ", ZERO) 1726 } else { 1727 builder.space() 1728 } 1729 } 1730 } 1731 1732 /** 1733 * Example: 1734 * ``` 1735 * @SuppressLint("MagicNumber") 1736 * print(10) 1737 * ``` 1738 * 1739 * in 1740 * 1741 * ``` 1742 * fun f() { 1743 * @SuppressLint("MagicNumber") 1744 * print(10) 1745 * } 1746 * ``` 1747 */ 1748 override fun visitAnnotatedExpression(expression: KtAnnotatedExpression) { 1749 builder.sync(expression) 1750 builder.block(ZERO) { 1751 val baseExpression = expression.baseExpression 1752 1753 builder.block(ZERO) { 1754 val annotationEntries = expression.annotationEntries 1755 for (annotationEntry in annotationEntries) { 1756 if (annotationEntry !== annotationEntries.first()) { 1757 builder.breakOp(Doc.FillMode.UNIFIED, " ", ZERO) 1758 } 1759 visit(annotationEntry) 1760 } 1761 } 1762 1763 // Binary expressions in a block have a different meaning according to their formatting. 1764 // If there in the line above, they refer to the entire expression, if they're in the same 1765 // line then only to the first operand of the operator. 1766 // We force a break to avoid such semantic changes 1767 when { 1768 (baseExpression is KtBinaryExpression || baseExpression is KtBinaryExpressionWithTypeRHS) && 1769 expression.parent is KtBlockExpression -> builder.forcedBreak() 1770 baseExpression is KtLambdaExpression -> builder.space() 1771 baseExpression is KtReturnExpression -> builder.forcedBreak() 1772 else -> builder.breakOp(Doc.FillMode.UNIFIED, " ", ZERO) 1773 } 1774 1775 visit(expression.baseExpression) 1776 } 1777 } 1778 1779 /** 1780 * For example, @field:[Inject Named("WEB_VIEW")] 1781 * 1782 * A KtAnnotation is used only to group multiple annotations with the same use-site-target. It 1783 * only appears in a modifier list since annotated expressions do not have use-site-targets. 1784 */ 1785 override fun visitAnnotation(annotation: KtAnnotation) { 1786 builder.sync(annotation) 1787 builder.block(ZERO) { 1788 builder.token("@") 1789 val useSiteTarget = annotation.useSiteTarget 1790 if (useSiteTarget != null) { 1791 visit(useSiteTarget) 1792 builder.token(":") 1793 } 1794 builder.block(expressionBreakIndent) { 1795 builder.token("[") 1796 1797 builder.block(ZERO) { 1798 var first = true 1799 builder.breakOp(Doc.FillMode.UNIFIED, "", ZERO) 1800 for (value in annotation.entries) { 1801 if (!first) { 1802 builder.breakOp(Doc.FillMode.UNIFIED, " ", ZERO) 1803 } 1804 first = false 1805 1806 visit(value) 1807 } 1808 } 1809 } 1810 builder.token("]") 1811 } 1812 builder.forcedBreak() 1813 } 1814 1815 /** For example, 'field' in @field:[Inject Named("WEB_VIEW")] */ 1816 override fun visitAnnotationUseSiteTarget( 1817 annotationTarget: KtAnnotationUseSiteTarget, 1818 data: Void? 1819 ): Void? { 1820 builder.token(annotationTarget.getAnnotationUseSiteTarget().renderName) 1821 return null 1822 } 1823 1824 /** For example `@Magic` or `@Fred(1, 5)` */ 1825 override fun visitAnnotationEntry(annotationEntry: KtAnnotationEntry) { 1826 builder.sync(annotationEntry) 1827 if (annotationEntry.atSymbol != null) { 1828 builder.token("@") 1829 } 1830 val useSiteTarget = annotationEntry.useSiteTarget 1831 if (useSiteTarget != null && useSiteTarget.parent == annotationEntry) { 1832 visit(useSiteTarget) 1833 builder.token(":") 1834 } 1835 visitCallElement( 1836 annotationEntry.calleeExpression, 1837 null, // Type-arguments are included in the annotation's callee expression. 1838 annotationEntry.valueArgumentList, 1839 listOf()) 1840 } 1841 1842 override fun visitFileAnnotationList( 1843 fileAnnotationList: KtFileAnnotationList, 1844 data: Void? 1845 ): Void? { 1846 for (child in fileAnnotationList.node.children()) { 1847 if (child is PsiElement) { 1848 continue 1849 } 1850 visit(child.psi) 1851 builder.forcedBreak() 1852 } 1853 1854 return null 1855 } 1856 1857 override fun visitSuperTypeList(list: KtSuperTypeList) { 1858 builder.sync(list) 1859 builder.block(expressionBreakIndent) { visitEachCommaSeparated(list.entries) } 1860 } 1861 1862 override fun visitSuperTypeCallEntry(call: KtSuperTypeCallEntry) { 1863 builder.sync(call) 1864 visitCallElement(call.calleeExpression, null, call.valueArgumentList, call.lambdaArguments) 1865 } 1866 1867 /** 1868 * Example `Collection<Int> by list` in `class MyList(list: List<Int>) : Collection<Int> by list` 1869 */ 1870 override fun visitDelegatedSuperTypeEntry(specifier: KtDelegatedSuperTypeEntry) { 1871 builder.sync(specifier) 1872 visit(specifier.typeReference) 1873 builder.space() 1874 builder.token("by") 1875 builder.space() 1876 visit(specifier.delegateExpression) 1877 } 1878 1879 override fun visitWhenExpression(expression: KtWhenExpression) { 1880 builder.sync(expression) 1881 builder.block(ZERO) { 1882 emitKeywordWithCondition("when", expression.subjectExpression) 1883 1884 builder.space() 1885 builder.token("{", Doc.Token.RealOrImaginary.REAL, blockIndent, Optional.of(blockIndent)) 1886 1887 expression.entries.forEachIndexed { index, whenEntry -> 1888 builder.block(blockIndent) { 1889 if (index != 0) { 1890 // preserve new line if there's one 1891 builder.blankLineWanted(OpsBuilder.BlankLineWanted.PRESERVE) 1892 } 1893 builder.forcedBreak() 1894 if (whenEntry.isElse) { 1895 builder.token("else") 1896 } else { 1897 builder.block(ZERO) { 1898 val conditions = whenEntry.conditions 1899 for ((index, condition) in conditions.withIndex()) { 1900 visit(condition) 1901 builder.guessToken(",") 1902 if (index != conditions.lastIndex) { 1903 builder.forcedBreak() 1904 } 1905 } 1906 } 1907 } 1908 val whenExpression = whenEntry.expression 1909 builder.space() 1910 builder.token("->") 1911 if (whenExpression is KtBlockExpression) { 1912 builder.space() 1913 visit(whenExpression) 1914 } else { 1915 builder.block(expressionBreakIndent) { 1916 builder.breakOp(Doc.FillMode.INDEPENDENT, " ", ZERO) 1917 visit(whenExpression) 1918 } 1919 } 1920 builder.guessToken(";") 1921 } 1922 builder.forcedBreak() 1923 } 1924 builder.token("}") 1925 } 1926 } 1927 1928 override fun visitClassBody(body: KtClassBody) { 1929 builder.sync(body) 1930 emitBracedBlock(body) { children -> 1931 val enumEntryList = EnumEntryList.extractChildList(body) 1932 val members = children.filter { it !is KtEnumEntry } 1933 1934 if (enumEntryList != null) { 1935 builder.block(ZERO) { 1936 builder.breakOp(Doc.FillMode.UNIFIED, "", ZERO) 1937 for (value in enumEntryList.enumEntries) { 1938 visit(value) 1939 if (builder.peekToken() == Optional.of(",")) { 1940 builder.token(",") 1941 builder.forcedBreak() 1942 } 1943 } 1944 } 1945 builder.guessToken(";") 1946 1947 if (members.isNotEmpty()) { 1948 builder.forcedBreak() 1949 builder.blankLineWanted(OpsBuilder.BlankLineWanted.YES) 1950 } 1951 } else { 1952 val parent = body.parent 1953 if (parent is KtClass && parent.isEnum() && children.isNotEmpty()) { 1954 builder.token(";") 1955 builder.forcedBreak() 1956 } 1957 } 1958 1959 var prev: PsiElement? = null 1960 for (curr in members) { 1961 val blankLineBetweenMembers = 1962 when { 1963 prev == null -> OpsBuilder.BlankLineWanted.PRESERVE 1964 prev !is KtProperty -> OpsBuilder.BlankLineWanted.YES 1965 prev.getter != null || prev.setter != null -> OpsBuilder.BlankLineWanted.YES 1966 curr is KtProperty -> OpsBuilder.BlankLineWanted.PRESERVE 1967 else -> OpsBuilder.BlankLineWanted.YES 1968 } 1969 builder.blankLineWanted(blankLineBetweenMembers) 1970 1971 builder.block(ZERO) { visit(curr) } 1972 builder.guessToken(";") 1973 builder.forcedBreak() 1974 1975 prev = curr 1976 } 1977 } 1978 } 1979 1980 override fun visitBlockExpression(expression: KtBlockExpression) { 1981 builder.sync(expression) 1982 emitBracedBlock(expression) { children -> visitStatements(children) } 1983 } 1984 1985 override fun visitWhenConditionWithExpression(condition: KtWhenConditionWithExpression) { 1986 builder.sync(condition) 1987 visit(condition.expression) 1988 } 1989 1990 override fun visitWhenConditionIsPattern(condition: KtWhenConditionIsPattern) { 1991 builder.sync(condition) 1992 builder.token(if (condition.isNegated) "!is" else "is") 1993 builder.space() 1994 visit(condition.typeReference) 1995 } 1996 1997 /** Example `in 1..2` as part of a when expression */ 1998 override fun visitWhenConditionInRange(condition: KtWhenConditionInRange) { 1999 builder.sync(condition) 2000 // TODO: replace with 'condition.isNegated' once https://youtrack.jetbrains.com/issue/KT-34395 2001 // is fixed. 2002 val isNegated = condition.firstChild?.node?.findChildByType(KtTokens.NOT_IN) != null 2003 builder.token(if (isNegated) "!in" else "in") 2004 builder.space() 2005 visit(condition.rangeExpression) 2006 } 2007 2008 override fun visitIfExpression(expression: KtIfExpression) { 2009 builder.sync(expression) 2010 builder.block(ZERO) { 2011 emitKeywordWithCondition("if", expression.condition) 2012 2013 if (expression.then is KtBlockExpression) { 2014 builder.space() 2015 builder.block(ZERO) { visit(expression.then) } 2016 } else { 2017 builder.breakOp(Doc.FillMode.INDEPENDENT, " ", expressionBreakIndent) 2018 builder.block(expressionBreakIndent) { visit(expression.then) } 2019 } 2020 2021 if (expression.elseKeyword != null) { 2022 if (expression.then is KtBlockExpression) { 2023 builder.space() 2024 } else { 2025 builder.breakOp(Doc.FillMode.UNIFIED, " ", ZERO) 2026 } 2027 2028 builder.block(ZERO) { 2029 builder.token("else") 2030 if (expression.`else` is KtBlockExpression || expression.`else` is KtIfExpression) { 2031 builder.space() 2032 builder.block(ZERO) { visit(expression.`else`) } 2033 } else { 2034 builder.breakOp(Doc.FillMode.INDEPENDENT, " ", expressionBreakIndent) 2035 builder.block(expressionBreakIndent) { visit(expression.`else`) } 2036 } 2037 } 2038 } 2039 } 2040 } 2041 2042 /** Example `a[3]`, `b["a", 5]` or `a.b.c[4]` */ 2043 override fun visitArrayAccessExpression(expression: KtArrayAccessExpression) { 2044 builder.sync(expression) 2045 if (expression.arrayExpression is KtQualifiedExpression) { 2046 emitQualifiedExpression(expression) 2047 } else { 2048 visit(expression.arrayExpression) 2049 visitArrayAccessBrackets(expression) 2050 } 2051 } 2052 2053 /** 2054 * Example `[3]` in `a[3]` or `a[3].b` Separated since it needs to be used from a top level array 2055 * expression (`a[3]`) and from within a qualified chain (`a[3].b) 2056 */ 2057 private fun visitArrayAccessBrackets(expression: KtArrayAccessExpression) { 2058 builder.block(ZERO) { 2059 builder.token("[") 2060 builder.breakOp(Doc.FillMode.UNIFIED, "", expressionBreakIndent) 2061 builder.block(expressionBreakIndent) { 2062 visitEachCommaSeparated( 2063 expression.indexExpressions, expression.trailingComma != null, wrapInBlock = true) 2064 } 2065 } 2066 builder.token("]") 2067 } 2068 2069 /** Example `val (a, b: Int) = Pair(1, 2)` */ 2070 override fun visitDestructuringDeclaration(destructuringDeclaration: KtDestructuringDeclaration) { 2071 builder.sync(destructuringDeclaration) 2072 val valOrVarKeyword = destructuringDeclaration.valOrVarKeyword 2073 if (valOrVarKeyword != null) { 2074 builder.token(valOrVarKeyword.text) 2075 builder.space() 2076 } 2077 val hasTrailingComma = destructuringDeclaration.trailingComma != null 2078 builder.block(ZERO) { 2079 builder.token("(") 2080 builder.breakOp(Doc.FillMode.UNIFIED, "", expressionBreakIndent) 2081 builder.block(expressionBreakIndent) { 2082 visitEachCommaSeparated( 2083 destructuringDeclaration.entries, hasTrailingComma, wrapInBlock = true) 2084 } 2085 } 2086 builder.token(")") 2087 val initializer = destructuringDeclaration.initializer 2088 if (initializer != null) { 2089 builder.space() 2090 builder.token("=") 2091 if (hasTrailingComma) { 2092 builder.space() 2093 } else { 2094 builder.breakOp(Doc.FillMode.INDEPENDENT, " ", expressionBreakIndent) 2095 } 2096 builder.block(expressionBreakIndent, !hasTrailingComma) { visit(initializer) } 2097 } 2098 } 2099 2100 /** Example `a: String` which is part of `(a: String, b: String)` */ 2101 override fun visitDestructuringDeclarationEntry( 2102 multiDeclarationEntry: KtDestructuringDeclarationEntry 2103 ) { 2104 builder.sync(multiDeclarationEntry) 2105 declareOne( 2106 initializer = null, 2107 kind = DeclarationKind.PARAMETER, 2108 modifiers = multiDeclarationEntry.modifierList, 2109 name = multiDeclarationEntry.nameIdentifier?.text ?: fail(), 2110 type = multiDeclarationEntry.typeReference, 2111 valOrVarKeyword = null, 2112 ) 2113 } 2114 2115 /** Example `"Hello $world!"` or `"""Hello world!"""` */ 2116 override fun visitStringTemplateExpression(expression: KtStringTemplateExpression) { 2117 builder.sync(expression) 2118 builder.token(WhitespaceTombstones.replaceTrailingWhitespaceWithTombstone(expression.text)) 2119 } 2120 2121 /** Example `super` in `super.doIt(5)` or `super<Foo>` in `super<Foo>.doIt(5)` */ 2122 override fun visitSuperExpression(expression: KtSuperExpression) { 2123 builder.sync(expression) 2124 builder.token("super") 2125 val superTypeQualifier = expression.superTypeQualifier 2126 if (superTypeQualifier != null) { 2127 builder.token("<") 2128 visit(superTypeQualifier) 2129 builder.token(">") 2130 } 2131 visit(expression.labelQualifier) 2132 } 2133 2134 /** Example `<T, S>` */ 2135 override fun visitTypeParameterList(list: KtTypeParameterList) { 2136 builder.sync(list) 2137 builder.block(expressionBreakIndent) { 2138 visitEachCommaSeparated( 2139 list = list.parameters, 2140 hasTrailingComma = list.trailingComma != null, 2141 prefix = "<", 2142 postfix = ">", 2143 wrapInBlock = !options.manageTrailingCommas, 2144 ) 2145 } 2146 } 2147 2148 override fun visitTypeParameter(parameter: KtTypeParameter) { 2149 builder.sync(parameter) 2150 visit(parameter.modifierList) 2151 builder.token(parameter.nameIdentifier?.text ?: "") 2152 val extendsBound = parameter.extendsBound 2153 if (extendsBound != null) { 2154 builder.space() 2155 builder.token(":") 2156 builder.space() 2157 visit(extendsBound) 2158 } 2159 } 2160 2161 /** Example `where T : View, T : Listener` */ 2162 override fun visitTypeConstraintList(list: KtTypeConstraintList) { 2163 builder.token("where") 2164 builder.space() 2165 builder.sync(list) 2166 visitEachCommaSeparated(list.constraints) 2167 } 2168 2169 /** Example `T : Foo` */ 2170 override fun visitTypeConstraint(constraint: KtTypeConstraint) { 2171 builder.sync(constraint) 2172 // TODO(nreid260): What about annotations on the type reference? `where @A T : Int` 2173 visit(constraint.subjectTypeParameterName) 2174 builder.space() 2175 builder.token(":") 2176 builder.space() 2177 visit(constraint.boundTypeReference) 2178 } 2179 2180 /** Example `for (i in items) { ... }` */ 2181 override fun visitForExpression(expression: KtForExpression) { 2182 builder.sync(expression) 2183 builder.block(ZERO) { 2184 builder.token("for") 2185 builder.space() 2186 builder.token("(") 2187 visit(expression.loopParameter) 2188 builder.space() 2189 builder.token("in") 2190 builder.block(ZERO) { 2191 builder.breakOp(Doc.FillMode.UNIFIED, " ", expressionBreakIndent) 2192 builder.block(expressionBreakIndent) { visit(expression.loopRange) } 2193 } 2194 builder.token(")") 2195 builder.space() 2196 visit(expression.body) 2197 } 2198 } 2199 2200 /** Example `while (a < b) { ... }` */ 2201 override fun visitWhileExpression(expression: KtWhileExpression) { 2202 builder.sync(expression) 2203 emitKeywordWithCondition("while", expression.condition) 2204 builder.space() 2205 visit(expression.body) 2206 } 2207 2208 /** Example `do { ... } while (a < b)` */ 2209 override fun visitDoWhileExpression(expression: KtDoWhileExpression) { 2210 builder.sync(expression) 2211 builder.token("do") 2212 builder.space() 2213 if (expression.body != null) { 2214 visit(expression.body) 2215 builder.space() 2216 } 2217 emitKeywordWithCondition("while", expression.condition) 2218 } 2219 2220 /** Example `break` or `break@foo` in a loop */ 2221 override fun visitBreakExpression(expression: KtBreakExpression) { 2222 builder.sync(expression) 2223 builder.token("break") 2224 visit(expression.labelQualifier) 2225 } 2226 2227 /** Example `continue` or `continue@foo` in a loop */ 2228 override fun visitContinueExpression(expression: KtContinueExpression) { 2229 builder.sync(expression) 2230 builder.token("continue") 2231 visit(expression.labelQualifier) 2232 } 2233 2234 /** Example `f: String`, or `private val n: Int` or `(a: Int, b: String)` (in for-loops) */ 2235 override fun visitParameter(parameter: KtParameter) { 2236 builder.sync(parameter) 2237 builder.block(ZERO) { 2238 val destructuringDeclaration = parameter.destructuringDeclaration 2239 val typeReference = parameter.typeReference 2240 if (destructuringDeclaration != null) { 2241 builder.block(ZERO) { 2242 visit(destructuringDeclaration) 2243 if (typeReference != null) { 2244 builder.token(":") 2245 builder.space() 2246 visit(typeReference) 2247 } 2248 } 2249 } else { 2250 declareOne( 2251 kind = DeclarationKind.PARAMETER, 2252 modifiers = parameter.modifierList, 2253 valOrVarKeyword = parameter.valOrVarKeyword?.text, 2254 name = parameter.nameIdentifier?.text, 2255 type = typeReference, 2256 initializer = parameter.defaultValue) 2257 } 2258 } 2259 } 2260 2261 /** Example `String::isNullOrEmpty` */ 2262 override fun visitCallableReferenceExpression(expression: KtCallableReferenceExpression) { 2263 builder.sync(expression) 2264 visit(expression.receiverExpression) 2265 2266 // For some reason, expression.receiverExpression doesn't contain the question-mark token in 2267 // case of a nullable type, e.g., in String?::isNullOrEmpty. 2268 // Instead, KtCallableReferenceExpression exposes a method that looks for the QUEST token in 2269 // its children. 2270 if (expression.hasQuestionMarks) { 2271 builder.token("?") 2272 } 2273 2274 builder.block(expressionBreakIndent) { 2275 builder.token("::") 2276 builder.breakOp(Doc.FillMode.INDEPENDENT, "", ZERO) 2277 visit(expression.callableReference) 2278 } 2279 } 2280 2281 override fun visitClassLiteralExpression(expression: KtClassLiteralExpression) { 2282 builder.sync(expression) 2283 val receiverExpression = expression.receiverExpression 2284 if (receiverExpression is KtCallExpression) { 2285 visitCallElement( 2286 receiverExpression.calleeExpression, 2287 receiverExpression.typeArgumentList, 2288 receiverExpression.valueArgumentList, 2289 receiverExpression.lambdaArguments) 2290 } else { 2291 visit(receiverExpression) 2292 } 2293 builder.token("::") 2294 builder.token("class") 2295 } 2296 2297 override fun visitFunctionType(type: KtFunctionType) { 2298 builder.sync(type) 2299 2300 type.contextReceiverList?.let { visitContextReceiverList(it) } 2301 2302 val receiver = type.receiver 2303 if (receiver != null) { 2304 visit(receiver) 2305 builder.token(".") 2306 } 2307 builder.block(expressionBreakIndent) { 2308 val parameterList = type.parameterList 2309 if (parameterList != null) { 2310 visitEachCommaSeparated( 2311 parameterList.parameters, 2312 prefix = "(", 2313 postfix = ")", 2314 hasTrailingComma = parameterList.trailingComma != null, 2315 ) 2316 } 2317 } 2318 builder.space() 2319 builder.token("->") 2320 builder.space() 2321 builder.block(expressionBreakIndent) { visit(type.returnTypeReference) } 2322 } 2323 2324 /** Example `a is Int` or `b !is Int` */ 2325 override fun visitIsExpression(expression: KtIsExpression) { 2326 builder.sync(expression) 2327 val openGroupBeforeLeft = expression.leftHandSide !is KtQualifiedExpression 2328 if (openGroupBeforeLeft) builder.open(ZERO) 2329 visit(expression.leftHandSide) 2330 if (!openGroupBeforeLeft) builder.open(ZERO) 2331 val parent = expression.parent 2332 if (parent is KtValueArgument || 2333 parent is KtParenthesizedExpression || 2334 parent is KtContainerNode) { 2335 builder.breakOp(Doc.FillMode.UNIFIED, " ", expressionBreakIndent) 2336 } else { 2337 builder.space() 2338 } 2339 visit(expression.operationReference) 2340 builder.breakOp(Doc.FillMode.INDEPENDENT, " ", expressionBreakIndent) 2341 builder.block(expressionBreakIndent) { visit(expression.typeReference) } 2342 builder.close() 2343 } 2344 2345 /** Example `a as Int` or `a as? Int` */ 2346 override fun visitBinaryWithTypeRHSExpression(expression: KtBinaryExpressionWithTypeRHS) { 2347 builder.sync(expression) 2348 val openGroupBeforeLeft = expression.left !is KtQualifiedExpression 2349 if (openGroupBeforeLeft) builder.open(ZERO) 2350 visit(expression.left) 2351 if (!openGroupBeforeLeft) builder.open(ZERO) 2352 builder.breakOp(Doc.FillMode.UNIFIED, " ", expressionBreakIndent) 2353 visit(expression.operationReference) 2354 builder.breakOp(Doc.FillMode.INDEPENDENT, " ", expressionBreakIndent) 2355 builder.block(expressionBreakIndent) { visit(expression.right) } 2356 builder.close() 2357 } 2358 2359 /** 2360 * Example: 2361 * ``` 2362 * fun f() { 2363 * val a: Array<Int> = [1, 2, 3] 2364 * } 2365 * ``` 2366 */ 2367 override fun visitCollectionLiteralExpression(expression: KtCollectionLiteralExpression) { 2368 builder.sync(expression) 2369 builder.block(expressionBreakIndent) { 2370 visitEachCommaSeparated( 2371 expression.getInnerExpressions(), 2372 expression.trailingComma != null, 2373 prefix = "[", 2374 postfix = "]", 2375 wrapInBlock = !options.manageTrailingCommas) 2376 } 2377 } 2378 2379 override fun visitTryExpression(expression: KtTryExpression) { 2380 builder.sync(expression) 2381 builder.token("try") 2382 builder.space() 2383 visit(expression.tryBlock) 2384 for (catchClause in expression.catchClauses) { 2385 visit(catchClause) 2386 } 2387 visit(expression.finallyBlock) 2388 } 2389 2390 override fun visitCatchSection(catchClause: KtCatchClause) { 2391 builder.sync(catchClause) 2392 builder.space() 2393 builder.token("catch") 2394 builder.space() 2395 builder.block(ZERO) { 2396 builder.token("(") 2397 builder.block(expressionBreakIndent) { 2398 builder.breakOp(Doc.FillMode.UNIFIED, "", ZERO) 2399 visit(catchClause.catchParameter) 2400 builder.guessToken(",") 2401 } 2402 } 2403 builder.token(")") 2404 builder.space() 2405 visit(catchClause.catchBody) 2406 } 2407 2408 override fun visitFinallySection(finallySection: KtFinallySection) { 2409 builder.sync(finallySection) 2410 builder.space() 2411 builder.token("finally") 2412 builder.space() 2413 visit(finallySection.finalExpression) 2414 } 2415 2416 override fun visitThrowExpression(expression: KtThrowExpression) { 2417 builder.sync(expression) 2418 builder.token("throw") 2419 builder.space() 2420 visit(expression.thrownExpression) 2421 } 2422 2423 /** Example `RED(0xFF0000)` in an enum class */ 2424 override fun visitEnumEntry(enumEntry: KtEnumEntry) { 2425 builder.sync(enumEntry) 2426 builder.block(ZERO) { 2427 visit(enumEntry.modifierList) 2428 builder.token(enumEntry.nameIdentifier?.text ?: fail()) 2429 enumEntry.initializerList?.initializers?.forEach { visit(it) } 2430 enumEntry.body?.let { enumBody -> 2431 builder.space() 2432 visit(enumBody) 2433 } 2434 } 2435 } 2436 2437 /** Example `private typealias TextChangedListener = (string: String) -> Unit` */ 2438 override fun visitTypeAlias(typeAlias: KtTypeAlias) { 2439 builder.sync(typeAlias) 2440 builder.block(ZERO) { 2441 visit(typeAlias.modifierList) 2442 builder.token("typealias") 2443 builder.space() 2444 builder.token(typeAlias.nameIdentifier?.text ?: fail()) 2445 visit(typeAlias.typeParameterList) 2446 2447 builder.space() 2448 builder.token("=") 2449 builder.breakOp(Doc.FillMode.INDEPENDENT, " ", expressionBreakIndent) 2450 builder.block(expressionBreakIndent) { 2451 visit(typeAlias.getTypeReference()) 2452 visit(typeAlias.typeConstraintList) 2453 builder.guessToken(";") 2454 } 2455 builder.forcedBreak() 2456 } 2457 } 2458 2459 /** 2460 * visitElement is called for almost all types of AST nodes. We use it to keep track of whether 2461 * we're currently inside an expression or not. 2462 * 2463 * @throws FormattingError 2464 */ 2465 override fun visitElement(element: PsiElement) { 2466 inExpression.addLast(element is KtExpression || inExpression.last()) 2467 val previous = builder.depth() 2468 try { 2469 super.visitElement(element) 2470 } catch (e: FormattingError) { 2471 throw e 2472 } catch (t: Throwable) { 2473 throw FormattingError(builder.diagnostic(Throwables.getStackTraceAsString(t))) 2474 } finally { 2475 inExpression.removeLast() 2476 } 2477 builder.checkClosed(previous) 2478 } 2479 2480 override fun visitKtFile(file: KtFile) { 2481 markForPartialFormat() 2482 val importListEmpty = file.importList?.text?.isBlank() ?: true 2483 2484 var isFirst = true 2485 for (child in file.children) { 2486 if (child.text.isBlank()) { 2487 continue 2488 } 2489 2490 builder.blankLineWanted( 2491 when { 2492 isFirst -> OpsBuilder.BlankLineWanted.NO 2493 child is PsiComment -> continue 2494 child is KtScript && importListEmpty -> OpsBuilder.BlankLineWanted.PRESERVE 2495 else -> OpsBuilder.BlankLineWanted.YES 2496 }) 2497 2498 visit(child) 2499 isFirst = false 2500 } 2501 markForPartialFormat() 2502 } 2503 2504 override fun visitScript(script: KtScript) { 2505 markForPartialFormat() 2506 var lastChildHadBlankLineBefore = false 2507 var lastChildIsContextReceiver = false 2508 var first = true 2509 for (child in script.blockExpression.children) { 2510 if (child.text.isBlank()) { 2511 continue 2512 } 2513 builder.forcedBreak() 2514 val childGetsBlankLineBefore = child !is KtProperty 2515 if (first) { 2516 builder.blankLineWanted(OpsBuilder.BlankLineWanted.PRESERVE) 2517 } else if (lastChildIsContextReceiver) { 2518 builder.blankLineWanted(OpsBuilder.BlankLineWanted.NO) 2519 } else if (child !is PsiComment && 2520 (childGetsBlankLineBefore || lastChildHadBlankLineBefore)) { 2521 builder.blankLineWanted(OpsBuilder.BlankLineWanted.YES) 2522 } 2523 visit(child) 2524 builder.guessToken(";") 2525 lastChildHadBlankLineBefore = childGetsBlankLineBefore 2526 lastChildIsContextReceiver = 2527 child is KtScriptInitializer && 2528 child.firstChild?.firstChild?.firstChild?.text == "context" 2529 first = false 2530 } 2531 markForPartialFormat() 2532 } 2533 2534 /** 2535 * markForPartialFormat is used to delineate the smallest areas of code that must be formatted 2536 * together. 2537 * 2538 * When only parts of the code are being formatted, the requested area is expanded until it's 2539 * covered by an area marked by this method. 2540 */ 2541 private fun markForPartialFormat() { 2542 if (!inExpression.last()) { 2543 builder.markForPartialFormat() 2544 } 2545 } 2546 2547 /** 2548 * Emit a [Doc.Token]. 2549 * 2550 * @param token the [String] to wrap in a [Doc.Token] 2551 * @param plusIndentCommentsBefore extra block for comments before this token 2552 */ 2553 private fun OpsBuilder.token(token: String, plusIndentCommentsBefore: Indent = ZERO) { 2554 token( 2555 token, 2556 Doc.Token.RealOrImaginary.REAL, 2557 plusIndentCommentsBefore, 2558 /* breakAndIndentTrailingComment */ Optional.empty()) 2559 } 2560 2561 /** 2562 * Opens a new level, emits into it and closes it. 2563 * 2564 * This is a helper method to make it easier to keep track of [OpsBuilder.open] and 2565 * [OpsBuilder.close] calls 2566 * 2567 * @param plusIndent the block level to pass to the block 2568 * @param block a code block to be run in this block level 2569 */ 2570 private fun OpsBuilder.block(plusIndent: Indent, isEnabled: Boolean = true, block: () -> Unit) { 2571 if (isEnabled) { 2572 open(plusIndent) 2573 } 2574 block() 2575 if (isEnabled) { 2576 close() 2577 } 2578 } 2579 2580 /** Helper method to sync the current offset to match any element in the AST */ 2581 private fun OpsBuilder.sync(psiElement: PsiElement) { 2582 sync(psiElement.startOffset) 2583 } 2584 2585 /** Prevent subsequent comments from being moved ahead of this point, into parent [Level]s. */ 2586 private fun OpsBuilder.fenceComments() { 2587 addAll(FenceCommentsOp.AS_LIST) 2588 } 2589 2590 /** 2591 * Throws a formatting error 2592 * 2593 * This is used as `expr ?: fail()` to avoid using the !! operator and provide better error 2594 * messages. 2595 */ 2596 private fun fail(message: String = "Unexpected"): Nothing { 2597 throw FormattingError(builder.diagnostic(message)) 2598 } 2599 2600 /** Helper function to improve readability */ 2601 private fun visit(element: PsiElement?) { 2602 element?.accept(this) 2603 } 2604 2605 /** Emits a key word followed by a condition, e.g. `if (b)` or `while (c < d )` */ 2606 private fun emitKeywordWithCondition(keyword: String, condition: KtExpression?) { 2607 if (condition == null) { 2608 builder.token(keyword) 2609 return 2610 } 2611 2612 builder.block(ZERO) { 2613 builder.token(keyword) 2614 builder.space() 2615 builder.token("(") 2616 if (options.manageTrailingCommas) { 2617 builder.block(expressionBreakIndent) { 2618 builder.breakOp(Doc.FillMode.UNIFIED, "", ZERO) 2619 visit(condition) 2620 builder.breakOp(Doc.FillMode.UNIFIED, "", expressionBreakNegativeIndent) 2621 } 2622 } else { 2623 builder.block(ZERO) { visit(condition) } 2624 } 2625 } 2626 builder.token(")") 2627 } 2628 } 2629