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.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
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.caches.resolve.analyzeFully
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||||
import org.jetbrains.kotlin.idea.core.CollectingNameValidator
|
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.core.quoteIfNeeded
|
||||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny
|
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.resolve.source.getPsi
|
||||||
import org.jetbrains.kotlin.types.TypeUtils
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||||
@@ -75,10 +76,20 @@ class KotlinGenerateEqualsAndHashcodeAction : KotlinGenerateMemberActionBase<Kot
|
|||||||
&& targetClass !is KtEnumEntry
|
&& targetClass !is KtEnumEntry
|
||||||
&& !targetClass.isAnnotation()
|
&& !targetClass.isAnnotation()
|
||||||
&& !targetClass.isInterface()
|
&& !targetClass.isInterface()
|
||||||
&& !targetClass.hasModifier(KtTokens.DATA_KEYWORD)
|
&& (!targetClass.isData() || isValidForDataClass(targetClass))
|
||||||
&& getPropertiesToUseInGeneratedMember(targetClass).isNotEmpty()
|
&& 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? {
|
override fun prepareMembersInfo(klass: KtClassOrObject, project: Project, editor: Editor?): Info? {
|
||||||
if (klass !is KtClass) throw AssertionError("Not a class: ${klass.getElementTextWithContext()}")
|
if (klass !is KtClass) throw AssertionError("Not a class: ${klass.getElementTextWithContext()}")
|
||||||
|
|
||||||
|
|||||||
@@ -16,16 +16,18 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.inspections
|
package org.jetbrains.kotlin.idea.inspections
|
||||||
|
|
||||||
import com.intellij.codeInspection.LocalInspectionToolSession
|
import com.intellij.codeInsight.FileModificationService
|
||||||
import com.intellij.codeInspection.ProblemHighlightType
|
import com.intellij.codeInspection.*
|
||||||
import com.intellij.codeInspection.ProblemsHolder
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.psi.PsiElementVisitor
|
import com.intellij.psi.PsiElementVisitor
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
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.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.KtClass
|
import org.jetbrains.kotlin.psi.KtClass
|
||||||
import org.jetbrains.kotlin.psi.KtFunction
|
import org.jetbrains.kotlin.psi.KtFunction
|
||||||
import org.jetbrains.kotlin.psi.KtVisitorVoid
|
import org.jetbrains.kotlin.psi.KtVisitorVoid
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
@@ -44,7 +46,8 @@ class ArrayInDataClassInspection : AbstractKotlinInspection() {
|
|||||||
if (KotlinBuiltIns.isArray(type) || KotlinBuiltIns.isPrimitiveArray(type)) {
|
if (KotlinBuiltIns.isArray(type) || KotlinBuiltIns.isPrimitiveArray(type)) {
|
||||||
holder.registerProblem(parameter,
|
holder.registerProblem(parameter,
|
||||||
"Array property in data class: it's recommended to override equals() / hashCode()",
|
"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);
|
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")
|
@TestMetadata("enum.kt")
|
||||||
public void testEnum() throws Exception {
|
public void testEnum() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/enum.kt");
|
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);
|
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")
|
@TestMetadata("idea/testData/inspectionsLocal/copyWithoutNamedArguments")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user