Add quickfix for "Interface doesn't have constructors" #KT-17687 Fixed

This commit is contained in:
Toshiaki Kameyama
2018-07-17 11:50:50 +09:00
committed by Alexey Sedunov
parent b5ba475696
commit e2945b1d12
13 changed files with 232 additions and 0 deletions
@@ -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 ""
}
}
}
+13
View File
@@ -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()
}
}
}
@@ -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)