diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml index cd4328259e0..4470e47ac4f 100644 --- a/idea/resources/META-INF/plugin-common.xml +++ b/idea/resources/META-INF/plugin-common.xml @@ -3577,6 +3577,15 @@ language="kotlin" /> + + diff --git a/idea/resources/META-INF/plugin-common.xml.191 b/idea/resources/META-INF/plugin-common.xml.191 index 117f26f7f53..5d0061af27b 100644 --- a/idea/resources/META-INF/plugin-common.xml.191 +++ b/idea/resources/META-INF/plugin-common.xml.191 @@ -3578,6 +3578,15 @@ language="kotlin" /> + + diff --git a/idea/resources/META-INF/plugin-common.xml.192 b/idea/resources/META-INF/plugin-common.xml.192 index 117f26f7f53..5d0061af27b 100644 --- a/idea/resources/META-INF/plugin-common.xml.192 +++ b/idea/resources/META-INF/plugin-common.xml.192 @@ -3578,6 +3578,15 @@ language="kotlin" /> + + diff --git a/idea/resources/inspectionDescriptions/RedundantElvisReturnNull.html b/idea/resources/inspectionDescriptions/RedundantElvisReturnNull.html new file mode 100644 index 00000000000..bbdee967e37 --- /dev/null +++ b/idea/resources/inspectionDescriptions/RedundantElvisReturnNull.html @@ -0,0 +1,15 @@ + + +This inspection reports redundant '?: return null': +

+
+fun foo(): Int? {
+  ...
+}
+
+fun test() : Int? {
+  return foo() ?: return null
+}
+
+ + \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantElvisReturnNullInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantElvisReturnNullInspection.kt new file mode 100644 index 00000000000..86f26c2bae2 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantElvisReturnNullInspection.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.openapi.util.TextRange +import com.intellij.psi.PsiElementVisitor +import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.startOffset + +class RedundantElvisReturnNullInspection : AbstractKotlinInspection() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { + return returnExpressionVisitor(fun(returnExpression: KtReturnExpression) { + if ((returnExpression.returnedExpression?.deparenthesize() as? KtConstantExpression)?.text != KtTokens.NULL_KEYWORD.value) return + + val binaryExpression = returnExpression.getStrictParentOfType()?.takeIf { + it == it.getStrictParentOfType()?.returnedExpression?.deparenthesize() + } ?: return + val right = binaryExpression.right?.deparenthesize()?.takeIf { it == returnExpression } ?: return + if (binaryExpression.operationToken == KtTokens.ELSE_KEYWORD) return + if (binaryExpression.left?.resolveToCall()?.resultingDescriptor?.returnType?.isMarkedNullable != true) return + + holder.registerProblem( + binaryExpression, + "Redundant '?: return null'", + ProblemHighlightType.LIKE_UNUSED_SYMBOL, + TextRange(binaryExpression.operationReference.startOffset, right.endOffset).shiftLeft(binaryExpression.startOffset), + RemoveRedundantElvisReturnNull() + ) + }) + } + + private fun KtExpression.deparenthesize() = KtPsiUtil.deparenthesize(this) + + private class RemoveRedundantElvisReturnNull : LocalQuickFix { + override fun getName() = "Remove redundant '?: return null'" + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val binaryExpression = descriptor.psiElement as? KtBinaryExpression ?: return + val left = binaryExpression.left ?: return + binaryExpression.replace(left) + } + } +} diff --git a/idea/testData/inspectionsLocal/redundantElvisReturnNull/.inspection b/idea/testData/inspectionsLocal/redundantElvisReturnNull/.inspection new file mode 100644 index 00000000000..4d7799343fd --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantElvisReturnNull/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.RedundantElvisReturnNullInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantElvisReturnNull/basic.kt b/idea/testData/inspectionsLocal/redundantElvisReturnNull/basic.kt new file mode 100644 index 00000000000..9243f7ada54 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantElvisReturnNull/basic.kt @@ -0,0 +1,5 @@ +fun foo(): Int? = null + +fun test() : Int? { + return foo() ?: return null +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantElvisReturnNull/basic.kt.after b/idea/testData/inspectionsLocal/redundantElvisReturnNull/basic.kt.after new file mode 100644 index 00000000000..c7e497d726d --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantElvisReturnNull/basic.kt.after @@ -0,0 +1,5 @@ +fun foo(): Int? = null + +fun test() : Int? { + return foo() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantElvisReturnNull/notInReturn.kt b/idea/testData/inspectionsLocal/redundantElvisReturnNull/notInReturn.kt new file mode 100644 index 00000000000..e2db13b2b96 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantElvisReturnNull/notInReturn.kt @@ -0,0 +1,7 @@ +// PROBLEM: none +fun foo(): Int? = null + +fun test() : Int? { + val i = foo() ?: return null + return 0 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantElvisReturnNull/notReturnNull.kt b/idea/testData/inspectionsLocal/redundantElvisReturnNull/notReturnNull.kt new file mode 100644 index 00000000000..c0a886c1d2f --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantElvisReturnNull/notReturnNull.kt @@ -0,0 +1,6 @@ +// PROBLEM: none +fun foo(): Int? = null + +fun test() : Int? { + return foo() ?: return 1 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantElvisReturnNull/uselessElvis.kt b/idea/testData/inspectionsLocal/redundantElvisReturnNull/uselessElvis.kt new file mode 100644 index 00000000000..088d355bfc7 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantElvisReturnNull/uselessElvis.kt @@ -0,0 +1,6 @@ +// PROBLEM: none +fun bar(): Int = 1 + +fun test() : Int? { + return bar() ?: return null // USELESS_ELVIS +} \ 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 6e50cc2804d..5a4a9754de4 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -7144,6 +7144,39 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/redundantElvisReturnNull") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class RedundantElvisReturnNull extends AbstractLocalInspectionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInRedundantElvisReturnNull() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/redundantElvisReturnNull"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), true); + } + + @TestMetadata("basic.kt") + public void testBasic() throws Exception { + runTest("idea/testData/inspectionsLocal/redundantElvisReturnNull/basic.kt"); + } + + @TestMetadata("notInReturn.kt") + public void testNotInReturn() throws Exception { + runTest("idea/testData/inspectionsLocal/redundantElvisReturnNull/notInReturn.kt"); + } + + @TestMetadata("notReturnNull.kt") + public void testNotReturnNull() throws Exception { + runTest("idea/testData/inspectionsLocal/redundantElvisReturnNull/notReturnNull.kt"); + } + + @TestMetadata("uselessElvis.kt") + public void testUselessElvis() throws Exception { + runTest("idea/testData/inspectionsLocal/redundantElvisReturnNull/uselessElvis.kt"); + } + } + @TestMetadata("idea/testData/inspectionsLocal/redundantEmptyInitializerBlock") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)