From d3908aeb2ed593c9f42b24a7be48c5e032831c74 Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Thu, 25 Oct 2018 23:46:30 +0900 Subject: [PATCH] Add "map.get() with not-null assertion operator" inspection #KT-25171 Fixed --- .../jetbrains/kotlin/psi/VisitorWrappers.kt | 9 ++- .../MapGetWithNotNullAssertionOperator.html | 5 ++ idea/src/META-INF/plugin-common.xml | 9 +++ ...tWithNotNullAssertionOperatorInspection.kt | 64 +++++++++++++++++++ .../.inspection | 1 + .../mapGetWithNotNullAssertionOperator/get.kt | 4 ++ .../get.kt.after | 4 ++ .../getValue.kt | 5 ++ .../indexedAccess.kt | 4 ++ .../indexedAccess.kt.after | 4 ++ .../list.kt | 5 ++ .../noNotNullAssersion.kt | 5 ++ .../LocalInspectionTestGenerated.java | 38 +++++++++++ 13 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 idea/resources/inspectionDescriptions/MapGetWithNotNullAssertionOperator.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/MapGetWithNotNullAssertionOperatorInspection.kt create mode 100644 idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/.inspection create mode 100644 idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/get.kt create mode 100644 idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/get.kt.after create mode 100644 idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/getValue.kt create mode 100644 idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/indexedAccess.kt create mode 100644 idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/indexedAccess.kt.after create mode 100644 idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/list.kt create mode 100644 idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/noNotNullAssersion.kt diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/VisitorWrappers.kt b/compiler/psi/src/org/jetbrains/kotlin/psi/VisitorWrappers.kt index 62f221c3bb6..1028eaa69da 100644 --- a/compiler/psi/src/org/jetbrains/kotlin/psi/VisitorWrappers.kt +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/VisitorWrappers.kt @@ -425,7 +425,6 @@ fun returnExpressionVisitor(block: (KtReturnExpression) -> Unit) = } } - fun delegatedSuperTypeEntry(block: (KtDelegatedSuperTypeEntry) -> Unit) = object : KtVisitorVoid() { override fun visitDelegatedSuperTypeEntry(delegatedSuperTypeEntry: KtDelegatedSuperTypeEntry) { @@ -433,3 +432,11 @@ fun delegatedSuperTypeEntry(block: (KtDelegatedSuperTypeEntry) -> Unit) = block(delegatedSuperTypeEntry) } } + +fun postfixExpressionVisitor(block: (KtPostfixExpression) -> Unit) = + object : KtVisitorVoid() { + override fun visitPostfixExpression(expression: KtPostfixExpression) { + super.visitPostfixExpression(expression) + block(expression) + } + } diff --git a/idea/resources/inspectionDescriptions/MapGetWithNotNullAssertionOperator.html b/idea/resources/inspectionDescriptions/MapGetWithNotNullAssertionOperator.html new file mode 100644 index 00000000000..a5884cc186a --- /dev/null +++ b/idea/resources/inspectionDescriptions/MapGetWithNotNullAssertionOperator.html @@ -0,0 +1,5 @@ + + +This inspection reports map.get()!! that can be replaced with map.getValue(). + + \ No newline at end of file diff --git a/idea/src/META-INF/plugin-common.xml b/idea/src/META-INF/plugin-common.xml index 9fb3b068048..62dcd0637f2 100644 --- a/idea/src/META-INF/plugin-common.xml +++ b/idea/src/META-INF/plugin-common.xml @@ -3117,6 +3117,15 @@ language="kotlin" /> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/MapGetWithNotNullAssertionOperatorInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/MapGetWithNotNullAssertionOperatorInspection.kt new file mode 100644 index 00000000000..06fd623191a --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/MapGetWithNotNullAssertionOperatorInspection.kt @@ -0,0 +1,64 @@ +/* + * 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.inspections + +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall +import org.jetbrains.kotlin.idea.core.replaced +import org.jetbrains.kotlin.idea.intentions.callExpression +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe + +class MapGetWithNotNullAssertionOperatorInspection : AbstractKotlinInspection() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = + postfixExpressionVisitor(fun(expression: KtPostfixExpression) { + if (expression.operationToken != KtTokens.EXCLEXCL) return + if (expression.getReplacementData() == null) return + if (expression.baseExpression?.resolveToCall()?.resultingDescriptor?.fqNameSafe != FqName("kotlin.collections.Map.get")) return + holder.registerProblem( + expression.operationReference, + "map.get() with not-null assertion operator (!!)", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + ReplaceWithGetValueCallFix() + ) + }) + + private class ReplaceWithGetValueCallFix : LocalQuickFix { + override fun getName() = "Replace with 'getValue' call" + override fun getFamilyName() = name + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val expression = descriptor.psiElement.parent as? KtPostfixExpression ?: return + val (reference, index) = expression.getReplacementData() ?: return + val replaced = expression.replaced(KtPsiFactory(expression).createExpressionByPattern("$0.getValue($1)", reference, index)) + replaced.findExistingEditor()?.caretModel?.moveToOffset(replaced.endOffset) + } + } +} + +private fun KtPostfixExpression.getReplacementData(): Pair? { + val base = baseExpression + when (base) { + is KtQualifiedExpression -> { + if (base.callExpression?.calleeExpression?.text != "get") return null + val reference = base.receiverExpression + val index = base.callExpression?.valueArguments?.firstOrNull()?.getArgumentExpression() ?: return null + return reference to index + } + is KtArrayAccessExpression -> { + val reference = base.arrayExpression ?: return null + val index = base.indexExpressions.firstOrNull() ?: return null + return reference to index + } + else -> return null + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/.inspection b/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/.inspection new file mode 100644 index 00000000000..006dfd8a004 --- /dev/null +++ b/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.MapGetWithNotNullAssertionOperatorInspection diff --git a/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/get.kt b/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/get.kt new file mode 100644 index 00000000000..26e1fe89a2b --- /dev/null +++ b/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/get.kt @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun test(map: Map) { + val s = map.get(1)!! +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/get.kt.after b/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/get.kt.after new file mode 100644 index 00000000000..43f6104cd3c --- /dev/null +++ b/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/get.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun test(map: Map) { + val s = map.getValue(1) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/getValue.kt b/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/getValue.kt new file mode 100644 index 00000000000..0cc2bef1d54 --- /dev/null +++ b/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/getValue.kt @@ -0,0 +1,5 @@ +// PROBLEM: none +// WITH_RUNTIME +fun test(map: Map) { + val s = map.getValue(1)!! +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/indexedAccess.kt b/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/indexedAccess.kt new file mode 100644 index 00000000000..1aaa64a7e05 --- /dev/null +++ b/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/indexedAccess.kt @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun test(map: Map) { + val s = map[1]!! +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/indexedAccess.kt.after b/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/indexedAccess.kt.after new file mode 100644 index 00000000000..43f6104cd3c --- /dev/null +++ b/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/indexedAccess.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun test(map: Map) { + val s = map.getValue(1) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/list.kt b/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/list.kt new file mode 100644 index 00000000000..05a2119bd1c --- /dev/null +++ b/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/list.kt @@ -0,0 +1,5 @@ +// PROBLEM: none +// WITH_RUNTIME +fun test(list: List) { + val s = list.get(1)!! +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/noNotNullAssersion.kt b/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/noNotNullAssersion.kt new file mode 100644 index 00000000000..3c679834394 --- /dev/null +++ b/idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/noNotNullAssersion.kt @@ -0,0 +1,5 @@ +// PROBLEM: none +// WITH_RUNTIME +fun test(map: Map) { + val s = map.get(1) +} \ 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 2ae7908297f..09d94cac49d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -3280,6 +3280,44 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class MapGetWithNotNullAssertionOperator extends AbstractLocalInspectionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath); + } + + public void testAllFilesPresentInMapGetWithNotNullAssertionOperator() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("get.kt") + public void testGet() throws Exception { + runTest("idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/get.kt"); + } + + @TestMetadata("getValue.kt") + public void testGetValue() throws Exception { + runTest("idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/getValue.kt"); + } + + @TestMetadata("indexedAccess.kt") + public void testIndexedAccess() throws Exception { + runTest("idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/indexedAccess.kt"); + } + + @TestMetadata("list.kt") + public void testList() throws Exception { + runTest("idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/list.kt"); + } + + @TestMetadata("noNotNullAssersion.kt") + public void testNoNotNullAssersion() throws Exception { + runTest("idea/testData/inspectionsLocal/mapGetWithNotNullAssertionOperator/noNotNullAssersion.kt"); + } + } + @TestMetadata("idea/testData/inspectionsLocal/mayBeConstant") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)