Refactoring: "replace with operator assignment" is now an inspection

This commit is contained in:
Mikhail Glukhikh
2017-12-20 20:01:12 +03:00
parent c995f70631
commit 35d85ddd1f
26 changed files with 131 additions and 127 deletions
@@ -1,4 +0,0 @@
fun foo(x: Int) {
var y = 1
y += x
}
@@ -1,4 +0,0 @@
fun foo(x: Int) {
var y = 1
<spot>y = y + x</spot>
}
@@ -1,5 +0,0 @@
<html>
<body>
This intention transforms a modification of a variable with a simple assignment into an operation-assignment.
</body>
</html>
+1 -6
View File
@@ -1072,11 +1072,6 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.ReplaceWithOperatorAssignmentIntention</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.ReplaceWithOrdinaryAssignmentIntention</className>
<category>Kotlin</category>
@@ -1656,7 +1651,7 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ReplaceWithOperatorAssignmentInspection"
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.ReplaceWithOperatorAssignmentInspection"
displayName="Assignment that can be replaced with operator assignment"
groupPath="Kotlin"
groupName="Style issues"
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* 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.
@@ -14,28 +14,34 @@
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.intentions
package org.jetbrains.kotlin.idea.inspections
import com.intellij.codeInspection.LocalInspectionToolSession
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.tree.IElementType
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.analysis.analyzeAsReplacement
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection
import org.jetbrains.kotlin.idea.util.psi.patternMatching.matches
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.doNotAnalyze
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class ReplaceWithOperatorAssignmentInspection : IntentionBasedInspection<KtBinaryExpression>(ReplaceWithOperatorAssignmentIntention::class)
class ReplaceWithOperatorAssignmentInspection : AbstractApplicabilityBasedInspection<KtBinaryExpression>() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): KtVisitorVoid =
object : KtVisitorVoid() {
override fun visitBinaryExpression(expression: KtBinaryExpression) {
super.visitBinaryExpression(expression)
visitTargetElement(expression, holder, isOnTheFly)
}
}
class ReplaceWithOperatorAssignmentIntention : SelfTargetingOffsetIndependentIntention<KtBinaryExpression>(KtBinaryExpression::class.java, "Replace with operator-assignment") {
override fun isApplicableTo(element: KtBinaryExpression): Boolean {
override fun isApplicable(element: KtBinaryExpression): Boolean {
if (element.operationToken != KtTokens.EQ) return false
val left = element.left as? KtNameReferenceExpression ?: return false
val right = element.right as? KtBinaryExpression ?: return false
@@ -51,13 +57,18 @@ class ReplaceWithOperatorAssignmentIntention : SelfTargetingOffsetIndependentInt
return newBindingContext.diagnostics.forElement(opAssign.operationReference).isEmpty()
}
override fun inspectionText(element: KtBinaryExpression) = "Replaceable with operator-assignment"
override val defaultFixText = "Replace with operator-assignment"
override fun fixText(element: KtBinaryExpression) =
"Replace with ${element.operationReference.operationSignTokenType?.value}= expression"
private fun checkExpressionRepeat(variableExpression: KtNameReferenceExpression, expression: KtBinaryExpression, bindingContext: BindingContext): Boolean {
val descriptor = bindingContext[BindingContext.REFERENCE_TARGET, expression.operationReference]?.containingDeclaration
val isPrimitiveOperation = descriptor is ClassDescriptor && KotlinBuiltIns.isPrimitiveType(descriptor.defaultType)
val operationToken = expression.operationToken
text = "Replace with ${expression.operationReference.text}= expression"
val expressionLeft = expression.left
val expressionRight = expression.right
return when {
@@ -87,8 +98,8 @@ class ReplaceWithOperatorAssignmentIntention : SelfTargetingOffsetIndependentInt
operationToken == KtTokens.DIV ||
operationToken == KtTokens.PERC
override fun applyTo(element: KtBinaryExpression, editor: Editor?) {
element.replace(buildOperatorAssignment(element))
override fun applyTo(element: PsiElement, project: Project, editor: Editor?) {
(element as? KtBinaryExpression)?.replace(buildOperatorAssignment(element))
}
private fun buildOperatorAssignment(element: KtBinaryExpression): KtBinaryExpression {
@@ -100,21 +111,32 @@ class ReplaceWithOperatorAssignmentIntention : SelfTargetingOffsetIndependentInt
return KtPsiFactory(element).createExpression(replacement) as KtBinaryExpression
}
tailrec private fun buildOperatorAssignmentText(variableExpression: KtNameReferenceExpression, expression: KtBinaryExpression, tail: String): String {
private tailrec fun buildOperatorAssignmentText(
variableExpression: KtNameReferenceExpression,
expression: KtBinaryExpression,
tail: String
): String {
val operationText = expression.operationReference.text
val variableName = variableExpression.text
fun String.appendTail() = if (tail.isEmpty()) this else "$this $tail"
return when {
variableExpression.matches(expression.left) -> "$variableName $operationText= ${expression.right!!.text}".appendTail()
variableExpression.matches(expression.left) ->
"$variableName $operationText= ${expression.right!!.text}".appendTail()
variableExpression.matches(expression.right) -> "$variableName $operationText= ${expression.left!!.text}".appendTail()
variableExpression.matches(expression.right) ->
"$variableName $operationText= ${expression.left!!.text}".appendTail()
expression.left is KtBinaryExpression ->
buildOperatorAssignmentText(variableExpression, expression.left as KtBinaryExpression, "$operationText ${expression.right!!.text}".appendTail())
buildOperatorAssignmentText(
variableExpression,
expression.left as KtBinaryExpression,
"$operationText ${expression.right!!.text}".appendTail()
)
else -> tail
else ->
tail
}
}
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.inspections.ReplaceWithOperatorAssignmentInspection
@@ -1,4 +1,4 @@
// IS_APPLICABLE: false
// PROBLEM: none
// WITH_RUNTIME
fun foo() {
var list1 = java.util.Collections.emptyList<String>()
@@ -1,4 +1,4 @@
// IS_APPLICABLE: false
// PROBLEM: none
fun foo() {
var x = 0
x =<caret> x / 1 + 1
@@ -1,4 +1,4 @@
// IS_APPLICABLE: false
// PROBLEM: none
fun foo() {
var x = 0
val y = 0
@@ -1,4 +1,4 @@
// IS_APPLICABLE: false
// PROBLEM: none
fun foo() {
var x = 0
<caret>x = 1 - x
@@ -1,4 +1,4 @@
// IS_APPLICABLE: false
// PROBLEM: none
fun foo() {
var x = 0
x =<caret> x - 1 - 1
@@ -1,4 +1,4 @@
// IS_APPLICABLE: false
// PROBLEM: none
fun foo() {
var x = 0
val y = 0
@@ -1,4 +1,4 @@
// IS_APPLICABLE: false
// PROBLEM: none
class A
operator fun A.plus(a: A): A = A()
@@ -1 +0,0 @@
org.jetbrains.kotlin.idea.intentions.ReplaceWithOperatorAssignmentIntention
@@ -3294,6 +3294,87 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
}
}
@TestMetadata("idea/testData/inspectionsLocal/replaceWithOperatorAssignment")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ReplaceWithOperatorAssignment extends AbstractLocalInspectionTest {
public void testAllFilesPresentInReplaceWithOperatorAssignment() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/replaceWithOperatorAssignment"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("flexibleTypeBug.kt")
public void testFlexibleTypeBug() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/replaceWithOperatorAssignment/flexibleTypeBug.kt");
doTest(fileName);
}
@TestMetadata("illegalMultipleOperators.kt")
public void testIllegalMultipleOperators() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/replaceWithOperatorAssignment/illegalMultipleOperators.kt");
doTest(fileName);
}
@TestMetadata("illegalMultipleOperatorsMiddle.kt")
public void testIllegalMultipleOperatorsMiddle() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/replaceWithOperatorAssignment/illegalMultipleOperatorsMiddle.kt");
doTest(fileName);
}
@TestMetadata("invalidSubtraction.kt")
public void testInvalidSubtraction() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/replaceWithOperatorAssignment/invalidSubtraction.kt");
doTest(fileName);
}
@TestMetadata("multipleOperators.kt")
public void testMultipleOperators() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/replaceWithOperatorAssignment/multipleOperators.kt");
doTest(fileName);
}
@TestMetadata("multipleOperatorsRightSideRepeat.kt")
public void testMultipleOperatorsRightSideRepeat() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/replaceWithOperatorAssignment/multipleOperatorsRightSideRepeat.kt");
doTest(fileName);
}
@TestMetadata("nonCommutativeRepeat.kt")
public void testNonCommutativeRepeat() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/replaceWithOperatorAssignment/nonCommutativeRepeat.kt");
doTest(fileName);
}
@TestMetadata("nonRepeatingAssignment.kt")
public void testNonRepeatingAssignment() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/replaceWithOperatorAssignment/nonRepeatingAssignment.kt");
doTest(fileName);
}
@TestMetadata("plusAssignConflict.kt")
public void testPlusAssignConflict() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/replaceWithOperatorAssignment/plusAssignConflict.kt");
doTest(fileName);
}
@TestMetadata("rightSideRepeat.kt")
public void testRightSideRepeat() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/replaceWithOperatorAssignment/rightSideRepeat.kt");
doTest(fileName);
}
@TestMetadata("simpleAssign.kt")
public void testSimpleAssign() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/replaceWithOperatorAssignment/simpleAssign.kt");
doTest(fileName);
}
@TestMetadata("validSubtraction.kt")
public void testValidSubtraction() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/replaceWithOperatorAssignment/validSubtraction.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/inspectionsLocal/selfAssignment")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -14154,87 +14154,6 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
@TestMetadata("idea/testData/intentions/replaceWithOperatorAssignment")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ReplaceWithOperatorAssignment extends AbstractIntentionTest {
public void testAllFilesPresentInReplaceWithOperatorAssignment() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/replaceWithOperatorAssignment"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("flexibleTypeBug.kt")
public void testFlexibleTypeBug() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/flexibleTypeBug.kt");
doTest(fileName);
}
@TestMetadata("illegalMultipleOperators.kt")
public void testIllegalMultipleOperators() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/illegalMultipleOperators.kt");
doTest(fileName);
}
@TestMetadata("illegalMultipleOperatorsMiddle.kt")
public void testIllegalMultipleOperatorsMiddle() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/illegalMultipleOperatorsMiddle.kt");
doTest(fileName);
}
@TestMetadata("invalidSubtraction.kt")
public void testInvalidSubtraction() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/invalidSubtraction.kt");
doTest(fileName);
}
@TestMetadata("multipleOperators.kt")
public void testMultipleOperators() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/multipleOperators.kt");
doTest(fileName);
}
@TestMetadata("multipleOperatorsRightSideRepeat.kt")
public void testMultipleOperatorsRightSideRepeat() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/multipleOperatorsRightSideRepeat.kt");
doTest(fileName);
}
@TestMetadata("nonCommutativeRepeat.kt")
public void testNonCommutativeRepeat() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/nonCommutativeRepeat.kt");
doTest(fileName);
}
@TestMetadata("nonRepeatingAssignment.kt")
public void testNonRepeatingAssignment() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/nonRepeatingAssignment.kt");
doTest(fileName);
}
@TestMetadata("plusAssignConflict.kt")
public void testPlusAssignConflict() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/plusAssignConflict.kt");
doTest(fileName);
}
@TestMetadata("rightSideRepeat.kt")
public void testRightSideRepeat() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/rightSideRepeat.kt");
doTest(fileName);
}
@TestMetadata("simpleAssign.kt")
public void testSimpleAssign() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/simpleAssign.kt");
doTest(fileName);
}
@TestMetadata("validSubtraction.kt")
public void testValidSubtraction() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/validSubtraction.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/intentions/replaceWithOrdinaryAssignment")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)