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 // assign operator - Gradle compiler plugin 81 // https://blog.gradle.org/simpler-kotlin-dsl-property-assignment 82 "assign", 83 ) 84 85 private val COMPONENT_OPERATOR_REGEX = Regex("component\\d+") 86 87 private val KDOC_TAG_SKIP_FIRST_REFERENCE_REGEX = Regex("^@(param|property) (.+)") 88 } 89 90 private var thisPackage: FqName? = null 91 92 private val usedReferences = OPERATORS.toMutableSet() 93 94 private lateinit var importCleanUpCandidates: Set<KtImportDirective> 95 96 private var isPackageElement = false 97 private var isImportElement = false 98 99 fun takePackageDirective(directive: KtPackageDirective, superBlock: () -> Unit) { 100 if (!enabled) { 101 return superBlock.invoke() 102 } 103 104 thisPackage = directive.fqName 105 106 isPackageElement = true 107 superBlock.invoke() 108 isPackageElement = false 109 } 110 111 fun takeImportList(importList: KtImportList, superBlock: () -> Unit) { 112 if (!enabled) { 113 return superBlock.invoke() 114 } 115 116 importCleanUpCandidates = 117 importList.imports 118 .filter { import -> 119 val identifier = import.identifier ?: return@filter false 120 import.isValidImport && 121 identifier !in OPERATORS && 122 !COMPONENT_OPERATOR_REGEX.matches(identifier) 123 } 124 .toSet() 125 126 isImportElement = true 127 superBlock.invoke() 128 isImportElement = false 129 } 130 131 fun takeKdoc(kdoc: KDocImpl) { 132 kdoc.getChildrenOfType<KDocSection>().forEach { kdocSection -> 133 val tagLinks = 134 kdocSection.getChildrenOfType<KDocTag>().flatMap { tag -> 135 val tagLinks = tag.getChildrenOfType<KDocLink>().toList() 136 when { 137 KDOC_TAG_SKIP_FIRST_REFERENCE_REGEX.matches(tag.text) -> tagLinks.drop(1) 138 else -> tagLinks 139 } 140 } 141 142 val links = kdocSection.getChildrenOfType<KDocLink>() + tagLinks 143 144 val references = 145 links.flatMap { link -> 146 link.getChildrenOfType<KDocName>().mapNotNull { 147 it.getQualifiedName().firstOrNull()?.trim('[', ']') 148 } 149 } 150 151 usedReferences += references 152 } 153 } 154 155 fun takeReferenceExpression(expression: KtReferenceExpression) { 156 if (!enabled) return 157 158 if (!isPackageElement && !isImportElement && expression.children.isEmpty()) { 159 usedReferences += expression.text.trim('`') 160 } 161 } 162 163 fun getRedundantImportElements(): List<PsiElement> { 164 if (!enabled) return emptyList() 165 166 val identifierCounts = 167 importCleanUpCandidates.groupBy { it.identifier }.mapValues { it.value.size } 168 169 return importCleanUpCandidates.filter { 170 val isUsed = it.identifier in usedReferences 171 val isFromThisPackage = it.importedFqName?.parent() == thisPackage 172 val hasAlias = it.alias != null 173 val isOverload = requireNotNull(identifierCounts[it.identifier]) > 1 174 // Remove if... 175 !isUsed || (isFromThisPackage && !hasAlias && !isOverload) 176 } 177 } 178 179 /** The imported short name, possibly an alias name, if any. */ 180 private inline val KtImportDirective.identifier: String? 181 get() = importPath?.importedName?.identifier?.trim('`') 182 } 183