Code Insight: "Generate equals/hashCode" action

This commit is contained in:
Alexey Sedunov
2015-10-16 14:15:07 +03:00
parent d1d0807229
commit 915236a6ba
42 changed files with 1072 additions and 11 deletions
@@ -45,6 +45,7 @@ import org.jetbrains.kotlin.idea.actions.AbstractGotoTestOrCodeActionTest
import org.jetbrains.kotlin.idea.caches.resolve.AbstractIdeLightClassTest
import org.jetbrains.kotlin.idea.codeInsight.*
import org.jetbrains.kotlin.idea.codeInsight.generate.AbstractGenerateActionTest
import org.jetbrains.kotlin.idea.codeInsight.generate.AbstractGenerateHashCodeAndEqualsActionTest
import org.jetbrains.kotlin.idea.codeInsight.generate.AbstractGenerateTestSupportMethodActionTest
import org.jetbrains.kotlin.idea.codeInsight.moveUpDown.AbstractCodeMoverTest
import org.jetbrains.kotlin.idea.codeInsight.surroundWith.AbstractSurroundWithTest
@@ -741,6 +742,10 @@ fun main(args: Array<String>) {
model("codeInsight/generate/testFrameworkSupport")
}
testClass<AbstractGenerateHashCodeAndEqualsActionTest>() {
model("codeInsight/generate/equalsWithHashCode")
}
testClass<AbstractGenerateActionTest>() {
model("codeInsight/generate/secondaryConstructors")
}
+3
View File
@@ -173,6 +173,9 @@
<action id="KotlinGenerateSecondaryConstructor"
class="org.jetbrains.kotlin.idea.actions.generate.KotlinGenerateSecondaryConstructorAction"
text="Secondary Constructor" />
<action id="KotlinGenerateEqualsAndHashCode"
class="org.jetbrains.kotlin.idea.actions.generate.KotlinGenerateEqualsAndHashcodeAction"
text="equals() and hashCode()" />
<add-to-group group-id="GenerateGroup" anchor="first"/>
</group>
@@ -0,0 +1,286 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.actions.generate
import com.intellij.codeInsight.CodeInsightBundle
import com.intellij.codeInsight.CodeInsightSettings
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.intellij.util.IncorrectOperationException
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.core.CollectingNameValidator
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject
import org.jetbrains.kotlin.idea.core.overrideImplement.generateMember
import org.jetbrains.kotlin.idea.core.refactoring.quoteIfNeeded
import org.jetbrains.kotlin.idea.quickfix.insertMembersAfter
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy
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.source.getPsi
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.lastIsInstanceOrNull
import java.util.*
class KotlinGenerateEqualsAndHashcodeAction : KotlinGenerateMemberActionBase<KotlinGenerateEqualsAndHashcodeAction.Info>() {
companion object {
private val LOG = Logger.getInstance(KotlinGenerateEqualsAndHashcodeAction::class.java)
private val MEMBER_RENDERER = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.withOptions {
modifiers = emptySet()
startFromName = true
parameterNameRenderingPolicy = ParameterNameRenderingPolicy.NONE
}
}
class Info(
val needEquals: Boolean,
val needHashCode: Boolean,
val classDescriptor: ClassDescriptor,
val variablesForEquals: List<VariableDescriptor>,
val variablesForHashCode: List<VariableDescriptor>
)
override fun isValidForClass(targetClass: JetClassOrObject): Boolean {
return targetClass is JetClass && targetClass !is JetEnumEntry && !targetClass.isAnnotation()
&& !targetClass.hasModifier(JetTokens.DATA_KEYWORD)
&& targetClass.getPropertiesToUse().isNotEmpty()
}
private tailrec fun ClassDescriptor.findDeclaredFunction(
name: String,
checkSuperClasses: Boolean,
filter: (FunctionDescriptor) -> Boolean
): FunctionDescriptor? {
unsubstitutedMemberScope
.getFunctions(Name.identifier(name), NoLookupLocation.FROM_IDE)
.firstOrNull { it.containingDeclaration == this && it.kind == CallableMemberDescriptor.Kind.DECLARATION && filter(it) }
?.let { return it }
return if (checkSuperClasses) getSuperClassOrAny().findDeclaredFunction(name, checkSuperClasses, filter) else null
}
private fun ClassDescriptor.findDeclaredEquals(checkSupers: Boolean): FunctionDescriptor? {
return findDeclaredFunction("equals", checkSupers) {
it.valueParameters.singleOrNull()?.type == it.builtIns.nullableAnyType && it.typeParameters.isEmpty()
}
}
private fun ClassDescriptor.findDeclaredHashCode(checkSupers: Boolean): FunctionDescriptor? {
return findDeclaredFunction("hashCode", checkSupers) { it.valueParameters.isEmpty() && it.typeParameters.isEmpty() }
}
private fun JetClassOrObject.getPropertiesToUse(): List<JetNamedDeclaration> {
return ArrayList<JetNamedDeclaration>().apply {
getPrimaryConstructorParameters().filterTo(this) { it.hasValOrVar() }
declarations.filterIsInstance<JetProperty>().filterTo(this) f@ {
val descriptor = it.resolveToDescriptor()
when (descriptor) {
is ValueParameterDescriptor -> true
is PropertyDescriptor -> descriptor.accessors.all { it.isDefault }
else -> false
}
}
}
}
private fun confirmRewrite(
targetClass: JetClass,
equalsDescriptor: FunctionDescriptor,
hashCodeDescriptor: FunctionDescriptor
): Boolean {
if (ApplicationManager.getApplication().isUnitTestMode) return true
val functionsText = "'${MEMBER_RENDERER.render(equalsDescriptor)}' and '${MEMBER_RENDERER.render(hashCodeDescriptor)}'"
val message = "Functions $functionsText are already defined\nfor class ${targetClass.name}. Do you want to delete them and proceed?"
return Messages.showYesNoDialog(targetClass.project, message,
CodeInsightBundle.message("generate.equals.and.hashcode.already.defined.title"),
Messages.getQuestionIcon()) == Messages.YES
}
override fun prepareMembersInfo(klass: JetClassOrObject, project: Project, editor: Editor): Info? {
if (klass !is JetClass) throw AssertionError("Not a class: ${klass.getElementTextWithContext()}")
val context = klass.analyzeFully()
val classDescriptor = context.get(BindingContext.CLASS, klass) ?: return null
val equalsDescriptor = classDescriptor.findDeclaredEquals(false)
val hashCodeDescriptor = classDescriptor.findDeclaredHashCode(false)
var needEquals = equalsDescriptor == null
var needHashCode = hashCodeDescriptor == null
if (!needEquals && !needHashCode) {
if (!confirmRewrite(klass, equalsDescriptor!!, hashCodeDescriptor!!)) return null
runWriteAction {
try {
equalsDescriptor.source.getPsi()?.delete()
hashCodeDescriptor.source.getPsi()?.delete()
needEquals = true
needHashCode = true
} catch(e: IncorrectOperationException) {
LOG.error(e)
}
}
}
val properties = klass.getPropertiesToUse()
if (properties.isEmpty() || ApplicationManager.getApplication().isUnitTestMode) {
val descriptors = properties.map { context[BindingContext.DECLARATION_TO_DESCRIPTOR, it] as VariableDescriptor }
return Info(needEquals, needHashCode, classDescriptor, descriptors, descriptors)
}
return with(KotlinGenerateEqualsWizard(project, klass, properties, needEquals, needHashCode)) {
if (!showAndGet()) return null
Info(needEquals,
needHashCode,
classDescriptor,
getPropertiesForEquals().map { context[BindingContext.DECLARATION_TO_DESCRIPTOR, it] as VariableDescriptor },
getPropertiesForHashCode().map { context[BindingContext.DECLARATION_TO_DESCRIPTOR, it] as VariableDescriptor })
}
}
private fun FunctionDescriptor.isMemberOfAny() = containingDeclaration == builtIns.any
private fun generateFunctionSkeleton(descriptor: FunctionDescriptor, project: Project): JetNamedFunction {
return OverrideMemberChooserObject
.create(project, descriptor, descriptor, OverrideMemberChooserObject.BodyType.EMPTY)
.generateMember(project) as JetNamedFunction
}
private fun generateEquals(project: Project, info: Info): JetNamedFunction? {
with(info) {
if (!needEquals) return null
val superEquals = classDescriptor.getSuperClassOrAny().findDeclaredEquals(true)!!
val equalsFun = generateFunctionSkeleton(superEquals, project)
val paramName = equalsFun.valueParameters.first().name!!.quoteIfNeeded()
var typeForCast = IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(classDescriptor)
val typeParams = classDescriptor.defaultType.constructor.parameters
if (typeParams.isNotEmpty()) {
typeForCast += typeParams.map { "*" }.joinToString(prefix = "<", postfix = ">")
}
val useIsCheck = CodeInsightSettings.getInstance().USE_INSTANCEOF_ON_EQUALS_PARAMETER
val isNotInstanceCondition = if (useIsCheck) "$paramName !is $typeForCast" else "$paramName?.javaClass != javaClass"
val bodyText = StringBuilder().apply {
append("if (this === $paramName) return true\n")
append("if ($isNotInstanceCondition) return false\n")
if (!superEquals.isMemberOfAny()) {
append("if (!super.equals($paramName)) return false\n")
}
if (variablesForEquals.isNotEmpty()) {
if (!useIsCheck) {
append("\n$paramName as $typeForCast\n")
}
append('\n')
variablesForEquals.forEach {
val propName = it.name.asString()
append("if ($propName != $paramName.$propName) return false\n")
}
append('\n')
}
append("return true")
}
equalsFun.bodyExpression!!.replace(JetPsiFactory(project).createExpression("{\n$bodyText\n}"))
return equalsFun
}
}
private fun generateHashCode(project: Project, info: Info): JetNamedFunction? {
fun VariableDescriptor.genVariableHashCode(parenthesesNeeded: Boolean): String {
val ref = name.asString().quoteIfNeeded()
val isNullable = TypeUtils.isNullableType(type)
val builtIns = builtIns
var text = when (type.constructor.declarationDescriptor) {
builtIns.byte, builtIns.short, builtIns.int -> ref
else -> if (isNullable) "$ref?.hashCode()" else "$ref.hashCode()"
}
if (isNullable) {
text += " ?: 0"
if (parenthesesNeeded) {
text = "($text)"
}
}
return text
}
with(info) {
if (!needHashCode) return null
val superHashCode = classDescriptor.getSuperClassOrAny().findDeclaredHashCode(true)!!
val hashCodeFun = generateFunctionSkeleton(superHashCode, project)
val propertyIterator = variablesForHashCode.iterator()
val initialValue = when {
!superHashCode.isMemberOfAny() -> "super.hashCode()"
propertyIterator.hasNext() -> propertyIterator.next().genVariableHashCode(false)
else -> "0"
}
val bodyText = if (propertyIterator.hasNext()) {
val validator = CollectingNameValidator(variablesForEquals.map { it.name.asString().quoteIfNeeded() })
val resultVarName = KotlinNameSuggester.suggestNameByName("result", validator)
StringBuilder().apply {
append("var $resultVarName = $initialValue\n")
propertyIterator.forEach { append("$resultVarName += 31 * $resultVarName + ${it.genVariableHashCode(true)}\n") }
append("return $resultVarName")
}.toString()
} else "return $initialValue"
hashCodeFun.bodyExpression!!.replace(JetPsiFactory(project).createExpression("{\n$bodyText\n}"))
return hashCodeFun
}
}
override fun generateMembers(project: Project, editor: Editor, info: Info): List<JetDeclaration> {
val targetClass = info.classDescriptor.source.getPsi() as JetClass
val prototypes = ArrayList<JetDeclaration>(2)
.apply {
addIfNotNull(generateEquals(project, info))
addIfNotNull(generateHashCode(project, info))
}
val anchor = with(targetClass.declarations) { lastIsInstanceOrNull<JetNamedFunction>() ?: lastOrNull() }
return insertMembersAfter(editor, targetClass, prototypes, anchor)
}
}
@@ -0,0 +1,130 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.actions.generate
import com.intellij.codeInsight.CodeInsightBundle
import com.intellij.codeInsight.CodeInsightSettings
import com.intellij.codeInsight.generation.ui.AbstractGenerateEqualsWizard
import com.intellij.ide.wizard.StepAdapter
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.VerticalFlowLayout
import com.intellij.refactoring.classMembers.AbstractMemberInfoModel
import com.intellij.ui.NonFocusableCheckBox
import com.intellij.util.containers.HashMap
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionPanel
import org.jetbrains.kotlin.psi.JetClass
import org.jetbrains.kotlin.psi.JetNamedDeclaration
import org.jetbrains.kotlin.psi.psiUtil.isInheritable
import org.jetbrains.kotlin.utils.keysToMap
import javax.swing.JLabel
import javax.swing.JPanel
class KotlinGenerateEqualsWizard(
project: Project,
klass: JetClass,
properties: List<JetNamedDeclaration>,
needEquals: Boolean,
needHashCode: Boolean
) : AbstractGenerateEqualsWizard<JetClass, JetNamedDeclaration, KotlinMemberInfo>(
project, KotlinGenerateEqualsWizard.BuilderImpl(klass, properties, needEquals, needHashCode)
) {
private object MemberInfoModelImpl : AbstractMemberInfoModel<JetNamedDeclaration, KotlinMemberInfo>()
private class BuilderImpl(
private val klass: JetClass,
properties: List<JetNamedDeclaration>,
needEquals: Boolean,
needHashCode: Boolean
) : AbstractGenerateEqualsWizard.Builder<JetClass, JetNamedDeclaration, KotlinMemberInfo>() {
private val equalsPanel: KotlinMemberSelectionPanel?
private val hashCodePanel: KotlinMemberSelectionPanel?
private val memberInfos = properties.map { createMemberInfo(it) }
private val membersToHashCode = HashMap(properties.keysToMap { createMemberInfo(it) })
init {
equalsPanel = if (needEquals) {
KotlinMemberSelectionPanel("Choose p&roperties to be included in equals()", memberInfos, null).apply {
table.memberInfoModel = MemberInfoModelImpl
}
} else null
hashCodePanel = if (needHashCode) {
KotlinMemberSelectionPanel("Choose p&roperties to be included in hashCode()", memberInfos, null).apply {
table.memberInfoModel = MemberInfoModelImpl
}
} else null
}
private fun createMemberInfo(it: JetNamedDeclaration) = KotlinMemberInfo(it).apply { isChecked = true }
override fun getPsiClass() = klass
override fun getClassFields() = memberInfos
override fun getFieldsToHashCode() = membersToHashCode
override fun getFieldsToNonNull() = HashMap<JetNamedDeclaration, KotlinMemberInfo>()
override fun getEqualsPanel() = equalsPanel
override fun getHashCodePanel() = hashCodePanel
override fun getNonNullPanel() = null
override fun updateHashCodeMemberInfos(equalsMemberInfos: MutableCollection<KotlinMemberInfo>) {
hashCodePanel?.table?.setMemberInfos(equalsMemberInfos.map { membersToHashCode[it.member] })
}
override fun updateNonNullMemberInfos(equalsMemberInfos: MutableCollection<KotlinMemberInfo>?) {
}
}
private object OptionsStep : StepAdapter() {
private val panel = JPanel(VerticalFlowLayout())
init {
with(NonFocusableCheckBox(CodeInsightBundle.message("generate.equals.hashcode.accept.sublcasses"))) {
isSelected = CodeInsightSettings.getInstance().USE_INSTANCEOF_ON_EQUALS_PARAMETER
addActionListener { CodeInsightSettings.getInstance().USE_INSTANCEOF_ON_EQUALS_PARAMETER = isSelected }
panel.add(this)
}
panel.add(JLabel(CodeInsightBundle.message("generate.equals.hashcode.accept.sublcasses.explanation")))
}
override fun getComponent() = panel
}
override fun addSteps() {
if (myEqualsPanel != null && myClass.isInheritable()) {
addStep(OptionsStep)
}
super.addSteps()
}
override fun doOKAction() {
myEqualsPanel?.let { updateHashCodeMemberInfos(it.table.selectedMemberInfos) }
super.doOKAction()
}
fun getPropertiesForEquals() = myEqualsPanel?.table?.selectedMemberInfos?.map { it.member } ?: emptyList()
fun getPropertiesForHashCode() = myHashCodePanel?.table?.selectedMemberInfos?.map { it.member } ?: emptyList()
}
@@ -28,18 +28,18 @@ import org.jetbrains.kotlin.psi.JetClassOrObject
import org.jetbrains.kotlin.psi.JetDeclaration
abstract class KotlinGenerateMemberActionBase<Info : Any> : KotlinGenerateActionBase() {
protected abstract fun prepareMembersInfo(klass: JetClassOrObject, project: Project): Info?
protected abstract fun prepareMembersInfo(klass: JetClassOrObject, project: Project, editor: Editor): Info?
protected abstract fun generateMembers(editor: Editor, info: Info): List<JetDeclaration>
protected abstract fun generateMembers(project: Project, editor: Editor, info: Info): List<JetDeclaration>
override fun invoke(project: Project, editor: Editor, file: PsiFile) {
if (!CodeInsightUtilBase.prepareEditorForWrite(editor)) return
if (!FileDocumentManager.getInstance().requestWriting(editor.document, project)) return
val klass = getTargetClass(editor, file) ?: return
val membersInfo = prepareMembersInfo(klass, project) ?: return
val membersInfo = prepareMembersInfo(klass, project, editor) ?: return
project.executeWriteCommand(commandName, this) {
val newMembers = generateMembers(editor, membersInfo)
val newMembers = generateMembers(project, editor, membersInfo)
GlobalInspectionContextBase.cleanupElements(project, null, *newMembers.toTypedArray())
newMembers.firstOrNull()?.let { GenerateMembersUtil.positionCaret(editor, it, false) }
}
@@ -100,7 +100,7 @@ class KotlinGenerateSecondaryConstructorAction : KotlinGenerateMemberActionBase<
}
}
override fun prepareMembersInfo(klass: JetClassOrObject, project: Project): Info? {
override fun prepareMembersInfo(klass: JetClassOrObject, project: Project, editor: Editor): Info? {
val context = klass.analyzeFully()
val classDescriptor = context.get(BindingContext.CLASS, klass) ?: return null
val superConstructors = chooseSuperConstructors(klass, classDescriptor).map { it.descriptor as ConstructorDescriptor }
@@ -108,7 +108,7 @@ class KotlinGenerateSecondaryConstructorAction : KotlinGenerateMemberActionBase<
return Info(propertiesToInitialize, superConstructors, classDescriptor)
}
override fun generateMembers(editor: Editor, info: Info): List<JetDeclaration> {
override fun generateMembers(project: Project, editor: Editor, info: Info): List<JetDeclaration> {
val targetClass = info.classDescriptor.source.getPsi() as? JetClass ?: return emptyList()
fun Info.findAnchor(): PsiElement? {
@@ -0,0 +1,2 @@
// NOT_APPLICABLE
annotation class A<caret>()
@@ -0,0 +1,2 @@
// NOT_APPLICABLE
data class A<caret>(val n: Int)
@@ -0,0 +1,6 @@
// NOT_APPLICABLE
enum class A {<caret>
fun foo() {
}
}
@@ -0,0 +1,5 @@
class A<T>(val n: T) {<caret>
fun foo() {
}
}
@@ -0,0 +1,20 @@
class A<T>(val n: T) {
fun foo() {
}
<caret>override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as A<*>
if (n != other.n) return false
return true
}
override fun hashCode(): Int{
return n?.hashCode() ?: 0
}
}
@@ -0,0 +1,6 @@
// USE_IS_CHECK
class A<T>(val n: T) {<caret>
fun foo() {
}
}
@@ -0,0 +1,19 @@
// USE_IS_CHECK
class A<T>(val n: T) {
fun foo() {
}
<caret>override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other !is A<*>) return false
if (n != other.n) return false
return true
}
override fun hashCode(): Int{
return n?.hashCode() ?: 0
}
}
@@ -0,0 +1,6 @@
// NOT_APPLICABLE
interface A {<caret>
fun foo() {
}
}
@@ -0,0 +1,7 @@
class A(val n: Int, val s: String) {<caret>
val f: Float = 1.0f
fun foo() {
}
}
@@ -0,0 +1,27 @@
class A(val n: Int, val s: String) {
val f: Float = 1.0f
fun foo() {
}
<caret>override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as A
if (n != other.n) return false
if (s != other.s) return false
if (f != other.f) return false
return true
}
override fun hashCode(): Int{
var result = n
result += 31 * result + s.hashCode()
result += 31 * result + f.hashCode()
return result
}
}
@@ -0,0 +1,7 @@
class A(val n: Int?, val s: String) {<caret>
val f: Float? = null
fun foo() {
}
}
@@ -0,0 +1,27 @@
class A(val n: Int?, val s: String) {
val f: Float? = null
fun foo() {
}
<caret>override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as A
if (n != other.n) return false
if (s != other.s) return false
if (f != other.f) return false
return true
}
override fun hashCode(): Int{
var result = n ?: 0
result += 31 * result + s.hashCode()
result += 31 * result + (f?.hashCode() ?: 0)
return result
}
}
@@ -0,0 +1,12 @@
open class X {
override fun equals(other: Any?) = super.equals(other)
override fun hashCode() = super.hashCode()
}
class A(val n: Int, val s: String) : X() {<caret>
val f: Float = 1.0f
fun foo() {
}
}
@@ -0,0 +1,34 @@
open class X {
override fun equals(other: Any?) = super.equals(other)
override fun hashCode() = super.hashCode()
}
class A(val n: Int, val s: String) : X() {
val f: Float = 1.0f
fun foo() {
}
<caret>override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other?.javaClass != javaClass) return false
if (!super.equals(other)) return false
other as A
if (n != other.n) return false
if (s != other.s) return false
if (f != other.f) return false
return true
}
override fun hashCode(): Int{
var result = super.hashCode()
result += 31 * result + n
result += 31 * result + s.hashCode()
result += 31 * result + f.hashCode()
return result
}
}
@@ -0,0 +1,5 @@
class A(val result: Int, val s: String) {<caret>
fun foo() {
}
}
@@ -0,0 +1,23 @@
class A(val result: Int, val s: String) {
fun foo() {
}
<caret>override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as A
if (result != other.result) return false
if (s != other.s) return false
return true
}
override fun hashCode(): Int{
var result1 = result
result1 += 31 * result1 + s.hashCode()
return result1
}
}
@@ -0,0 +1,6 @@
// NOT_APPLICABLE
class A {<caret>
fun foo() {
}
}
@@ -0,0 +1,6 @@
// FORCED
class A {<caret>
fun foo() {
}
}
@@ -0,0 +1,16 @@
// FORCED
class A {
fun foo() {
}
<caret>override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other?.javaClass != javaClass) return false
return true
}
override fun hashCode(): Int{
return 0
}
}
@@ -0,0 +1,11 @@
// FORCED
open class X {
override fun equals(other: Any?) = super.equals(other)
override fun hashCode() = super.hashCode()
}
class A : X() {<caret>
fun foo() {
}
}
@@ -0,0 +1,22 @@
// FORCED
open class X {
override fun equals(other: Any?) = super.equals(other)
override fun hashCode() = super.hashCode()
}
class A : X() {
fun foo() {
}
<caret>override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other?.javaClass != javaClass) return false
if (!super.equals(other)) return false
return true
}
override fun hashCode(): Int{
return super.hashCode()
}
}
@@ -0,0 +1,6 @@
// NOT_APPLICABLE
object A {<caret>
fun foo() {
}
}
@@ -0,0 +1,5 @@
class A(val n: Int) {<caret>
fun foo() {
}
}
@@ -0,0 +1,20 @@
class A(val n: Int) {
fun foo() {
}
<caret>override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as A
if (n != other.n) return false
return true
}
override fun hashCode(): Int{
return n
}
}
@@ -0,0 +1,5 @@
class A(val n: Int?) {<caret>
fun foo() {
}
}
@@ -0,0 +1,20 @@
class A(val n: Int?) {
fun foo() {
}
<caret>override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as A
if (n != other.n) return false
return true
}
override fun hashCode(): Int{
return n ?: 0
}
}
@@ -0,0 +1,6 @@
// USE_IS_CHECK
class A(val n: Int) {<caret>
fun foo() {
}
}
@@ -0,0 +1,19 @@
// USE_IS_CHECK
class A(val n: Int) {
fun foo() {
}
<caret>override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other !is A) return false
if (n != other.n) return false
return true
}
override fun hashCode(): Int{
return n
}
}
@@ -0,0 +1,9 @@
public class X {
public boolean equals(Object object) {
return super.equals(object);
}
public int hashCode() {
return super.hashCode();
}
}
@@ -0,0 +1,5 @@
class A(val n: Int) : X() {<caret>
fun foo() {
}
}
@@ -0,0 +1,23 @@
class A(val n: Int) : X() {
fun foo() {
}
<caret>override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other?.javaClass != javaClass) return false
if (!super.equals(other)) return false
other as A
if (n != other.n) return false
return true
}
override fun hashCode(): Int{
var result = super.hashCode()
result += 31 * result + n
return result
}
}
@@ -0,0 +1,10 @@
open class X {
override fun equals(other: Any?) = super.equals(other)
override fun hashCode() = super.hashCode()
}
class A(val n: Int) : X() {<caret>
fun foo() {
}
}
@@ -0,0 +1,28 @@
open class X {
override fun equals(other: Any?) = super.equals(other)
override fun hashCode() = super.hashCode()
}
class A(val n: Int) : X() {
fun foo() {
}
<caret>override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other?.javaClass != javaClass) return false
if (!super.equals(other)) return false
other as A
if (n != other.n) return false
return true
}
override fun hashCode(): Int{
var result = super.hashCode()
result += 31 * result + n
return result
}
}
@@ -17,9 +17,12 @@
package org.jetbrains.kotlin.idea.codeInsight.generate
import com.intellij.codeInsight.actions.CodeInsightAction
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.Presentation
import com.intellij.openapi.util.io.FileUtil
import com.intellij.refactoring.util.CommonRefactoringUtil
import com.intellij.testFramework.PlatformTestUtil
import com.intellij.testFramework.TestActionEvent
import junit.framework.TestCase
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase
@@ -29,6 +32,20 @@ import org.jetbrains.kotlin.test.JetTestUtils
import java.io.File
abstract class AbstractGenerateActionTest : JetLightCodeInsightFixtureTestCase() {
protected open fun createAction(fileText: String): CodeInsightAction {
val actionClassName = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// ACTION_CLASS: ")
return Class.forName(actionClassName).newInstance() as CodeInsightAction
}
private fun testAction(action: AnAction, forced: Boolean): Presentation {
val e = TestActionEvent(action)
action.beforeActionPerformedUpdate(e)
if (forced || (e.presentation.isEnabled && e.presentation.isVisible)) {
action.actionPerformed(e)
}
return e.presentation
}
protected open fun doTest(path: String) {
val fileText = FileUtil.loadFile(File(path), true)
@@ -50,17 +67,19 @@ abstract class AbstractGenerateActionTest : JetLightCodeInsightFixtureTestCase()
}
myFixture.configureByFile(path)
val actionClassName = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// ACTION_CLASS: ")
val action = Class.forName(actionClassName).newInstance() as CodeInsightAction
val action = createAction(fileText)
val isApplicableExpected = !InTextDirectivesUtils.isDirectiveDefined(fileText, "// NOT_APPLICABLE")
val isForced = InTextDirectivesUtils.isDirectiveDefined(fileText, "// FORCED")
val presentation = myFixture.testAction(action)
TestCase.assertEquals(isApplicableExpected, presentation.isEnabled)
val presentation = testAction(action, isForced)
if (!isForced) {
TestCase.assertEquals(isApplicableExpected, presentation.isEnabled)
}
assert(!conflictFile.exists()) { "Conflict file $conflictFile should not exist" }
if (isApplicableExpected) {
if (isForced || isApplicableExpected) {
val afterFile = File("$path.after")
TestCase.assertTrue(afterFile.exists())
myFixture.checkResult(FileUtil.loadFile(afterFile, true))
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.codeInsight.generate
import com.intellij.codeInsight.CodeInsightSettings
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.idea.actions.generate.KotlinGenerateEqualsAndHashcodeAction
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import java.io.File
abstract class AbstractGenerateHashCodeAndEqualsActionTest : AbstractGenerateActionTest() {
override fun createAction(fileText: String) = KotlinGenerateEqualsAndHashcodeAction()
override fun doTest(path: String) {
val fileText = FileUtil.loadFile(File(path), true)
val codeInsightSettings = CodeInsightSettings.getInstance()
val useInstanceOfOnEqualsParameterOld = codeInsightSettings.USE_INSTANCEOF_ON_EQUALS_PARAMETER
try {
codeInsightSettings.USE_INSTANCEOF_ON_EQUALS_PARAMETER =
InTextDirectivesUtils.isDirectiveDefined(fileText, "// USE_IS_CHECK")
super.doTest(path)
} finally {
codeInsightSettings.USE_INSTANCEOF_ON_EQUALS_PARAMETER = useInstanceOfOnEqualsParameterOld
}
}
}
@@ -0,0 +1,151 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.codeInsight.generate;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.JetTestUtils;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/codeInsight/generate/equalsWithHashCode")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class GenerateHashCodeAndEqualsActionTestGenerated extends AbstractGenerateHashCodeAndEqualsActionTest {
public void testAllFilesPresentInEqualsWithHashCode() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/codeInsight/generate/equalsWithHashCode"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("annotation.kt")
public void testAnnotation() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/annotation.kt");
doTest(fileName);
}
@TestMetadata("dataClass.kt")
public void testDataClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/dataClass.kt");
doTest(fileName);
}
@TestMetadata("enum.kt")
public void testEnum() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/enum.kt");
doTest(fileName);
}
@TestMetadata("genericClass.kt")
public void testGenericClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/genericClass.kt");
doTest(fileName);
}
@TestMetadata("genericClassWithIsCheck.kt")
public void testGenericClassWithIsCheck() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/genericClassWithIsCheck.kt");
doTest(fileName);
}
@TestMetadata("interface.kt")
public void testInterface() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/interface.kt");
doTest(fileName);
}
@TestMetadata("multipleVars.kt")
public void testMultipleVars() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/multipleVars.kt");
doTest(fileName);
}
@TestMetadata("multipleVarsNullable.kt")
public void testMultipleVarsNullable() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsNullable.kt");
doTest(fileName);
}
@TestMetadata("multipleVarsWithSuperClass.kt")
public void testMultipleVarsWithSuperClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/multipleVarsWithSuperClass.kt");
doTest(fileName);
}
@TestMetadata("nameClash.kt")
public void testNameClash() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/nameClash.kt");
doTest(fileName);
}
@TestMetadata("noVars.kt")
public void testNoVars() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/noVars.kt");
doTest(fileName);
}
@TestMetadata("noVarsForced.kt")
public void testNoVarsForced() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/noVarsForced.kt");
doTest(fileName);
}
@TestMetadata("noVarsForcedWithSuperClass.kt")
public void testNoVarsForcedWithSuperClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/noVarsForcedWithSuperClass.kt");
doTest(fileName);
}
@TestMetadata("object.kt")
public void testObject() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/object.kt");
doTest(fileName);
}
@TestMetadata("singleVar.kt")
public void testSingleVar() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/singleVar.kt");
doTest(fileName);
}
@TestMetadata("singleVarNullable.kt")
public void testSingleVarNullable() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/singleVarNullable.kt");
doTest(fileName);
}
@TestMetadata("singleVarWithIsCheck.kt")
public void testSingleVarWithIsCheck() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/singleVarWithIsCheck.kt");
doTest(fileName);
}
@TestMetadata("singleVarWithJavaSuperClass.kt")
public void testSingleVarWithJavaSuperClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/singleVarWithJavaSuperClass.kt");
doTest(fileName);
}
@TestMetadata("singleVarWithSuperClass.kt")
public void testSingleVarWithSuperClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/codeInsight/generate/equalsWithHashCode/singleVarWithSuperClass.kt");
doTest(fileName);
}
}