KotlinCommonIntentionActionsFactory extensions for IDEA-85507 and KT-11906 (#1093)
* `KotlinCommonModifications` renamed to `KotlinCommonIntentionActionsFactory` * IDEA-85507: KotlinCommonIntentionActionsFactory#createAddMethodAction implementation * KT-11906 `KotlinCommonIntentionActionsFactory#createAddBeanPropertyActions` implementation * `KotlinCommonIntentionActionsFactory#createAddMethodAction` now uses a `CallableBuilder` for making a function * `KotlinCommonIntentionActionsFactory#createAddBeanPropertyActions` privides a "TODO" initializer and also suggests a `lateinit` property * KT-11980 `KotlinCommonIntentionActionsFactory#createAddConstructorActions` implementation * `KotlinCommonIntentionActionsFactory` upgraded to new `JvmCommonIntentionActionsFactory`-api * KT-11980 `KotlinCommonIntentionActionsFactory` is able to change primary constructors * `JvmCommonIntentionActionsFactory` api update
This commit is contained in:
@@ -2480,7 +2480,7 @@
|
|||||||
<projectOpenProcessor implementation="org.jetbrains.kotlin.gradle.GradleKotlinDSLProjectOpenProcessor"/>
|
<projectOpenProcessor implementation="org.jetbrains.kotlin.gradle.GradleKotlinDSLProjectOpenProcessor"/>
|
||||||
<projectImportProvider implementation="org.jetbrains.kotlin.gradle.GradleKotlinDSLProjectImportProvider"/>
|
<projectImportProvider implementation="org.jetbrains.kotlin.gradle.GradleKotlinDSLProjectImportProvider"/>
|
||||||
|
|
||||||
<codeInsight.intention.jvmCommonIntentionActionsFactory language="kotlin" implementationClass="org.jetbrains.kotlin.idea.intentions.KotlinCommonModifications"/>
|
<codeInsight.intention.jvmCommonIntentionActionsFactory language="kotlin" implementationClass="org.jetbrains.kotlin.idea.intentions.KotlinCommonIntentionActionsFactory"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="org.jetbrains.uast">
|
<extensions defaultExtensionNs="org.jetbrains.uast">
|
||||||
|
|||||||
@@ -0,0 +1,255 @@
|
|||||||
|
/*
|
||||||
|
* 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.*
|
||||||
|
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> =
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,55 +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.*
|
|
||||||
import com.intellij.psi.PsiModifier
|
|
||||||
import org.jetbrains.kotlin.asJava.elements.KtLightElement
|
|
||||||
import org.jetbrains.kotlin.idea.quickfix.AddModifierFix
|
|
||||||
import org.jetbrains.kotlin.idea.quickfix.RemoveModifierFix
|
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
|
||||||
import org.jetbrains.kotlin.psi.KtModifierListOwner
|
|
||||||
import org.jetbrains.uast.UDeclaration
|
|
||||||
|
|
||||||
|
|
||||||
class KotlinCommonModifications : JvmCommonIntentionActionsFactory() {
|
|
||||||
override fun createChangeModifierAction(declaration: UDeclaration, modifier: String, shouldPresent: Boolean): IntentionAction? {
|
|
||||||
val kModifierOwner = (declaration.psi as? KtLightElement<*, *>?)?.kotlinOrigin as? 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.psi.JavaPsiFacade
|
||||||
|
import com.intellij.psi.PsiModifier
|
||||||
|
import com.intellij.psi.PsiType
|
||||||
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
|
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
|
||||||
|
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase
|
||||||
|
import org.jetbrains.uast.*
|
||||||
|
import org.junit.Assert
|
||||||
|
|
||||||
|
class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() {
|
||||||
|
|
||||||
|
fun testMakeNotFinal() {
|
||||||
|
myFixture.configureByText("foo.kt", """
|
||||||
|
class Foo {
|
||||||
|
fun bar<caret>(){}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
|
||||||
|
myFixture.launchAction(codeModifications.createChangeModifierAction(atCaret<UDeclaration>(myFixture), PsiModifier.FINAL, false)!!)
|
||||||
|
myFixture.checkResult("""
|
||||||
|
class Foo {
|
||||||
|
open fun bar(){}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testMakePrivate() {
|
||||||
|
myFixture.configureByText("foo.kt", """
|
||||||
|
class Foo<caret> {
|
||||||
|
fun bar(){}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
|
||||||
|
myFixture.launchAction(codeModifications.createChangeModifierAction(atCaret<UDeclaration>(myFixture), PsiModifier.PRIVATE, true)!!)
|
||||||
|
myFixture.checkResult("""
|
||||||
|
private class Foo {
|
||||||
|
fun bar(){}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testMakeNotPrivate() {
|
||||||
|
myFixture.configureByText("foo.kt", """
|
||||||
|
private class Foo<caret> {
|
||||||
|
fun bar(){}
|
||||||
|
}
|
||||||
|
""".trim())
|
||||||
|
|
||||||
|
myFixture.launchAction(codeModifications.createChangeModifierAction(atCaret<UDeclaration>(myFixture), PsiModifier.PRIVATE, false)!!)
|
||||||
|
myFixture.checkResult("""
|
||||||
|
class Foo {
|
||||||
|
fun bar(){}
|
||||||
|
}
|
||||||
|
""".trim(), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testDontMakeFunInObjectsOpen() {
|
||||||
|
myFixture.configureByText("foo.kt", """
|
||||||
|
object Foo {
|
||||||
|
fun bar<caret>(){}
|
||||||
|
}
|
||||||
|
""".trim())
|
||||||
|
Assert.assertNull(codeModifications.createChangeModifierAction(atCaret<UDeclaration>(myFixture), PsiModifier.FINAL, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testAddVoidVoidMethod() {
|
||||||
|
myFixture.configureByText("foo.kt", """
|
||||||
|
|class Foo<caret> {
|
||||||
|
| fun bar() {}
|
||||||
|
|}
|
||||||
|
""".trim().trimMargin())
|
||||||
|
|
||||||
|
myFixture.launchAction(codeModifications.createAddCallableMemberActions(MethodInsertionInfo.simpleMethodInfo(
|
||||||
|
atCaret<UClass>(myFixture), "baz", PsiModifier.PRIVATE, PsiType.VOID, emptyList())).first())
|
||||||
|
myFixture.checkResult("""
|
||||||
|
|class Foo {
|
||||||
|
| fun bar() {}
|
||||||
|
| private fun baz() {
|
||||||
|
|
|
||||||
|
| }
|
||||||
|
|}
|
||||||
|
""".trim().trimMargin(), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testAddIntIntMethod() {
|
||||||
|
myFixture.configureByText("foo.kt", """
|
||||||
|
|class Foo<caret> {
|
||||||
|
| fun bar() {}
|
||||||
|
|}
|
||||||
|
""".trim().trimMargin())
|
||||||
|
|
||||||
|
myFixture.launchAction(codeModifications.createAddCallableMemberActions(MethodInsertionInfo.simpleMethodInfo(
|
||||||
|
atCaret<UClass>(myFixture), "baz", PsiModifier.PUBLIC, PsiType.INT, makeParams(PsiType.INT))).first())
|
||||||
|
myFixture.checkResult("""
|
||||||
|
|class Foo {
|
||||||
|
| fun bar() {}
|
||||||
|
| fun baz(param0: Int): Int {
|
||||||
|
|
|
||||||
|
| }
|
||||||
|
|}
|
||||||
|
""".trim().trimMargin(), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testAddIntPrimaryConstructor() {
|
||||||
|
myFixture.configureByText("foo.kt", """
|
||||||
|
|class Foo<caret> {
|
||||||
|
|}
|
||||||
|
""".trim().trimMargin())
|
||||||
|
|
||||||
|
myFixture.launchAction(codeModifications.createAddCallableMemberActions(MethodInsertionInfo.constructorInfo(
|
||||||
|
atCaret<UClass>(myFixture), makeParams(PsiType.INT))).findWithText("Add primary constructor to 'Foo'"))
|
||||||
|
myFixture.checkResult("""
|
||||||
|
|class Foo(param0: Int) {
|
||||||
|
|}
|
||||||
|
""".trim().trimMargin(), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testAddIntSecondaryConstructor() {
|
||||||
|
myFixture.configureByText("foo.kt", """
|
||||||
|
|class <caret>Foo() {
|
||||||
|
|}
|
||||||
|
""".trim().trimMargin())
|
||||||
|
|
||||||
|
myFixture.launchAction(codeModifications.createAddCallableMemberActions(MethodInsertionInfo.constructorInfo(
|
||||||
|
atCaret<UClass>(myFixture), makeParams(PsiType.INT))).findWithText("Add secondary constructor to 'Foo'"))
|
||||||
|
myFixture.checkResult("""
|
||||||
|
|class Foo() {
|
||||||
|
| constructor(param0: Int) {
|
||||||
|
|
|
||||||
|
| }
|
||||||
|
|}
|
||||||
|
""".trim().trimMargin(), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testChangePrimaryConstructorInt() {
|
||||||
|
myFixture.configureByText("foo.kt", """
|
||||||
|
|class <caret>Foo() {
|
||||||
|
|}
|
||||||
|
""".trim().trimMargin())
|
||||||
|
|
||||||
|
myFixture.launchAction(codeModifications.createAddCallableMemberActions(MethodInsertionInfo.constructorInfo(
|
||||||
|
atCaret<UClass>(myFixture), makeParams(PsiType.INT))).findWithText("Add 'int' as 1st parameter to method 'Foo'"))
|
||||||
|
myFixture.checkResult("""
|
||||||
|
|class Foo(param0: Int) {
|
||||||
|
|}
|
||||||
|
""".trim().trimMargin(), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testAddStringVarProperty() {
|
||||||
|
myFixture.configureByText("foo.kt", """
|
||||||
|
|class Foo<caret> {
|
||||||
|
| fun bar() {}
|
||||||
|
|}
|
||||||
|
""".trim().trimMargin())
|
||||||
|
|
||||||
|
myFixture.launchAction(codeModifications.createAddBeanPropertyActions(
|
||||||
|
atCaret<UClass>(myFixture), "baz", PsiModifier.PUBLIC, PsiType.getTypeByName("java.lang.String", project, GlobalSearchScope.allScope(project)), true, true)
|
||||||
|
.findWithText("Add 'var' property 'baz' to 'Foo'"))
|
||||||
|
myFixture.checkResult("""
|
||||||
|
|class Foo {
|
||||||
|
| var baz: String = TODO("initialize me")
|
||||||
|
| fun bar() {}
|
||||||
|
|}
|
||||||
|
""".trim().trimMargin(), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testAddLateInitStringVarProperty() {
|
||||||
|
myFixture.configureByText("foo.kt", """
|
||||||
|
|class Foo<caret> {
|
||||||
|
| fun bar() {}
|
||||||
|
|}
|
||||||
|
""".trim().trimMargin())
|
||||||
|
|
||||||
|
myFixture.launchAction(codeModifications.createAddBeanPropertyActions(
|
||||||
|
atCaret<UClass>(myFixture), "baz", PsiModifier.PUBLIC, PsiType.getTypeByName("java.lang.String", project, GlobalSearchScope.allScope(project)), true, true)
|
||||||
|
.findWithText("Add 'lateinit var' property 'baz' to 'Foo'"))
|
||||||
|
myFixture.checkResult("""
|
||||||
|
|class Foo {
|
||||||
|
| lateinit var baz: String
|
||||||
|
| fun bar() {}
|
||||||
|
|}
|
||||||
|
""".trim().trimMargin(), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testAddStringValProperty() {
|
||||||
|
myFixture.configureByText("foo.kt", """
|
||||||
|
|class Foo<caret> {
|
||||||
|
| fun bar() {}
|
||||||
|
|}
|
||||||
|
""".trim().trimMargin())
|
||||||
|
|
||||||
|
myFixture.launchAction(codeModifications.createAddBeanPropertyActions(
|
||||||
|
atCaret<UClass>(myFixture), "baz", PsiModifier.PUBLIC, PsiType.getTypeByName("java.lang.String", project, GlobalSearchScope.allScope(project)), false, true).first())
|
||||||
|
myFixture.checkResult("""
|
||||||
|
|class Foo {
|
||||||
|
| val baz: String = TODO("initialize me")
|
||||||
|
| fun bar() {}
|
||||||
|
|}
|
||||||
|
""".trim().trimMargin(), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeParams(vararg psyTypes: PsiType): List<UParameter> {
|
||||||
|
val uastContext = UastContext(myFixture.project)
|
||||||
|
val factory = JavaPsiFacade.getElementFactory(myFixture.project)
|
||||||
|
val parameters = psyTypes.mapIndexed { index, psiType -> factory.createParameter("param$index", psiType) }
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("CAST_NEVER_SUCCEEDS")
|
||||||
|
private fun Array<IntentionAction>.findWithText(text: String): IntentionAction = this.asList().findWithText(text)
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
private val codeModifications: JvmCommonIntentionActionsFactory
|
||||||
|
get() = JvmCommonIntentionActionsFactory.forLanguage(Language.findLanguageByID("kotlin")!!)!!
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,98 +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.quickfix
|
|
||||||
|
|
||||||
import com.intellij.codeInsight.intention.JvmCommonIntentionActionsFactory
|
|
||||||
import com.intellij.lang.Language
|
|
||||||
import com.intellij.openapi.components.ServiceManager
|
|
||||||
import com.intellij.psi.PsiModifier
|
|
||||||
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
|
|
||||||
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase
|
|
||||||
import org.jetbrains.uast.UDeclaration
|
|
||||||
import org.jetbrains.uast.UastContext
|
|
||||||
import org.jetbrains.uast.convert
|
|
||||||
import org.junit.Assert
|
|
||||||
|
|
||||||
|
|
||||||
class CommonModificationsTest : LightPlatformCodeInsightFixtureTestCase() {
|
|
||||||
|
|
||||||
fun testMakeNotFinal() {
|
|
||||||
myFixture.configureByText("foo.kt", """
|
|
||||||
class Foo {
|
|
||||||
fun bar<caret>(){}
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
|
|
||||||
myFixture.launchAction(codeModifications.createChangeModifierAction(uastElementAtCaret(myFixture), PsiModifier.FINAL, false )!!)
|
|
||||||
myFixture.checkResult("""
|
|
||||||
class Foo {
|
|
||||||
open fun bar(){}
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun testMakePrivate() {
|
|
||||||
myFixture.configureByText("foo.kt", """
|
|
||||||
class Foo<caret> {
|
|
||||||
fun bar(){}
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
|
|
||||||
myFixture.launchAction(codeModifications.createChangeModifierAction(uastElementAtCaret(myFixture), PsiModifier.PRIVATE, true )!!)
|
|
||||||
myFixture.checkResult("""
|
|
||||||
private class Foo {
|
|
||||||
fun bar(){}
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun testMakeNotPrivate() {
|
|
||||||
myFixture.configureByText("foo.kt", """
|
|
||||||
private class Foo<caret> {
|
|
||||||
fun bar(){}
|
|
||||||
}
|
|
||||||
""".trim())
|
|
||||||
|
|
||||||
myFixture.launchAction(codeModifications.createChangeModifierAction(uastElementAtCaret(myFixture), PsiModifier.PRIVATE, false )!!)
|
|
||||||
myFixture.checkResult("""
|
|
||||||
class Foo {
|
|
||||||
fun bar(){}
|
|
||||||
}
|
|
||||||
""".trim(), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun testDontMakeFunInObjectsOpen() {
|
|
||||||
myFixture.configureByText("foo.kt", """
|
|
||||||
object Foo {
|
|
||||||
fun bar<caret>(){}
|
|
||||||
}
|
|
||||||
""".trim())
|
|
||||||
Assert.assertNull(codeModifications.createChangeModifierAction(uastElementAtCaret(myFixture), PsiModifier.FINAL, false))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun uastElementAtCaret(myFixture: CodeInsightTestFixture): UDeclaration {
|
|
||||||
val elementAtCaret = myFixture.elementAtCaret
|
|
||||||
val uastContext = ServiceManager.getService(elementAtCaret.project, UastContext::class.java) ?: error("UastContext not found")
|
|
||||||
val uastLanguagePlugin = uastContext.findPlugin(elementAtCaret) ?: error("Language plugin was not found for $this (${this.javaClass.name})")
|
|
||||||
return uastLanguagePlugin.convert<UDeclaration>(elementAtCaret, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val codeModifications: JvmCommonIntentionActionsFactory
|
|
||||||
get() = JvmCommonIntentionActionsFactory.forLanguage(Language.findLanguageByID("kotlin")!!)!!
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user