Add quickfix for "Interface doesn't have constructors" #KT-17687 Fixed
This commit is contained in:
committed by
Alexey Sedunov
parent
b5ba475696
commit
e2945b1d12
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.quickfix
|
||||
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
|
||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||
import org.jetbrains.kotlin.resolve.calls.tower.WrongResolutionToClassifier
|
||||
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor
|
||||
import org.jetbrains.kotlin.types.typeUtil.isUnit
|
||||
|
||||
class ConvertToAnonymousObjectFix(
|
||||
callExpression: KtCallExpression,
|
||||
private val interfaceName: String,
|
||||
private val functionName: String,
|
||||
private val functionParameters: String,
|
||||
private val functionReturnType: String?
|
||||
) : KotlinQuickFixAction<KtCallExpression>(callExpression) {
|
||||
override fun getFamilyName() = "Convert to anonymous object"
|
||||
|
||||
override fun getText() = familyName
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||
val callExpression = element ?: return
|
||||
val lambda = callExpression.lambdaArguments.singleOrNull()?.getLambdaExpression() ?: return
|
||||
|
||||
val psiFactory = KtPsiFactory(project)
|
||||
val body = lambda.bodyExpression
|
||||
if (body != null) {
|
||||
body.forEachDescendantOfType<KtReturnExpression> {
|
||||
if (it.getLabelName() == interfaceName) it.labeledExpression?.delete()
|
||||
}
|
||||
val last = body.statements.lastOrNull()
|
||||
if (last !is KtReturnExpression) last?.replace(psiFactory.createExpressionByPattern("return $0", last))
|
||||
}
|
||||
|
||||
val anonymousObject = psiFactory.buildExpression {
|
||||
appendFixedText("object : $interfaceName {")
|
||||
appendFixedText("override fun $functionName$functionParameters")
|
||||
if (functionReturnType != null) appendFixedText(": $functionReturnType")
|
||||
appendFixedText("{")
|
||||
if (body != null) appendExpression(body)
|
||||
appendFixedText("}")
|
||||
appendFixedText("}")
|
||||
}
|
||||
callExpression.replace(anonymousObject)
|
||||
}
|
||||
|
||||
companion object : KotlinSingleIntentionActionFactory() {
|
||||
override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction<KtCallExpression>? {
|
||||
val casted = Errors.RESOLUTION_TO_CLASSIFIER.cast(diagnostic)
|
||||
if (casted.b != WrongResolutionToClassifier.INTERFACE_AS_FUNCTION) return null
|
||||
|
||||
val callExpression = casted.psiElement.parent as? KtCallExpression ?: return null
|
||||
if (callExpression.lambdaArguments.singleOrNull()?.getLambdaExpression() == null) return null
|
||||
val interfaceName = callExpression.calleeExpression?.text ?: return null
|
||||
|
||||
val classDescriptor = casted.a as? LazyClassDescriptor ?: return null
|
||||
val function = classDescriptor.declaredCallableMembers.singleOrNull() as? SimpleFunctionDescriptor ?: return null
|
||||
val functionDeclaration = DescriptorToSourceUtils.descriptorToDeclaration(function) as? KtNamedFunction ?: return null
|
||||
val functionName = functionDeclaration.name ?: return null
|
||||
val functionParameters = functionDeclaration.valueParameterList?.text ?: return null
|
||||
val functionReturnType = function.returnType?.takeIf { !it.isUnit() }?.toString()
|
||||
|
||||
return ConvertToAnonymousObjectFix(callExpression, interfaceName, functionName, functionParameters, functionReturnType)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -572,5 +572,7 @@ class QuickFixRegistrar : QuickFixContributor {
|
||||
|
||||
DEFAULT_VALUE_NOT_ALLOWED_IN_OVERRIDE.registerFactory(RemoveDefaultParameterValueFix)
|
||||
ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS.registerFactory(RemoveDefaultParameterValueFix)
|
||||
|
||||
RESOLUTION_TO_CLASSIFIER.registerFactory(ConvertToAnonymousObjectFix)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// "Convert to anonymous object" "true"
|
||||
interface I {
|
||||
fun foo(): String
|
||||
}
|
||||
|
||||
fun test() {
|
||||
<caret>I {
|
||||
return@I ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// "Convert to anonymous object" "true"
|
||||
interface I {
|
||||
fun foo(): String
|
||||
}
|
||||
|
||||
fun test() {
|
||||
object : I {
|
||||
override fun foo(): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// "Convert to anonymous object" "false"
|
||||
// ERROR: Interface I does not have constructors
|
||||
interface I {
|
||||
fun foo(): String
|
||||
fun bar(): Unit
|
||||
}
|
||||
|
||||
fun test() {
|
||||
<caret>I {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// "Convert to anonymous object" "true"
|
||||
interface I {
|
||||
fun foo(a: String, b: Int): Int
|
||||
}
|
||||
|
||||
fun test() {
|
||||
<caret>I {
|
||||
1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// "Convert to anonymous object" "true"
|
||||
interface I {
|
||||
fun foo(a: String, b: Int): Int
|
||||
}
|
||||
|
||||
fun test() {
|
||||
object : I {
|
||||
override fun foo(a: String, b: Int): Int {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Convert to anonymous object" "true"
|
||||
interface I {
|
||||
fun foo(): String
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val i = <caret>I { "" }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// "Convert to anonymous object" "true"
|
||||
interface I {
|
||||
fun foo(): String
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val i = object : I {
|
||||
override fun foo(): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// "Convert to anonymous object" "true"
|
||||
interface I {
|
||||
fun bar(): Unit
|
||||
}
|
||||
|
||||
fun foo() {
|
||||
}
|
||||
|
||||
fun test() {
|
||||
<caret>I {
|
||||
foo()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// "Convert to anonymous object" "true"
|
||||
interface I {
|
||||
fun bar(): Unit
|
||||
}
|
||||
|
||||
fun foo() {
|
||||
}
|
||||
|
||||
fun test() {
|
||||
object : I {
|
||||
override fun bar() {
|
||||
return foo()
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -1257,6 +1257,19 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/convertToAnonymousObject")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class ConvertToAnonymousObject extends AbstractQuickFixMultiFileTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTestWithExtraFile, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInConvertToAnonymousObject() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/convertToAnonymousObject"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/createFromUsage")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
@@ -2002,6 +2002,44 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/convertToAnonymousObject")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class ConvertToAnonymousObject extends AbstractQuickFixTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInConvertToAnonymousObject() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/convertToAnonymousObject"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("labeledReturn.kt")
|
||||
public void testLabeledReturn() throws Exception {
|
||||
runTest("idea/testData/quickfix/convertToAnonymousObject/labeledReturn.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("multiMethod.kt")
|
||||
public void testMultiMethod() throws Exception {
|
||||
runTest("idea/testData/quickfix/convertToAnonymousObject/multiMethod.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("parameter.kt")
|
||||
public void testParameter() throws Exception {
|
||||
runTest("idea/testData/quickfix/convertToAnonymousObject/parameter.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
runTest("idea/testData/quickfix/convertToAnonymousObject/simple.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("unit.kt")
|
||||
public void testUnit() throws Exception {
|
||||
runTest("idea/testData/quickfix/convertToAnonymousObject/unit.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/createFromUsage")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user