New J2K: rewrite getters and setters conversion in post-processing
KT-31700 fixed
This commit is contained in:
-428
@@ -1,428 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.nj2k.postProcessing
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.LocalSearchScope
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.MemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.core.setVisibility
|
||||
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference
|
||||
import org.jetbrains.kotlin.idea.refactoring.isInterfaceClass
|
||||
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
|
||||
import org.jetbrains.kotlin.lexer.KtKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.nj2k.NewJ2kPostProcessing
|
||||
import org.jetbrains.kotlin.nj2k.parentOfType
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.addRemoveModifier.setModifierList
|
||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
|
||||
|
||||
class ConvertGettersAndSetters : NewJ2kPostProcessing {
|
||||
override val writeActionNeeded: Boolean = true
|
||||
|
||||
data class PropertyData(
|
||||
var name: String,
|
||||
var getter: Accessor? = null,
|
||||
var setter: Accessor? = null
|
||||
)
|
||||
|
||||
data class PropertyDataExtended(
|
||||
val name: String,
|
||||
val getter: Accessor,
|
||||
val setter: Accessor?,
|
||||
val needOverride: Boolean,
|
||||
val target: KtProperty,
|
||||
val visibility: KtModifierKeywordToken
|
||||
)
|
||||
|
||||
data class Accessor(
|
||||
val name: String,
|
||||
val target: KtProperty?,
|
||||
val ktFunction: KtFunction,
|
||||
val isPure: Boolean,
|
||||
val isFromCurrentDeclaration: Boolean
|
||||
)
|
||||
|
||||
private fun KtExpression.statements() =
|
||||
if (this is KtBlockExpression) statements
|
||||
else listOf(this)
|
||||
|
||||
|
||||
private fun KtFunction.getGetterTarget(): KtProperty? =
|
||||
collectDescendantsOfType<KtReturnExpression>()
|
||||
.singleOrNull()
|
||||
?.returnedExpression
|
||||
?.unpackedReferenceToProperty()
|
||||
?.takeIf {
|
||||
it.type() == this.type()!!
|
||||
}
|
||||
|
||||
|
||||
private fun KtFunction.asGetter(klass: KtClassOrObject): Accessor? {
|
||||
if (valueParameters.isNotEmpty()) return null
|
||||
if (typeParameters.isNotEmpty()) return null
|
||||
val name = getterName() ?: return null
|
||||
val target = getGetterTarget()
|
||||
val isPure = target != null
|
||||
&& bodyExpression?.statements()?.size == 1
|
||||
|
||||
return Accessor(
|
||||
name,
|
||||
target,
|
||||
this,
|
||||
isPure,
|
||||
isFromCurrentDeclaration = containingClassOrObject == klass
|
||||
)
|
||||
}
|
||||
|
||||
private inline fun <reified D : MemberDescriptor, reified P : KtElement> KtClassOrObject.declarations(): List<P> =
|
||||
(analyze()[BindingContext.DECLARATION_TO_DESCRIPTOR, this] as ClassDescriptor)
|
||||
.unsubstitutedMemberScope.getContributedDescriptors { true }
|
||||
.filterIsInstance<D>()
|
||||
.mapNotNull { it.findPsi() as? P }
|
||||
|
||||
|
||||
private fun KtFunction.isProcedure() =
|
||||
bodyExpression is KtBlockExpression? && !hasDeclaredReturnType()
|
||||
|| getReturnTypeReference()?.typeElement?.text == "Unit"
|
||||
|
||||
private fun KtFunction.asSetter(klass: KtClassOrObject): Accessor? {
|
||||
if (typeParameters.isNotEmpty()) return null
|
||||
if (valueParameters.size != 1) return null
|
||||
if (!isProcedure()) return null
|
||||
val name = setterName() ?: return null
|
||||
|
||||
val target = bodyExpression
|
||||
?.statements()
|
||||
?.singleOrNull()
|
||||
?.let {
|
||||
if (it is KtBinaryExpression) {
|
||||
if (it.operationToken != KtTokens.EQ) return@let null
|
||||
val right = it.right as? KtNameReferenceExpression ?: return@let null
|
||||
if (right.resolve() != valueParameters.single()) return@let null
|
||||
it.left?.unpackedReferenceToProperty()
|
||||
} else null
|
||||
}?.takeIf {
|
||||
it.type() == valueParameters.single().type()
|
||||
}
|
||||
return Accessor(
|
||||
name,
|
||||
target,
|
||||
this,
|
||||
isPure = target != null || bodyExpression?.statements()?.isEmpty() == true,
|
||||
isFromCurrentDeclaration = containingClassOrObject == klass
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private fun KtFunction.getterName() =
|
||||
name?.takeIf { JvmAbi.isGetterName(it) }
|
||||
?.removePrefix("get")
|
||||
?.takeIf {
|
||||
it.isNotEmpty() && it.first().isUpperCase()
|
||||
|| it.startsWith("is") && it.length > 2 && it[2].isUpperCase()
|
||||
}?.decapitalize()
|
||||
?.escaped()
|
||||
|
||||
private fun KtFunction.setterName() =
|
||||
name?.takeIf { JvmAbi.isSetterName(it) }
|
||||
?.removePrefix("set")
|
||||
?.takeIf { it.isNotEmpty() && it.first().isUpperCase() }
|
||||
?.decapitalize()
|
||||
?.escaped()
|
||||
|
||||
private fun KtClassOrObject.collectGetterAndSettersPairs(): Sequence<PropertyData> {
|
||||
val properties = mutableMapOf<String, PropertyData>()
|
||||
val functions = declarations<SimpleFunctionDescriptor, KtFunction>()
|
||||
|
||||
for (declaration in functions) {
|
||||
declaration.asGetter(this)
|
||||
?.also { getter ->
|
||||
properties.getOrPut(getter.name.removePrefix("is").decapitalize()) {
|
||||
PropertyData(getter.name)
|
||||
}.also { it.getter = getter }
|
||||
}
|
||||
|
||||
declaration.asSetter(this)
|
||||
?.also { setter ->
|
||||
properties.getOrPut(setter.name) {
|
||||
PropertyData(setter.name)
|
||||
}.also { it.setter = setter }
|
||||
}
|
||||
}
|
||||
return properties.values.asSequence()
|
||||
}
|
||||
|
||||
private fun List<KtModifierListOwner>.maxVisibility(): KtModifierKeywordToken =
|
||||
map { it.visibilityModifierTypeOrDefault() }
|
||||
.maxBy {
|
||||
when (it) {
|
||||
KtTokens.PUBLIC_KEYWORD -> 4
|
||||
KtTokens.INTERNAL_KEYWORD -> 3
|
||||
KtTokens.PROTECTED_KEYWORD -> 2
|
||||
KtTokens.PRIVATE_KEYWORD -> 1
|
||||
else -> 0
|
||||
}
|
||||
} ?: KtTokens.DEFAULT_VISIBILITY_KEYWORD
|
||||
|
||||
private fun generatePropertiesData(klass: KtClassOrObject): List<PropertyDataExtended> {
|
||||
val propertyByName = klass
|
||||
.declarations<PropertyDescriptor, KtProperty>()
|
||||
.map { it.name!! to it }
|
||||
.toMap()
|
||||
val factory = KtPsiFactory(klass)
|
||||
|
||||
|
||||
return klass.collectGetterAndSettersPairs().mapNotNull { (name, getter, setter) ->
|
||||
if (getter == null) return@mapNotNull null
|
||||
|
||||
val referencedProperty = propertyByName[name]
|
||||
if (referencedProperty != null
|
||||
&& getter.target != referencedProperty
|
||||
&& referencedProperty.containingClassOrObject == klass
|
||||
) return@mapNotNull null
|
||||
|
||||
val setterAndGetterHaveTheSameTypes =
|
||||
setter != null
|
||||
&& setter.ktFunction.valueParameters.single().type() == getter.ktFunction.type()
|
||||
|
||||
if (klass.isInterfaceClass()
|
||||
&& setter != null
|
||||
&& !setterAndGetterHaveTheSameTypes
|
||||
) return@mapNotNull null
|
||||
|
||||
val needOverride =
|
||||
getter.target?.hasModifier(KtTokens.OVERRIDE_KEYWORD) == true
|
||||
|| getter.ktFunction.hasModifier(KtTokens.OVERRIDE_KEYWORD)
|
||||
|| setter?.ktFunction?.hasModifier(KtTokens.OVERRIDE_KEYWORD) == true
|
||||
|
||||
val realTarget =
|
||||
getter.target?.takeIf {
|
||||
!it.hasUsagesOutsideOf(klass.containingKtFile, listOfNotNull(getter.ktFunction, setter?.ktFunction))
|
||||
|| getter.isPure && setter?.isPure != false
|
||||
}
|
||||
?.takeIf {
|
||||
getter.isFromCurrentDeclaration
|
||||
}
|
||||
|
||||
if (getter.target?.visibilityModifierType() == KtTokens.PUBLIC_KEYWORD
|
||||
&& (!getter.isPure || setter?.isPure == false)
|
||||
) return@mapNotNull null
|
||||
|
||||
if (realTarget == null
|
||||
&& referencedProperty != null
|
||||
&& referencedProperty.containingClassOrObject == klass
|
||||
) return@mapNotNull null
|
||||
|
||||
val target = realTarget
|
||||
// ?.takeIf {
|
||||
// getter.isFromCurrentDeclaration
|
||||
// }
|
||||
?: factory.createProperty(
|
||||
name,
|
||||
getter.ktFunction.getReturnTypeReference()?.text,
|
||||
isVar = true
|
||||
)
|
||||
|
||||
val visibility =
|
||||
listOfNotNull(setter?.ktFunction, getter.ktFunction, target).maxVisibility()
|
||||
|
||||
|
||||
|
||||
PropertyDataExtended(
|
||||
name,
|
||||
getter,
|
||||
setter?.takeIf { setterAndGetterHaveTheSameTypes },
|
||||
needOverride,
|
||||
target,
|
||||
visibility
|
||||
)
|
||||
}.toList()
|
||||
}
|
||||
|
||||
private fun KtExpression.isReferenceToThis() =
|
||||
when (this) {
|
||||
is KtThisExpression -> instanceReference.resolve() == parentOfType<KtClassOrObject>()
|
||||
is KtReferenceExpression -> resolve() == parentOfType<KtClassOrObject>()
|
||||
else -> false
|
||||
}
|
||||
|
||||
private fun <T : KtExpression> T.withReplacedExpressionInBody(
|
||||
from: KtElement,
|
||||
to: KtExpression,
|
||||
replaceOnlyWriteUsages: Boolean
|
||||
): T = also {
|
||||
from.usages()
|
||||
.map { it.element }
|
||||
.filter { it.isInsideOf(listOf(this)) }
|
||||
.forEach { reference ->
|
||||
val parent = reference.parent
|
||||
val referenceExpression = when {
|
||||
parent is KtQualifiedExpression
|
||||
&& parent.receiverExpression.isReferenceToThis() ->
|
||||
parent
|
||||
else -> reference
|
||||
}
|
||||
if (!replaceOnlyWriteUsages
|
||||
|| (referenceExpression.parent as? KtExpression)?.asAssignment()?.left == referenceExpression
|
||||
) {
|
||||
referenceExpression.replace(to)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun KtProperty.addGetter(getterFunction: Accessor, target: KtProperty): KtPropertyAccessor {
|
||||
val factory = KtPsiFactory(this)
|
||||
val newBody =
|
||||
if (getterFunction.isPure && getterFunction.name == getterFunction.target?.name) null
|
||||
else {
|
||||
getterFunction.ktFunction.bodyExpression
|
||||
?.withReplacedExpressionInBody(target, factory.createExpression("field"), replaceOnlyWriteUsages = false)
|
||||
}
|
||||
add(factory.createGetter(newBody))
|
||||
getterFunction.ktFunction.modifierList?.also {
|
||||
getter!!.addModifiers(it)
|
||||
}
|
||||
return getter!!
|
||||
}
|
||||
|
||||
|
||||
private fun KtPropertyAccessor.addModifiers(newModifiers: KtModifierList) {
|
||||
setModifierList(newModifiers)
|
||||
removeModifier(KtTokens.OVERRIDE_KEYWORD)
|
||||
removeModifier(KtTokens.FINAL_KEYWORD)
|
||||
removeModifier(KtTokens.ABSTRACT_KEYWORD)
|
||||
}
|
||||
|
||||
|
||||
private fun KtProperty.addSetter(setterInfo: Accessor): KtPropertyAccessor {
|
||||
val factory = KtPsiFactory(this)
|
||||
val newBody =
|
||||
if (setterInfo.isPure && setterInfo.name == setterInfo.target?.name) null
|
||||
else {
|
||||
setterInfo.ktFunction.bodyExpression?.withReplacedExpressionInBody(this, factory.createExpression("field"), true)
|
||||
}
|
||||
add(factory.createSetter(newBody, setterInfo.ktFunction.valueParameters.single().name!!))
|
||||
setterInfo.ktFunction.modifierList?.also {
|
||||
setter!!.addModifiers(it)
|
||||
}
|
||||
return setter!!
|
||||
}
|
||||
|
||||
private fun KtElement.forAllUsages(action: (KtElement) -> Unit) {
|
||||
ReferencesSearch.search(this, LocalSearchScope(containingKtFile))
|
||||
.forEach { action(it.element as KtElement) }
|
||||
}
|
||||
|
||||
private fun KtElement.usages() =
|
||||
ReferencesSearch.search(this, LocalSearchScope(containingKtFile))
|
||||
|
||||
|
||||
private fun KtProperty.addGetterAndChangeAllUsages(getter: Accessor, name: String) =
|
||||
addGetter(getter, this).also {
|
||||
val factory = KtPsiFactory(this)
|
||||
getter.ktFunction.forAllUsages { usage ->
|
||||
usage.parentOfType<KtCallExpression>()!!.replace(factory.createExpression(name))
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtProperty.addSetterAndChangeAllUsages(setter: Accessor) =
|
||||
addSetter(setter).also {
|
||||
val factory = KtPsiFactory(this)
|
||||
setter.ktFunction.forAllUsages { usage ->
|
||||
val callExpression = usage.parentOfType<KtCallExpression>()!!
|
||||
val qualifier = callExpression.getQualifiedExpressionForSelector()
|
||||
val newValue = callExpression.valueArguments.single()
|
||||
if (qualifier != null) {
|
||||
qualifier.replace(factory.createExpression("${qualifier.receiverExpression.text}.$name = ${newValue.text}"))
|
||||
} else {
|
||||
callExpression.replace(factory.createExpression("$name = ${newValue.text}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtProperty.addDefaultSetter() =
|
||||
KtPsiFactory(this).createSetter(null, "value").let { setter ->
|
||||
this.add(setter) as KtPropertyAccessor
|
||||
}
|
||||
|
||||
override fun createAction(element: PsiElement, diagnostics: Diagnostics): (() -> Unit)? {
|
||||
if (element !is KtClassOrObject) return null
|
||||
return {
|
||||
val factory = KtPsiFactory(element)
|
||||
for ((name,
|
||||
getterAccessor,
|
||||
setterAccessor,
|
||||
needOverride,
|
||||
target,
|
||||
visibility
|
||||
) in generatePropertiesData(element)) {
|
||||
if (target.name != name) {//TODO use refactoring
|
||||
target.forAllUsages { usage ->
|
||||
val parent = (usage.parent as? KtExpression)
|
||||
?.asAssignment()
|
||||
?.takeIf { it.left == usage }
|
||||
|
||||
val expression =
|
||||
if (parent != null) factory.createExpression("this.$name")
|
||||
else factory.createExpression(name)
|
||||
|
||||
usage.replace(expression)
|
||||
}
|
||||
target.setName(name)
|
||||
}
|
||||
target.addGetterAndChangeAllUsages(getterAccessor, name).also { getter ->
|
||||
getter.setVisibility(visibility)
|
||||
}
|
||||
|
||||
val propertySetter =
|
||||
setterAccessor
|
||||
?.takeIf { it.isFromCurrentDeclaration }
|
||||
?.let { target.addSetterAndChangeAllUsages(it) }
|
||||
?: if (target.isVar
|
||||
&& (target.visibilityModifierTypeOrDefault() == KtTokens.PRIVATE_KEYWORD
|
||||
|| target.visibilityModifierTypeOrDefault() == KtTokens.PROTECTED_KEYWORD)
|
||||
) target.addDefaultSetter().also { it.setVisibility(target.visibilityModifierTypeOrDefault()) }
|
||||
else null
|
||||
|
||||
target.setVisibility(visibility)
|
||||
val isVar = propertySetter != null
|
||||
|
||||
if (target.isVar != isVar) {
|
||||
target.valOrVarKeyword.replace(if (isVar) factory.createVarKeyword() else factory.createValKeyword())
|
||||
}
|
||||
|
||||
if (target.parent?.isPhysical != true) {
|
||||
when {
|
||||
getterAccessor.isFromCurrentDeclaration ->
|
||||
element.addDeclarationAfter(target, getterAccessor.ktFunction)
|
||||
setterAccessor?.isFromCurrentDeclaration == true ->
|
||||
element.addDeclarationAfter(target, setterAccessor.ktFunction)
|
||||
}
|
||||
}
|
||||
if (getterAccessor.isFromCurrentDeclaration) {
|
||||
getterAccessor.ktFunction.delete()
|
||||
}
|
||||
if (setterAccessor?.isFromCurrentDeclaration == true) {
|
||||
setterAccessor.ktFunction.delete()
|
||||
}
|
||||
if (!target.hasModifier(KtTokens.OVERRIDE_KEYWORD) && needOverride) {
|
||||
target.addModifier(KtTokens.OVERRIDE_KEYWORD)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+618
@@ -0,0 +1,618 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.nj2k.postProcessing.processings
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.LocalSearchScope
|
||||
import com.intellij.psi.search.searches.OverridingMethodsSearch
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.refactoring.rename.RenamePsiElementProcessor
|
||||
import org.jetbrains.kotlin.asJava.toLightMethods
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.core.setVisibility
|
||||
import org.jetbrains.kotlin.idea.refactoring.isAbstract
|
||||
import org.jetbrains.kotlin.idea.refactoring.isInterfaceClass
|
||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.idea.references.readWriteAccess
|
||||
import org.jetbrains.kotlin.idea.util.CommentSaver
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.nj2k.*
|
||||
import org.jetbrains.kotlin.nj2k.postProcessing.*
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperclassesWithoutAny
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
|
||||
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
||||
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
|
||||
import org.jetbrains.kotlin.types.typeUtil.isUnit
|
||||
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
|
||||
import org.jetbrains.kotlin.util.isJavaDescriptor
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.cast
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
import org.jetbrains.kotlin.utils.mapToIndex
|
||||
|
||||
class ConvertGettersAndSettersToPropertyProcessing : ElementsBasedPostProcessing() {
|
||||
private fun KtNamedFunction.hasOverrides(): Boolean =
|
||||
toLightMethods().singleOrNull()?.let { lightMethod ->
|
||||
OverridingMethodsSearch.search(lightMethod).findFirst()
|
||||
} != null
|
||||
|
||||
private fun KtNamedFunction.hasSuperFunction(): Boolean =
|
||||
resolveToDescriptorIfAny()?.original?.overriddenDescriptors?.isNotEmpty() == true
|
||||
|
||||
private fun KtNamedFunction.hasInvalidSuperDescriptors(): Boolean =
|
||||
resolveToDescriptorIfAny()
|
||||
?.original
|
||||
?.overriddenDescriptors
|
||||
?.any { it.isJavaDescriptor || it is FunctionDescriptor } == true
|
||||
|
||||
private fun KtExpression.statements() =
|
||||
if (this is KtBlockExpression) statements
|
||||
else listOf(this)
|
||||
|
||||
private fun KtNamedFunction.asGetter(): Getter? {
|
||||
val name = name?.asGetterName() ?: return null
|
||||
if (valueParameters.isNotEmpty()) return null
|
||||
if (typeParameters.isNotEmpty()) return null
|
||||
val target = bodyExpression
|
||||
?.statements()
|
||||
?.singleOrNull()
|
||||
?.safeAs<KtReturnExpression>()
|
||||
?.returnedExpression
|
||||
?.unpackedReferenceToProperty()
|
||||
?.takeIf {
|
||||
it.type() == this.type()!!
|
||||
}
|
||||
return RealGetter(this, target, name)
|
||||
}
|
||||
|
||||
private fun KtNamedFunction.asSetter(): Setter? {
|
||||
val name = name?.asSetterName() ?: return null
|
||||
if (typeParameters.isNotEmpty()) return null
|
||||
if (valueParameters.size != 1) return null
|
||||
val descriptor = resolveToDescriptorIfAny() ?: return null
|
||||
if (descriptor.returnType?.isUnit() != true) return null
|
||||
val target = bodyExpression
|
||||
?.statements()
|
||||
?.singleOrNull()
|
||||
?.let { expression ->
|
||||
if (expression is KtBinaryExpression) {
|
||||
if (expression.operationToken != KtTokens.EQ) return@let null
|
||||
val right = expression.right as? KtNameReferenceExpression ?: return@let null
|
||||
if (right.resolve() != valueParameters.single()) return@let null
|
||||
expression.left?.unpackedReferenceToProperty()
|
||||
} else null
|
||||
}?.takeIf {
|
||||
it.type() == valueParameters.single().type()
|
||||
}
|
||||
return RealSetter(this, target, name)
|
||||
}
|
||||
|
||||
private fun KtDeclaration.asPropertyAccessor(): PropertyInfo? {
|
||||
return when (this) {
|
||||
is KtProperty -> RealProperty(this, name ?: return null)
|
||||
is KtNamedFunction -> asGetter() ?: asSetter()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun KtElement.forAllUsages(action: (KtElement) -> Unit) {
|
||||
usages().forEach { action(it.element as KtElement) }
|
||||
}
|
||||
|
||||
private fun addGetter(
|
||||
getter: Getter,
|
||||
property: KtProperty,
|
||||
factory: KtPsiFactory,
|
||||
isFakeProperty: Boolean
|
||||
): KtPropertyAccessor {
|
||||
val body =
|
||||
if (isFakeProperty) getter.body
|
||||
else getter.body?.withReplacedExpressionInBody(
|
||||
property,
|
||||
factory.createExpression(KtTokens.FIELD_KEYWORD.value),
|
||||
replaceOnlyWriteUsages = false
|
||||
)
|
||||
|
||||
val ktGetter = factory.createGetter(body, getter.modifiersText)
|
||||
ktGetter.filterModifiers()
|
||||
return property.add(ktGetter).cast<KtPropertyAccessor>().let {
|
||||
if (getter is RealGetter) {
|
||||
getter.function.forAllUsages { usage ->
|
||||
usage.parentOfType<KtCallExpression>()!!.replace(factory.createExpression(getter.name))
|
||||
}
|
||||
}
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtParameter.rename(newName: String) {
|
||||
val renamer = RenamePsiElementProcessor.forElement(this)
|
||||
val usageInfos =
|
||||
renamer.findReferences(this, false).mapNotNull { reference ->
|
||||
val element = reference.element
|
||||
val isBackingField = element is KtNameReferenceExpression &&
|
||||
element.text == KtTokens.FIELD_KEYWORD.value
|
||||
&& element.mainReference.resolve() == this
|
||||
&& isAncestor(element)
|
||||
if (isBackingField) return@mapNotNull null
|
||||
renamer.createUsageInfo(this, reference, reference.element)
|
||||
}.toTypedArray()
|
||||
renamer.renameElement(this, newName, usageInfos, null)
|
||||
}
|
||||
|
||||
private fun createSetter(
|
||||
setter: Setter,
|
||||
property: KtProperty,
|
||||
factory: KtPsiFactory,
|
||||
isFakeProperty: Boolean
|
||||
): KtPropertyAccessor {
|
||||
if (setter is RealSetter) {
|
||||
setter.function.valueParameters.single().rename(setter.parameterName)
|
||||
}
|
||||
val body =
|
||||
if (isFakeProperty) setter.body
|
||||
else setter.body?.withReplacedExpressionInBody(
|
||||
property,
|
||||
factory.createExpression(KtTokens.FIELD_KEYWORD.value),
|
||||
true
|
||||
)
|
||||
|
||||
val ktSetter = factory.createSetter(
|
||||
body,
|
||||
setter.parameterName,
|
||||
setter.modifiersText?.takeIf { it.isNotEmpty() } ?: property.visibilityModifierTypeOrDefault().value
|
||||
)
|
||||
ktSetter.filterModifiers()
|
||||
if (setter is RealSetter) {
|
||||
setter.function.forAllUsages { usage ->
|
||||
val callExpression = usage.parentOfType<KtCallExpression>()!!
|
||||
val qualifier = callExpression.getQualifiedExpressionForSelector()
|
||||
val newValue = callExpression.valueArguments.single()
|
||||
if (qualifier != null) {
|
||||
qualifier.replace(
|
||||
factory.createExpression("${qualifier.receiverExpression.text}.${setter.name} = ${newValue.text}")
|
||||
)
|
||||
} else {
|
||||
callExpression.replace(factory.createExpression("${setter.name} = ${newValue.text}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return ktSetter
|
||||
}
|
||||
|
||||
private fun KtPropertyAccessor.filterModifiers() {
|
||||
removeModifier(KtTokens.OVERRIDE_KEYWORD)
|
||||
removeModifier(KtTokens.FINAL_KEYWORD)
|
||||
removeModifier(KtTokens.OPEN_KEYWORD)
|
||||
}
|
||||
|
||||
private fun KtExpression.isReferenceToThis() =
|
||||
when (this) {
|
||||
is KtThisExpression -> instanceReference.resolve() == parentOfType<KtClassOrObject>()
|
||||
is KtReferenceExpression -> resolve() == parentOfType<KtClassOrObject>()
|
||||
else -> false
|
||||
}
|
||||
|
||||
private fun KtElement.usages() =
|
||||
ReferencesSearch.search(this, LocalSearchScope(containingKtFile))
|
||||
|
||||
private fun <T : KtExpression> T.withReplacedExpressionInBody(
|
||||
from: KtElement,
|
||||
to: KtExpression,
|
||||
replaceOnlyWriteUsages: Boolean
|
||||
): T = also {
|
||||
from.usages()
|
||||
.asSequence()
|
||||
.map { it.element }
|
||||
.filter { it.isInsideOf(listOf(this)) }
|
||||
.forEach { reference ->
|
||||
val parent = reference.parent
|
||||
val referenceExpression = when {
|
||||
parent is KtQualifiedExpression && parent.receiverExpression.isReferenceToThis() ->
|
||||
parent
|
||||
else -> reference
|
||||
}
|
||||
if (!replaceOnlyWriteUsages
|
||||
|| (referenceExpression.parent as? KtExpression)?.asAssignment()?.left == referenceExpression
|
||||
) {
|
||||
referenceExpression.replace(to)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun List<KtClassOrObject>.sortedByInheritance(): List<KtClassOrObject> {
|
||||
fun ClassDescriptor.superClassAndSuperInterfaces() =
|
||||
getSuperInterfaces() + listOfNotNull(getSuperClassNotAny())
|
||||
|
||||
val sorted = mutableListOf<KtClassOrObject>()
|
||||
val visited = Array(size) { false }
|
||||
val descriptors = map { it.resolveToDescriptorIfAny()!! }
|
||||
val descriptorToIndex = descriptors.mapToIndex()
|
||||
val outers = descriptors.map { descriptor ->
|
||||
descriptor.superClassAndSuperInterfaces().mapNotNull { descriptorToIndex[it] }
|
||||
}
|
||||
|
||||
fun dfs(current: Int) {
|
||||
visited[current] = true
|
||||
for (outer in outers[current]) {
|
||||
if (!visited[outer]) {
|
||||
dfs(outer)
|
||||
}
|
||||
}
|
||||
sorted.add(get(current))
|
||||
}
|
||||
|
||||
for (index in descriptors.indices) {
|
||||
if (!visited[index]) {
|
||||
dfs(index)
|
||||
}
|
||||
}
|
||||
return sorted
|
||||
}
|
||||
|
||||
|
||||
override fun runProcessing(elements: List<PsiElement>, converterContext: NewJ2kConverterContext) {
|
||||
val classes = elements.descendantsOfType<KtClassOrObject>().sortedByInheritance()
|
||||
for (klass in classes) {
|
||||
convertClass(klass)
|
||||
}
|
||||
}
|
||||
|
||||
private fun causesNameConflictInCurrentDeclarationAndItsParents(name: String, declaration: DeclarationDescriptor?): Boolean =
|
||||
when (declaration) {
|
||||
is ClassDescriptor -> {
|
||||
declaration.unsubstitutedMemberScope.getDescriptorsFiltered(DescriptorKindFilter.VARIABLES) {
|
||||
it.asString() == name
|
||||
}.isNotEmpty()
|
||||
|| causesNameConflictInCurrentDeclarationAndItsParents(name, declaration.containingDeclaration)
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
|
||||
private fun KtClassOrObject.collectGettersAndSetters(factory: KtPsiFactory): List<PropertyWithAccessors> {
|
||||
val classDescriptor = resolveToDescriptorIfAny() ?: return emptyList()
|
||||
|
||||
val variablesDescriptorsMap =
|
||||
(listOfNotNull(classDescriptor.getSuperClassNotAny()) + classDescriptor.getSuperInterfaces())
|
||||
.flatMap { superClass ->
|
||||
superClass
|
||||
.unsubstitutedMemberScope
|
||||
.getDescriptorsFiltered(DescriptorKindFilter.VARIABLES)
|
||||
}
|
||||
.asSequence()
|
||||
.filterIsInstance<VariableDescriptor>()
|
||||
.associateBy { it.name.asString() }
|
||||
|
||||
return declarations
|
||||
.asSequence()
|
||||
.mapNotNull { it.asPropertyAccessor() }
|
||||
.groupBy { it.name.removePrefix("is").decapitalize() }
|
||||
.values
|
||||
.mapNotNull { group ->
|
||||
val realGetter = group.firstIsInstanceOrNull<RealGetter>()
|
||||
val realSetter = group.firstIsInstanceOrNull<RealSetter>()?.takeIf { setter ->
|
||||
if (realGetter == null) return@takeIf true
|
||||
if (setter.function.valueParameters.first().type()?.makeNotNullable() !=
|
||||
realGetter.function.type()?.makeNotNullable()
|
||||
) {
|
||||
if (isInterfaceClass()) return@mapNotNull null
|
||||
false
|
||||
} else true
|
||||
}
|
||||
val realProperty = group.firstIsInstanceOrNull<RealProperty>()
|
||||
if (realGetter == null && realSetter == null) return@mapNotNull null
|
||||
val name = realGetter?.name ?: realSetter!!.name
|
||||
val type =
|
||||
realGetter?.function?.typeReference?.text
|
||||
?: realSetter?.function?.valueParameters?.first()?.typeReference?.text!!
|
||||
|
||||
if (realSetter != null
|
||||
&& realGetter != null
|
||||
&& isInterfaceClass()
|
||||
&& realSetter.function.hasOverrides() != realGetter.function.hasOverrides()
|
||||
) return@mapNotNull null
|
||||
|
||||
if (realSetter != null
|
||||
&& realGetter != null
|
||||
&& realSetter.function.hasSuperFunction() != realGetter.function.hasSuperFunction()
|
||||
) return@mapNotNull null
|
||||
|
||||
if (realProperty != null
|
||||
&& realGetter?.target != null
|
||||
&& realGetter.target != realProperty.property
|
||||
&& realProperty.property.hasInitializer()
|
||||
) return@mapNotNull null
|
||||
|
||||
|
||||
|
||||
if (realGetter?.function?.hasInvalidSuperDescriptors() == true
|
||||
|| realSetter?.function?.hasInvalidSuperDescriptors() == true
|
||||
) return@mapNotNull null
|
||||
|
||||
if (realProperty == null) {
|
||||
if (causesNameConflictInCurrentDeclarationAndItsParents(
|
||||
name,
|
||||
classDescriptor.containingDeclaration
|
||||
)
|
||||
) return@mapNotNull null
|
||||
}
|
||||
|
||||
if (realProperty != null && (realGetter != null && realGetter.target == null || realSetter != null && realSetter.target == null)) {
|
||||
if (!realProperty.property.isPrivate()) return@mapNotNull null
|
||||
val hasUsages =
|
||||
realProperty.property.hasUsagesOutsideOf(
|
||||
containingKtFile,
|
||||
listOfNotNull(realGetter?.function, realSetter?.function)
|
||||
)
|
||||
if (hasUsages) return@mapNotNull null
|
||||
}
|
||||
|
||||
if (realSetter != null && realProperty != null) {
|
||||
val assignFieldOfOtherInstance = realProperty.property.usages().any { usage ->
|
||||
val element = usage.safeAs<KtSimpleNameReference>()?.element ?: return@any false
|
||||
if (!element.readWriteAccess(useResolveForReadWrite = true).isWrite) return@any false
|
||||
val parent = element.parent
|
||||
parent is KtQualifiedExpression && !parent.receiverExpression.isReferenceToThis()
|
||||
}
|
||||
if (assignFieldOfOtherInstance) return@mapNotNull null
|
||||
}
|
||||
|
||||
|
||||
if (realGetter != null && realProperty != null) {
|
||||
val getFieldOfOtherInstanceInGetter = realProperty.property.usages().any { usage ->
|
||||
val element = usage.safeAs<KtSimpleNameReference>()?.element ?: return@any false
|
||||
val parent = element.parent
|
||||
parent is KtQualifiedExpression
|
||||
&& !parent.receiverExpression.isReferenceToThis()
|
||||
&& realGetter.function.isAncestor(element)
|
||||
}
|
||||
if (getFieldOfOtherInstanceInGetter) return@mapNotNull null
|
||||
}
|
||||
|
||||
|
||||
val getter = realGetter ?: when {
|
||||
realProperty?.property?.resolveToDescriptorIfAny()?.overriddenDescriptors?.any {
|
||||
it.safeAs<VariableDescriptor>()?.isVar == true
|
||||
} == true -> FakeGetter(name, null, "")
|
||||
|
||||
variablesDescriptorsMap[name]?.let { variable ->
|
||||
variable.isVar && variable.containingDeclaration != classDescriptor
|
||||
} == true ->
|
||||
FakeGetter(name, factory.createExpression("super.$name"), "")
|
||||
|
||||
else -> return@mapNotNull null
|
||||
}
|
||||
|
||||
val mergedProperty =
|
||||
if (getter is RealGetter
|
||||
&& getter.target != null
|
||||
&& getter.target!!.name != getter.name
|
||||
&& (realSetter == null || realSetter.target != null)
|
||||
) {
|
||||
MergedProperty(name, type, realSetter != null, getter.target!!)
|
||||
} else null
|
||||
|
||||
val setter = realSetter ?: when {
|
||||
realProperty?.property?.isVar == true ->
|
||||
FakeSetter(name, null, "")
|
||||
|
||||
realProperty?.property?.resolveToDescriptorIfAny()?.overriddenDescriptors?.any {
|
||||
it.safeAs<VariableDescriptor>()?.isVar == true
|
||||
} == true
|
||||
|| variablesDescriptorsMap[name]?.isVar == true ->
|
||||
FakeSetter(
|
||||
name,
|
||||
factory.createBlock("super.$name = $name"),
|
||||
""
|
||||
)
|
||||
|
||||
realGetter != null
|
||||
&& (realProperty != null
|
||||
&& realProperty.property.visibilityModifierTypeOrDefault() != realGetter.function.visibilityModifierTypeOrDefault()
|
||||
&& realProperty.property.isVar
|
||||
|| mergedProperty != null
|
||||
&& mergedProperty.mergeTo.visibilityModifierTypeOrDefault() != realGetter.function.visibilityModifierTypeOrDefault()
|
||||
&& mergedProperty.mergeTo.isVar
|
||||
) ->
|
||||
FakeSetter(name, null, null)
|
||||
|
||||
else -> null
|
||||
}
|
||||
val isVar = setter != null
|
||||
|
||||
val property = mergedProperty?.copy(isVar = isVar)
|
||||
?: realProperty?.copy(isVar = isVar)
|
||||
?: FakeProperty(name, type, isVar)
|
||||
|
||||
PropertyWithAccessors(
|
||||
property,
|
||||
getter,
|
||||
setter
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtProperty.renameTo(newName: String, factory: KtPsiFactory) {
|
||||
for (usage in usages().toList()) {
|
||||
val element = usage.element
|
||||
val isBackingField = element is KtNameReferenceExpression
|
||||
&& element.text == KtTokens.FIELD_KEYWORD.value
|
||||
&& element.mainReference.resolve() == this
|
||||
&& isAncestor(element)
|
||||
if (isBackingField) continue
|
||||
val replacer =
|
||||
if (element.parent is KtQualifiedExpression) factory.createExpression(newName)
|
||||
else factory.createExpression("this.$newName")
|
||||
element.replace(replacer)
|
||||
}
|
||||
setName(newName)
|
||||
}
|
||||
|
||||
private fun convertClass(klass: KtClassOrObject) {
|
||||
val factory = KtPsiFactory(klass)
|
||||
val accessors = klass.collectGettersAndSetters(factory)
|
||||
for ((property, getter, setter) in accessors) {
|
||||
val ktProperty = when (property) {
|
||||
is RealProperty -> {
|
||||
if (property.property.isVar != property.isVar) {
|
||||
property.property.valOrVarKeyword.replace(
|
||||
if (property.isVar) factory.createVarKeyword() else factory.createValKeyword()
|
||||
)
|
||||
}
|
||||
property.property
|
||||
}
|
||||
is FakeProperty -> factory.createProperty(property.name, property.type, property.isVar).let {
|
||||
val anchor = getter.safeAs<RealAccessor>()?.function ?: setter.cast<RealAccessor>().function
|
||||
klass.addDeclarationBefore(it, anchor)
|
||||
}
|
||||
is MergedProperty -> {
|
||||
property.mergeTo
|
||||
}
|
||||
}
|
||||
|
||||
val isOpen = getter.safeAs<RealGetter>()?.function?.hasModifier(KtTokens.OPEN_KEYWORD) == true
|
||||
|| setter.safeAs<RealSetter>()?.function?.hasModifier(KtTokens.OPEN_KEYWORD) == true
|
||||
|
||||
val ktGetter = addGetter(getter, ktProperty, factory, property.isFake)
|
||||
val ktSetter =
|
||||
setter?.let {
|
||||
createSetter(it, ktProperty, factory, property.isFake)
|
||||
}
|
||||
val getterVisibility = getter.safeAs<RealGetter>()?.function?.visibilityModifierTypeOrDefault()
|
||||
if (getter is RealGetter) {
|
||||
if (getter.function.isAbstract()) {
|
||||
ktProperty.addModifier(KtTokens.ABSTRACT_KEYWORD)
|
||||
}
|
||||
val commentSaver = CommentSaver(getter.function)
|
||||
getter.function.delete()
|
||||
commentSaver.restore(ktProperty)
|
||||
}
|
||||
if (setter is RealSetter) {
|
||||
val commentSaver = CommentSaver(setter.function)
|
||||
setter.function.delete()
|
||||
commentSaver.restore(ktProperty)
|
||||
}
|
||||
|
||||
val propertyVisibility = ktProperty.visibilityModifierTypeOrDefault()
|
||||
getterVisibility?.let { ktProperty.setVisibility(it) }
|
||||
if (ktSetter != null) {
|
||||
if (setter !is RealSetter) {
|
||||
ktSetter.setVisibility(propertyVisibility)
|
||||
}
|
||||
ktProperty.addAfter(ktSetter, ktGetter)
|
||||
}
|
||||
|
||||
if (property is MergedProperty) {
|
||||
ktProperty.renameTo(property.name, factory)
|
||||
}
|
||||
if (isOpen) {
|
||||
ktProperty.addModifier(KtTokens.OPEN_KEYWORD)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private data class PropertyWithAccessors(
|
||||
val property: Property,
|
||||
val getter: Getter,
|
||||
val setter: Setter?
|
||||
)
|
||||
|
||||
private interface PropertyInfo {
|
||||
val name: String
|
||||
}
|
||||
|
||||
private interface Accessor : PropertyInfo {
|
||||
val target: KtProperty?
|
||||
val body: KtExpression?
|
||||
val modifiersText: String?
|
||||
val isPure: Boolean
|
||||
}
|
||||
|
||||
private sealed class Property : PropertyInfo {
|
||||
abstract val isVar: Boolean
|
||||
}
|
||||
|
||||
private data class RealProperty(
|
||||
val property: KtProperty,
|
||||
override val name: String,
|
||||
override val isVar: Boolean = property.isVar
|
||||
) : Property()
|
||||
|
||||
private data class FakeProperty(override val name: String, val type: String, override val isVar: Boolean) : Property()
|
||||
private data class MergedProperty(
|
||||
override val name: String,
|
||||
val type: String,
|
||||
override val isVar: Boolean,
|
||||
val mergeTo: KtProperty
|
||||
) : Property()
|
||||
|
||||
private sealed class Getter : Accessor
|
||||
private sealed class Setter : Accessor {
|
||||
abstract val parameterName: String
|
||||
}
|
||||
|
||||
private interface FakeAccessor : Accessor {
|
||||
override val target: KtProperty?
|
||||
get() = null
|
||||
override val isPure: Boolean
|
||||
get() = true
|
||||
}
|
||||
|
||||
private interface RealAccessor : Accessor {
|
||||
val function: KtNamedFunction
|
||||
override val body: KtExpression?
|
||||
get() = function.bodyExpression
|
||||
override val modifiersText: String
|
||||
get() = function.modifierList?.text.orEmpty()
|
||||
override val isPure: Boolean
|
||||
get() = target != null
|
||||
}
|
||||
|
||||
private data class RealGetter(
|
||||
override val function: KtNamedFunction,
|
||||
override val target: KtProperty?,
|
||||
override val name: String
|
||||
) : Getter(), RealAccessor
|
||||
|
||||
private data class FakeGetter(
|
||||
override val name: String,
|
||||
override val body: KtExpression?,
|
||||
override val modifiersText: String
|
||||
) : Getter(), FakeAccessor
|
||||
|
||||
private data class RealSetter(
|
||||
override val function: KtNamedFunction,
|
||||
override val target: KtProperty?,
|
||||
override val name: String
|
||||
) : Setter(), RealAccessor {
|
||||
override val parameterName: String
|
||||
get() = (function.valueParameters.first().name ?: name).fixSetterParameterName()
|
||||
}
|
||||
|
||||
private data class FakeSetter(
|
||||
override val name: String,
|
||||
override val body: KtExpression?,
|
||||
override val modifiersText: String?
|
||||
) : Setter(), FakeAccessor {
|
||||
override val parameterName: String
|
||||
get() = name.fixSetterParameterName()
|
||||
}
|
||||
|
||||
private fun String.fixSetterParameterName() =
|
||||
if (this == KtTokens.FIELD_KEYWORD.value) "value"
|
||||
else this
|
||||
|
||||
private val PropertyInfo.isFake: Boolean
|
||||
get() = this is FakeAccessor
|
||||
@@ -5,25 +5,20 @@
|
||||
|
||||
package org.jetbrains.kotlin.nj2k.postProcessing
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.LocalSearchScope
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.formatter.commitAndUnblockDocument
|
||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.lexer.KtKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
|
||||
fun KtExpression.asProperty(): KtProperty? =
|
||||
(this as? KtNameReferenceExpression)
|
||||
?.mainReference
|
||||
?.resolve() as? KtProperty
|
||||
|
||||
fun KtExpression.unpackedReferenceToProperty(): KtProperty? =
|
||||
when (this) {
|
||||
is KtDotQualifiedExpression ->
|
||||
@@ -39,17 +34,20 @@ fun KtExpression.unpackedReferenceToProperty(): KtProperty? =
|
||||
fun KtDeclaration.type() =
|
||||
(resolveToDescriptorIfAny() as? CallableDescriptor)?.returnType
|
||||
|
||||
fun KtElement.topLevelContainingClassOrObject(): KtClassOrObject? =
|
||||
generateSequence(getStrictParentOfType<KtClassOrObject>()) {
|
||||
it.getStrictParentOfType()
|
||||
}.lastOrNull()
|
||||
|
||||
fun KtReferenceExpression.resolve() =
|
||||
mainReference.resolve()
|
||||
|
||||
fun KtPsiFactory.createGetter(body: KtExpression?): KtPropertyAccessor {
|
||||
fun KtPsiFactory.createGetter(body: KtExpression?, modifiers: String?): KtPropertyAccessor {
|
||||
val property =
|
||||
createProperty("val x get" + if (body == null) "" else if (body is KtBlockExpression) "() { return 1 }" else "() = 1")
|
||||
createProperty(
|
||||
"val x\n ${modifiers.orEmpty()} get" +
|
||||
when (body) {
|
||||
is KtBlockExpression -> "() { return 1 }"
|
||||
null -> ""
|
||||
else -> "() = 1"
|
||||
} + "\n"
|
||||
|
||||
)
|
||||
val getter = property.getter!!
|
||||
val bodyExpression = getter.bodyExpression
|
||||
|
||||
@@ -57,11 +55,12 @@ fun KtPsiFactory.createGetter(body: KtExpression?): KtPropertyAccessor {
|
||||
return getter
|
||||
}
|
||||
|
||||
fun KtPsiFactory.createSetter(body: KtExpression?, fieldName: String): KtPropertyAccessor {
|
||||
fun KtPsiFactory.createSetter(body: KtExpression?, fieldName: String?, modifiers: String?): KtPropertyAccessor {
|
||||
val modifiersText = modifiers.orEmpty()
|
||||
val property = when (body) {
|
||||
null -> createProperty("var x = 1\n get() = 1\n set")
|
||||
is KtBlockExpression -> createProperty("var x get() = 1\nset($fieldName) {\n field = $fieldName\n }")
|
||||
else -> createProperty("var x get() = 1\nset($fieldName) = TODO()")
|
||||
null -> createProperty("var x = 1\n get() = 1\n $modifiersText set")
|
||||
is KtBlockExpression -> createProperty("var x get() = 1\n $modifiersText set($fieldName) {\n field = $fieldName\n }")
|
||||
else -> createProperty("var x get() = 1\n $modifiersText set($fieldName) = TODO()")
|
||||
}
|
||||
val setter = property.setter!!
|
||||
if (body != null) {
|
||||
@@ -70,18 +69,20 @@ fun KtPsiFactory.createSetter(body: KtExpression?, fieldName: String): KtPropert
|
||||
return setter
|
||||
}
|
||||
|
||||
fun KtClassOrObject.parentClassForCompanionOrThis(): KtClassOrObject =
|
||||
if (safeAs<KtObjectDeclaration>()?.isCompanion() == true)
|
||||
getStrictParentOfType() ?: this
|
||||
else this
|
||||
|
||||
fun KtElement.hasUsagesOutsideOf(inElement: KtElement, outsideElements: List<KtElement>): Boolean =
|
||||
ReferencesSearch.search(this, LocalSearchScope(inElement)).any { reference ->
|
||||
outsideElements.none { it.isAncestor(reference.element) }
|
||||
}
|
||||
|
||||
fun String.escaped() =
|
||||
if (this in keywords || '$' in this) "`$this`"
|
||||
else this
|
||||
|
||||
private val keywords = KtTokens.KEYWORDS.types.map { (it as KtKeywordToken).value }.toSet()
|
||||
//hack until KT-30804 is fixed
|
||||
fun KtModifierListOwner.removeModifierSmart(modifierToken: KtModifierKeywordToken) {
|
||||
val newElement = copy() as KtModifierListOwner
|
||||
newElement.removeModifier(modifierToken)
|
||||
replace(newElement)
|
||||
containingFile.commitAndUnblockDocument()
|
||||
}
|
||||
|
||||
inline fun <reified T : PsiElement> List<PsiElement>.descendantsOfType(): List<T> =
|
||||
flatMap { it.collectDescendantsOfType() }
|
||||
@@ -17,8 +17,6 @@
|
||||
package org.jetbrains.kotlin.nj2k
|
||||
|
||||
import org.jetbrains.kotlin.j2k.ast.Nullability
|
||||
import org.jetbrains.kotlin.lexer.KtKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.nj2k.NewCodeBuilder.ParenthesisKind.*
|
||||
import org.jetbrains.kotlin.nj2k.conversions.parentOfType
|
||||
@@ -997,13 +995,6 @@ private fun JKDelegationConstructorCall.isCallOfConstructorOf(type: JKType): Boo
|
||||
}
|
||||
}
|
||||
|
||||
private val KEYWORDS = KtTokens.KEYWORDS.types.map { (it as KtKeywordToken).value }.toSet()
|
||||
|
||||
private fun String.escaped() =
|
||||
if (this in KEYWORDS || '$' in this) "`$this`"
|
||||
else this
|
||||
|
||||
|
||||
private val mappedToKotlinFqNames =
|
||||
setOf(
|
||||
"java.util.ArrayList",
|
||||
|
||||
@@ -15,6 +15,10 @@ import org.jetbrains.kotlin.nj2k.tree.impl.psi
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
class DefaultArgumentsConversion(private val context: NewJ2kConverterContext) : RecursiveApplicableConversionBase() {
|
||||
private fun JKMethod.canBeGetterOrSetter() =
|
||||
name.value.asGetterName() != null
|
||||
|| name.value.asSetterName() != null
|
||||
|
||||
|
||||
private fun JKMethod.canNotBeMerged(): Boolean =
|
||||
modality == Modality.ABSTRACT
|
||||
@@ -23,6 +27,7 @@ class DefaultArgumentsConversion(private val context: NewJ2kConverterContext) :
|
||||
|| hasExtraModifier(ExtraModifier.SYNCHRONIZED)
|
||||
|| context.converter.converterServices.oldServices.referenceSearcher.hasOverrides(psi()!!)
|
||||
|| annotationList.annotations.isNotEmpty()
|
||||
|| canBeGetterOrSetter()
|
||||
|
||||
|
||||
override fun applyToElement(element: JKTreeElement): JKTreeElement {
|
||||
|
||||
@@ -8,6 +8,10 @@ package org.jetbrains.kotlin.nj2k
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.psi.util.parentsOfType
|
||||
import org.jetbrains.kotlin.lexer.KtKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
|
||||
fun <T> List<T>.replace(element: T, replacer: T): List<T> {
|
||||
val mutableList = toMutableList()
|
||||
@@ -21,3 +25,24 @@ inline fun <reified T : PsiElement> PsiElement.parentOfType(): T? =
|
||||
|
||||
inline fun <reified T : PsiElement> PsiElement.parentsOfType(): Sequence<T> = parentsOfType(T::class.java)
|
||||
|
||||
fun String.asGetterName() =
|
||||
takeIf { JvmAbi.isGetterName(it) }
|
||||
?.removePrefix("get")
|
||||
?.takeIf {
|
||||
it.isNotEmpty() && it.first().isUpperCase()
|
||||
|| it.startsWith("is") && it.length > 2 && it[2].isUpperCase()
|
||||
}?.decapitalize()
|
||||
?.escaped()
|
||||
|
||||
fun String.asSetterName() =
|
||||
takeIf { JvmAbi.isSetterName(it) }
|
||||
?.removePrefix("set")
|
||||
?.takeIf { it.isNotEmpty() && it.first().isUpperCase() }
|
||||
?.decapitalize()
|
||||
?.escaped()
|
||||
|
||||
private val KEYWORDS = KtTokens.KEYWORDS.types.map { (it as KtKeywordToken).value }.toSet()
|
||||
|
||||
fun String.escaped() =
|
||||
if (this in KEYWORDS || '$' in this) "`$this`"
|
||||
else this
|
||||
@@ -0,0 +1,62 @@
|
||||
interface I {
|
||||
boolean isSomething1();
|
||||
|
||||
Boolean isSomething2();
|
||||
|
||||
int isSomething3();
|
||||
|
||||
boolean isSomething4();
|
||||
void setSomething4(boolean value);
|
||||
|
||||
boolean isSomething5();
|
||||
void setSomething5(boolean value);
|
||||
|
||||
boolean getSomething6();
|
||||
void setSomething6(boolean value);
|
||||
}
|
||||
|
||||
abstract class A extends B implements I {
|
||||
@Override
|
||||
public boolean isSomething1() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setSomething1(boolean b) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSomething4() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSomething5(boolean value) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSomething6(boolean value) {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class B extends A implements I {
|
||||
@Override
|
||||
public boolean isSomething1() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setSomething1(boolean b) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSomething4() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSomething5(boolean value) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSomething6(boolean value) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// ERROR: There's a cycle in the inheritance hierarchy for this type
|
||||
// ERROR: There's a cycle in the inheritance hierarchy for this type
|
||||
internal interface I {
|
||||
val isSomething1: Boolean
|
||||
val isSomething2: Boolean?
|
||||
val isSomething3: Int
|
||||
fun isSomething4(): Boolean
|
||||
fun setSomething4(value: Boolean)
|
||||
fun isSomething5(): Boolean
|
||||
fun setSomething5(value: Boolean)
|
||||
fun getSomething6(): Boolean
|
||||
fun setSomething6(value: Boolean)
|
||||
}
|
||||
|
||||
internal abstract class A : B(), I {
|
||||
override var isSomething1: Boolean
|
||||
get() = true
|
||||
set(b) {}
|
||||
|
||||
override fun isSomething4(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun setSomething5(value: Boolean) {}
|
||||
override fun setSomething6(value: Boolean) {}
|
||||
}
|
||||
|
||||
internal abstract class B : A(), I {
|
||||
override var isSomething1: Boolean
|
||||
get() = true
|
||||
set(b) {}
|
||||
|
||||
override fun isSomething4(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun setSomething5(value: Boolean) {}
|
||||
override fun setSomething6(value: Boolean) {}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
//Interface
|
||||
public interface Parent {
|
||||
public void getX();
|
||||
}
|
||||
|
||||
//Subclass
|
||||
public class Child implements Parent {
|
||||
@Override
|
||||
public void getX() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//Interface
|
||||
interface Parent {
|
||||
val x: Unit
|
||||
}
|
||||
|
||||
//Subclass
|
||||
class Child : Parent {
|
||||
override val x: Unit get() {}
|
||||
}
|
||||
+10
@@ -1376,6 +1376,11 @@ public class NewJavaToKotlinConverterSingleFileTestGenerated extends AbstractNew
|
||||
runTest("nj2k/testData/newJ2k/detectProperties/Comments.java");
|
||||
}
|
||||
|
||||
@TestMetadata("CyclicInheritance.java")
|
||||
public void testCyclicInheritance() throws Exception {
|
||||
runTest("nj2k/testData/newJ2k/detectProperties/CyclicInheritance.java");
|
||||
}
|
||||
|
||||
@TestMetadata("DataClass.java")
|
||||
public void testDataClass() throws Exception {
|
||||
runTest("nj2k/testData/newJ2k/detectProperties/DataClass.java");
|
||||
@@ -1511,6 +1516,11 @@ public class NewJavaToKotlinConverterSingleFileTestGenerated extends AbstractNew
|
||||
runTest("nj2k/testData/newJ2k/detectProperties/KeywordPropertyName.java");
|
||||
}
|
||||
|
||||
@TestMetadata("kt-31700.java")
|
||||
public void testKt_31700() throws Exception {
|
||||
runTest("nj2k/testData/newJ2k/detectProperties/kt-31700.java");
|
||||
}
|
||||
|
||||
@TestMetadata("Overrides.java")
|
||||
public void ignoreTestOverrides() throws Exception {
|
||||
runTest("nj2k/testData/newJ2k/detectProperties/Overrides.java");
|
||||
|
||||
Reference in New Issue
Block a user