Intentions: Implement intention which converts object literal to class

#KT-15056 Fixed
This commit is contained in:
Alexey Sedunov
2016-12-22 21:17:59 +03:00
parent 7def536751
commit 534a773816
15 changed files with 232 additions and 0 deletions
+1
View File
@@ -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
@@ -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
}
@@ -0,0 +1,7 @@
open class K
fun foo(n: Int) {
val x = <spot>object</spot> : K() {
fun bar() = n
}
}
@@ -0,0 +1,5 @@
<html>
<body>
This intention converts object literal to class and replaces object literal expression with constructor call.
</body>
</html>
+5
View File
@@ -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"
@@ -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
@@ -0,0 +1,7 @@
open class K
fun foo(n: Int) {
val x = <caret>object : K() {
fun bar() = 1
}
}
@@ -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
}
}
@@ -0,0 +1,7 @@
fun foo(n: Int) {
val x = <caret>O(n)
}
class O(private val n: Int) {
fun bar() = n
}
@@ -0,0 +1,7 @@
open class K
fun foo(n: Int) {
val x = <caret>object : K() {
fun bar() = n
}
}
@@ -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)