Inspection for scope functions conversion
#KT-17047 Fixed
This commit is contained in:
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.psi
|
||||
@@ -85,6 +74,9 @@ class KtPsiFactory @JvmOverloads constructor(private val project: Project, val m
|
||||
fun createThisExpression() =
|
||||
(createExpression("this.x") as KtQualifiedExpression).receiverExpression as KtThisExpression
|
||||
|
||||
fun createThisExpression(qualifier: String) =
|
||||
(createExpression("this@$qualifier.x") as KtQualifiedExpression).receiverExpression as KtThisExpression
|
||||
|
||||
fun createCallArguments(text: String): KtValueArgumentList {
|
||||
val property = createProperty("val x = foo $text")
|
||||
return (property.initializer as KtCallExpression).valueArgumentList!!
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
Provides actions for converting scope functions ('let', 'run', 'apply', 'also') between each other.
|
||||
</body>
|
||||
</html>
|
||||
@@ -2644,6 +2644,15 @@
|
||||
language="kotlin"
|
||||
/>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.ScopeFunctionConversionInspection"
|
||||
displayName="Scope function can be converted to another one"
|
||||
groupPath="Kotlin"
|
||||
groupName="Style issues"
|
||||
enabledByDefault="true"
|
||||
level="INFORMATION"
|
||||
language="kotlin"
|
||||
/>
|
||||
|
||||
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
||||
|
||||
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
||||
|
||||
@@ -0,0 +1,364 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.inspections
|
||||
|
||||
import com.intellij.codeInspection.*
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.psi.SmartPsiElementPointer
|
||||
import com.intellij.refactoring.rename.inplace.VariableInplaceRenameHandler
|
||||
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
|
||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.refactoring.getThisLabelName
|
||||
import org.jetbrains.kotlin.idea.references.resolveMainReferenceToDescriptors
|
||||
import org.jetbrains.kotlin.idea.util.getReceiverTargetDescriptor
|
||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getOrCreateParameterList
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.BindingContext.FUNCTION
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
|
||||
import org.jetbrains.kotlin.resolve.scopes.utils.collectDescriptorsFiltered
|
||||
import org.jetbrains.kotlin.resolve.scopes.utils.findVariable
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
private val counterpartNames = mapOf(
|
||||
"apply" to "also",
|
||||
"run" to "let",
|
||||
"also" to "apply",
|
||||
"let" to "run"
|
||||
)
|
||||
|
||||
class ScopeFunctionConversionInspection : AbstractKotlinInspection() {
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
|
||||
return callExpressionVisitor { expression ->
|
||||
val counterpartName = getCounterpart(expression)
|
||||
if (counterpartName != null) {
|
||||
holder.registerProblem(
|
||||
expression.calleeExpression!!,
|
||||
"Call can be replaced with another scope function",
|
||||
ProblemHighlightType.INFORMATION,
|
||||
if (counterpartName == "also" || counterpartName == "let")
|
||||
ConvertScopeFunctionToParameter(counterpartName)
|
||||
else
|
||||
ConvertScopeFunctionToReceiver(counterpartName)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCounterpart(expression: KtCallExpression): String? {
|
||||
val callee = expression.calleeExpression as? KtNameReferenceExpression ?: return null
|
||||
val calleeName = callee.getReferencedName()
|
||||
val counterpartName = counterpartNames[calleeName]
|
||||
val lambdaArgument = expression.lambdaArguments.singleOrNull()
|
||||
if (counterpartName != null && lambdaArgument != null) {
|
||||
if (lambdaArgument.getLambdaExpression().valueParameters.isNotEmpty()) {
|
||||
return null
|
||||
}
|
||||
val bindingContext = callee.analyze(BodyResolveMode.PARTIAL)
|
||||
val resolvedCall = callee.getResolvedCall(bindingContext) ?: return null
|
||||
if (resolvedCall.resultingDescriptor.fqNameSafe.asString() == "kotlin.$calleeName" &&
|
||||
nameResolvesToStdlib(expression, bindingContext, counterpartName)
|
||||
) {
|
||||
return counterpartName
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun nameResolvesToStdlib(expression: KtCallExpression, bindingContext: BindingContext, name: String): Boolean {
|
||||
val scope = expression.getResolutionScope(bindingContext) ?: return true
|
||||
val descriptors = scope.collectDescriptorsFiltered(nameFilter = { it.asString() == name })
|
||||
return descriptors.isNotEmpty() && descriptors.all { it.fqNameSafe.asString() == "kotlin.$name" }
|
||||
}
|
||||
|
||||
class Replacement<T : PsiElement> private constructor(
|
||||
private val elementPointer: SmartPsiElementPointer<T>,
|
||||
private val replacementFactory: KtPsiFactory.(T) -> PsiElement
|
||||
) {
|
||||
companion object {
|
||||
fun <T : PsiElement> create(element: T, replacementFactory: KtPsiFactory.(T) -> PsiElement): Replacement<T> {
|
||||
return Replacement(element.createSmartPointer(), replacementFactory)
|
||||
}
|
||||
}
|
||||
|
||||
fun apply(factory: KtPsiFactory) {
|
||||
elementPointer.element?.let {
|
||||
it.replace(factory.replacementFactory(it))
|
||||
}
|
||||
}
|
||||
|
||||
val endOffset
|
||||
get() = elementPointer.element!!.endOffset
|
||||
}
|
||||
|
||||
class ReplacementCollection {
|
||||
private lateinit var project: Project
|
||||
private val replacements = mutableListOf<Replacement<out PsiElement>>()
|
||||
var createParameter: KtPsiFactory.() -> PsiElement? = { null }
|
||||
var elementToRename: PsiElement? = null
|
||||
|
||||
fun <T : PsiElement> add(element: T, replacementFactory: KtPsiFactory.(T) -> PsiElement) {
|
||||
project = element.project
|
||||
replacements.add(Replacement.create(element, replacementFactory))
|
||||
}
|
||||
|
||||
fun apply() {
|
||||
if (replacements.isNotEmpty()) {
|
||||
val factory = KtPsiFactory(project)
|
||||
elementToRename = factory.createParameter()
|
||||
|
||||
// Calls need to be processed in outside-in order
|
||||
replacements.sortBy { it.endOffset }
|
||||
|
||||
for (replacement in replacements) {
|
||||
replacement.apply(factory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isNotEmpty() = replacements.isNotEmpty()
|
||||
}
|
||||
|
||||
abstract class ConvertScopeFunctionFix(private val counterpartName: String) : LocalQuickFix {
|
||||
override fun getFamilyName() = "Convert to '$counterpartName'"
|
||||
|
||||
override fun applyFix(project: Project, problemDescriptor: ProblemDescriptor) {
|
||||
val callee = problemDescriptor.psiElement as KtNameReferenceExpression
|
||||
val callExpression = callee.parent as? KtCallExpression ?: return
|
||||
val bindingContext = callExpression.analyze()
|
||||
|
||||
val lambda = callExpression.lambdaArguments.firstOrNull() ?: return
|
||||
val functionLiteral = lambda.getLambdaExpression().functionLiteral
|
||||
val lambdaDescriptor = bindingContext[FUNCTION, functionLiteral] ?: return
|
||||
|
||||
val replacements = ReplacementCollection()
|
||||
analyzeLambda(bindingContext, lambda, lambdaDescriptor, replacements)
|
||||
callee.replace(KtPsiFactory(project).createExpression(counterpartName) as KtNameReferenceExpression)
|
||||
if (replacements.isNotEmpty()) {
|
||||
replacements.apply()
|
||||
}
|
||||
postprocessLambda(lambda)
|
||||
|
||||
if (replacements.isNotEmpty() && replacements.elementToRename != null && !ApplicationManager.getApplication().isUnitTestMode) {
|
||||
replacements.elementToRename!!.startInPlaceRename()
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun postprocessLambda(lambda: KtLambdaArgument)
|
||||
|
||||
protected abstract fun analyzeLambda(
|
||||
bindingContext: BindingContext,
|
||||
lambda: KtLambdaArgument,
|
||||
lambdaDescriptor: SimpleFunctionDescriptor,
|
||||
replacements: ReplacementCollection
|
||||
)
|
||||
}
|
||||
|
||||
class ConvertScopeFunctionToParameter(counterpartName: String) : ConvertScopeFunctionFix(counterpartName) {
|
||||
override fun analyzeLambda(
|
||||
bindingContext: BindingContext,
|
||||
lambda: KtLambdaArgument,
|
||||
lambdaDescriptor: SimpleFunctionDescriptor,
|
||||
replacements: ReplacementCollection
|
||||
) {
|
||||
val project = lambda.project
|
||||
val factory = KtPsiFactory(project)
|
||||
val functionLiteral = lambda.getLambdaExpression().functionLiteral
|
||||
val lambdaExtensionReceiver = lambdaDescriptor.extensionReceiverParameter
|
||||
val lambdaDispatchReceiver = lambdaDescriptor.dispatchReceiverParameter
|
||||
|
||||
var parameterName = "it"
|
||||
val scopes = mutableSetOf<LexicalScope>()
|
||||
if (needUniqueNameForParameter(lambda, scopes)) {
|
||||
val parameterType = lambdaExtensionReceiver?.type ?: lambdaDispatchReceiver?.type
|
||||
parameterName = findUniqueParameterName(parameterType, scopes)
|
||||
replacements.createParameter = {
|
||||
val lambdaParameterList = functionLiteral.getOrCreateParameterList()
|
||||
val parameterToAdd = createLambdaParameterList(parameterName).parameters.first()
|
||||
lambdaParameterList.addParameterBefore(parameterToAdd, lambdaParameterList.parameters.firstOrNull())
|
||||
}
|
||||
}
|
||||
|
||||
lambda.accept(object : KtTreeVisitorVoid() {
|
||||
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
|
||||
super.visitSimpleNameExpression(expression)
|
||||
val resolvedCall = expression.getResolvedCall(bindingContext) ?: return
|
||||
val dispatchReceiverTarget = resolvedCall.dispatchReceiver?.getReceiverTargetDescriptor(bindingContext)
|
||||
val extensionReceiverTarget = resolvedCall.extensionReceiver?.getReceiverTargetDescriptor(bindingContext)
|
||||
if (dispatchReceiverTarget == lambdaDescriptor || extensionReceiverTarget == lambdaDescriptor) {
|
||||
val parent = expression.parent
|
||||
if (parent is KtCallExpression && expression == parent.calleeExpression) {
|
||||
replacements.add(parent) { element ->
|
||||
factory.createExpressionByPattern("$0.$1", parameterName, element)
|
||||
}
|
||||
} else if (parent is KtQualifiedExpression && parent.receiverExpression is KtThisExpression) {
|
||||
// do nothing
|
||||
} else {
|
||||
val referencedName = expression.getReferencedName()
|
||||
replacements.add(expression) {
|
||||
createExpression("$parameterName.$referencedName")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitThisExpression(expression: KtThisExpression) {
|
||||
val resolvedCall = expression.getResolvedCall(bindingContext) ?: return
|
||||
if (resolvedCall.resultingDescriptor == lambdaDispatchReceiver ||
|
||||
resolvedCall.resultingDescriptor == lambdaExtensionReceiver) {
|
||||
replacements.add(expression) { createExpression(parameterName) }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun postprocessLambda(lambda: KtLambdaArgument) {
|
||||
ShortenReferences { ShortenReferences.Options(removeThisLabels = true) }.process(lambda) { element ->
|
||||
if (element is KtThisExpression && element.getLabelName() != null)
|
||||
ShortenReferences.FilterResult.PROCESS
|
||||
else
|
||||
ShortenReferences.FilterResult.GO_INSIDE
|
||||
}
|
||||
}
|
||||
|
||||
private fun needUniqueNameForParameter(
|
||||
lambdaArgument: KtLambdaArgument,
|
||||
scopes: MutableSet<LexicalScope>
|
||||
): Boolean {
|
||||
val resolutionScope = lambdaArgument.getResolutionScope()
|
||||
scopes.add(resolutionScope)
|
||||
var needUniqueName = false
|
||||
if (resolutionScope.findVariable(Name.identifier("it"), NoLookupLocation.FROM_IDE) != null) {
|
||||
needUniqueName = true
|
||||
// Don't return here - we still need to gather the list of nested scopes
|
||||
}
|
||||
|
||||
lambdaArgument.accept(object : KtTreeVisitorVoid() {
|
||||
override fun visitDeclaration(dcl: KtDeclaration) {
|
||||
super.visitDeclaration(dcl)
|
||||
checkNeedUniqueName(dcl)
|
||||
}
|
||||
|
||||
override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
|
||||
super.visitLambdaExpression(lambdaExpression)
|
||||
lambdaExpression.bodyExpression?.statements?.firstOrNull()?.let { checkNeedUniqueName(it) }
|
||||
}
|
||||
|
||||
private fun checkNeedUniqueName(dcl: KtElement) {
|
||||
val nestedResolutionScope = dcl.getResolutionScope()
|
||||
scopes.add(nestedResolutionScope)
|
||||
if (nestedResolutionScope.findVariable(Name.identifier("it"), NoLookupLocation.FROM_IDE) != null) {
|
||||
needUniqueName = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return needUniqueName
|
||||
}
|
||||
|
||||
|
||||
private fun findUniqueParameterName(
|
||||
parameterType: KotlinType?,
|
||||
resolutionScopes: Collection<LexicalScope>
|
||||
): String {
|
||||
fun isNameUnique(parameterName: String): Boolean {
|
||||
return resolutionScopes.none { it.findVariable(Name.identifier(parameterName), NoLookupLocation.FROM_IDE) != null }
|
||||
}
|
||||
|
||||
return if (parameterType != null)
|
||||
KotlinNameSuggester.suggestNamesByType(parameterType, ::isNameUnique).first()
|
||||
else {
|
||||
KotlinNameSuggester.suggestNameByName("p", ::isNameUnique)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ConvertScopeFunctionToReceiver(counterpartName: String) : ConvertScopeFunctionFix(counterpartName) {
|
||||
override fun analyzeLambda(
|
||||
bindingContext: BindingContext,
|
||||
lambda: KtLambdaArgument,
|
||||
lambdaDescriptor: SimpleFunctionDescriptor,
|
||||
replacements: ReplacementCollection
|
||||
) {
|
||||
lambda.accept(object : KtTreeVisitorVoid() {
|
||||
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
|
||||
super.visitSimpleNameExpression(expression)
|
||||
if (expression.getReferencedName() == "it") {
|
||||
val result = expression.resolveMainReferenceToDescriptors().singleOrNull()
|
||||
if (result is ValueParameterDescriptor && result.containingDeclaration == lambdaDescriptor) {
|
||||
replacements.add(expression) { createThisExpression() }
|
||||
}
|
||||
} else {
|
||||
val resolvedCall = expression.getResolvedCall(bindingContext) ?: return
|
||||
val dispatchReceiver = resolvedCall.dispatchReceiver
|
||||
if (dispatchReceiver is ImplicitReceiver) {
|
||||
val parent = expression.parent
|
||||
val thisLabelName = dispatchReceiver.declarationDescriptor.getThisLabelName()
|
||||
if (parent is KtCallExpression && expression == parent.calleeExpression) {
|
||||
replacements.add(parent) { element ->
|
||||
createExpressionByPattern("this@$0.$1", thisLabelName, element)
|
||||
}
|
||||
} else {
|
||||
val referencedName = expression.getReferencedName()
|
||||
replacements.add(expression) {
|
||||
createExpression("this@$thisLabelName.$referencedName")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitThisExpression(expression: KtThisExpression) {
|
||||
val resolvedCall = expression.getResolvedCall(bindingContext) ?: return
|
||||
val qualifierName = resolvedCall.resultingDescriptor.containingDeclaration.name
|
||||
replacements.add(expression) { createThisExpression(qualifierName.asString()) }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun postprocessLambda(lambda: KtLambdaArgument) {
|
||||
ShortenReferences { ShortenReferences.Options(removeThis = true, removeThisLabels = true) }.process(lambda) { element ->
|
||||
if (element is KtThisExpression && element.getLabelName() != null)
|
||||
ShortenReferences.FilterResult.PROCESS
|
||||
else if (element is KtQualifiedExpression && element.receiverExpression is KtThisExpression)
|
||||
ShortenReferences.FilterResult.PROCESS
|
||||
else
|
||||
ShortenReferences.FilterResult.GO_INSIDE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun PsiElement.startInPlaceRename() {
|
||||
val project = project
|
||||
val document = containingFile.viewProvider.document ?: return
|
||||
val editor = FileEditorManager.getInstance(project).selectedTextEditor ?: return
|
||||
if (editor.document == document) {
|
||||
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.document)
|
||||
|
||||
editor.caretModel.moveToOffset(startOffset)
|
||||
VariableInplaceRenameHandler().doRename(this, editor, null)
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring
|
||||
@@ -835,6 +824,7 @@ internal fun DeclarationDescriptor.getThisLabelName(): String {
|
||||
if (this is AnonymousFunctionDescriptor) {
|
||||
val function = source.getPsi() as? KtFunction
|
||||
val argument = function?.parent as? KtValueArgument
|
||||
?: (function?.parent as? KtLambdaExpression)?.parent as? KtValueArgument
|
||||
val callElement = argument?.getStrictParentOfType<KtCallElement>()
|
||||
val callee = callElement?.calleeExpression as? KtSimpleNameExpression
|
||||
if (callee != null) return callee.text
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.inspections.ScopeFunctionConversionInspection
|
||||
@@ -0,0 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
val x = "".<caret>also {
|
||||
it.length
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
val x = "".<caret>apply {
|
||||
length
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
val x = hashSetOf("abc").<caret>apply {
|
||||
forEach {
|
||||
forEach {
|
||||
println(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
val x = hashSetOf("abc").also { hashSet ->
|
||||
hashSet.forEach {
|
||||
hashSet.forEach {
|
||||
println(hashSet)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Convert to 'also'
|
||||
|
||||
val x = "".also {
|
||||
"".<caret>apply {
|
||||
this.length + it.length
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Convert to 'also'
|
||||
|
||||
val x = "".also {
|
||||
"".also { s ->
|
||||
s.length + it.length
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
val x = hashSetOf("abc").<caret>apply {
|
||||
forEach {
|
||||
println(this)
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
val x = hashSetOf("abc").also { hashSet ->
|
||||
hashSet.forEach {
|
||||
println(hashSet)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Convert to 'also'
|
||||
|
||||
val x = hashSetOf<String>().<caret>apply {
|
||||
add("x")
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Convert to 'also'
|
||||
|
||||
val x = hashSetOf<String>().<caret>also {
|
||||
it.add("x")
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Convert to 'also'
|
||||
|
||||
val x = "".<caret>apply {
|
||||
"".apply {
|
||||
this.length
|
||||
length
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Convert to 'also'
|
||||
|
||||
val x = "".<caret>also {
|
||||
"".apply {
|
||||
this.length
|
||||
length
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Convert to 'also'
|
||||
|
||||
val x = "".<caret>apply {
|
||||
this.length
|
||||
length
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Convert to 'also'
|
||||
|
||||
val x = "".<caret>also {
|
||||
it.length
|
||||
it.length
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Convert to 'also'
|
||||
|
||||
class C {
|
||||
val x = hashSetOf<C>().<caret>apply {
|
||||
add(this@C)
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Convert to 'also'
|
||||
|
||||
class C {
|
||||
val x = hashSetOf<C>().<caret>also {
|
||||
it.add(this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
val x = hashSetOf("abc").<caret>let {
|
||||
it.forEach {
|
||||
println(it)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
val x = hashSetOf("abc").<caret>run {
|
||||
forEach {
|
||||
println(it)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
class C {
|
||||
val c = "abc".<caret>let {
|
||||
println(it + this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
class C {
|
||||
val c = "abc".<caret>run {
|
||||
println(this + this@C)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
class C {
|
||||
fun foo() = "c"
|
||||
val bar = "C"
|
||||
}
|
||||
|
||||
class D {
|
||||
fun foo() = "d"
|
||||
val bar = "D"
|
||||
|
||||
val x = C().<caret>let {
|
||||
foo() + it.foo() + bar + it.bar
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
class C {
|
||||
fun foo() = "c"
|
||||
val bar = "C"
|
||||
}
|
||||
|
||||
class D {
|
||||
fun foo() = "d"
|
||||
val bar = "D"
|
||||
|
||||
val x = C().<caret>run {
|
||||
this@D.foo() + foo() + this@D.bar + bar
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
class C {
|
||||
fun foo(s: String, s2: String = ""): String = "c"
|
||||
val bar = "C"
|
||||
}
|
||||
|
||||
class D {
|
||||
fun baz(s: String): String = "d"
|
||||
val quux = "D"
|
||||
|
||||
val x = C().<caret>let {
|
||||
it.foo(baz(it.foo(quux + it.bar)), baz(it.bar))
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
class C {
|
||||
fun foo(s: String, s2: String = ""): String = "c"
|
||||
val bar = "C"
|
||||
}
|
||||
|
||||
class D {
|
||||
fun baz(s: String): String = "d"
|
||||
val quux = "D"
|
||||
|
||||
val x = C().<caret>run {
|
||||
foo(baz(foo(quux + bar)), baz(bar))
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
class C {
|
||||
fun foo() = "c"
|
||||
val bar = "C"
|
||||
}
|
||||
|
||||
class D {
|
||||
fun foo() = "d"
|
||||
val bar = "D"
|
||||
|
||||
}
|
||||
|
||||
val d = D()
|
||||
val x = d.apply {
|
||||
C().<caret>let {
|
||||
foo() + it.foo() + bar + it.bar
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
class C {
|
||||
fun foo() = "c"
|
||||
val bar = "C"
|
||||
}
|
||||
|
||||
class D {
|
||||
fun foo() = "d"
|
||||
val bar = "D"
|
||||
|
||||
}
|
||||
|
||||
val d = D()
|
||||
val x = d.apply {
|
||||
C().<caret>run {
|
||||
this@apply.foo() + foo() + this@apply.bar + bar
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
class C {
|
||||
fun foo() = "c"
|
||||
val bar = "C"
|
||||
}
|
||||
|
||||
class D {
|
||||
fun baz() = "d"
|
||||
val quux = "D"
|
||||
|
||||
}
|
||||
|
||||
val d = D()
|
||||
val x = d.apply {
|
||||
C().<caret>let {
|
||||
baz() + it.foo() + quux + it.bar
|
||||
}
|
||||
}
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
class C {
|
||||
fun foo() = "c"
|
||||
val bar = "C"
|
||||
}
|
||||
|
||||
class D {
|
||||
fun baz() = "d"
|
||||
val quux = "D"
|
||||
|
||||
}
|
||||
|
||||
val d = D()
|
||||
val x = d.apply {
|
||||
C().<caret>run {
|
||||
baz() + foo() + quux + bar
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Convert to 'run'
|
||||
|
||||
val x = "".<caret>let {
|
||||
it.length
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Convert to 'run'
|
||||
|
||||
val x = "".<caret>run {
|
||||
length
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
class C {
|
||||
val c = "abc".<caret>let {
|
||||
println("$it")
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
class C {
|
||||
val c = "abc".<caret>run {
|
||||
println("$this")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Convert to 'let'
|
||||
|
||||
val x = "".<caret>run {
|
||||
this.length
|
||||
length
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
// FIX: Convert to 'let'
|
||||
|
||||
val x = "".<caret>let {
|
||||
it.length
|
||||
it.length
|
||||
}
|
||||
+147
@@ -3421,6 +3421,153 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/inspectionsLocal/scopeFunctions")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class ScopeFunctions extends AbstractLocalInspectionTest {
|
||||
public void testAllFilesPresentInScopeFunctions() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/scopeFunctions"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/inspectionsLocal/scopeFunctions/alsoToApply")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class AlsoToApply extends AbstractLocalInspectionTest {
|
||||
public void testAllFilesPresentInAlsoToApply() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/scopeFunctions/alsoToApply"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/alsoToApply/simple.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/inspectionsLocal/scopeFunctions/applyToAlso")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class ApplyToAlso extends AbstractLocalInspectionTest {
|
||||
public void testAllFilesPresentInApplyToAlso() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/scopeFunctions/applyToAlso"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("doubleNestedLambdas.kt")
|
||||
public void testDoubleNestedLambdas() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/doubleNestedLambdas.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("innerLambda.kt")
|
||||
public void testInnerLambda() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/innerLambda.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("itInNestedLambda.kt")
|
||||
public void testItInNestedLambda() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/itInNestedLambda.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("method.kt")
|
||||
public void testMethod() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/method.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("outerLambda.kt")
|
||||
public void testOuterLambda() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/outerLambda.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/simple.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("thisQualifier.kt")
|
||||
public void testThisQualifier() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/applyToAlso/thisQualifier.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/inspectionsLocal/scopeFunctions/letToRun")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class LetToRun extends AbstractLocalInspectionTest {
|
||||
public void testAllFilesPresentInLetToRun() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/scopeFunctions/letToRun"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("nestedLambda.kt")
|
||||
public void testNestedLambda() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/letToRun/nestedLambda.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("outerThis.kt")
|
||||
public void testOuterThis() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/letToRun/outerThis.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("qualifyThis.kt")
|
||||
public void testQualifyThis() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThis.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("qualifyThisNoConflict.kt")
|
||||
public void testQualifyThisNoConflict() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisNoConflict.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("qualifyThisWithLambda.kt")
|
||||
public void testQualifyThisWithLambda() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisWithLambda.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("qualifyThisWithLambdaNoConflict.kt")
|
||||
public void testQualifyThisWithLambdaNoConflict() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/letToRun/qualifyThisWithLambdaNoConflict.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/letToRun/simple.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("thisInStringTemplate.kt")
|
||||
public void testThisInStringTemplate() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/letToRun/thisInStringTemplate.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/inspectionsLocal/scopeFunctions/runToLet")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class RunToLet extends AbstractLocalInspectionTest {
|
||||
public void testAllFilesPresentInRunToLet() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/scopeFunctions/runToLet"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/scopeFunctions/runToLet/simple.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/inspectionsLocal/selfAssignment")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user