diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceJavaStaticMethodWithTopLevelFunctionInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceJavaStaticMethodWithTopLevelFunctionInspection.kt index 1fdbde6195b..9f948922582 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceJavaStaticMethodWithTopLevelFunctionInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceJavaStaticMethodWithTopLevelFunctionInspection.kt @@ -8,8 +8,14 @@ package org.jetbrains.kotlin.idea.inspections import com.intellij.codeInspection.LocalQuickFix import com.intellij.codeInspection.ProblemDescriptor 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.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.psi.PsiElement import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference import org.jetbrains.kotlin.idea.core.ShortenReferences @@ -17,6 +23,7 @@ import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.inspections.collections.isCalling import org.jetbrains.kotlin.idea.intentions.callExpression 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.psi.KtDotQualifiedExpression import org.jetbrains.kotlin.psi.KtPsiFactory @@ -32,25 +39,50 @@ class ReplaceJavaStaticMethodWithTopLevelFunctionInspection : AbstractKotlinInsp val callee = call.calleeExpression ?: return val dotQualified = call.getStrictParentOfType() ?: return val calleeText = callee.text - val replacement = replacements[calleeText] - ?.takeIf { call.getResolvedCall(call.analyze(BodyResolveMode.PARTIAL))?.isCalling(FqName(it.javaMethodFqName)) == true } - ?: return + val replacements = replacements[calleeText] ?: return + val replacement = replacements.first() + + if (call.getResolvedCall(call.analyze(BodyResolveMode.PARTIAL))?.isCalling(FqName(replacement.javaMethodFqName)) != true) return if (replacement.toExtensionFunction && call.valueArguments.isEmpty()) return holder.registerProblem( dotQualified, TextRange(0, callee.endOffset - dotQualified.startOffset), "Should be replaced with '${replacement.kotlinFunctionShortName}()'", - ReplaceWithTopLevelFunction(replacement) + ReplaceWithTopLevelFunction(replacements) ) }) - private class ReplaceWithTopLevelFunction(private val replacement: Replacement) : LocalQuickFix { - override fun getName() = "Replace with '${replacement.kotlinFunctionShortName}()'" + private class ReplaceWithTopLevelFunction(private val replacements: List) : LocalQuickFix { + override fun getName() = "Replace with '${replacements.first().kotlinFunctionShortName}()'" override fun getFamilyName() = name override fun applyFix(project: Project, descriptor: ProblemDescriptor) { - val dotQualified = descriptor.psiElement as? KtDotQualifiedExpression ?: return + val element = descriptor.psiElement ?: 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("Choose function", replacements) { + override fun onChosen(selectedValue: Replacement?, finalChoice: Boolean): PopupStep? { + 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 file = dotQualified.containingKtFile val psiFactory = KtPsiFactory(call) @@ -71,7 +103,7 @@ class ReplaceJavaStaticMethodWithTopLevelFunctionInspection : AbstractKotlinInsp } private data class Replacement( - val javaMethodFqName: String, + val javaMethodFqName: String, val kotlinFunctionFqName: String, val toExtensionFunction: Boolean = false ) { @@ -80,6 +112,8 @@ class ReplaceJavaStaticMethodWithTopLevelFunctionInspection : AbstractKotlinInsp val javaMethodShortName = javaMethodFqName.shortName() val kotlinFunctionShortName = kotlinFunctionFqName.shortName() + + override fun toString(): String = kotlinFunctionFqName } companion object { @@ -100,6 +134,8 @@ class ReplaceJavaStaticMethodWithTopLevelFunctionInspection : AbstractKotlinInsp Replacement("java.lang.Math.floor", "kotlin.math.floor"), Replacement("java.lang.Math.hypot", "kotlin.math.hypot"), Replacement("java.lang.Math.IEEEremainder", "kotlin.math.IEEErem", toExtensionFunction = true), + Replacement("java.lang.Math.log", "kotlin.math.ln"), + Replacement("java.lang.Math.log1p", "kotlin.math.ln1p"), Replacement("java.lang.Math.log10", "kotlin.math.log10"), Replacement("java.lang.Math.max", "kotlin.math.max"), Replacement("java.lang.Math.min", "kotlin.math.min"), @@ -109,12 +145,14 @@ class ReplaceJavaStaticMethodWithTopLevelFunctionInspection : AbstractKotlinInsp Replacement("java.lang.Math.pow", "kotlin.math.pow", toExtensionFunction = true), Replacement("java.lang.Math.rint", "kotlin.math.round"), Replacement("java.lang.Math.round", "kotlin.math.roundToLong", toExtensionFunction = true), + Replacement("java.lang.Math.round", "kotlin.math.roundToInt", toExtensionFunction = true), Replacement("java.lang.Math.signum", "kotlin.math.sign"), Replacement("java.lang.Math.sin", "kotlin.math.sin"), Replacement("java.lang.Math.sinh", "kotlin.math.sinh"), Replacement("java.lang.Math.sqrt", "kotlin.math.sqrt"), Replacement("java.lang.Math.tan", "kotlin.math.tan"), - Replacement("java.lang.Math.tanh", "kotlin.math.tanh") - ).associateBy { it.javaMethodShortName } + Replacement("java.lang.Math.tanh", "kotlin.math.tanh"), + Replacement("java.lang.Math.copySign", "kotlin.math.withSign", toExtensionFunction = true) + ).groupBy { it.javaMethodShortName } } } diff --git a/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/copySign.kt b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/copySign.kt new file mode 100644 index 00000000000..71a40ae3499 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/copySign.kt @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun test(x: Double, y: Double) { + Math.copySign(x, y) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/copySign.kt.after b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/copySign.kt.after new file mode 100644 index 00000000000..ef21acf1587 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/copySign.kt.after @@ -0,0 +1,6 @@ +import kotlin.math.withSign + +// WITH_RUNTIME +fun test(x: Double, y: Double) { + x.withSign(y) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/log.kt b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/log.kt new file mode 100644 index 00000000000..3e6b5d0c9fb --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/log.kt @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun test(x: Double) { + Math.log(x) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/log.kt.after b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/log.kt.after new file mode 100644 index 00000000000..ce65e0d7472 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/log.kt.after @@ -0,0 +1,6 @@ +import kotlin.math.ln + +// WITH_RUNTIME +fun test(x: Double) { + ln(x) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/log1p.kt b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/log1p.kt new file mode 100644 index 00000000000..b7eb491216a --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/log1p.kt @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun test(x: Double) { + Math.log1p(x) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/log1p.kt.after b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/log1p.kt.after new file mode 100644 index 00000000000..4e2fa5e2326 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/log1p.kt.after @@ -0,0 +1,6 @@ +import kotlin.math.ln1p + +// WITH_RUNTIME +fun test(x: Double) { + ln1p(x) +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 57e26ab4456..b74f206c114 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -7258,6 +7258,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/ceil.kt"); } + @TestMetadata("copySign.kt") + public void testCopySign() throws Exception { + runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/copySign.kt"); + } + @TestMetadata("cos.kt") public void testCos() throws Exception { runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/cos.kt"); @@ -7293,11 +7298,21 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/IEEEremainder.kt"); } + @TestMetadata("log.kt") + public void testLog() throws Exception { + runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/log.kt"); + } + @TestMetadata("log10.kt") public void testLog10() throws Exception { runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/log10.kt"); } + @TestMetadata("log1p.kt") + public void testLog1p() throws Exception { + runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/log1p.kt"); + } + @TestMetadata("max.kt") public void testMax() throws Exception { runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithTopLevelFunction/math/max.kt");