• 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               import.isValidImport &&
116                   !import.isAllUnder &&
117                   import.identifier != null &&
118                   requireNotNull(import.identifier) !in OPERATORS &&
119                   !COMPONENT_OPERATOR_REGEX.matches(import.identifier.orEmpty())
120             }
121             .toSet()
122 
123     isImportElement = true
124     superBlock.invoke()
125     isImportElement = false
126   }
127 
128   fun takeKdoc(kdoc: KDocImpl) {
129     kdoc.getChildrenOfType<KDocSection>().forEach { kdocSection ->
130       val tagLinks =
131           kdocSection.getChildrenOfType<KDocTag>().flatMap { tag ->
132             val tagLinks = tag.getChildrenOfType<KDocLink>().toList()
133             when {
134               KDOC_TAG_SKIP_FIRST_REFERENCE_REGEX.matches(tag.text) -> tagLinks.drop(1)
135               else -> tagLinks
136             }
137           }
138 
139       val links = kdocSection.getChildrenOfType<KDocLink>() + tagLinks
140 
141       val references =
142           links.flatMap { link ->
143             link.getChildrenOfType<KDocName>().mapNotNull {
144               it.getQualifiedName().firstOrNull()?.trim('[', ']')
145             }
146           }
147 
148       usedReferences += references
149     }
150   }
151 
152   fun takeReferenceExpression(expression: KtReferenceExpression) {
153     if (!enabled) return
154 
155     if (!isPackageElement && !isImportElement && expression.children.isEmpty()) {
156       usedReferences += expression.text.trim('`')
157     }
158   }
159 
160   fun getRedundantImportElements(): List<PsiElement> {
161     if (!enabled) return emptyList()
162 
163     val redundantImports = mutableListOf<PsiElement>()
164 
165     // Collect unused imports
166     for (import in importCleanUpCandidates) {
167       val isUnused = import.aliasName !in usedReferences && import.identifier !in usedReferences
168       val isFromSamePackage = import.importedFqName?.parent() == thisPackage && import.alias == null
169       if (isUnused || isFromSamePackage) {
170         redundantImports += import
171       }
172     }
173 
174     return redundantImports
175   }
176 
177   private inline val KtImportDirective.identifier: String?
178     get() = importPath?.importedName?.identifier?.trim('`')
179 }
180