Replace popup with several quick-fixes in ReplaceJavaStaticMethodWithKotlinAnalog
This commit is contained in:
+31
-46
@@ -8,14 +8,8 @@ package org.jetbrains.kotlin.idea.inspections
|
|||||||
import com.intellij.codeInspection.LocalQuickFix
|
import com.intellij.codeInspection.LocalQuickFix
|
||||||
import com.intellij.codeInspection.ProblemDescriptor
|
import com.intellij.codeInspection.ProblemDescriptor
|
||||||
import com.intellij.codeInspection.ProblemsHolder
|
import com.intellij.codeInspection.ProblemsHolder
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
|
||||||
import com.intellij.openapi.editor.Editor
|
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.ui.popup.JBPopupFactory
|
|
||||||
import com.intellij.openapi.ui.popup.PopupStep
|
|
||||||
import com.intellij.openapi.ui.popup.util.BaseListPopupStep
|
|
||||||
import com.intellij.openapi.util.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
import com.intellij.psi.PsiElement
|
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
|
||||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||||
@@ -23,8 +17,8 @@ import org.jetbrains.kotlin.idea.core.replaced
|
|||||||
import org.jetbrains.kotlin.idea.inspections.collections.isCalling
|
import org.jetbrains.kotlin.idea.inspections.collections.isCalling
|
||||||
import org.jetbrains.kotlin.idea.intentions.callExpression
|
import org.jetbrains.kotlin.idea.intentions.callExpression
|
||||||
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
|
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
|
||||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||||
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
|
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
|
||||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||||
import org.jetbrains.kotlin.psi.callExpressionVisitor
|
import org.jetbrains.kotlin.psi.callExpressionVisitor
|
||||||
@@ -38,53 +32,34 @@ class ReplaceJavaStaticMethodWithKotlinAnalogInspection : AbstractKotlinInspecti
|
|||||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = callExpressionVisitor(fun(call) {
|
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = callExpressionVisitor(fun(call) {
|
||||||
val callee = call.calleeExpression ?: return
|
val callee = call.calleeExpression ?: return
|
||||||
val dotQualified = call.getStrictParentOfType<KtDotQualifiedExpression>() ?: return
|
val dotQualified = call.getStrictParentOfType<KtDotQualifiedExpression>() ?: return
|
||||||
val calleeText = callee.text
|
val replacements = REPLACEMENTS[callee.text]
|
||||||
val replacements = REPLACEMENTS[calleeText]?.let { list ->
|
?.filter { it.filter(call) }
|
||||||
val callDescriptor = call.getResolvedCall(call.analyze(BodyResolveMode.PARTIAL)) ?: return
|
?.takeIf { it.isNotEmpty() }
|
||||||
list.filter { callDescriptor.isCalling(FqName(it.javaMethodFqName)) }
|
?.let { list ->
|
||||||
} ?: return
|
val callDescriptor = call.getResolvedCall(call.analyze(BodyResolveMode.PARTIAL)) ?: return
|
||||||
|
list.filter {
|
||||||
|
callDescriptor.isCalling(FqName(it.javaMethodFqName)) && (!it.toExtensionFunction || call.valueArguments.isNotEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?.takeIf { it.isNotEmpty() }
|
||||||
|
?.map { ReplaceWithKotlinAnalogFunction(it) }
|
||||||
|
?.toTypedArray() ?: return
|
||||||
|
|
||||||
val replacement = replacements.firstOrNull() ?: return
|
|
||||||
if (replacement.toExtensionFunction && call.valueArguments.isEmpty()) return
|
|
||||||
holder.registerProblem(
|
holder.registerProblem(
|
||||||
dotQualified,
|
dotQualified,
|
||||||
TextRange(0, callee.endOffset - dotQualified.startOffset),
|
TextRange(0, callee.endOffset - dotQualified.startOffset),
|
||||||
"Should be replaced with '${replacement.kotlinFunctionShortName}()'",
|
"Should be replaced with Kotlin function",
|
||||||
ReplaceWithKotlinAnalogFunction(replacements)
|
*replacements
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
private class ReplaceWithKotlinAnalogFunction(private val replacements: List<Replacement>) : LocalQuickFix {
|
private class ReplaceWithKotlinAnalogFunction(private val replacement: Replacement) : LocalQuickFix {
|
||||||
override fun getName() = "Replace with '${replacements.first().kotlinFunctionShortName}()'"
|
override fun getName() = "Replace with `${replacement.kotlinFunctionShortName}` function"
|
||||||
|
|
||||||
override fun getFamilyName() = name
|
override fun getFamilyName() = "Replace with Kotlin analog"
|
||||||
|
|
||||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||||
val element = descriptor.psiElement ?: return
|
val dotQualified = descriptor.psiElement as? KtDotQualifiedExpression ?: return
|
||||||
val editor = element.findExistingEditor()
|
|
||||||
if (editor != null && replacements.size > 1 && !ApplicationManager.getApplication().isUnitTestMode) {
|
|
||||||
chooseAndApplyFix(editor, project, element)
|
|
||||||
} else {
|
|
||||||
applyFix(replacements.first(), project, element)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun chooseAndApplyFix(editor: Editor, project: Project, element: PsiElement) {
|
|
||||||
JBPopupFactory.getInstance().createListPopup(object : BaseListPopupStep<Replacement>("Choose function", replacements) {
|
|
||||||
override fun onChosen(selectedValue: Replacement?, finalChoice: Boolean): PopupStep<String>? {
|
|
||||||
if (selectedValue == null || project.isDisposed) return null
|
|
||||||
project.executeWriteCommand(name) {
|
|
||||||
applyFix(selectedValue, project, element)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTextFor(value: Replacement) = value.kotlinFunctionFqName
|
|
||||||
}).showInBestPositionFor(editor)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun applyFix(replacement: Replacement, project: Project, element: PsiElement) {
|
|
||||||
val dotQualified = element as? KtDotQualifiedExpression ?: return
|
|
||||||
val call = dotQualified.callExpression ?: return
|
val call = dotQualified.callExpression ?: return
|
||||||
val file = dotQualified.containingKtFile
|
val file = dotQualified.containingKtFile
|
||||||
val psiFactory = KtPsiFactory(call)
|
val psiFactory = KtPsiFactory(call)
|
||||||
@@ -110,7 +85,8 @@ class ReplaceJavaStaticMethodWithKotlinAnalogInspection : AbstractKotlinInspecti
|
|||||||
private data class Replacement(
|
private data class Replacement(
|
||||||
val javaMethodFqName: String,
|
val javaMethodFqName: String,
|
||||||
val kotlinFunctionFqName: String,
|
val kotlinFunctionFqName: String,
|
||||||
val toExtensionFunction: Boolean = false
|
val toExtensionFunction: Boolean = false,
|
||||||
|
val filter: (KtCallExpression) -> Boolean = { true }
|
||||||
) {
|
) {
|
||||||
private fun String.shortName() = takeLastWhile { it != '.' }
|
private fun String.shortName() = takeLastWhile { it != '.' }
|
||||||
|
|
||||||
@@ -130,7 +106,16 @@ class ReplaceJavaStaticMethodWithKotlinAnalogInspection : AbstractKotlinInspecti
|
|||||||
"Float" to "Float"
|
"Float" to "Float"
|
||||||
).flatMap {
|
).flatMap {
|
||||||
listOf(
|
listOf(
|
||||||
Replacement("java.lang.${it.first}.toString", "kotlin.text.toString", toExtensionFunction = true),
|
Replacement(
|
||||||
|
"java.lang.${it.first}.toString",
|
||||||
|
"kotlin.text.toString",
|
||||||
|
toExtensionFunction = true
|
||||||
|
) { call -> call.valueArguments.size == 2 },
|
||||||
|
Replacement(
|
||||||
|
"java.lang.${it.first}.toString",
|
||||||
|
"kotlin.primitives.${it.second}.toString",
|
||||||
|
toExtensionFunction = true
|
||||||
|
) { call -> call.valueArguments.size == 1 },
|
||||||
Replacement("java.lang.${it.first}.compare", "kotlin.primitives.${it.second}.compareTo", toExtensionFunction = true)
|
Replacement("java.lang.${it.first}.compare", "kotlin.primitives.${it.second}.compareTo", toExtensionFunction = true)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
@@ -1,4 +1,5 @@
|
|||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
|
// FIX: Replace with `listOf` function
|
||||||
import java.util.Arrays
|
import java.util.Arrays
|
||||||
|
|
||||||
fun test() {
|
fun test() {
|
||||||
|
|||||||
Vendored
+1
@@ -1,4 +1,5 @@
|
|||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
|
// FIX: Replace with `listOf` function
|
||||||
import java.util.Arrays
|
import java.util.Arrays
|
||||||
|
|
||||||
fun test() {
|
fun test() {
|
||||||
|
|||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// FIX: Replace with `mutableListOf` function
|
||||||
|
import java.util.Arrays
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val a = Arrays.<caret>asList(1, 3, null)
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// FIX: Replace with `mutableListOf` function
|
||||||
|
import java.util.Arrays
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val a = mutableListOf(1, 3, null)
|
||||||
|
}
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// MIN_JAVA_VERSION: 9
|
||||||
|
// FIX: Replace with `mtableSetOf` function
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val a = java.util.Set.of<caret><String>()
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// MIN_JAVA_VERSION: 9
|
||||||
|
// FIX: Replace with `mtableSetOf` function
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val a = mutableSetOf<String>()
|
||||||
|
}
|
||||||
Vendored
+1
@@ -1,5 +1,6 @@
|
|||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// MIN_JAVA_VERSION: 9
|
// MIN_JAVA_VERSION: 9
|
||||||
|
// FIX: Replace with `setOf` function
|
||||||
|
|
||||||
fun test() {
|
fun test() {
|
||||||
val a = java.util.Set.of<caret><String>()
|
val a = java.util.Set.of<caret><String>()
|
||||||
|
|||||||
Vendored
+1
@@ -1,5 +1,6 @@
|
|||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// MIN_JAVA_VERSION: 9
|
// MIN_JAVA_VERSION: 9
|
||||||
|
// FIX: Replace with `setOf` function
|
||||||
|
|
||||||
fun test() {
|
fun test() {
|
||||||
val a = setOf<String>()
|
val a = setOf<String>()
|
||||||
|
|||||||
Vendored
+1
@@ -1,5 +1,6 @@
|
|||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// MIN_JAVA_VERSION: 9
|
// MIN_JAVA_VERSION: 9
|
||||||
|
// FIX: Replace with `setOf` function
|
||||||
|
|
||||||
fun test() {
|
fun test() {
|
||||||
val a = java.util.Set.of<caret>("sfsf", 25)
|
val a = java.util.Set.of<caret>("sfsf", 25)
|
||||||
|
|||||||
Vendored
+1
@@ -1,5 +1,6 @@
|
|||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// MIN_JAVA_VERSION: 9
|
// MIN_JAVA_VERSION: 9
|
||||||
|
// FIX: Replace with `setOf` function
|
||||||
|
|
||||||
fun test() {
|
fun test() {
|
||||||
val a = setOf("sfsf", 25)
|
val a = setOf("sfsf", 25)
|
||||||
|
|||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
|
// FIX: Replace with `roundToInt` function
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
fun test(x: Double) {
|
fun test(x: Double) {
|
||||||
<caret>Math.round(x)
|
<caret>Math.round(x)
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
// FIX: Replace with `roundToInt` function
|
||||||
|
// WITH_RUNTIME
|
||||||
|
fun test(x: Double) {
|
||||||
|
x.roundToInt()
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
// FIX: Replace with `roundToLong` function
|
||||||
|
// WITH_RUNTIME
|
||||||
|
fun test(x: Double) {
|
||||||
|
<caret>Math.round(x)
|
||||||
|
}
|
||||||
+1
@@ -1,5 +1,6 @@
|
|||||||
import kotlin.math.roundToLong
|
import kotlin.math.roundToLong
|
||||||
|
|
||||||
|
// FIX: Replace with `roundToLong` function
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
fun test(x: Double) {
|
fun test(x: Double) {
|
||||||
x.roundToLong()
|
x.roundToLong()
|
||||||
+18
-3
@@ -7172,6 +7172,16 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
|||||||
runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/collections/copyOf.kt");
|
runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/collections/copyOf.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("mutableListOf.kt")
|
||||||
|
public void testMutableListOf() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/collections/mutableListOf.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("mutableSetOf.kt")
|
||||||
|
public void testMutableSetOf() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/collections/mutableSetOf.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("nonArraysCopyOf.kt")
|
@TestMetadata("nonArraysCopyOf.kt")
|
||||||
public void testNonArraysCopyOf() throws Exception {
|
public void testNonArraysCopyOf() throws Exception {
|
||||||
runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/collections/nonArraysCopyOf.kt");
|
runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/collections/nonArraysCopyOf.kt");
|
||||||
@@ -7406,9 +7416,14 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
|||||||
runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/math/rint.kt");
|
runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/math/rint.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("round.kt")
|
@TestMetadata("roundToInt.kt")
|
||||||
public void testRound() throws Exception {
|
public void testRoundToInt() throws Exception {
|
||||||
runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/math/round.kt");
|
runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/math/roundToInt.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("roundToLong.kt")
|
||||||
|
public void testRoundToLong() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/math/roundToLong.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("signum.kt")
|
@TestMetadata("signum.kt")
|
||||||
|
|||||||
Reference in New Issue
Block a user