Add quick fix to generate equals / hashcode in data class with arrays

So #KT-15893 Fixed
This commit is contained in:
Toshiaki Kameyama
2017-06-14 11:45:33 +03:00
committed by Mikhail Glukhikh
parent 5df5a001a1
commit b9fb7cc3a5
9 changed files with 99 additions and 6 deletions
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.CollectingNameValidator
@@ -35,12 +36,12 @@ import org.jetbrains.kotlin.idea.core.insertMembersAfter
import org.jetbrains.kotlin.idea.core.quoteIfNeeded
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.utils.addIfNotNull
@@ -75,9 +76,19 @@ class KotlinGenerateEqualsAndHashcodeAction : KotlinGenerateMemberActionBase<Kot
&& targetClass !is KtEnumEntry
&& !targetClass.isAnnotation()
&& !targetClass.isInterface()
&& !targetClass.hasModifier(KtTokens.DATA_KEYWORD)
&& (!targetClass.isData() || isValidForDataClass(targetClass))
&& getPropertiesToUseInGeneratedMember(targetClass).isNotEmpty()
}
private fun isValidForDataClass(targetClass: KtClass): Boolean {
val constructor = targetClass.primaryConstructor ?: return false
val context = constructor.analyze(BodyResolveMode.PARTIAL)
return constructor.valueParameters.any { parameter ->
parameter.hasValOrVar() && context.get(BindingContext.TYPE, parameter.typeReference)?.let { type ->
KotlinBuiltIns.isArray(type) || KotlinBuiltIns.isPrimitiveArray(type)
} ?: false
}
}
override fun prepareMembersInfo(klass: KtClassOrObject, project: Project, editor: Editor?): Info? {
if (klass !is KtClass) throw AssertionError("Not a class: ${klass.getElementTextWithContext()}")
@@ -16,16 +16,18 @@
package org.jetbrains.kotlin.idea.inspections
import com.intellij.codeInspection.LocalInspectionToolSession
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.codeInsight.FileModificationService
import com.intellij.codeInspection.*
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.idea.actions.generate.KotlinGenerateEqualsAndHashcodeAction
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtVisitorVoid
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.util.OperatorNameConventions
@@ -44,7 +46,8 @@ class ArrayInDataClassInspection : AbstractKotlinInspection() {
if (KotlinBuiltIns.isArray(type) || KotlinBuiltIns.isPrimitiveArray(type)) {
holder.registerProblem(parameter,
"Array property in data class: it's recommended to override equals() / hashCode()",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING)
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
GenerateEqualsAndHashcodeFix())
}
}
}
@@ -71,4 +74,19 @@ class ArrayInDataClassInspection : AbstractKotlinInspection() {
}
}
}
class GenerateEqualsAndHashcodeFix : LocalQuickFix {
override fun getName() = "Generate equals() and hashCode()"
override fun getFamilyName() = name
override fun startInWriteAction() = false
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
if (!FileModificationService.getInstance().preparePsiElementForWrite(descriptor.psiElement)) return
descriptor.psiElement.getNonStrictParentOfType<KtClass>()?.run {
KotlinGenerateEqualsAndHashcodeAction().doInvoke(project, descriptor.psiElement.findExistingEditor(), this)
}
}
}
}
@@ -0,0 +1 @@
data class A<caret>(val a: IntArray)
@@ -0,0 +1,18 @@
import java.util.Arrays
data class A(val a: IntArray) {
<caret>override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as A
if (!Arrays.equals(a, other.a)) return false
return true
}
override fun hashCode(): Int {
return Arrays.hashCode(a)
}
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.inspections.ArrayInDataClassInspection
@@ -0,0 +1,3 @@
// WITH_RUNTIME
data class A(<caret>val a: IntArray)
@@ -0,0 +1,20 @@
import java.util.Arrays
// WITH_RUNTIME
data class A(val a: IntArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as A
if (!Arrays.equals(a, other.a)) return false
return true
}
override fun hashCode(): Int {
return Arrays.hashCode(a)
}
}
@@ -60,6 +60,12 @@ public class GenerateHashCodeAndEqualsActionTestGenerated extends AbstractGenera
doTest(fileName);
}
@TestMetadata("dataClassHasArrayProperty.kt")
public void testDataClassHasArrayProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/dataClassHasArrayProperty.kt");
doTest(fileName);
}
@TestMetadata("enum.kt")
public void testEnum() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/enum.kt");
@@ -36,6 +36,21 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("idea/testData/inspectionsLocal/arrayInDataClass")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ArrayInDataClass extends AbstractLocalInspectionTest {
public void testAllFilesPresentInArrayInDataClass() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/arrayInDataClass"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("test.kt")
public void testTest() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/arrayInDataClass/test.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/inspectionsLocal/copyWithoutNamedArguments")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)