1 /* 2 * 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 org.jetbrains.kotlin.com.intellij.psi.PsiElement 20 import org.jetbrains.kotlin.psi.KtContainerNodeForControlStructureBody 21 import org.jetbrains.kotlin.psi.KtDeclaration 22 import org.jetbrains.kotlin.psi.KtEnumEntry 23 import org.jetbrains.kotlin.psi.KtIfExpression 24 import org.jetbrains.kotlin.psi.KtLambdaExpression 25 import org.jetbrains.kotlin.psi.KtStringTemplateEntry 26 import org.jetbrains.kotlin.psi.KtStringTemplateExpression 27 import org.jetbrains.kotlin.psi.KtWhileExpression 28 import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments 29 import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespaceAndComments 30 import org.jetbrains.kotlin.psi.psiUtil.prevLeaf 31 import org.jetbrains.kotlin.psi.psiUtil.siblings 32 33 internal class RedundantSemicolonDetector { 34 private val extraSemicolons = mutableListOf<PsiElement>() 35 getRedundantSemicolonElementsnull36 fun getRedundantSemicolonElements(): List<PsiElement> = extraSemicolons 37 38 /** returns **true** if this element was an extra comma, **false** otherwise. */ 39 fun takeElement(element: PsiElement, superBlock: () -> Unit) { 40 if (isExtraSemicolon(element)) { 41 extraSemicolons += element 42 } else { 43 superBlock.invoke() 44 } 45 } 46 isExtraSemicolonnull47 private fun isExtraSemicolon(element: PsiElement): Boolean { 48 if (element.text != ";") { 49 return false 50 } 51 52 val parent = element.parent 53 if (parent is KtStringTemplateExpression || parent is KtStringTemplateEntry) { 54 return false 55 } 56 if (parent is KtEnumEntry && 57 parent.siblings(forward = true, withItself = false).any { it is KtDeclaration }) { 58 return false 59 } 60 61 val prevLeaf = element.prevLeaf(false) 62 val prevConcreteSibling = element.getPrevSiblingIgnoringWhitespaceAndComments() 63 if ((prevConcreteSibling is KtIfExpression || prevConcreteSibling is KtWhileExpression) && 64 prevLeaf is KtContainerNodeForControlStructureBody && 65 prevLeaf.text.isEmpty()) { 66 return false 67 } 68 69 val nextConcreteSibling = element.getNextSiblingIgnoringWhitespaceAndComments() 70 if (nextConcreteSibling is KtLambdaExpression) { 71 /** 72 * Example: `val x = foo(0) ; { dead -> lambda }` 73 * 74 * There are a huge number of cases here because the trailing lambda syntax is so flexible. 75 * Therefore, we just assume that all semicolons followed by lambdas are meaningful. The cases 76 * where they could be removed are too rare to justify the risk of changing behaviour. 77 */ 78 return false 79 } 80 81 return true 82 } 83 } 84