Add "Change type to mutable collection" quick fix for NO_SET_METHOD
#KT-29193 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
52c2547d95
commit
4113d06767
+8
-35
@@ -12,15 +12,18 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Severity
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||
import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.intentions.ReplaceWithOrdinaryAssignmentIntention
|
||||
import org.jetbrains.kotlin.idea.project.builtIns
|
||||
import org.jetbrains.kotlin.idea.quickfix.ChangeToMutableCollectionFix
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.lexer.KtSingleValueToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespaceAndComments
|
||||
import org.jetbrains.kotlin.psi.psiUtil.siblings
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
|
||||
@@ -81,44 +84,14 @@ class SuspiciousCollectionReassignmentInspection : AbstractKotlinInspection() {
|
||||
val binaryExpression = operationReference.parent as? KtBinaryExpression ?: return
|
||||
val left = binaryExpression.left ?: return
|
||||
val property = left.mainReference?.resolve() as? KtProperty ?: return
|
||||
val initializer = property.initializer ?: return
|
||||
val fqName = initializer.resolveToCall()?.resultingDescriptor?.fqNameOrNull()?.asString()
|
||||
val psiFactory = KtPsiFactory(binaryExpression)
|
||||
val mutableOf = mutableConversionMap[fqName]
|
||||
if (mutableOf != null) {
|
||||
(initializer as? KtCallExpression)?.calleeExpression?.replaced(psiFactory.createExpression(mutableOf)) ?: return
|
||||
} else {
|
||||
val builtIns = binaryExpression.builtIns
|
||||
val toMutable = when (type.constructor) {
|
||||
builtIns.list.defaultType.constructor -> "toMutableList"
|
||||
builtIns.set.defaultType.constructor -> "toMutableSet"
|
||||
builtIns.map.defaultType.constructor -> "toMutableMap"
|
||||
else -> null
|
||||
} ?: return
|
||||
val dotQualifiedExpression = initializer.replaced(
|
||||
psiFactory.createExpressionByPattern("($0).$1()", initializer, toMutable)
|
||||
) as KtDotQualifiedExpression
|
||||
val receiver = dotQualifiedExpression.receiverExpression
|
||||
val deparenthesize = KtPsiUtil.deparenthesize(dotQualifiedExpression.receiverExpression)
|
||||
if (deparenthesize != null && receiver != deparenthesize) receiver.replace(deparenthesize)
|
||||
}
|
||||
property.typeReference?.also { it.replace(psiFactory.createType("Mutable${it.text}")) }
|
||||
property.valOrVarKeyword.replace(psiFactory.createValKeyword())
|
||||
ChangeToMutableCollectionFix.applyFix(property, type)
|
||||
property.valOrVarKeyword.replace(KtPsiFactory(property).createValKeyword())
|
||||
binaryExpression.findExistingEditor()?.caretModel?.moveToOffset(property.endOffset)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val COLLECTIONS = "kotlin.collections"
|
||||
|
||||
private val mutableConversionMap = mapOf(
|
||||
"$COLLECTIONS.listOf" to "mutableListOf",
|
||||
"$COLLECTIONS.setOf" to "mutableSetOf",
|
||||
"$COLLECTIONS.mapOf" to "mutableMapOf"
|
||||
)
|
||||
|
||||
fun isApplicable(property: KtProperty): Boolean {
|
||||
return property.isLocal && property.initializer != null
|
||||
return ChangeToMutableCollectionFix.isApplicable(property)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.quickfix
|
||||
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||
import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.project.builtIns
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
class ChangeToMutableCollectionFix(property: KtProperty, private val type: String) : KotlinQuickFixAction<KtProperty>(property) {
|
||||
|
||||
override fun getText() = "Change type to Mutable$type"
|
||||
|
||||
override fun getFamilyName() = text
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||
val property = element ?: return
|
||||
val context = property.analyze(BodyResolveMode.PARTIAL)
|
||||
val type = property.initializer?.getType(context) ?: return
|
||||
applyFix(property, type)
|
||||
editor?.caretModel?.moveToOffset(property.endOffset)
|
||||
}
|
||||
|
||||
companion object : KotlinSingleIntentionActionFactory() {
|
||||
override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction<KtProperty>? {
|
||||
val element = Errors.NO_SET_METHOD.cast(diagnostic).psiElement as? KtArrayAccessExpression ?: return null
|
||||
val arrayExpr = element.arrayExpression ?: return null
|
||||
val context = arrayExpr.analyze(BodyResolveMode.PARTIAL)
|
||||
val type = arrayExpr.getType(context) ?: return null
|
||||
if (!type.isReadOnlyListOrMap(element.builtIns)) return null
|
||||
val property = arrayExpr.mainReference?.resolve() as? KtProperty ?: return null
|
||||
if (!isApplicable(property)) return null
|
||||
val typeName = type.constructor.declarationDescriptor?.name?.asString() ?: return null
|
||||
return ChangeToMutableCollectionFix(property, typeName)
|
||||
}
|
||||
|
||||
private fun KotlinType.isReadOnlyListOrMap(builtIns: KotlinBuiltIns): Boolean {
|
||||
val leftDefaultType = constructor.declarationDescriptor?.defaultType ?: return false
|
||||
return leftDefaultType in listOf(builtIns.list.defaultType, builtIns.map.defaultType)
|
||||
}
|
||||
|
||||
fun isApplicable(property: KtProperty): Boolean {
|
||||
return property.isLocal && property.initializer != null
|
||||
}
|
||||
|
||||
fun applyFix(property: KtProperty, type: KotlinType) {
|
||||
val initializer = property.initializer ?: return
|
||||
val fqName = initializer.resolveToCall()?.resultingDescriptor?.fqNameOrNull()?.asString()
|
||||
val psiFactory = KtPsiFactory(property)
|
||||
val mutableOf = mutableConversionMap[fqName]
|
||||
if (mutableOf != null) {
|
||||
(initializer as? KtCallExpression)?.calleeExpression?.replaced(psiFactory.createExpression(mutableOf)) ?: return
|
||||
} else {
|
||||
val builtIns = property.builtIns
|
||||
val toMutable = when (type.constructor) {
|
||||
builtIns.list.defaultType.constructor -> "toMutableList"
|
||||
builtIns.set.defaultType.constructor -> "toMutableSet"
|
||||
builtIns.map.defaultType.constructor -> "toMutableMap"
|
||||
else -> null
|
||||
} ?: return
|
||||
val dotQualifiedExpression = initializer.replaced(
|
||||
psiFactory.createExpressionByPattern("($0).$1()", initializer, toMutable)
|
||||
) as KtDotQualifiedExpression
|
||||
val receiver = dotQualifiedExpression.receiverExpression
|
||||
val deparenthesize = KtPsiUtil.deparenthesize(dotQualifiedExpression.receiverExpression)
|
||||
if (deparenthesize != null && receiver != deparenthesize) receiver.replace(deparenthesize)
|
||||
}
|
||||
property.typeReference?.also { it.replace(psiFactory.createType("Mutable${it.text}")) }
|
||||
}
|
||||
|
||||
private const val COLLECTIONS = "kotlin.collections"
|
||||
|
||||
private val mutableConversionMap = mapOf(
|
||||
"$COLLECTIONS.listOf" to "mutableListOf",
|
||||
"$COLLECTIONS.setOf" to "mutableSetOf",
|
||||
"$COLLECTIONS.mapOf" to "mutableMapOf"
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -598,5 +598,7 @@ class QuickFixRegistrar : QuickFixContributor {
|
||||
|
||||
TYPE_MISMATCH.registerFactory(SurroundWithLambdaFix)
|
||||
CONSTANT_EXPECTED_TYPE_MISMATCH.registerFactory(SurroundWithLambdaFix)
|
||||
|
||||
NO_SET_METHOD.registerFactory(ChangeToMutableCollectionFix)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Change type to MutableList" "true"
|
||||
// WITH_RUNTIME
|
||||
fun main() {
|
||||
val list = listOf(1, 2, 3)
|
||||
list[1]<caret> = 10
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Change type to MutableList" "true"
|
||||
// WITH_RUNTIME
|
||||
fun main() {
|
||||
val list = mutableListOf(1, 2, 3)<caret>
|
||||
list[1] = 10
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Change type to MutableList" "true"
|
||||
// WITH_RUNTIME
|
||||
fun main() {
|
||||
val list = foo()
|
||||
list[1]<caret> = 10
|
||||
}
|
||||
|
||||
fun foo() = listOf(1, 2, 3)
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Change type to MutableList" "true"
|
||||
// WITH_RUNTIME
|
||||
fun main() {
|
||||
val list = foo().toMutableList()<caret>
|
||||
list[1] = 10
|
||||
}
|
||||
|
||||
fun foo() = listOf(1, 2, 3)
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Change type to MutableMap" "true"
|
||||
// WITH_RUNTIME
|
||||
fun main() {
|
||||
val map = mapOf(1 to "a")
|
||||
map[2<caret>] = "b"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Change type to MutableMap" "true"
|
||||
// WITH_RUNTIME
|
||||
fun main() {
|
||||
val map = mutableMapOf(1 to "a")<caret>
|
||||
map[2] = "b"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Change type to MutableMap" "true"
|
||||
// WITH_RUNTIME
|
||||
fun main() {
|
||||
val map = foo()
|
||||
map[2<caret>] = "b"
|
||||
}
|
||||
|
||||
fun foo() = mapOf(1 to "a")
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Change type to MutableMap" "true"
|
||||
// WITH_RUNTIME
|
||||
fun main() {
|
||||
val map = foo().toMutableMap()<caret>
|
||||
map[2] = "b"
|
||||
}
|
||||
|
||||
fun foo() = mapOf(1 to "a")
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Change type to MutableSet" "false"
|
||||
// DISABLE-ERRORS
|
||||
// ACTION: Replace overloaded operator with function call
|
||||
// WITH_RUNTIME
|
||||
fun main() {
|
||||
val set = setOf(1, 2, 3)
|
||||
set[1]<caret> = 10
|
||||
}
|
||||
+13
@@ -1252,6 +1252,19 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/changeToMutableCollection")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class ChangeToMutableCollection extends AbstractQuickFixMultiFileTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTestWithExtraFile, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInChangeToMutableCollection() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/changeToMutableCollection"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/changeToUseSpreadOperator")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
@@ -2062,6 +2062,44 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/changeToMutableCollection")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class ChangeToMutableCollection extends AbstractQuickFixTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInChangeToMutableCollection() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/changeToMutableCollection"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("list.kt")
|
||||
public void testList() throws Exception {
|
||||
runTest("idea/testData/quickfix/changeToMutableCollection/list.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("list2.kt")
|
||||
public void testList2() throws Exception {
|
||||
runTest("idea/testData/quickfix/changeToMutableCollection/list2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("map.kt")
|
||||
public void testMap() throws Exception {
|
||||
runTest("idea/testData/quickfix/changeToMutableCollection/map.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("map2.kt")
|
||||
public void testMap2() throws Exception {
|
||||
runTest("idea/testData/quickfix/changeToMutableCollection/map2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("set.kt")
|
||||
public void testSet() throws Exception {
|
||||
runTest("idea/testData/quickfix/changeToMutableCollection/set.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/changeToUseSpreadOperator")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user