Add quick fix to generate equals / hashcode in data class with arrays
So #KT-15893 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
5df5a001a1
commit
b9fb7cc3a5
+13
-2
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
data class A<caret>(val a: IntArray)
|
||||
+18
@@ -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)
|
||||
}
|
||||
}
|
||||
+6
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user