Converting object literal to class allows to choose a name (KT-19254)

This commit is contained in:
Nicolay Mitropolsky
2019-01-08 14:33:32 +03:00
parent a9afee77d7
commit 41b6bcf8c3
8 changed files with 47 additions and 33 deletions
@@ -16,13 +16,16 @@
package org.jetbrains.kotlin.idea.intentions
import com.intellij.codeInsight.CodeInsightUtilCore
import com.intellij.codeInsight.template.TemplateBuilderImpl
import com.intellij.codeInsight.template.TemplateManager
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiComment
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.refactoring.chooseContainerElementIfNecessary
@@ -35,12 +38,7 @@ import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.allChildren
import org.jetbrains.kotlin.psi.psiUtil.containingClass
import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
class ConvertObjectLiteralToClassIntention : SelfTargetingRangeIntention<KtObjectLiteralExpression>(
@@ -55,16 +53,14 @@ class ConvertObjectLiteralToClassIntention : SelfTargetingRangeIntention<KtObjec
val project = element.project
val scope = element.getResolutionScope()
val context = element.analyze(BodyResolveMode.PARTIAL)
val objectLiteralType = element.getType(context)
val validator: (String) -> Boolean = { scope.findClassifier(Name.identifier(it), NoLookupLocation.FROM_IDE) == null }
val className = if (objectLiteralType != null) {
KotlinNameSuggester.suggestNamesByType(objectLiteralType, validator, "O").first()
}
else {
KotlinNameSuggester.suggestNameByName("O", validator)
}
val validator: (String) -> Boolean = { scope.findClassifier(Name.identifier(it), NoLookupLocation.FROM_IDE) == null }
val classNames = element.objectDeclaration.superTypeListEntries
.mapNotNull { it.typeReference?.typeElement?.let { KotlinNameSuggester.suggestTypeAliasNameByPsi(it, validator) } }
.takeIf { it.isNotEmpty() }
?: listOf(KotlinNameSuggester.suggestNameByName("O", validator))
val className = classNames.first()
val psiFactory = KtPsiFactory(element)
val targetSibling = element.parentsWithSelf.first { it.parent == targetParent }
@@ -102,8 +98,8 @@ class ConvertObjectLiteralToClassIntention : SelfTargetingRangeIntention<KtObjec
)
}
}
).run(editor, ExtractionData(element.containingKtFile, element.toRange(), targetSibling)) {
val functionDeclaration = it.declaration as KtFunction
).run(editor, ExtractionData(element.containingKtFile, element.toRange(), targetSibling)) { extractionResult ->
val functionDeclaration = extractionResult.declaration as KtFunction
if (functionDeclaration.valueParameters.isNotEmpty()) {
val valKeyword = psiFactory.createValKeyword()
newClass
@@ -115,10 +111,24 @@ class ConvertObjectLiteralToClassIntention : SelfTargetingRangeIntention<KtObjec
it.addModifier(KtTokens.PRIVATE_KEYWORD)
}
}
functionDeclaration.replaced(newClass).apply {
val introducedClass = functionDeclaration.replaced(newClass).apply {
if (hasMemberReference && containingClass == (parent.parent as? KtClass)) addModifier(KtTokens.INNER_KEYWORD)
primaryConstructor?.let { CodeStyleManager.getInstance(project).reformat(it) }
}.let { CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(it) }
val file = introducedClass.containingFile
val template = TemplateBuilderImpl(file).let { builder ->
builder.replaceElement(introducedClass.nameIdentifier!!, NEW_CLASS_NAME, ChooseStringExpression(classNames), true)
for (psiReference in ReferencesSearch.search(introducedClass, LocalSearchScope(file), false)) {
builder.replaceElement(psiReference.element, USAGE_VARIABLE_NAME, NEW_CLASS_NAME, false)
}
builder.buildInlineTemplate()
}
editor.caretModel.moveToOffset(file.startOffset)
TemplateManager.getInstance(project).startTemplate(editor, template)
}
}
}
@@ -146,3 +156,7 @@ class ConvertObjectLiteralToClassIntention : SelfTargetingRangeIntention<KtObjec
)
}
}
private const val NEW_CLASS_NAME = "NEW_CLASS_NAME"
private const val USAGE_VARIABLE_NAME = "OTHER_VAR"
@@ -1,9 +1,9 @@
class Test { // TARGET_BLOCK:
fun foo() {
O()
Runnable1()
}
class O : Runnable {
class Runnable1 : Runnable {
override fun run() {
}
}
@@ -2,10 +2,10 @@ class Test { // TARGET_BLOCK:
fun method() = 1
fun foo() {
O()
Runnable1()
}
inner class O : Runnable {
inner class Runnable1 : Runnable {
override fun run() {
method()
}
@@ -2,10 +2,10 @@ class Test { // TARGET_BLOCK:
var field = 1
fun foo() {
O()
Runnable1()
}
inner class O : Runnable {
inner class Runnable1 : Runnable {
override fun run() {
field = 2
}
@@ -2,12 +2,12 @@ class Test {
var field = 1
fun foo() { // TARGET_BLOCK:
class O : Runnable {
class Runnable1 : Runnable {
override fun run() {
field = 2
}
}
O()
Runnable1()
}
}
@@ -1,9 +1,9 @@
open class K
fun foo(n: Int) {
val x = <caret>O()
val x = K1()
}
class O : K() {
class K1<caret> : K() {
fun bar() = 1
}
@@ -1,7 +1,7 @@
fun foo(n: Int) {
val x = <caret>O(n)
val x = O(n)
}
class O(private val n: Int) {
class O<caret>(private val n: Int) {
fun bar() = n
}
@@ -1,9 +1,9 @@
open class K
fun foo(n: Int) {
val x = <caret>O(n)
val x = K1(n)
}
class O(private val n: Int) : K() {
class K1<caret>(private val n: Int) : K() {
fun bar() = n
}