Add quickfix for ASSIGN_OPERATOR_AMBIGUITY on mutable collection '+=', '-='
KT-26236 Fixed
This commit is contained in:
committed by
Natalia Selezneva
parent
725cb88f41
commit
b0c3461eab
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2010-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.quickfix
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
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.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
object AssignOperatorAmbiguityFactory : KotlinIntentionActionsFactory() {
|
||||
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
|
||||
val fixes = mutableListOf<IntentionAction>()
|
||||
val element = diagnostic.psiElement.parent
|
||||
if (element is KtBinaryExpression) {
|
||||
val left = element.left
|
||||
val right = element.right
|
||||
val operationText = when (element.operationToken) {
|
||||
KtTokens.PLUSEQ -> "plus"
|
||||
KtTokens.MINUSEQ -> "minus"
|
||||
else -> null
|
||||
}
|
||||
if (left != null && right != null && operationText != null) {
|
||||
val context = element.analyze(BodyResolveMode.PARTIAL)
|
||||
if (left.getType(context).isMutableCollection()) {
|
||||
val property = left.mainReference?.resolve() as? KtProperty
|
||||
val propertyName = property?.name
|
||||
if (property != null && propertyName != null && property.isLocal) {
|
||||
fixes.add(ChangeVariableMutabilityFix(property, false, "Change '$propertyName' to val"))
|
||||
}
|
||||
fixes.add(ReplaceWithAssignFunctionCallFix(element, operationText))
|
||||
}
|
||||
}
|
||||
}
|
||||
return fixes
|
||||
}
|
||||
}
|
||||
|
||||
private fun KotlinType?.isMutableCollection(): Boolean {
|
||||
if (this == null) return false
|
||||
return JavaToKotlinClassMap.isMutable(this) || constructor.supertypes.reversed().any { JavaToKotlinClassMap.isMutable(it) }
|
||||
}
|
||||
|
||||
private class ReplaceWithAssignFunctionCallFix(
|
||||
element: KtBinaryExpression,
|
||||
private val operationText: String
|
||||
) : KotlinQuickFixAction<KtBinaryExpression>(element) {
|
||||
override fun getText() = "Replace with '${operationText}Assign()' call"
|
||||
|
||||
override fun getFamilyName() = text
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||
val binaryExpression = element ?: return
|
||||
val left = binaryExpression.left ?: return
|
||||
val right = binaryExpression.right ?: return
|
||||
val replaced = binaryExpression.replace(
|
||||
KtPsiFactory(binaryExpression).createExpressionByPattern("$0.${operationText}Assign($1)", left, right)
|
||||
)
|
||||
editor?.caretModel?.moveToOffset(replaced.endOffset)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -578,5 +578,7 @@ class QuickFixRegistrar : QuickFixContributor {
|
||||
NOTHING_TO_INLINE.registerFactory(RemoveModifierFix.createRemoveModifierFactory(isRedundant = false))
|
||||
|
||||
DECLARATION_CANT_BE_INLINED.registerFactory(DeclarationCantBeInlinedFactory)
|
||||
|
||||
ASSIGN_OPERATOR_AMBIGUITY.registerFactory(AssignOperatorAmbiguityFactory)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Change 'list' to val" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
var list = ArrayList<Int>()
|
||||
list <caret>-= 2
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Change 'list' to val" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
val list = ArrayList<Int>()
|
||||
list <caret>-= 2
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// "Change 'list' to val" "false"
|
||||
// ACTION: Replace overloaded operator with function call
|
||||
// ACTION: Replace with 'plusAssign()' call
|
||||
// ACTION: Replace with ordinary assignment
|
||||
// ERROR: Assignment operators ambiguity: <br>public operator fun <T> Collection<Int>.plus(element: Int): List<Int> defined in kotlin.collections<br>@InlineOnly public inline operator fun <T> MutableCollection<in Int>.plusAssign(element: Int): Unit defined in kotlin.collections
|
||||
// WITH_RUNTIME
|
||||
|
||||
class Test {
|
||||
var list = mutableListOf(1)
|
||||
|
||||
fun test() {
|
||||
list <caret>+= 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Change 'set' to val" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
var set = HashMap<Int, Int>()
|
||||
set <caret>+= 2 to 2
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Change 'set' to val" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
val set = HashMap<Int, Int>()
|
||||
set <caret>+= 2 to 2
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Change 'set' to val" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
var set = HashSet<Int>()
|
||||
set <caret>-= 2
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Change 'set' to val" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
val set = HashSet<Int>()
|
||||
set <caret>-= 2
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Change 'list' to val" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
var list = mutableListOf(1)
|
||||
list <caret>+= 2
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Change 'list' to val" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
val list = mutableListOf(1)
|
||||
list <caret>+= 2
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Change 'map' to val" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
var map = mutableMapOf(1 to 1)
|
||||
map <caret>+= 2 to 2
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Change 'map' to val" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
val map = mutableMapOf(1 to 1)
|
||||
map <caret>+= 2 to 2
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Change 'set' to val" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
var set = mutableSetOf(1)
|
||||
set <caret>+= 2
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Change 'set' to val" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
val set = mutableSetOf(1)
|
||||
set <caret>+= 2
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Replace with 'minusAssign()' call" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
var list = ArrayList<Int>()
|
||||
list <caret>-= 2
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Replace with 'minusAssign()' call" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
var list = ArrayList<Int>()
|
||||
list.minusAssign(2)<caret>
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Replace with 'plusAssign()' call" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
var set = HashMap<Int, Int>()
|
||||
set <caret>+= 2 to 2
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Replace with 'plusAssign()' call" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
var set = HashMap<Int, Int>()
|
||||
set.plusAssign(2 to 2)
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Replace with 'minusAssign()' call" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
var set = HashSet<Int>()
|
||||
set <caret>-= 2
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Replace with 'minusAssign()' call" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
var set = HashSet<Int>()
|
||||
set.minusAssign(2)<caret>
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Replace with 'plusAssign()' call" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
var list = mutableListOf(1)
|
||||
list <caret>+= 2
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Replace with 'plusAssign()' call" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
var list = mutableListOf(1)
|
||||
list.plusAssign(2)<caret>
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Replace with 'plusAssign()' call" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
var map = mutableMapOf(1 to 1)
|
||||
map <caret>+= 2 to 2
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Replace with 'plusAssign()' call" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
var map = mutableMapOf(1 to 1)
|
||||
map.plusAssign(2 to 2)<caret>
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Replace with 'plusAssign()' call" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
var set = mutableSetOf(1)
|
||||
set <caret>+= 2
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Replace with 'plusAssign()' call" "true"
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun test() {
|
||||
var set = mutableSetOf(1)
|
||||
set.plusAssign(2)<caret>
|
||||
}
|
||||
+39
@@ -364,6 +364,45 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/assignOperatorAmbiguity")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class AssignOperatorAmbiguity extends AbstractQuickFixMultiFileTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTestWithExtraFile, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInAssignOperatorAmbiguity() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/assignOperatorAmbiguity"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/assignOperatorAmbiguity/changeToVal")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class ChangeToVal extends AbstractQuickFixMultiFileTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTestWithExtraFile, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInChangeToVal() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/assignOperatorAmbiguity/changeToVal"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/assignOperatorAmbiguity/replaceWithAssignCall")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class ReplaceWithAssignCall extends AbstractQuickFixMultiFileTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTestWithExtraFile, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInReplaceWithAssignCall() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/assignOperatorAmbiguity/replaceWithAssignCall"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/assignToProperty")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
@@ -1224,6 +1224,110 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/assignOperatorAmbiguity")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class AssignOperatorAmbiguity extends AbstractQuickFixTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInAssignOperatorAmbiguity() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/assignOperatorAmbiguity"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/assignOperatorAmbiguity/changeToVal")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class ChangeToVal extends AbstractQuickFixTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInChangeToVal() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/assignOperatorAmbiguity/changeToVal"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("arrayList.kt")
|
||||
public void testArrayList() throws Exception {
|
||||
runTest("idea/testData/quickfix/assignOperatorAmbiguity/changeToVal/arrayList.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("classVariable.kt")
|
||||
public void testClassVariable() throws Exception {
|
||||
runTest("idea/testData/quickfix/assignOperatorAmbiguity/changeToVal/classVariable.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("hashMap.kt")
|
||||
public void testHashMap() throws Exception {
|
||||
runTest("idea/testData/quickfix/assignOperatorAmbiguity/changeToVal/hashMap.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("hashSet.kt")
|
||||
public void testHashSet() throws Exception {
|
||||
runTest("idea/testData/quickfix/assignOperatorAmbiguity/changeToVal/hashSet.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("mutableList.kt")
|
||||
public void testMutableList() throws Exception {
|
||||
runTest("idea/testData/quickfix/assignOperatorAmbiguity/changeToVal/mutableList.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("mutableMap.kt")
|
||||
public void testMutableMap() throws Exception {
|
||||
runTest("idea/testData/quickfix/assignOperatorAmbiguity/changeToVal/mutableMap.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("mutableSet.kt")
|
||||
public void testMutableSet() throws Exception {
|
||||
runTest("idea/testData/quickfix/assignOperatorAmbiguity/changeToVal/mutableSet.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/assignOperatorAmbiguity/replaceWithAssignCall")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class ReplaceWithAssignCall extends AbstractQuickFixTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInReplaceWithAssignCall() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/assignOperatorAmbiguity/replaceWithAssignCall"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("arrayList.kt")
|
||||
public void testArrayList() throws Exception {
|
||||
runTest("idea/testData/quickfix/assignOperatorAmbiguity/replaceWithAssignCall/arrayList.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("hashMap.kt")
|
||||
public void testHashMap() throws Exception {
|
||||
runTest("idea/testData/quickfix/assignOperatorAmbiguity/replaceWithAssignCall/hashMap.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("hashSet.kt")
|
||||
public void testHashSet() throws Exception {
|
||||
runTest("idea/testData/quickfix/assignOperatorAmbiguity/replaceWithAssignCall/hashSet.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("mutableList.kt")
|
||||
public void testMutableList() throws Exception {
|
||||
runTest("idea/testData/quickfix/assignOperatorAmbiguity/replaceWithAssignCall/mutableList.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("mutableMap.kt")
|
||||
public void testMutableMap() throws Exception {
|
||||
runTest("idea/testData/quickfix/assignOperatorAmbiguity/replaceWithAssignCall/mutableMap.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("mutableSet.kt")
|
||||
public void testMutableSet() throws Exception {
|
||||
runTest("idea/testData/quickfix/assignOperatorAmbiguity/replaceWithAssignCall/mutableSet.kt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/assignToProperty")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user