• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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