From 18fbf5729d151423d4282e0fb8378e975f047bc5 Mon Sep 17 00:00:00 2001 From: kvirolainen Date: Thu, 3 Sep 2020 16:29:45 +0200 Subject: [PATCH] Inspection: convert initialized val to non-null type ^KT-35757 Fixed --- .../messages/KotlinBundle.properties | 1 + idea/resources/META-INF/inspections.xml | 8 +++ ...InitializedValToNonNullTypeInspection.html | 5 ++ ...rtInitializedValToNonNullTypeInspection.kt | 70 +++++++++++++++++++ .../.inspection | 1 + .../assignToMethodCall.kt | 5 ++ .../assignToMethodCall.kt.after | 5 ++ .../assignToVariable.kt | 4 ++ .../assignToVariable.kt.after | 4 ++ .../basic.kt | 3 + .../basic.kt.after | 3 + .../negative.kt | 7 ++ .../LocalInspectionTestGenerated.java | 33 +++++++++ 13 files changed, 149 insertions(+) create mode 100644 idea/resources/inspectionDescriptions/ConvertInitializedValToNonNullTypeInspection.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/ConvertInitializedValToNonNullTypeInspection.kt create mode 100644 idea/testData/inspectionsLocal/convertInitializedValToNonNullType/.inspection create mode 100644 idea/testData/inspectionsLocal/convertInitializedValToNonNullType/assignToMethodCall.kt create mode 100644 idea/testData/inspectionsLocal/convertInitializedValToNonNullType/assignToMethodCall.kt.after create mode 100644 idea/testData/inspectionsLocal/convertInitializedValToNonNullType/assignToVariable.kt create mode 100644 idea/testData/inspectionsLocal/convertInitializedValToNonNullType/assignToVariable.kt.after create mode 100644 idea/testData/inspectionsLocal/convertInitializedValToNonNullType/basic.kt create mode 100644 idea/testData/inspectionsLocal/convertInitializedValToNonNullType/basic.kt.after create mode 100644 idea/testData/inspectionsLocal/convertInitializedValToNonNullType/negative.kt diff --git a/idea/resources-en/messages/KotlinBundle.properties b/idea/resources-en/messages/KotlinBundle.properties index 82c2a78530a..deee2af6205 100644 --- a/idea/resources-en/messages/KotlinBundle.properties +++ b/idea/resources-en/messages/KotlinBundle.properties @@ -2000,6 +2000,7 @@ action.ConfigureKotlinInProject.text=Configure Kotlin in Project action.KotlinConsoleREPL.text=Kotlin REPL action.LibraryToSourceDependencySupportToggleAction.text=Toggle library to source dependency support +inspection.convert.initialized.val.to.non.null.type.display.name=Convert initialized 'val' to non-null type inspection.unused.unary.operator.display.name=Unused unary operator inspection.incomplete.destructuring.declaration.display.name=Incomplete destructuring declaration inspection.replace.guard.clause.with.function.call.display.name=Replace guard clause with kotlin's function call diff --git a/idea/resources/META-INF/inspections.xml b/idea/resources/META-INF/inspections.xml index de99d3c1ad5..228ddb90394 100644 --- a/idea/resources/META-INF/inspections.xml +++ b/idea/resources/META-INF/inspections.xml @@ -2503,6 +2503,14 @@ language="kotlin" key="inspection.unused.unary.operator.display.name" bundle="messages.KotlinBundle"/> + + + +This inspection reports nullable type of initialized 'val' should be converted into non-nullable type. + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/ConvertInitializedValToNonNullTypeInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/ConvertInitializedValToNonNullTypeInspection.kt new file mode 100644 index 00000000000..76fee41d613 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/ConvertInitializedValToNonNullTypeInspection.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2010-2020 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 org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.isNull +import org.jetbrains.kotlin.resolve.calls.callUtil.getType +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.TypeUtils + +class ConvertInitializedValToNonNullTypeInspection : AbstractKotlinInspection() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = + propertyVisitor(fun(property) { + val typeReference = property.typeReference ?: return + + if (shouldConvertToNonNullType(property)) { + holder.registerProblem( + typeReference, + "Initialized 'val' should be converted to non-null type", + ProblemHighlightType.LIKE_UNUSED_SYMBOL, + RemoveRedundantNullableTypeQuickfix() + ) + } + }) + + companion object { + private fun shouldConvertToNonNullType(property: KtProperty): Boolean { + val initializer = property.initializer ?: return false + val type = property.resolveToDescriptorIfAny()?.type ?: return false + if (!TypeUtils.isNullableType(type)) return false + + when (initializer) { + is KtConstantExpression -> { + return !initializer.isNull() + } + is KtStringTemplateExpression -> { + return true + } + is KtNameReferenceExpression, is KtCallExpression -> { + val context = initializer.analyze(BodyResolveMode.PARTIAL) + val assignedType = initializer.getType(context) ?: return false + return !TypeUtils.isNullableType(assignedType) + } + else -> return false + } + } + } +} + +class RemoveRedundantNullableTypeQuickfix : LocalQuickFix { + override fun getName() = "Convert initialized 'val' to non-null type" + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val element = descriptor.psiElement + val factory = KtPsiFactory(project) + element.replace(factory.createType(element.text.removeSuffix("?"))) + } +} diff --git a/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/.inspection b/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/.inspection new file mode 100644 index 00000000000..699cc6de182 --- /dev/null +++ b/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.ConvertInitializedValToNonNullTypeInspection diff --git a/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/assignToMethodCall.kt b/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/assignToMethodCall.kt new file mode 100644 index 00000000000..ee74e28354c --- /dev/null +++ b/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/assignToMethodCall.kt @@ -0,0 +1,5 @@ +fun foo() { + val s1: String? = bar() +} + +fun bar(): String = "" diff --git a/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/assignToMethodCall.kt.after b/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/assignToMethodCall.kt.after new file mode 100644 index 00000000000..c8ea1138d18 --- /dev/null +++ b/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/assignToMethodCall.kt.after @@ -0,0 +1,5 @@ +fun foo() { + val s1: String = bar() +} + +fun bar(): String = "" diff --git a/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/assignToVariable.kt b/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/assignToVariable.kt new file mode 100644 index 00000000000..e97453f5a08 --- /dev/null +++ b/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/assignToVariable.kt @@ -0,0 +1,4 @@ +fun foo() { + val s0: String = "" + val s1: String? = s0 +} diff --git a/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/assignToVariable.kt.after b/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/assignToVariable.kt.after new file mode 100644 index 00000000000..534c22a4756 --- /dev/null +++ b/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/assignToVariable.kt.after @@ -0,0 +1,4 @@ +fun foo() { + val s0: String = "" + val s1: String = s0 +} diff --git a/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/basic.kt b/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/basic.kt new file mode 100644 index 00000000000..763191b262e --- /dev/null +++ b/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/basic.kt @@ -0,0 +1,3 @@ +fun foo() { + val s: String? = "Hello" +} diff --git a/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/basic.kt.after b/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/basic.kt.after new file mode 100644 index 00000000000..b684aae813d --- /dev/null +++ b/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/basic.kt.after @@ -0,0 +1,3 @@ +fun foo() { + val s: String = "Hello" +} diff --git a/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/negative.kt b/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/negative.kt new file mode 100644 index 00000000000..4719b503902 --- /dev/null +++ b/idea/testData/inspectionsLocal/convertInitializedValToNonNullType/negative.kt @@ -0,0 +1,7 @@ +// PROBLEM: none +fun foo() { + val s1: String? = bar() + val s2: String? = null +} + +fun bar(): String? = "" diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index f2ca589e9cc..ecb25058a82 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -3480,6 +3480,39 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/convertInitializedValToNonNullType") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ConvertInitializedValToNonNullType extends AbstractLocalInspectionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInConvertInitializedValToNonNullType() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/inspectionsLocal/convertInitializedValToNonNullType"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), null, true); + } + + @TestMetadata("assignToMethodCall.kt") + public void testAssignToMethodCall() throws Exception { + runTest("idea/testData/inspectionsLocal/convertInitializedValToNonNullType/assignToMethodCall.kt"); + } + + @TestMetadata("assignToVariable.kt") + public void testAssignToVariable() throws Exception { + runTest("idea/testData/inspectionsLocal/convertInitializedValToNonNullType/assignToVariable.kt"); + } + + @TestMetadata("basic.kt") + public void testBasic() throws Exception { + runTest("idea/testData/inspectionsLocal/convertInitializedValToNonNullType/basic.kt"); + } + + @TestMetadata("negative.kt") + public void testNegative() throws Exception { + runTest("idea/testData/inspectionsLocal/convertInitializedValToNonNullType/negative.kt"); + } + } + @TestMetadata("idea/testData/inspectionsLocal/convertNaNEquality") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)