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