diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml index 0e013d6ab8e..10729271af9 100644 --- a/idea/resources/META-INF/plugin-common.xml +++ b/idea/resources/META-INF/plugin-common.xml @@ -2374,6 +2374,15 @@ language="kotlin" /> + + + +This inspection reports hashMapOf and similar function calls replaceable with the constructor of EnumMap. + + \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceWithEnumMapInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceWithEnumMapInspection.kt new file mode 100644 index 00000000000..4d7dfc43f88 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceWithEnumMapInspection.kt @@ -0,0 +1,67 @@ +/* + * 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.inspections + +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElementVisitor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference +import org.jetbrains.kotlin.idea.project.platform +import org.jetbrains.kotlin.idea.util.ImportInsertHelper +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.callExpressionVisitor +import org.jetbrains.kotlin.psi.createExpressionByPattern +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe +import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform +import org.jetbrains.kotlin.types.typeUtil.isEnum + +private val hashMapCreationFqNames = setOf( + "java.util.HashMap.", + "kotlin.collections.HashMap.", + "kotlin.collections.hashMapOf" +) + +class ReplaceWithEnumMapInspection : AbstractKotlinInspection() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { + return callExpressionVisitor(fun(element: KtCallExpression) { + if (element.platform !is JvmPlatform) return + val context = element.analyze() + val fqName = element.getResolvedCall(context)?.resultingDescriptor?.fqNameUnsafe?.asString() ?: return + if (!hashMapCreationFqNames.contains(fqName)) return + if (element.valueArguments.isNotEmpty()) return + + val expectedType = context[BindingContext.EXPECTED_EXPRESSION_TYPE, element] ?: return + val firstArgType = expectedType.arguments.firstOrNull()?.type ?: return + if (!firstArgType.isEnum()) return + val enumClassName = firstArgType.constructor.declarationDescriptor?.fqNameUnsafe?.asString() ?: return + holder.registerProblem(element, "Replaceable with EnumMap", ReplaceWithEnumMapFix(enumClassName)) + }) + } + + private class ReplaceWithEnumMapFix( + private val enumClassName: String + ) : LocalQuickFix { + override fun getFamilyName() = name + + override fun getName() = "Replace with EnumMap" + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val call = descriptor.psiElement as? KtCallExpression ?: return + val factory = KtPsiFactory(call) + val file = call.containingKtFile + val enumMapDescriptor = file.resolveImportReference(FqName("java.util.EnumMap")).firstOrNull() ?: return + ImportInsertHelper.getInstance(project).importDescriptor(call.containingKtFile, enumMapDescriptor) + call.replace(factory.createExpressionByPattern("EnumMap($0::class.java)", enumClassName)) + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replaceWithEnumMap/.inspection b/idea/testData/inspectionsLocal/replaceWithEnumMap/.inspection new file mode 100644 index 00000000000..54614515e61 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceWithEnumMap/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.ReplaceWithEnumMapInspection diff --git a/idea/testData/inspectionsLocal/replaceWithEnumMap/inferred.kt b/idea/testData/inspectionsLocal/replaceWithEnumMap/inferred.kt new file mode 100644 index 00000000000..9f2304550d3 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceWithEnumMap/inferred.kt @@ -0,0 +1,7 @@ +// RUNTIME_WITH_FULL_JDK + +enum class E { + A, B +} + +fun getMap(): Map = hashMapOf() diff --git a/idea/testData/inspectionsLocal/replaceWithEnumMap/inferred.kt.after b/idea/testData/inspectionsLocal/replaceWithEnumMap/inferred.kt.after new file mode 100644 index 00000000000..af51891aac7 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceWithEnumMap/inferred.kt.after @@ -0,0 +1,9 @@ +import java.util.EnumMap + +// RUNTIME_WITH_FULL_JDK + +enum class E { + A, B +} + +fun getMap(): Map = EnumMap(E::class.java) diff --git a/idea/testData/inspectionsLocal/replaceWithEnumMap/javaCollection.kt b/idea/testData/inspectionsLocal/replaceWithEnumMap/javaCollection.kt new file mode 100644 index 00000000000..a933877bba7 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceWithEnumMap/javaCollection.kt @@ -0,0 +1,8 @@ +// RUNTIME_WITH_FULL_JDK +import java.util.HashMap + +enum class E { + A, B +} + +fun getMap(): Map = HashMap() diff --git a/idea/testData/inspectionsLocal/replaceWithEnumMap/javaCollection.kt.after b/idea/testData/inspectionsLocal/replaceWithEnumMap/javaCollection.kt.after new file mode 100644 index 00000000000..797ab613610 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceWithEnumMap/javaCollection.kt.after @@ -0,0 +1,9 @@ +// RUNTIME_WITH_FULL_JDK +import java.util.EnumMap +import java.util.HashMap + +enum class E { + A, B +} + +fun getMap(): Map = EnumMap(E::class.java) diff --git a/idea/testData/inspectionsLocal/replaceWithEnumMap/noInfer.kt b/idea/testData/inspectionsLocal/replaceWithEnumMap/noInfer.kt new file mode 100644 index 00000000000..08c395ee834 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceWithEnumMap/noInfer.kt @@ -0,0 +1,10 @@ +// PROBLEM: none +// WITH_RUNTIME + +enum class E { + A, B +} + +fun main() { + val map = hashMapOf() +} diff --git a/idea/testData/inspectionsLocal/replaceWithEnumMap/simple.kt b/idea/testData/inspectionsLocal/replaceWithEnumMap/simple.kt new file mode 100644 index 00000000000..97c8d27991d --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceWithEnumMap/simple.kt @@ -0,0 +1,9 @@ +// RUNTIME_WITH_FULL_JDK + +enum class E { + A, B +} + +fun main() { + val test: Map = HashMap() +} diff --git a/idea/testData/inspectionsLocal/replaceWithEnumMap/simple.kt.after b/idea/testData/inspectionsLocal/replaceWithEnumMap/simple.kt.after new file mode 100644 index 00000000000..815b59900a7 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceWithEnumMap/simple.kt.after @@ -0,0 +1,11 @@ +import java.util.EnumMap + +// RUNTIME_WITH_FULL_JDK + +enum class E { + A, B +} + +fun main() { + val test: Map = EnumMap(E::class.java) +} diff --git a/idea/testData/inspectionsLocal/replaceWithEnumMap/withArguments.kt b/idea/testData/inspectionsLocal/replaceWithEnumMap/withArguments.kt new file mode 100644 index 00000000000..98af3d0a901 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceWithEnumMap/withArguments.kt @@ -0,0 +1,6 @@ +// PROBLEM: none +// WITH_RUNTIME + +fun main() { + val map = hashMapOf(5 to 1) +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 83a52a39c57..080f9ec0d08 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -7646,6 +7646,44 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/replaceWithEnumMap") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ReplaceWithEnumMap extends AbstractLocalInspectionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath); + } + + public void testAllFilesPresentInReplaceWithEnumMap() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/replaceWithEnumMap"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("inferred.kt") + public void testInferred() throws Exception { + runTest("idea/testData/inspectionsLocal/replaceWithEnumMap/inferred.kt"); + } + + @TestMetadata("javaCollection.kt") + public void testJavaCollection() throws Exception { + runTest("idea/testData/inspectionsLocal/replaceWithEnumMap/javaCollection.kt"); + } + + @TestMetadata("noInfer.kt") + public void testNoInfer() throws Exception { + runTest("idea/testData/inspectionsLocal/replaceWithEnumMap/noInfer.kt"); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + runTest("idea/testData/inspectionsLocal/replaceWithEnumMap/simple.kt"); + } + + @TestMetadata("withArguments.kt") + public void testWithArguments() throws Exception { + runTest("idea/testData/inspectionsLocal/replaceWithEnumMap/withArguments.kt"); + } + } + @TestMetadata("idea/testData/inspectionsLocal/replaceWithOperatorAssignment") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)