• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 org.jetbrains.kotlin.com.intellij.psi.PsiElement
20 import org.jetbrains.kotlin.kdoc.psi.impl.KDocImpl
21 import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink
22 import org.jetbrains.kotlin.kdoc.psi.impl.KDocName
23 import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
24 import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
25 import org.jetbrains.kotlin.name.FqName
26 import org.jetbrains.kotlin.psi.KtImportDirective
27 import org.jetbrains.kotlin.psi.KtImportList
28 import org.jetbrains.kotlin.psi.KtPackageDirective
29 import org.jetbrains.kotlin.psi.KtReferenceExpression
30 import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
31 
32 internal class RedundantImportDetector(val enabled: Boolean) {
33   companion object {
34     private val OPERATORS =
35         setOf(
36             // Unary prefix operators
37             "unaryPlus",
38             "unaryMinus",
39             "not",
40             // Increments and decrements
41             "inc",
42             "dec",
43             // Arithmetic operators
44             "plus",
45             "minus",
46             "times",
47             "div",
48             "rem",
49             "mod", // deprecated
50             "rangeTo",
51             // 'In' operator
52             "contains",
53             // Indexed access operator
54             "get",
55             "set",
56             // Invoke operator
57             "invoke",
58             // Augmented assignments
59             "plusAssign",
60             "minusAssign",
61             "timesAssign",
62             "divAssign",
63             "remAssign",
64             "modAssign", // deprecated
65             // Equality and inequality operators
66             "equals",
67             // Comparison operators
68             "compareTo",
69             // Iterator operators
70             "iterator",
71             "next",
72             "hasNext",
73             // Bitwise operators
74             "and",
75             "or",
76             // Property delegation operators
77             "getValue",
78             "setValue",
79             "provideDelegate")
80 
81     private val COMPONENT_OPERATOR_REGEX = Regex("component\\d+")
82 
83     private val KDOC_TAG_SKIP_FIRST_REFERENCE_REGEX = Regex("^@(param|property) (.+)")
84   }
85 
86   private var thisPackage: FqName? = null
87 
88   private val usedReferences = OPERATORS.toMutableSet()
89 
90   private lateinit var importCleanUpCandidates: Set<KtImportDirective>
91 
92   private var isPackageElement = false
93   private var isImportElement = false
94 
95   fun takePackageDirective(directive: KtPackageDirective, superBlock: () -> Unit) {
96     if (!enabled) {
97       return superBlock.invoke()
98     }
99 
100     thisPackage = directive.fqName
101 
102     isPackageElement = true
103     superBlock.invoke()
104     isPackageElement = false
105   }
106 
107   fun takeImportList(importList: KtImportList, superBlock: () -> Unit) {
108     if (!enabled) {
109       return superBlock.invoke()
110     }
111 
112     importCleanUpCandidates =
113         importList.imports
114             .filter { import ->
115               val identifier = import.identifier ?: return@filter false
116               import.isValidImport &&
117                   identifier !in OPERATORS &&
118                   !COMPONENT_OPERATOR_REGEX.matches(identifier)
119             }
120             .toSet()
121 
122     isImportElement = true
123     superBlock.invoke()
124     isImportElement = false
125   }
126 
127   fun takeKdoc(kdoc: KDocImpl) {
128     kdoc.getChildrenOfType<KDocSection>().forEach { kdocSection ->
129       val tagLinks =
130           kdocSection.getChildrenOfType<KDocTag>().flatMap { tag ->
131             val tagLinks = tag.getChildrenOfType<KDocLink>().toList()
132             when {
133               KDOC_TAG_SKIP_FIRST_REFERENCE_REGEX.matches(tag.text) -> tagLinks.drop(1)
134               else -> tagLinks
135             }
136           }
137 
138       val links = kdocSection.getChildrenOfType<KDocLink>() + tagLinks
139 
140       val references =
141           links.flatMap { link ->
142             link.getChildrenOfType<KDocName>().mapNotNull {
143               it.getQualifiedName().firstOrNull()?.trim('[', ']')
144             }
145           }
146 
147       usedReferences += references
148     }
149   }
150 
151   fun takeReferenceExpression(expression: KtReferenceExpression) {
152     if (!enabled) return
153 
154     if (!isPackageElement && !isImportElement && expression.children.isEmpty()) {
155       usedReferences += expression.text.trim('`')
156     }
157   }
158 
159   fun getRedundantImportElements(): List<PsiElement> {
160     if (!enabled) return emptyList()
161 
162     val identifierCounts =
163         importCleanUpCandidates.groupBy { it.identifier }.mapValues { it.value.size }
164 
165     return importCleanUpCandidates.filter {
166       val isUsed = it.identifier in usedReferences
167       val isFromThisPackage = it.importedFqName?.parent() == thisPackage
168       val hasAlias = it.alias != null
169       val isOverload = requireNotNull(identifierCounts[it.identifier]) > 1
170       // Remove if...
171       !isUsed || (isFromThisPackage && !hasAlias && !isOverload)
172     }
173   }
174 
175   /** The imported short name, possibly an alias name, if any. */
176   private inline val KtImportDirective.identifier: String?
177     get() = importPath?.importedName?.identifier?.trim('`')
178 }
179