• 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.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