"Fossies" - the Fresh Open Source Software Archive

Member "kotlin-1.3.61/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantLetInspection.kt" (26 Nov 2019, 10755 Bytes) of package /linux/misc/kotlin-1.3.61.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Kotlin source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 /*
    2  * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
    3  * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
    4  */
    5 
    6 package org.jetbrains.kotlin.idea.inspections
    7 
    8 import com.intellij.codeInspection.ProblemHighlightType
    9 import com.intellij.openapi.editor.Editor
   10 import com.intellij.openapi.project.Project
   11 import com.intellij.psi.PsiElement
   12 import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
   13 import org.jetbrains.kotlin.idea.caches.resolve.analyze
   14 import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
   15 import org.jetbrains.kotlin.idea.core.replaced
   16 import org.jetbrains.kotlin.idea.intentions.*
   17 import org.jetbrains.kotlin.idea.intentions.branchedTransformations.lineCount
   18 import org.jetbrains.kotlin.idea.util.textRangeIn
   19 import org.jetbrains.kotlin.psi.*
   20 import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
   21 import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
   22 import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector
   23 import org.jetbrains.kotlin.psi.psiUtil.startOffset
   24 import org.jetbrains.kotlin.resolve.BindingContext
   25 import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets
   26 import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
   27 import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
   28 import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
   29 
   30 abstract class RedundantLetInspection : AbstractApplicabilityBasedInspection<KtCallExpression>(
   31     KtCallExpression::class.java
   32 ) {
   33     override fun inspectionText(element: KtCallExpression) = "Redundant `let` call could be removed"
   34 
   35     final override fun inspectionHighlightRangeInElement(element: KtCallExpression) = element.calleeExpression?.textRangeIn(element)
   36 
   37     final override val defaultFixText = "Remove `let` call"
   38 
   39     final override fun isApplicable(element: KtCallExpression): Boolean {
   40         if (!element.isLetMethodCall()) return false
   41         val lambdaExpression = element.lambdaArguments.firstOrNull()?.getLambdaExpression() ?: return false
   42         val parameterName = lambdaExpression.getParameterName() ?: return false
   43 
   44         return isApplicable(
   45             element,
   46             lambdaExpression.bodyExpression?.children?.singleOrNull() ?: return false,
   47             lambdaExpression,
   48             parameterName
   49         )
   50     }
   51 
   52     protected abstract fun isApplicable(
   53         element: KtCallExpression,
   54         bodyExpression: PsiElement,
   55         lambdaExpression: KtLambdaExpression,
   56         parameterName: String
   57     ): Boolean
   58 
   59     final override fun applyTo(element: KtCallExpression, project: Project, editor: Editor?) {
   60         val lambdaExpression = element.lambdaArguments.firstOrNull()?.getLambdaExpression() ?: return
   61         when (val bodyExpression = lambdaExpression.bodyExpression?.children?.singleOrNull() ?: return) {
   62             is KtDotQualifiedExpression -> bodyExpression.applyTo(element)
   63             is KtBinaryExpression -> bodyExpression.applyTo(element)
   64             is KtCallExpression -> bodyExpression.applyTo(element, lambdaExpression.functionLiteral, editor)
   65         }
   66     }
   67 }
   68 
   69 class SimpleRedundantLetInspection : RedundantLetInspection() {
   70     override fun isApplicable(
   71         element: KtCallExpression,
   72         bodyExpression: PsiElement,
   73         lambdaExpression: KtLambdaExpression,
   74         parameterName: String
   75     ): Boolean = if (bodyExpression is KtDotQualifiedExpression) bodyExpression.isApplicable(parameterName) else false
   76 }
   77 
   78 class ComplexRedundantLetInspection : RedundantLetInspection() {
   79     override fun isApplicable(
   80         element: KtCallExpression,
   81         bodyExpression: PsiElement,
   82         lambdaExpression: KtLambdaExpression,
   83         parameterName: String
   84     ): Boolean = when (bodyExpression) {
   85         is KtBinaryExpression ->
   86             element.parent !is KtSafeQualifiedExpression && bodyExpression.isApplicable(parameterName)
   87         is KtCallExpression ->
   88             if (element.parent is KtSafeQualifiedExpression) {
   89                 false
   90             } else {
   91                 val count = lambdaExpression.functionLiteral.valueParameterReferences(bodyExpression).count()
   92                 val destructuringDeclaration = lambdaExpression.functionLiteral.valueParameters.firstOrNull()?.destructuringDeclaration
   93                 count == 0 || (count == 1 && destructuringDeclaration == null)
   94             }
   95         else ->
   96             false
   97     }
   98 
   99     override fun inspectionHighlightType(element: KtCallExpression): ProblemHighlightType = if (isSingleLine(element))
  100         ProblemHighlightType.GENERIC_ERROR_OR_WARNING
  101     else
  102         ProblemHighlightType.INFORMATION
  103 }
  104 
  105 private fun KtBinaryExpression.applyTo(element: KtCallExpression) {
  106     val left = left ?: return
  107     val factory = KtPsiFactory(element.project)
  108     when (val parent = element.parent) {
  109         is KtQualifiedExpression -> {
  110             val receiver = parent.receiverExpression
  111             val newLeft = when (left) {
  112                 is KtDotQualifiedExpression -> left.replaceFirstReceiver(factory, receiver, parent is KtSafeQualifiedExpression)
  113                 else -> receiver
  114             }
  115             val newExpression = factory.createExpressionByPattern("$0 $1 $2", newLeft, operationReference, right!!)
  116             parent.replace(newExpression)
  117         }
  118         else -> {
  119             val newLeft = when (left) {
  120                 is KtDotQualifiedExpression -> left.deleteFirstReceiver()
  121                 else -> factory.createThisExpression()
  122             }
  123             val newExpression = factory.createExpressionByPattern("$0 $1 $2", newLeft, operationReference, right!!)
  124             element.replace(newExpression)
  125         }
  126     }
  127 }
  128 
  129 private fun KtDotQualifiedExpression.applyTo(element: KtCallExpression) {
  130     when (val parent = element.parent) {
  131         is KtQualifiedExpression -> {
  132             val factory = KtPsiFactory(element.project)
  133             val receiver = parent.receiverExpression
  134             parent.replace(replaceFirstReceiver(factory, receiver, parent is KtSafeQualifiedExpression))
  135         }
  136         else -> {
  137             element.replace(deleteFirstReceiver())
  138         }
  139     }
  140 }
  141 
  142 private fun KtCallExpression.applyTo(element: KtCallExpression, functionLiteral: KtFunctionLiteral, editor: Editor?) {
  143     val parent = element.parent as? KtQualifiedExpression
  144     val reference = functionLiteral.valueParameterReferences(this).firstOrNull()
  145     val replaced = if (parent != null) {
  146         reference?.replace(parent.receiverExpression)
  147         parent.replaced(this)
  148     } else {
  149         reference?.replace(KtPsiFactory(this).createThisExpression())
  150         element.replaced(this)
  151     }
  152     editor?.caretModel?.moveToOffset(replaced.startOffset)
  153 }
  154 
  155 private fun KtBinaryExpression.isApplicable(parameterName: String, isTopLevel: Boolean = true): Boolean {
  156     val left = left ?: return false
  157     if (isTopLevel) {
  158         when (left) {
  159             is KtNameReferenceExpression -> if (left.text != parameterName) return false
  160             is KtDotQualifiedExpression -> if (!left.isApplicable(parameterName)) return false
  161             else -> return false
  162         }
  163     } else {
  164         if (!left.isApplicable(parameterName)) return false
  165     }
  166 
  167     val right = right ?: return false
  168     return right.isApplicable(parameterName)
  169 }
  170 
  171 private fun KtExpression.isApplicable(parameterName: String): Boolean = when (this) {
  172     is KtNameReferenceExpression -> text != parameterName
  173     is KtDotQualifiedExpression -> !hasLambdaExpression() && !nameUsed(parameterName)
  174     is KtBinaryExpression -> isApplicable(parameterName, isTopLevel = false)
  175     is KtCallExpression -> isApplicable(parameterName)
  176     is KtConstantExpression -> true
  177     else -> false
  178 }
  179 
  180 private fun KtCallExpression.isApplicable(parameterName: String): Boolean = valueArguments.all {
  181     val argumentExpression = it.getArgumentExpression() ?: return@all false
  182     argumentExpression.isApplicable(parameterName)
  183 }
  184 
  185 private fun KtDotQualifiedExpression.isApplicable(parameterName: String) =
  186     !hasLambdaExpression() && getLeftMostReceiverExpression().let { receiver ->
  187         receiver is KtNameReferenceExpression &&
  188                 receiver.getReferencedName() == parameterName &&
  189                 !nameUsed(parameterName, except = receiver)
  190     } && callExpression?.resolveToCall() !is VariableAsFunctionResolvedCall
  191 
  192 private fun KtDotQualifiedExpression.hasLambdaExpression() = selectorExpression?.anyDescendantOfType<KtLambdaExpression>() ?: false
  193 
  194 private fun KtCallExpression.isLetMethodCall() = calleeExpression?.text == "let" && isMethodCall("kotlin.let")
  195 
  196 private fun KtLambdaExpression.getParameterName(): String? {
  197     val parameters = valueParameters
  198     if (parameters.size > 1) return null
  199     return if (parameters.size == 1) parameters[0].text else "it"
  200 }
  201 
  202 private fun KtExpression.nameUsed(name: String, except: KtNameReferenceExpression? = null): Boolean =
  203     anyDescendantOfType<KtNameReferenceExpression> { it != except && it.getReferencedName() == name }
  204 
  205 private fun KtFunctionLiteral.valueParameterReferences(callExpression: KtCallExpression): List<KtNameReferenceExpression> {
  206     val context = analyze(BodyResolveMode.PARTIAL)
  207     val parameterDescriptor = context[BindingContext.FUNCTION, this]?.valueParameters?.singleOrNull() ?: return emptyList()
  208     val variableDescriptorByName = if (parameterDescriptor is ValueParameterDescriptorImpl.WithDestructuringDeclaration)
  209         parameterDescriptor.destructuringVariables.associateBy { it.name }
  210     else
  211         mapOf(parameterDescriptor.name to parameterDescriptor)
  212 
  213     val callee = (callExpression.calleeExpression as? KtNameReferenceExpression)?.let {
  214         val descriptor = variableDescriptorByName[it.getReferencedNameAsName()]
  215         if (descriptor != null && it.getReferenceTargets(context).singleOrNull() == descriptor) listOf(it) else null
  216     } ?: emptyList()
  217     return callee + callExpression.valueArguments.flatMap { arg ->
  218         arg.collectDescendantsOfType<KtNameReferenceExpression>().filter {
  219             val descriptor = variableDescriptorByName[it.getReferencedNameAsName()]
  220             descriptor != null && it.getResolvedCall(context)?.resultingDescriptor == descriptor
  221         }
  222     }
  223 }
  224 
  225 private fun isSingleLine(element: KtCallExpression): Boolean {
  226     val qualifiedExpression = element.getQualifiedExpressionForSelector() ?: return true
  227     var receiver = qualifiedExpression.receiverExpression
  228     if (receiver.lineCount() > 1) return false
  229     var count = 1
  230     while (true) {
  231         if (count > 2) return false
  232         receiver = (receiver  as? KtQualifiedExpression)?.receiverExpression ?: break
  233         count++
  234     }
  235     return true
  236 }