diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index d62c02d6bf0..02a3ced4e26 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2676,8 +2676,6 @@ - - diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/KotlinCommonIntentionActionsFactory.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/KotlinCommonIntentionActionsFactory.kt deleted file mode 100644 index 6e043e9bb23..00000000000 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/KotlinCommonIntentionActionsFactory.kt +++ /dev/null @@ -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() - ?: 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 { - - 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()!! - insertMembersAfter(null, ktClassOrObject, listOf(function), null) - } - - if (setterRequired) - return listOf(addPropertyFix(), addPropertyFix(lateinit = true)) - else - return listOf(addPropertyFix()) - } - - override fun createAddCallableMemberActions(info: MethodInsertionInfo): List = - 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 = 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 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()!! - insertMembersAfter(null, ktClassOrObject, listOf(function), ktClassOrObject.declarations.lastOrNull()) - } - - } - - private fun createAddConstructorActions(info: MethodInsertionInfo.Constructor): List { - val constructorString = CallableBuilder(CONSTRUCTOR).apply { - modifier("") - typeParams() - name() - paramsFromInfo(info) - noReturnType() - blockBody("") - }.asString() - val primaryConstructor = info.containingClass.asKtElement()!!.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()!! - 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()!! - 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): Array = - when { - parameters.isEmpty() -> emptyArray() - 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) - } - -} - diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonIntentionActionsTest.kt b/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonIntentionActionsTest.kt index b4e2b043f63..29d91ecd36f 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonIntentionActionsTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonIntentionActionsTest.kt @@ -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 = emptyList(), + override val returnType: ExpectedTypes = emptyList(), + override val annotations: Collection = emptyList(), + override val parameters: List = 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(){} } """.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 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 CodeInsightTestFixture.atCaret() = elementAtCaret.toUElement() as T @Suppress("CAST_NEVER_SUCCEEDS") private fun List.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")!!)!! - }