Converting object literal to class allows to choose a name (KT-19254)
This commit is contained in:
+33
-19
@@ -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
-2
@@ -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
-2
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -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
|
||||||
}
|
}
|
||||||
+2
-2
@@ -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
|
||||||
}
|
}
|
||||||
+2
-2
@@ -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
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user