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.KtClassBody 21 import org.jetbrains.kotlin.psi.KtContainerNodeForControlStructureBody 22 import org.jetbrains.kotlin.psi.KtDotQualifiedExpression 23 import org.jetbrains.kotlin.psi.KtEnumEntry 24 import org.jetbrains.kotlin.psi.KtIfExpression 25 import org.jetbrains.kotlin.psi.KtLambdaExpression 26 import org.jetbrains.kotlin.psi.KtStringTemplateEntry 27 import org.jetbrains.kotlin.psi.KtStringTemplateExpression 28 import org.jetbrains.kotlin.psi.KtWhileExpression 29 import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments 30 import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespaceAndComments 31 import org.jetbrains.kotlin.psi.psiUtil.prevLeaf 32 33 internal class RedundantSemicolonDetector { 34 private val extraSemicolons = mutableListOf<PsiElement>() 35 getRedundantSemicolonElementsnull36 fun getRedundantSemicolonElements(): List<PsiElement> = extraSemicolons 37 38 fun takeElement(element: PsiElement) { 39 if (isExtraSemicolon(element)) { 40 extraSemicolons += element 41 } 42 } 43 44 /** returns **true** if this element was an extra comma, **false** otherwise. */ isExtraSemicolonnull45 private fun isExtraSemicolon(element: PsiElement): Boolean { 46 if (element.text != ";") { 47 return false 48 } 49 50 val parent = element.parent 51 if (parent is KtStringTemplateExpression || parent is KtStringTemplateEntry) { 52 return false 53 } 54 55 if (parent is KtEnumEntry) { 56 val classBody = parent.parent as KtClassBody 57 // Terminating semicolon with no other class members. 58 return classBody.children.last() == parent 59 } 60 if (parent is KtClassBody) { 61 val enumEntryList = EnumEntryList.extractChildList(parent) ?: return true 62 // Is not terminating semicolon or is terminating with no members. 63 return element != enumEntryList.terminatingSemicolon || parent.children.isEmpty() 64 } 65 66 val prevLeaf = element.prevLeaf(false) 67 val prevConcreteSibling = element.getPrevSiblingIgnoringWhitespaceAndComments() 68 if ((prevConcreteSibling is KtIfExpression || prevConcreteSibling is KtWhileExpression) && 69 prevLeaf is KtContainerNodeForControlStructureBody && 70 prevLeaf.text.isEmpty()) { 71 return false 72 } 73 74 val nextConcreteSibling = element.getNextSiblingIgnoringWhitespaceAndComments() 75 76 /** 77 * Examples: 78 * ``` 79 * val x = foo(0) ; { dead -> lambda } 80 * val y = foo(1) ; { dead -> lambda }.bar() 81 * ``` 82 * 83 * There are a huge number of cases here because the trailing lambda syntax is so flexible. 84 * Therefore, we just assume that all semicolons followed by lambdas are meaningful. The cases 85 * where they could be removed are too rare to justify the risk of changing behaviour. 86 */ 87 val nextSiblingIsLambda = 88 (nextConcreteSibling is KtLambdaExpression || 89 (nextConcreteSibling is KtDotQualifiedExpression && 90 nextConcreteSibling.receiverExpression is KtLambdaExpression)) 91 92 return !nextSiblingIsLambda 93 } 94 } 95