Quick Fixes: Drop deprecated KotlinCommonIntentionActionsFactory

Also update relevant test to use new action factory API
This commit is contained in:
Alexey Sedunov
2017-12-20 14:07:43 +03:00
parent 908bf71ae6
commit ba0a91d74c
3 changed files with 101 additions and 321 deletions
-2
View File
@@ -2676,8 +2676,6 @@
<resolveScopeProvider implementation="org.jetbrains.kotlin.idea.core.script.dependencies.KotlinScriptResolveScopeProvider"/>
<resolveScopeProvider implementation="org.jetbrains.kotlin.idea.core.script.dependencies.ScriptDependenciesResolveScopeProvider"/>
<codeInsight.intention.jvmCommonIntentionActionsFactory language="kotlin" implementationClass="org.jetbrains.kotlin.idea.intentions.KotlinCommonIntentionActionsFactory"/>
<projectService serviceInterface="org.jetbrains.uast.kotlin.KotlinUastBindingContextProviderService"
serviceImplementation="org.jetbrains.uast.kotlin.internal.IdeaKotlinUastBindingContextProviderService"/>
</extensions>
@@ -1,261 +0,0 @@
/*
* Copyright 2010-2017 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.codeInsight.intention.IntentionAction
import com.intellij.codeInsight.intention.JvmCommonIntentionActionsFactory
import com.intellij.codeInsight.intention.MethodInsertionInfo
import com.intellij.codeInsight.intention.QuickFixFactory
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl
import org.jetbrains.kotlin.asJava.LightClassUtil
import org.jetbrains.kotlin.asJava.elements.KtLightElement
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.insertMembersAfter
import org.jetbrains.kotlin.idea.quickfix.AddModifierFix
import org.jetbrains.kotlin.idea.quickfix.RemoveModifierFix
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtPsiFactory.CallableBuilder
import org.jetbrains.kotlin.psi.KtPsiFactory.CallableBuilder.Target.CONSTRUCTOR
import org.jetbrains.kotlin.psi.KtPsiFactory.CallableBuilder.Target.FUNCTION
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.uast.UClass
import org.jetbrains.uast.UDeclaration
import org.jetbrains.uast.UElement
class KotlinCommonIntentionActionsFactory : JvmCommonIntentionActionsFactory() {
override fun createChangeModifierAction(declaration: UDeclaration, modifier: String, shouldPresent: Boolean): IntentionAction? {
val kModifierOwner = declaration.asKtElement<KtModifierListOwner>()
?: throw IllegalArgumentException("$declaration is expected to contain KtLightElement with KtModifierListOwner")
val (kToken, shouldPresentMapped) = if (PsiModifier.FINAL == modifier)
KtTokens.OPEN_KEYWORD to !shouldPresent
else
javaPsiModifiersMapping[modifier] to shouldPresent
if (kToken == null) return null
return if (shouldPresentMapped)
AddModifierFix.createIfApplicable(kModifierOwner, kToken)
else
RemoveModifierFix(kModifierOwner, kToken, false)
}
override fun createAddBeanPropertyActions(uClass: UClass,
propertyName: String,
visibilityModifier: String,
propertyType: PsiType,
setterRequired: Boolean,
getterRequired: Boolean): List<IntentionAction> {
fun addPropertyFix(lateinit: Boolean = false) =
Fix(uClass,
"Add property",
"Add '${if (lateinit) "lateinit " else ""}" +
"${if (setterRequired) "var" else "val"}' property '$propertyName' to '${uClass.name}'")
{ uClass ->
val visibilityStr = javaVisibilityMapping.getValue(visibilityModifier)
val psiFactory = KtPsiFactory(uClass)
val modifiersString = if (lateinit) "lateinit $visibilityStr" else visibilityStr
val function = psiFactory.createProperty(
modifiersString,
propertyName,
typeString(propertyType),
setterRequired,
if (lateinit) null else "TODO(\"initialize me\")")
val ktClassOrObject = uClass.asKtElement<KtClassOrObject>()!!
insertMembersAfter(null, ktClassOrObject, listOf(function), null)
}
if (setterRequired)
return listOf(addPropertyFix(), addPropertyFix(lateinit = true))
else
return listOf(addPropertyFix())
}
override fun createAddCallableMemberActions(info: MethodInsertionInfo): List<IntentionAction> =
when (info) {
is MethodInsertionInfo.Method ->
createAddMethodAction(info)?.let { listOf(it) } ?: emptyList()
is MethodInsertionInfo.Constructor ->
createAddConstructorActions(info)
}
companion object {
val javaPsiModifiersMapping = mapOf(
PsiModifier.PRIVATE to KtTokens.PRIVATE_KEYWORD,
PsiModifier.PUBLIC to KtTokens.PUBLIC_KEYWORD,
PsiModifier.PROTECTED to KtTokens.PUBLIC_KEYWORD,
PsiModifier.ABSTRACT to KtTokens.ABSTRACT_KEYWORD
)
val javaVisibilityMapping: Map<String, String> = mapOf(
PsiModifier.PRIVATE to Visibilities.PRIVATE.displayName,
PsiModifier.PUBLIC to "",
PsiModifier.PROTECTED to Visibilities.PROTECTED.displayName,
PsiModifier.PACKAGE_LOCAL to Visibilities.INTERNAL.displayName
).withDefault { Visibilities.DEFAULT_VISIBILITY.displayName }
fun typeString(str: PsiType): String {
var typeName: String? = when (str) {
PsiType.VOID -> ""
PsiType.INT -> "kotlin.Int"
PsiType.LONG -> "kotlin.Long"
PsiType.SHORT -> "kotlin.Short"
PsiType.BOOLEAN -> "kotlin.Boolean"
PsiType.BYTE -> "kotlin.Byte"
PsiType.CHAR -> "kotlin.Char"
PsiType.DOUBLE -> "kotlin.Double"
PsiType.FLOAT -> "kotlin.Float"
else -> null
}
if (typeName == null)
typeName = JavaToKotlinClassMap.mapJavaToKotlin(FqName(str.canonicalText), DefaultBuiltIns.Instance)?.fqNameSafe?.asString()
return typeName ?: str.canonicalText
}
}
private inline fun <reified T : KtElement> UElement.asKtElement(): T? =
(psi as? KtLightElement<*, *>?)?.kotlinOrigin as? T
private fun CallableBuilder.paramsFromInfo(info: MethodInsertionInfo) {
for ((index, param) in info.parameters.withIndex()) {
param(param.name ?: "arg${index + 1}", typeString(param.type))
}
}
private fun createAddMethodAction(info: MethodInsertionInfo.Method): IntentionAction? {
val visibilityStr = info.modifiers.map { javaVisibilityMapping.get(it) ?: it }.joinToString(" ")
val functionString = CallableBuilder(FUNCTION).apply {
modifier(visibilityStr)
typeParams()
name(info.name)
paramsFromInfo(info)
info.returnType.let {
when (it) {
PsiType.VOID -> noReturnType()
else -> returnType(typeString(it))
}
}
blockBody("")
}
return Fix(info.containingClass, "Add method", "Add method '${info.name}' to '${info.containingClass.name}'") {
uClass ->
val psiFactory = KtPsiFactory(uClass)
val function = psiFactory.createFunction(functionString.asString())
val ktClassOrObject = uClass.asKtElement<KtClassOrObject>()!!
insertMembersAfter(null, ktClassOrObject, listOf(function), ktClassOrObject.declarations.lastOrNull())
}
}
private fun createAddConstructorActions(info: MethodInsertionInfo.Constructor): List<IntentionAction> {
val constructorString = CallableBuilder(CONSTRUCTOR).apply {
modifier("")
typeParams()
name()
paramsFromInfo(info)
noReturnType()
blockBody("")
}.asString()
val primaryConstructor = info.containingClass.asKtElement<KtClass>()!!.primaryConstructor
val addConstructorAction = if (primaryConstructor == null)
Fix(info.containingClass,
"Add method",
"Add primary constructor to '${info.containingClass.name}'",
{ uClass ->
val psiFactory = KtPsiFactory(uClass)
val constructor = psiFactory.createSecondaryConstructor(constructorString)
val ktClass = uClass.asKtElement<KtClass>()!!
val newPrimaryConstructor = ktClass.createPrimaryConstructorIfAbsent()
newPrimaryConstructor.valueParameterList!!.replace(constructor.valueParameterList!!)
ShortenReferences.DEFAULT.process(newPrimaryConstructor)
})
else Fix(info.containingClass,
"Add method",
"Add secondary constructor to '${info.containingClass.name}'",
{ uClass ->
val psiFactory = KtPsiFactory(uClass)
val constructor = psiFactory.createSecondaryConstructor(constructorString)
val ktClassOrObject = uClass.asKtElement<KtClassOrObject>()!!
insertMembersAfter(null, ktClassOrObject, listOf(constructor), null)
})
val changePrimaryConstructorAction = run {
if (primaryConstructor == null) return@run null
QuickFixFactory.getInstance()
.createChangeMethodSignatureFromUsageFix(
LightClassUtil.getLightClassMethod(primaryConstructor)!!,
fakeParametersExpressions(info.parameters),
PsiSubstitutor.EMPTY, info.containingClass, false, 2
).takeIf { it.isAvailable(info.containingClass.project, null, info.containingClass.containingFile) }
}
return listOf(changePrimaryConstructorAction, addConstructorAction).filterNotNull()
}
private fun fakeParametersExpressions(parameters: List<PsiParameter>): Array<PsiExpression> =
when {
parameters.isEmpty() -> emptyArray<PsiExpression>()
else -> JavaPsiFacade.getElementFactory(parameters.first().project)
.createParameterList(
parameters.map { it.name }.toTypedArray(),
parameters.map { it.type }.toTypedArray()
).parameters.map { FakeExpressionFromParameter(it) }.toTypedArray()
}
private class FakeExpressionFromParameter(private val psiParam: PsiParameter) : PsiReferenceExpressionImpl() {
override fun getText(): String = psiParam.name!!
override fun getProject(): Project = psiParam.project
override fun getParent(): PsiElement = psiParam.parent
override fun getType(): PsiType? = psiParam.type
override fun isValid(): Boolean = true
override fun getContainingFile(): PsiFile = psiParam.containingFile
override fun getReferenceName(): String? = psiParam.name
override fun resolve(): PsiElement? = psiParam
}
private class Fix(uClass: UClass, private val familyName: String, private val text: String, private val action: (uClass: UClass) -> Unit) : LocalQuickFixAndIntentionActionOnPsiElement(uClass) {
override fun getFamilyName(): String = familyName
override fun getText(): String = text
override fun invoke(project: Project, file: PsiFile, editor: Editor?, startElement: PsiElement, endElement: PsiElement) =
action(startElement as UClass)
}
}
@@ -17,19 +17,42 @@
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.codeInsight.intention.JvmCommonIntentionActionsFactory
import com.intellij.codeInsight.intention.MethodInsertionInfo
import com.intellij.lang.Language
import com.intellij.lang.jvm.JvmElement
import com.intellij.lang.jvm.JvmModifier
import com.intellij.lang.jvm.actions.*
import com.intellij.lang.jvm.types.JvmSubstitutor
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiModifier
import com.intellij.psi.PsiJvmSubstitutor
import com.intellij.psi.PsiSubstitutor
import com.intellij.psi.PsiType
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.codeStyle.SuggestedNameInfo
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase
import org.jetbrains.uast.*
import org.jetbrains.kotlin.idea.search.allScope
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.uast.UParameter
import org.jetbrains.uast.UastContext
import org.jetbrains.uast.toUElement
import org.junit.Assert
class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() {
private class SimpleMethodRequest(
project: Project,
override val methodName: String,
override val modifiers: Collection<JvmModifier> = emptyList(),
override val returnType: ExpectedTypes = emptyList(),
override val annotations: Collection<AnnotationRequest> = emptyList(),
override val parameters: List<ExpectedParameter> = emptyList(),
override val targetSubstitutor: JvmSubstitutor = PsiJvmSubstitutor(project, PsiSubstitutor.EMPTY)
) : CreateMethodRequest {
override val isValid: Boolean = true
}
private class NameInfo(vararg names: String) : SuggestedNameInfo(names)
override fun getProjectDescriptor() = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE_FULL_JDK
fun testMakeNotFinal() {
myFixture.configureByText("foo.kt", """
class Foo {
@@ -38,9 +61,9 @@ class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() {
""")
myFixture.launchAction(
codeModifications
.createChangeModifierAction(atCaret(myFixture), PsiModifier.FINAL, false)!!
.ensureHasText("Make 'bar' open")
createModifierActions(
myFixture.atCaret(), MemberRequest.Modifier(JvmModifier.FINAL, false)
).findWithText("Make 'bar' open")
)
myFixture.checkResult("""
class Foo {
@@ -57,9 +80,9 @@ class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() {
""")
myFixture.launchAction(
codeModifications
.createChangeModifierAction(atCaret(myFixture), PsiModifier.PRIVATE, true)!!
.ensureHasText("Make 'Foo' private")
createModifierActions(
myFixture.atCaret(), MemberRequest.Modifier(JvmModifier.PRIVATE, true)
).findWithText("Make 'Foo' private")
)
myFixture.checkResult("""
private class Foo {
@@ -76,9 +99,9 @@ class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() {
""".trim())
myFixture.launchAction(
codeModifications
.createChangeModifierAction(atCaret(myFixture), PsiModifier.PRIVATE, false)!!
.ensureHasText("Remove 'private' modifier")
createModifierActions(
myFixture.atCaret(), MemberRequest.Modifier(JvmModifier.PRIVATE, false)
).findWithText("Remove 'private' modifier")
)
myFixture.checkResult("""
class Foo {
@@ -93,7 +116,7 @@ class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() {
fun bar<caret>(){}
}
""".trim())
Assert.assertNull(codeModifications.createChangeModifierAction(atCaret(myFixture), PsiModifier.FINAL, false))
Assert.assertTrue(createModifierActions(myFixture.atCaret(), MemberRequest.Modifier(JvmModifier.FINAL, false)).isEmpty())
}
fun testAddVoidVoidMethod() {
@@ -104,8 +127,9 @@ class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() {
""".trim().trimMargin())
myFixture.launchAction(
codeModifications.createAddCallableMemberActions(
MethodInsertionInfo.simpleMethodInfo(atCaret(myFixture), "baz", PsiModifier.PRIVATE, PsiType.VOID, emptyList())
createMethodActions(
myFixture.atCaret(),
methodRequest(project, "baz", JvmModifier.PRIVATE, PsiType.VOID)
).findWithText("Add method 'baz' to 'Foo'")
)
myFixture.checkResult("""
@@ -126,15 +150,20 @@ class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() {
""".trim().trimMargin())
myFixture.launchAction(
codeModifications.createAddCallableMemberActions(
MethodInsertionInfo.simpleMethodInfo(atCaret(myFixture), "baz", PsiModifier.PUBLIC, PsiType.INT, makeParams(PsiType.INT))
createMethodActions(
myFixture.atCaret(),
SimpleMethodRequest(project,
methodName = "baz",
modifiers = listOf(JvmModifier.PUBLIC),
returnType = expectedTypes(PsiType.INT),
parameters = expectedParams(PsiType.INT))
).findWithText("Add method 'baz' to 'Foo'")
)
myFixture.checkResult("""
|class Foo {
| fun bar() {}
| fun baz(param0: Int): Int {
|
| TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
| }
|}
""".trim().trimMargin(), true)
@@ -147,8 +176,9 @@ class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() {
""".trim().trimMargin())
myFixture.launchAction(
codeModifications.createAddCallableMemberActions(
MethodInsertionInfo.constructorInfo(atCaret(myFixture), makeParams(PsiType.INT))
createConstructorActions(
myFixture.atCaret(),
MemberRequest.Constructor(parameters = makeParams(PsiType.INT))
).findWithText("Add primary constructor to 'Foo'")
)
myFixture.checkResult("""
@@ -164,8 +194,9 @@ class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() {
""".trim().trimMargin())
myFixture.launchAction(
codeModifications.createAddCallableMemberActions(
MethodInsertionInfo.constructorInfo(atCaret(myFixture), makeParams(PsiType.INT))
createConstructorActions(
myFixture.atCaret(),
MemberRequest.Constructor(parameters = makeParams(PsiType.INT))
).findWithText("Add secondary constructor to 'Foo'")
)
myFixture.checkResult("""
@@ -184,8 +215,9 @@ class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() {
""".trim().trimMargin())
myFixture.launchAction(
codeModifications.createAddCallableMemberActions(
MethodInsertionInfo.constructorInfo(atCaret(myFixture), makeParams(PsiType.INT))
createConstructorActions(
myFixture.atCaret(),
MemberRequest.Constructor(parameters = makeParams(PsiType.INT))
).findWithText("Add 'int' as 1st parameter to method 'Foo'")
)
myFixture.checkResult("""
@@ -201,8 +233,9 @@ class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() {
""".trim().trimMargin())
myFixture.launchAction(
codeModifications.createAddCallableMemberActions(
MethodInsertionInfo.constructorInfo(atCaret(myFixture), makeParams())
createConstructorActions(
myFixture.atCaret(),
MemberRequest.Constructor()
).findWithText("Remove 1st parameter from method 'Foo'")
)
myFixture.checkResult("""
@@ -219,18 +252,21 @@ class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() {
""".trim().trimMargin())
myFixture.launchAction(
codeModifications.createAddBeanPropertyActions(
atCaret(myFixture),
"baz",
PsiModifier.PUBLIC,
PsiType.getTypeByName("java.lang.String", project, GlobalSearchScope.allScope(project)),
true,
true
createPropertyActions(
myFixture.atCaret(),
MemberRequest.Property(
propertyName = "baz",
visibilityModifier = JvmModifier.PUBLIC,
propertyType = PsiType.getTypeByName("java.lang.String", project, project.allScope()),
getterRequired = true,
setterRequired = true
)
).findWithText("Add 'var' property 'baz' to 'Foo'")
)
myFixture.checkResult("""
|class Foo {
| var baz: String = TODO("initialize me")
|
| fun bar() {}
|}
""".trim().trimMargin(), true)
@@ -243,12 +279,22 @@ class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() {
|}
""".trim().trimMargin())
myFixture.launchAction(codeModifications.createAddBeanPropertyActions(
atCaret(myFixture), "baz", PsiModifier.PUBLIC, PsiType.getTypeByName("java.lang.String", project, GlobalSearchScope.allScope(project)), true, true)
.findWithText("Add 'lateinit var' property 'baz' to 'Foo'"))
myFixture.launchAction(
createPropertyActions(
myFixture.atCaret(),
MemberRequest.Property(
propertyName = "baz",
visibilityModifier = JvmModifier.PUBLIC,
propertyType = PsiType.getTypeByName("java.lang.String", project, project.allScope()),
getterRequired = true,
setterRequired = true
)
).findWithText("Add 'lateinit var' property 'baz' to 'Foo'")
)
myFixture.checkResult("""
|class Foo {
| lateinit var baz: String
|
| fun bar() {}
|}
""".trim().trimMargin(), true)
@@ -262,18 +308,21 @@ class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() {
""".trim().trimMargin())
myFixture.launchAction(
codeModifications.createAddBeanPropertyActions(
atCaret(myFixture),
"baz",
PsiModifier.PUBLIC,
PsiType.getTypeByName("java.lang.String", project, GlobalSearchScope.allScope(project)),
false,
true
createPropertyActions(
myFixture.atCaret(),
MemberRequest.Property(
propertyName = "baz",
visibilityModifier = JvmModifier.PUBLIC,
propertyType = PsiType.getTypeByName("java.lang.String", project, project.allScope()),
getterRequired = true,
setterRequired = false
)
).findWithText("Add 'val' property 'baz' to 'Foo'")
)
myFixture.checkResult("""
|class Foo {
| val baz: String = TODO("initialize me")
|
| fun bar() {}
|}
""".trim().trimMargin(), true)
@@ -286,22 +335,16 @@ class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() {
return parameters.map { uastContext.convertElement(it, null, UParameter::class.java) as UParameter }
}
@Suppress("UNCHECKED_CAST")
private fun <T : UElement> atCaret(myFixture: CodeInsightTestFixture): T {
return myFixture.elementAtCaret.toUElement() as T
}
private fun expectedTypes(vararg psiTypes: PsiType) = psiTypes.map { expectedType(it) }
private fun expectedParams(vararg psyTypes: PsiType) =
psyTypes.mapIndexed { index, psiType -> NameInfo("param$index") to expectedTypes(psiType) }
private inline fun <reified T : JvmElement> CodeInsightTestFixture.atCaret() = elementAtCaret.toUElement() as T
@Suppress("CAST_NEVER_SUCCEEDS")
private fun List<IntentionAction>.findWithText(text: String): IntentionAction =
this.firstOrNull { it.text == text } ?:
Assert.fail("intention with text '$text' was not found, only ${this.joinToString { "\"${it.text}\"" }} available") as Nothing
@Suppress("CAST_NEVER_SUCCEEDS")
private fun IntentionAction.ensureHasText(text: String): IntentionAction =
if (this.text == text) this else Assert.fail("intention with text '$text' was not found, only \"${this.text}\" available") as Nothing
private val codeModifications: JvmCommonIntentionActionsFactory
get() = JvmCommonIntentionActionsFactory.forLanguage(Language.findLanguageByID("kotlin")!!)!!
}