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 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.application.ApplicationManager
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiComment import com.intellij.psi.PsiComment
import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch 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.KotlinNameSuggester
import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.refactoring.chooseContainerElementIfNecessary 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.lexer.KtTokens
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.allChildren import org.jetbrains.kotlin.psi.psiUtil.*
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.resolve.scopes.utils.findClassifier import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
class ConvertObjectLiteralToClassIntention : SelfTargetingRangeIntention<KtObjectLiteralExpression>( class ConvertObjectLiteralToClassIntention : SelfTargetingRangeIntention<KtObjectLiteralExpression>(
@@ -55,16 +53,14 @@ class ConvertObjectLiteralToClassIntention : SelfTargetingRangeIntention<KtObjec
val project = element.project val project = element.project
val scope = element.getResolutionScope() 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 psiFactory = KtPsiFactory(element)
val targetSibling = element.parentsWithSelf.first { it.parent == targetParent } val targetSibling = element.parentsWithSelf.first { it.parent == targetParent }
@@ -102,8 +98,8 @@ class ConvertObjectLiteralToClassIntention : SelfTargetingRangeIntention<KtObjec
) )
} }
} }
).run(editor, ExtractionData(element.containingKtFile, element.toRange(), targetSibling)) { ).run(editor, ExtractionData(element.containingKtFile, element.toRange(), targetSibling)) { extractionResult ->
val functionDeclaration = it.declaration as KtFunction val functionDeclaration = extractionResult.declaration as KtFunction
if (functionDeclaration.valueParameters.isNotEmpty()) { if (functionDeclaration.valueParameters.isNotEmpty()) {
val valKeyword = psiFactory.createValKeyword() val valKeyword = psiFactory.createValKeyword()
newClass newClass
@@ -115,10 +111,24 @@ class ConvertObjectLiteralToClassIntention : SelfTargetingRangeIntention<KtObjec
it.addModifier(KtTokens.PRIVATE_KEYWORD) 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) if (hasMemberReference && containingClass == (parent.parent as? KtClass)) addModifier(KtTokens.INNER_KEYWORD)
primaryConstructor?.let { CodeStyleManager.getInstance(project).reformat(it) } 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: class Test { // TARGET_BLOCK:
fun foo() { fun foo() {
O() Runnable1()
} }
class O : Runnable { class Runnable1 : Runnable {
override fun run() { override fun run() {
} }
} }
@@ -2,10 +2,10 @@ class Test { // TARGET_BLOCK:
fun method() = 1 fun method() = 1
fun foo() { fun foo() {
O() Runnable1()
} }
inner class O : Runnable { inner class Runnable1 : Runnable {
override fun run() { override fun run() {
method() method()
} }
@@ -2,10 +2,10 @@ class Test { // TARGET_BLOCK:
var field = 1 var field = 1
fun foo() { fun foo() {
O() Runnable1()
} }
inner class O : Runnable { inner class Runnable1 : Runnable {
override fun run() { override fun run() {
field = 2 field = 2
} }
@@ -2,12 +2,12 @@ class Test {
var field = 1 var field = 1
fun foo() { // TARGET_BLOCK: fun foo() { // TARGET_BLOCK:
class O : Runnable { class Runnable1 : Runnable {
override fun run() { override fun run() {
field = 2 field = 2
} }
} }
O() Runnable1()
} }
} }
@@ -1,9 +1,9 @@
open class K open class K
fun foo(n: Int) { fun foo(n: Int) {
val x = <caret>O() val x = K1()
} }
class O : K() { class K1<caret> : K() {
fun bar() = 1 fun bar() = 1
} }
@@ -1,7 +1,7 @@
fun foo(n: Int) { 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 fun bar() = n
} }
@@ -1,9 +1,9 @@
open class K open class K
fun foo(n: Int) { 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 fun bar() = n
} }