From 7b43d5c97255b132ef579d25c9f0e55776f9f452 Mon Sep 17 00:00:00 2001 From: kenji tomita Date: Sat, 17 Nov 2018 03:37:38 +0300 Subject: [PATCH] Add inspection for converting !collection.isEmpty() -> isNotEmpty() #KT-27556 Fixed --- idea/resources/META-INF/plugin-common.xml | 9 ++ .../ReplaceNegatedIsEmptyWithIsNotEmpty.html | 5 ++ ...eNegatedIsEmptyWithIsNotEmptyInspection.kt | 82 +++++++++++++++++++ .../.inspection | 1 + .../simple.kt | 8 ++ .../simple.kt.after | 8 ++ .../LocalInspectionTestGenerated.java | 18 ++++ 7 files changed, 131 insertions(+) create mode 100644 idea/resources/inspectionDescriptions/ReplaceNegatedIsEmptyWithIsNotEmpty.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceNegatedIsEmptyWithIsNotEmptyInspection.kt create mode 100644 idea/testData/inspectionsLocal/replaceNegatedIsEmptyWithIsNotEmpty/.inspection create mode 100644 idea/testData/inspectionsLocal/replaceNegatedIsEmptyWithIsNotEmpty/simple.kt create mode 100644 idea/testData/inspectionsLocal/replaceNegatedIsEmptyWithIsNotEmpty/simple.kt.after diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml index cf8b6e8726b..a83121bf5f9 100644 --- a/idea/resources/META-INF/plugin-common.xml +++ b/idea/resources/META-INF/plugin-common.xml @@ -3196,6 +3196,15 @@ language="kotlin" /> + + diff --git a/idea/resources/inspectionDescriptions/ReplaceNegatedIsEmptyWithIsNotEmpty.html b/idea/resources/inspectionDescriptions/ReplaceNegatedIsEmptyWithIsNotEmpty.html new file mode 100644 index 00000000000..6bcceb66364 --- /dev/null +++ b/idea/resources/inspectionDescriptions/ReplaceNegatedIsEmptyWithIsNotEmpty.html @@ -0,0 +1,5 @@ + + +This inspection reports !collection.isEmpty() call can be replaced with collection.isNotEmpty() + + \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceNegatedIsEmptyWithIsNotEmptyInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceNegatedIsEmptyWithIsNotEmptyInspection.kt new file mode 100644 index 00000000000..86836b56644 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/ReplaceNegatedIsEmptyWithIsNotEmptyInspection.kt @@ -0,0 +1,82 @@ +/* + * 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 com.intellij.psi.PsiElement +import com.intellij.psi.PsiElementVisitor +import org.jetbrains.kotlin.idea.core.replaced +import org.jetbrains.kotlin.idea.inspections.collections.isCalling +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getLastParentOfTypeInRow + +class ReplaceNegatedIsEmptyWithIsNotEmptyInspection : AbstractKotlinInspection() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { + + return simpleNameExpressionVisitor { simpleNameExpression -> + if (simpleNameExpression.isEmptyNegation()) { + holder.registerProblem( + simpleNameExpression, + "Replace negated 'isEmpty' with 'isNotEmpty'", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + ReplaceNegatedIsEmptyWithIsNotEmptyQuickFix() + ) + } + } + } +} + +class ReplaceNegatedIsEmptyWithIsNotEmptyQuickFix : LocalQuickFix { + override fun getName() = "Replace negated 'isEmpty' with 'isNotEmpty'" + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val element = descriptor.psiElement as? KtSimpleNameExpression ?: return + val callExpression = (element.parent as? KtCallExpression) ?: return + val qualifiedExpression = (callExpression.parent as? KtDotQualifiedExpression) ?: return + val prefixExpression = qualifiedExpression.getWrappingPrefixExpressionIfAny() ?: return + + prefixExpression.replaced( + KtPsiFactory(element).createExpressionByPattern( + "$0.isNotEmpty()", + qualifiedExpression.receiverExpression + ) + ) + } +} + +private fun KtSimpleNameExpression.isEmptyNegation(): Boolean { + val callExpression = (parent as? KtCallExpression) ?: return false + val qualifiedExpression = (callExpression.parent as? KtDotQualifiedExpression) ?: return false + val prefixExpression = qualifiedExpression.getWrappingPrefixExpressionIfAny() ?: return false + if (prefixExpression.operationToken != KtTokens.EXCL) return false + return transformations.any { callExpression.isCalling(FqName(it)) } +} + +private fun PsiElement.getWrappingPrefixExpressionIfAny() = + (getLastParentOfTypeInRow() ?: this).parent as? KtPrefixExpression + +private val transformations = listOf( + "java.util.ArrayList.isEmpty", + "java.util.HashMap.isEmpty", + "java.util.HashSet.isEmpty", + "java.util.LinkedHashMap.isEmpty", + "java.util.LinkedHashSet.isEmpty", + "kotlin.collections.isEmpty", + "kotlin.collections.List.isEmpty", + "kotlin.collections.Set.isEmpty", + "kotlin.collections.Map.isEmpty", + "kotlin.collections.MutableList.isEmpty", + "kotlin.collections.MutableSet.isEmpty", + "kotlin.collections.MutableMap.isEmpty" +) \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replaceNegatedIsEmptyWithIsNotEmpty/.inspection b/idea/testData/inspectionsLocal/replaceNegatedIsEmptyWithIsNotEmpty/.inspection new file mode 100644 index 00000000000..96f7774bad4 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceNegatedIsEmptyWithIsNotEmpty/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.ReplaceNegatedIsEmptyWithIsNotEmptyInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replaceNegatedIsEmptyWithIsNotEmpty/simple.kt b/idea/testData/inspectionsLocal/replaceNegatedIsEmptyWithIsNotEmpty/simple.kt new file mode 100644 index 00000000000..4717a67bf5f --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceNegatedIsEmptyWithIsNotEmpty/simple.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// FIX: Replace negated 'isEmpty' with 'isNotEmpty' + +fun test() { + val list = listOf(1,2,3) + if (!list.isEmpty()) { + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replaceNegatedIsEmptyWithIsNotEmpty/simple.kt.after b/idea/testData/inspectionsLocal/replaceNegatedIsEmptyWithIsNotEmpty/simple.kt.after new file mode 100644 index 00000000000..6ec8c225a75 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceNegatedIsEmptyWithIsNotEmpty/simple.kt.after @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// FIX: Replace negated 'isEmpty' with 'isNotEmpty' + +fun test() { + val list = listOf(1,2,3) + if (list.isNotEmpty()) { + } +} \ 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 dba90af24c9..b7bc4bcd650 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -6036,6 +6036,24 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/replaceNegatedIsEmptyWithIsNotEmpty") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ReplaceNegatedIsEmptyWithIsNotEmpty extends AbstractLocalInspectionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath); + } + + public void testAllFilesPresentInReplaceNegatedIsEmptyWithIsNotEmpty() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/replaceNegatedIsEmptyWithIsNotEmpty"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + runTest("idea/testData/inspectionsLocal/replaceNegatedIsEmptyWithIsNotEmpty/simple.kt"); + } + } + @TestMetadata("idea/testData/inspectionsLocal/replacePutWithAssignment") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)