diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateEqualsAndHashcodeAction.kt b/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateEqualsAndHashcodeAction.kt index 14f44938b08..69e44cc0c9f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateEqualsAndHashcodeAction.kt +++ b/idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateEqualsAndHashcodeAction.kt @@ -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 + 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()}") diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/ArrayInDataClassInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/ArrayInDataClassInspection.kt index a053d567a6c..ce530b023e0 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/ArrayInDataClassInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/ArrayInDataClassInspection.kt @@ -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()?.run { + KotlinGenerateEqualsAndHashcodeAction().doInvoke(project, descriptor.psiElement.findExistingEditor(), this) + } + } + } } diff --git a/idea/testData/codeInsight/generate/equalsWithHashCode/dataClassHasArrayProperty.kt b/idea/testData/codeInsight/generate/equalsWithHashCode/dataClassHasArrayProperty.kt new file mode 100644 index 00000000000..cc3147906ab --- /dev/null +++ b/idea/testData/codeInsight/generate/equalsWithHashCode/dataClassHasArrayProperty.kt @@ -0,0 +1 @@ +data class A(val a: IntArray) \ No newline at end of file diff --git a/idea/testData/codeInsight/generate/equalsWithHashCode/dataClassHasArrayProperty.kt.after b/idea/testData/codeInsight/generate/equalsWithHashCode/dataClassHasArrayProperty.kt.after new file mode 100644 index 00000000000..4f612a74720 --- /dev/null +++ b/idea/testData/codeInsight/generate/equalsWithHashCode/dataClassHasArrayProperty.kt.after @@ -0,0 +1,18 @@ +import java.util.Arrays + +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) + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/arrayInDataClass/.inspection b/idea/testData/inspectionsLocal/arrayInDataClass/.inspection new file mode 100644 index 00000000000..5ec9f6b4eeb --- /dev/null +++ b/idea/testData/inspectionsLocal/arrayInDataClass/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.ArrayInDataClassInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/arrayInDataClass/test.kt b/idea/testData/inspectionsLocal/arrayInDataClass/test.kt new file mode 100644 index 00000000000..5898ab2e7ec --- /dev/null +++ b/idea/testData/inspectionsLocal/arrayInDataClass/test.kt @@ -0,0 +1,3 @@ +// WITH_RUNTIME + +data class A(val a: IntArray) \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/arrayInDataClass/test.kt.after b/idea/testData/inspectionsLocal/arrayInDataClass/test.kt.after new file mode 100644 index 00000000000..17bbc47c276 --- /dev/null +++ b/idea/testData/inspectionsLocal/arrayInDataClass/test.kt.after @@ -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) + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/generate/GenerateHashCodeAndEqualsActionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/generate/GenerateHashCodeAndEqualsActionTestGenerated.java index f65f757387c..18702ca8df7 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/generate/GenerateHashCodeAndEqualsActionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/generate/GenerateHashCodeAndEqualsActionTestGenerated.java @@ -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"); diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 26e32210c38..213f8a41d92 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -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)