Intentions: Implement intention which converts object literal to class
#KT-15056 Fixed
This commit is contained in:
@@ -476,6 +476,7 @@ These artifacts include extensions for the types available in the latter JDKs, s
|
||||
|
||||
- [`KT-15068`](https://youtrack.jetbrains.com/issue/KT-15068) Implement intention which rename file according to the top-level class name
|
||||
- Implement quickfix which enables/disables coroutine support in module or project
|
||||
- [`KT-15056`](https://youtrack.jetbrains.com/issue/KT-15056) Implement intention which converts object literal to class
|
||||
|
||||
## 1.0.6
|
||||
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
open class K
|
||||
|
||||
fun foo(n: Int) {
|
||||
val x = O(n)
|
||||
}
|
||||
|
||||
class O(private val n: Int) : K() {
|
||||
fun bar() = n
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
open class K
|
||||
|
||||
fun foo(n: Int) {
|
||||
val x = <spot>object</spot> : K() {
|
||||
fun bar() = n
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
This intention converts object literal to class and replaces object literal expression with constructor call.
|
||||
</body>
|
||||
</html>
|
||||
@@ -1485,6 +1485,11 @@
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>org.jetbrains.kotlin.idea.intentions.ConvertObjectLiteralToClassIntention</className>
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection"
|
||||
displayName="Object literal can be converted to lambda"
|
||||
groupName="Kotlin"
|
||||
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2010-2016 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.intentions
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager
|
||||
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
|
||||
import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||
import org.jetbrains.kotlin.idea.util.psi.patternMatching.toRange
|
||||
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.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.utils.addToStdlib.singletonList
|
||||
|
||||
class ConvertObjectLiteralToClassIntention : SelfTargetingRangeIntention<KtObjectLiteralExpression>(
|
||||
KtObjectLiteralExpression::class.java,
|
||||
"Convert object literal to class"
|
||||
) {
|
||||
override fun applicabilityRange(element: KtObjectLiteralExpression) = element.objectDeclaration.getObjectKeyword()?.textRange
|
||||
|
||||
override fun startInWriteAction() = false
|
||||
|
||||
private fun doApply(editor: Editor, element: KtObjectLiteralExpression, targetParent: KtElement) {
|
||||
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 psiFactory = KtPsiFactory(element)
|
||||
|
||||
val targetSibling = element.parentsWithSelf.first { it.parent == targetParent }
|
||||
|
||||
val objectDeclaration = element.objectDeclaration
|
||||
val newClass = psiFactory.createClass("class $className")
|
||||
objectDeclaration.getSuperTypeList()?.let {
|
||||
newClass.add(psiFactory.createColon())
|
||||
newClass.add(it)
|
||||
}
|
||||
objectDeclaration.getBody()?.let {
|
||||
newClass.add(it)
|
||||
}
|
||||
|
||||
project.executeWriteCommand(text) {
|
||||
ExtractionEngine(
|
||||
object : ExtractionEngineHelper(text) {
|
||||
override fun configureAndRun(
|
||||
project: Project,
|
||||
editor: Editor,
|
||||
descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts,
|
||||
onFinish: (ExtractionResult) -> Unit
|
||||
) {
|
||||
val descriptor = descriptorWithConflicts.descriptor.copy(suggestedNames = className.singletonList())
|
||||
doRefactor(
|
||||
ExtractionGeneratorConfiguration(descriptor, ExtractionGeneratorOptions.DEFAULT),
|
||||
onFinish
|
||||
)
|
||||
}
|
||||
}
|
||||
).run(editor, ExtractionData(element.containingKtFile, element.toRange(), targetSibling)) {
|
||||
val functionDeclaration = it.declaration as KtFunction
|
||||
if (functionDeclaration.valueParameters.isNotEmpty()) {
|
||||
val valKeyword = psiFactory.createValKeyword()
|
||||
newClass
|
||||
.createPrimaryConstructorParameterListIfAbsent()
|
||||
.replaced(functionDeclaration.valueParameterList!!)
|
||||
.parameters
|
||||
.forEach {
|
||||
it.addAfter(valKeyword, null)
|
||||
it.addModifier(KtTokens.PRIVATE_KEYWORD)
|
||||
}
|
||||
}
|
||||
functionDeclaration.replaced(newClass).apply {
|
||||
primaryConstructor?.let { CodeStyleManager.getInstance(project).reformat(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun applyTo(element: KtObjectLiteralExpression, editor: Editor?) {
|
||||
if (editor == null) return
|
||||
|
||||
val containers = element.getExtractionContainers(strict = true, includeAll = true)
|
||||
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
return doApply(editor, element, containers.last())
|
||||
}
|
||||
|
||||
chooseContainerElementIfNecessary(
|
||||
containers,
|
||||
editor,
|
||||
if (containers.first() is KtFile) "Select target file" else "Select target code block / file",
|
||||
true,
|
||||
{ it },
|
||||
{ doApply(editor, element, it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.intentions.ConvertObjectLiteralToClassIntention
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
open class K
|
||||
|
||||
fun foo(n: Int) {
|
||||
val x = <caret>object : K() {
|
||||
fun bar() = 1
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
open class K
|
||||
|
||||
fun foo(n: Int) {
|
||||
val x = <caret>O()
|
||||
}
|
||||
|
||||
class O : K() {
|
||||
fun bar() = 1
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(n: Int) {
|
||||
val x = <caret>object {
|
||||
fun bar() = n
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fun foo(n: Int) {
|
||||
val x = <caret>O(n)
|
||||
}
|
||||
|
||||
class O(private val n: Int) {
|
||||
fun bar() = n
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
open class K
|
||||
|
||||
fun foo(n: Int) {
|
||||
val x = <caret>object : K() {
|
||||
fun bar() = n
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
open class K
|
||||
|
||||
fun foo(n: Int) {
|
||||
val x = <caret>O(n)
|
||||
}
|
||||
|
||||
class O(private val n: Int) : K() {
|
||||
fun bar() = n
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// "Make 'object : T {}' abstract" "false"
|
||||
// ACTION: Implement members
|
||||
// ACTION: Split property declaration
|
||||
// ACTION: Convert object literal to class
|
||||
// ERROR: Object must be declared abstract or implement abstract member public abstract fun foo(): Unit defined in T
|
||||
interface T {
|
||||
fun foo()
|
||||
|
||||
@@ -4680,6 +4680,33 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/intentions/convertObjectLiteralToClass")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class ConvertObjectLiteralToClass extends AbstractIntentionTest {
|
||||
public void testAllFilesPresentInConvertObjectLiteralToClass() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertObjectLiteralToClass"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("objectLiteralNoCapture.kt")
|
||||
public void testObjectLiteralNoCapture() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertObjectLiteralToClass/objectLiteralNoCapture.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("objectLiteralNoSupers.kt")
|
||||
public void testObjectLiteralNoSupers() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertObjectLiteralToClass/objectLiteralNoSupers.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("objectLiteralWithCapture.kt")
|
||||
public void testObjectLiteralWithCapture() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertObjectLiteralToClass/objectLiteralWithCapture.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/intentions/convertParameterToReceiver")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user