From bcfab83d1c775371e6d070661aa1649dc9c4bb00 Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Thu, 7 Mar 2019 11:46:35 +0300 Subject: [PATCH] Add "Constructor has non-null self reference parameter" inspection (KT-29799) #KT-29799 Fixed --- idea/resources/META-INF/plugin-common.xml | 9 +++ .../SelfReferenceConstructorParameter.html | 5 ++ ...ReferenceConstructorParameterInspection.kt | 62 +++++++++++++++++++ .../.inspection | 1 + .../basic.kt | 1 + .../basic.kt.after | 1 + .../nullable.kt | 2 + .../LocalInspectionTestGenerated.java | 23 +++++++ 8 files changed, 104 insertions(+) create mode 100644 idea/resources/inspectionDescriptions/SelfReferenceConstructorParameter.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/SelfReferenceConstructorParameterInspection.kt create mode 100644 idea/testData/inspectionsLocal/selfReferenceConstructorParameter/.inspection create mode 100644 idea/testData/inspectionsLocal/selfReferenceConstructorParameter/basic.kt create mode 100644 idea/testData/inspectionsLocal/selfReferenceConstructorParameter/basic.kt.after create mode 100644 idea/testData/inspectionsLocal/selfReferenceConstructorParameter/nullable.kt diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml index ed85296b866..358a1f4d37d 100644 --- a/idea/resources/META-INF/plugin-common.xml +++ b/idea/resources/META-INF/plugin-common.xml @@ -3364,6 +3364,15 @@ language="kotlin" /> + + diff --git a/idea/resources/inspectionDescriptions/SelfReferenceConstructorParameter.html b/idea/resources/inspectionDescriptions/SelfReferenceConstructorParameter.html new file mode 100644 index 00000000000..181a44d678a --- /dev/null +++ b/idea/resources/inspectionDescriptions/SelfReferenceConstructorParameter.html @@ -0,0 +1,5 @@ + + +This inspection reports constructor that has a non-null self reference parameter. A class with such a constructor can never be instantiated. + + \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/SelfReferenceConstructorParameterInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/SelfReferenceConstructorParameterInspection.kt new file mode 100644 index 00000000000..5d877d9a0dd --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/SelfReferenceConstructorParameterInspection.kt @@ -0,0 +1,62 @@ +/* + * 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.ProblemsHolder +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.core.setType +import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.psi.KtParameterList +import org.jetbrains.kotlin.psi.primaryConstructorVisitor +import org.jetbrains.kotlin.psi.psiUtil.containingClass +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.isNullable +import org.jetbrains.kotlin.types.typeUtil.makeNullable + +class SelfReferenceConstructorParameterInspection : AbstractKotlinInspection() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = primaryConstructorVisitor(fun(constructor) { + val parameter = constructor.valueParameterList?.selfReferenceParameter() ?: return + val rangeInElement = parameter.typeReference?.textRange?.shiftRight(-parameter.startOffset) ?: return + holder.registerProblem( + parameter, + rangeInElement, + "Constructor has non-null self reference parameter", + ConvertToNullableTypeFix() + ) + }) + + private fun KtParameterList.selfReferenceParameter(): KtParameter? { + val containingClass = this.containingClass() ?: return null + val className = containingClass.name ?: return null + val parameter = this.parameters.firstOrNull { it.typeReference?.text == className } ?: return null + + val typeReference = parameter.typeReference ?: return null + val context = analyze(BodyResolveMode.PARTIAL) + val type = context[BindingContext.TYPE, typeReference] ?: return null + if (type.isNullable()) return null + if (type.constructor.declarationDescriptor != context[BindingContext.DECLARATION_TO_DESCRIPTOR, containingClass]) return null + + return parameter + } + + private class ConvertToNullableTypeFix : LocalQuickFix { + override fun getName() = "Convert to nullable type" + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val parameter = descriptor.psiElement as? KtParameter ?: return + val typeReference = parameter.typeReference ?: return + val type = parameter.analyze(BodyResolveMode.PARTIAL)[BindingContext.TYPE, typeReference] ?: return + parameter.setType(type.makeNullable()) + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/selfReferenceConstructorParameter/.inspection b/idea/testData/inspectionsLocal/selfReferenceConstructorParameter/.inspection new file mode 100644 index 00000000000..a1d47e6665a --- /dev/null +++ b/idea/testData/inspectionsLocal/selfReferenceConstructorParameter/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.SelfReferenceConstructorParameterInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/selfReferenceConstructorParameter/basic.kt b/idea/testData/inspectionsLocal/selfReferenceConstructorParameter/basic.kt new file mode 100644 index 00000000000..ab640d75448 --- /dev/null +++ b/idea/testData/inspectionsLocal/selfReferenceConstructorParameter/basic.kt @@ -0,0 +1 @@ +class SelfRef(val ref: SelfRef) \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/selfReferenceConstructorParameter/basic.kt.after b/idea/testData/inspectionsLocal/selfReferenceConstructorParameter/basic.kt.after new file mode 100644 index 00000000000..baaa924dae7 --- /dev/null +++ b/idea/testData/inspectionsLocal/selfReferenceConstructorParameter/basic.kt.after @@ -0,0 +1 @@ +class SelfRef(val ref: SelfRef?) \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/selfReferenceConstructorParameter/nullable.kt b/idea/testData/inspectionsLocal/selfReferenceConstructorParameter/nullable.kt new file mode 100644 index 00000000000..9019333ea0a --- /dev/null +++ b/idea/testData/inspectionsLocal/selfReferenceConstructorParameter/nullable.kt @@ -0,0 +1,2 @@ +// PROBLEM: none +class SelfRef(val ref: SelfRef?) \ 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 0cf7b0a8c2e..22cb822bab8 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -7917,6 +7917,29 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/selfReferenceConstructorParameter") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class SelfReferenceConstructorParameter extends AbstractLocalInspectionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath); + } + + public void testAllFilesPresentInSelfReferenceConstructorParameter() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/selfReferenceConstructorParameter"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("basic.kt") + public void testBasic() throws Exception { + runTest("idea/testData/inspectionsLocal/selfReferenceConstructorParameter/basic.kt"); + } + + @TestMetadata("nullable.kt") + public void testNullable() throws Exception { + runTest("idea/testData/inspectionsLocal/selfReferenceConstructorParameter/nullable.kt"); + } + } + @TestMetadata("idea/testData/inspectionsLocal/setterBackingFieldAssignment") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)