diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml index b52f5a8201a..83bdc03a458 100644 --- a/idea/resources/META-INF/plugin-common.xml +++ b/idea/resources/META-INF/plugin-common.xml @@ -3248,6 +3248,15 @@ language="kotlin" /> + + diff --git a/idea/resources/inspectionDescriptions/RedundantRequireNotNullCall.html b/idea/resources/inspectionDescriptions/RedundantRequireNotNullCall.html new file mode 100644 index 00000000000..989194a2afc --- /dev/null +++ b/idea/resources/inspectionDescriptions/RedundantRequireNotNullCall.html @@ -0,0 +1,11 @@ + +This inspection reports redundant requireNotNull or checkNotNull call: + +
+fun foo(i: Int) {
+  requireNotNull(i) // This 'i' is always not null, so this 'requireNotNull' call is redundant.
+}
+
+ + + \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantRequireNotNullCallInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantRequireNotNullCallInspection.kt new file mode 100644 index 00000000000..cacf8ae28da --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantRequireNotNullCallInspection.kt @@ -0,0 +1,74 @@ +/* + * 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.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade +import org.jetbrains.kotlin.idea.inspections.collections.isCalling +import org.jetbrains.kotlin.idea.project.languageVersionSettings +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtReferenceExpression +import org.jetbrains.kotlin.psi.callExpressionVisitor +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.referenceExpression +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoBefore +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.isNullable + +class RedundantRequireNotNullCallInspection : AbstractKotlinInspection() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = callExpressionVisitor(fun(callExpression) { + val callee = callExpression.calleeExpression ?: return + val context = callExpression.analyze(BodyResolveMode.PARTIAL) + if (!callExpression.isCalling(FqName("kotlin.requireNotNull"), context) + && !callExpression.isCalling(FqName("kotlin.checkNotNull"), context) + ) return + + val argument = callExpression.valueArguments.firstOrNull()?.getArgumentExpression()?.referenceExpression() ?: return + val descriptor = argument.getResolvedCall(context)?.resultingDescriptor ?: return + val type = descriptor.returnType ?: return + if (argument.isNullable(descriptor, type, context)) return + + val functionName = callee.text + holder.registerProblem( + callee, + "Redundant '$functionName' call", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + RemoveRequireNotNullCallFix(functionName) + ) + }) + + private fun KtReferenceExpression.isNullable(descriptor: CallableDescriptor, type: KotlinType, context: BindingContext): Boolean { + if (!type.isNullable()) return false + val dataFlowValueFactory = this.getResolutionFacade().getFrontendService(DataFlowValueFactory::class.java) + val dataFlow = dataFlowValueFactory.createDataFlowValue(this, type, context, descriptor) + val stableTypes = context.getDataFlowInfoBefore(this).getStableTypes(dataFlow, this.languageVersionSettings) + return stableTypes.none { !it.isNullable() } + } +} + +private class RemoveRequireNotNullCallFix(private val functionName: String) : LocalQuickFix { + override fun getName() = "Remove '$functionName' call" + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val callExpression = descriptor.psiElement.getStrictParentOfType() ?: return + val qualifiedExpression = callExpression.getQualifiedExpressionForSelector() + (qualifiedExpression ?: callExpression).delete() + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantRequireNotNullCall/.inspection b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/.inspection new file mode 100644 index 00000000000..dbb0ce71354 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.RedundantRequireNotNullCallInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantRequireNotNullCall/checkNotNull.kt b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/checkNotNull.kt new file mode 100644 index 00000000000..0b21f827230 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/checkNotNull.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME +fun test(s: String) { + println(1) + kotlin.checkNotNull(s) + println(2) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantRequireNotNullCall/checkNotNull.kt.after b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/checkNotNull.kt.after new file mode 100644 index 00000000000..9e26962c0ce --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/checkNotNull.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME +fun test(s: String) { + println(1) + println(2) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull.kt b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull.kt new file mode 100644 index 00000000000..0aeb1d72ae9 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME +fun test(s: String) { + println(1) + requireNotNull(s) + println(2) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull.kt.after b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull.kt.after new file mode 100644 index 00000000000..9e26962c0ce --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME +fun test(s: String) { + println(1) + println(2) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull2.kt b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull2.kt new file mode 100644 index 00000000000..1f608d7ff21 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull2.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME +fun test(s: String?) { + if (s != null) { + println(1) + requireNotNull(s) { "" } + println(2) + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull2.kt.after b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull2.kt.after new file mode 100644 index 00000000000..05ecbff68ff --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull2.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +fun test(s: String?) { + if (s != null) { + println(1) + println(2) + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull3.kt b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull3.kt new file mode 100644 index 00000000000..7a73ac5aaa1 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull3.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME +fun test(s: String?) { + requireNotNull(s) + requireNotNull(s) + println(1) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull3.kt.after b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull3.kt.after new file mode 100644 index 00000000000..4b9f7246171 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull3.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME +fun test(s: String?) { + requireNotNull(s) + println(1) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantRequireNotNullCall/nullable.kt b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/nullable.kt new file mode 100644 index 00000000000..3a14d124cd7 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/nullable.kt @@ -0,0 +1,5 @@ +// PROBLEM: none +// WITH_RUNTIME +fun test(s: String?) { + requireNotNull(s) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantRequireNotNullCall/nullable2.kt b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/nullable2.kt new file mode 100644 index 00000000000..8f8ee26ada0 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/nullable2.kt @@ -0,0 +1,11 @@ +// PROBLEM: none +// WITH_RUNTIME +class Test { + var s: String? = null + + fun test() { + if (s != null) { + requireNotNull(s) + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantRequireNotNullCall/require.kt b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/require.kt new file mode 100644 index 00000000000..12cce5ff2ab --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantRequireNotNullCall/require.kt @@ -0,0 +1,5 @@ +// PROBLEM: none +// WITH_RUNTIME +fun test(b: Boolean) { + require(b) +} \ 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 2a1de9df46c..b146948826e 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -5206,6 +5206,54 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/redundantRequireNotNullCall") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class RedundantRequireNotNullCall extends AbstractLocalInspectionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath); + } + + public void testAllFilesPresentInRedundantRequireNotNullCall() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/redundantRequireNotNullCall"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("checkNotNull.kt") + public void testCheckNotNull() throws Exception { + runTest("idea/testData/inspectionsLocal/redundantRequireNotNullCall/checkNotNull.kt"); + } + + @TestMetadata("notNull.kt") + public void testNotNull() throws Exception { + runTest("idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull.kt"); + } + + @TestMetadata("notNull2.kt") + public void testNotNull2() throws Exception { + runTest("idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull2.kt"); + } + + @TestMetadata("notNull3.kt") + public void testNotNull3() throws Exception { + runTest("idea/testData/inspectionsLocal/redundantRequireNotNullCall/notNull3.kt"); + } + + @TestMetadata("nullable.kt") + public void testNullable() throws Exception { + runTest("idea/testData/inspectionsLocal/redundantRequireNotNullCall/nullable.kt"); + } + + @TestMetadata("nullable2.kt") + public void testNullable2() throws Exception { + runTest("idea/testData/inspectionsLocal/redundantRequireNotNullCall/nullable2.kt"); + } + + @TestMetadata("require.kt") + public void testRequire() throws Exception { + runTest("idea/testData/inspectionsLocal/redundantRequireNotNullCall/require.kt"); + } + } + @TestMetadata("idea/testData/inspectionsLocal/redundantReturnLabel") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)