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