diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/crossLanguage/ChangeMethodParameters.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/crossLanguage/ChangeMethodParameters.kt index e12c6b0396b..3acd1b71588 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/crossLanguage/ChangeMethodParameters.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/crossLanguage/ChangeMethodParameters.kt @@ -5,38 +5,50 @@ package org.jetbrains.kotlin.idea.quickfix.crossLanguage import com.intellij.codeInsight.daemon.QuickFixBundle +import com.intellij.lang.jvm.actions.ChangeParametersRequest +import com.intellij.lang.jvm.actions.ExpectedParameter +import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.util.text.StringUtil +import com.intellij.psi.JvmPsiConversionHelper +import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.SourceElement import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl +import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.core.ShortenReferences import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers -import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName import org.jetbrains.kotlin.load.java.NOT_NULL_ANNOTATIONS import org.jetbrains.kotlin.load.java.NULLABLE_ANNOTATIONS import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.KtNamedFunction -import org.jetbrains.kotlin.psi.KtParameterList -import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.ErrorUtils import org.jetbrains.kotlin.types.KotlinType internal class ChangeMethodParameters( target: KtNamedFunction, - private val request: List>, - private val isValid: () -> Boolean + val request: ChangeParametersRequest ) : KotlinQuickFixAction(target) { + override fun getText(): String { - val parametersString = request.joinToString(", ", "(", ")") { (name, type) -> - "${name.asString()}: ${IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_NO_ANNOTATIONS.renderType(type)}" + + val target = element ?: return "" + + val helper = JvmPsiConversionHelper.getInstance(target.project) + + val parametersString = request.expectedParameters.joinToString(", ", "(", ")") { ep -> + val kotlinType = + ep.expectedTypes.firstOrNull()?.theType?.let { helper.convertType(it).resolveToKotlinType(target.getResolutionFacade()) } + "${ep.semanticNames.firstOrNull() ?: "parameter"}: ${kotlinType?.let { + IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_NO_ANNOTATIONS.renderType(it) + } ?: ""}" } val shortenParameterString = StringUtil.shortenTextWithEllipsis(parametersString, 30, 5) @@ -45,12 +57,57 @@ internal class ChangeMethodParameters( override fun getFamilyName(): String = QuickFixBundle.message("change.method.parameters.family") - override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean = element != null && isValid() + override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean = element != null && request.isValid + + private data class ParameterModification( + val name: String, + val ktType: KotlinType + ) + + private fun getParametersModifications( + target: KtNamedFunction, + expectedParameters: List + ): List { + val helper = JvmPsiConversionHelper.getInstance(target.project) + + return expectedParameters.mapIndexed { index, expectedParameter -> + val theType = expectedParameter.expectedTypes.firstOrNull()?.theType + val kotlinType = theType + ?.let { helper.convertType(it).resolveToKotlinType(target.getResolutionFacade()) } + ?: ErrorUtils.createErrorType("unknown type") + + ParameterModification( + expectedParameter.semanticNames.firstOrNull() ?: "param$index", kotlinType + ) + } + } override fun invoke(project: Project, editor: Editor?, file: KtFile) { + if (!request.isValid) return + val target = element ?: return val functionDescriptor = target.resolveToDescriptorIfAny(BodyResolveMode.FULL) ?: return + target.valueParameterList!!.parameters.forEach { target.valueParameterList!!.removeParameter(it) } + val parameterActions = getParametersModifications(target, request.expectedParameters) + + val parametersGenerated = parameterActions.let { + it zip generateParameterList(project, functionDescriptor, it).parameters + }.toMap() + + for (action in parameterActions) { + val parameter = parametersGenerated.getValue(action) + target.valueParameterList!!.addParameter(parameter) + } + + ShortenReferences.DEFAULT.process(target.valueParameterList!!) + } + + private fun generateParameterList( + project: Project, + functionDescriptor: FunctionDescriptor, + paramsToAdd: List + ): KtParameterList { val newFunctionDescriptor = SimpleFunctionDescriptorImpl.create( functionDescriptor.containingDeclaration, functionDescriptor.annotations, @@ -62,11 +119,11 @@ internal class ChangeMethodParameters( functionDescriptor.extensionReceiverParameter?.copy(this), functionDescriptor.dispatchReceiverParameter, functionDescriptor.typeParameters, - request.withIndex().map { (index, parameter) -> + paramsToAdd.mapIndexed { index, parameter -> ValueParameterDescriptorImpl( this, null, index, Annotations.EMPTY, - Name.identifier(parameter.first.toString()), - parameter.second, false, + Name.identifier(parameter.name), + parameter.ktType, false, false, false, null, SourceElement.NO_SOURCE ) }, @@ -92,9 +149,12 @@ internal class ChangeMethodParameters( } } - val newParameterList = target.valueParameterList!!.replace(newFunction.valueParameterList!!) as KtParameterList - ShortenReferences.DEFAULT.process(newParameterList) + return newFunction.valueParameterList!! } + companion object { + fun create(ktNamedFunction: KtNamedFunction, request: ChangeParametersRequest): ChangeMethodParameters? = + ChangeMethodParameters(ktNamedFunction, request) + } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/crossLanguage/ChangeMethodParameters.kt.192 b/idea/src/org/jetbrains/kotlin/idea/quickfix/crossLanguage/ChangeMethodParameters.kt.192 new file mode 100644 index 00000000000..6e7aa53fb4c --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/crossLanguage/ChangeMethodParameters.kt.192 @@ -0,0 +1,225 @@ +/* + * 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.crossLanguage + +import com.intellij.codeInsight.daemon.QuickFixBundle +import com.intellij.lang.jvm.actions.ChangeParametersRequest +import com.intellij.lang.jvm.actions.ExpectedParameter +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.text.StringUtil +import com.intellij.psi.JvmPsiConversionHelper +import org.jetbrains.kotlin.asJava.elements.KtLightElement +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.SourceElement +import org.jetbrains.kotlin.descriptors.annotations.Annotations +import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl +import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl +import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.idea.core.ShortenReferences +import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction +import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.load.java.NOT_NULL_ANNOTATIONS +import org.jetbrains.kotlin.load.java.NULLABLE_ANNOTATIONS +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.KotlinType + +internal class ChangeMethodParameters( + target: KtNamedFunction, + val request: ChangeParametersRequest +) : KotlinQuickFixAction(target) { + + + override fun getText(): String { + + val target = element ?: return "" + + val helper = JvmPsiConversionHelper.getInstance(target.project) + + val parametersString = request.expectedParameters.joinToString(", ", "(", ")") { ep -> + val kotlinType = + ep.expectedTypes.firstOrNull()?.theType?.let { helper.convertType(it).resolveToKotlinType(target.getResolutionFacade()) } + "${ep.semanticNames.firstOrNull() ?: "parameter"}: ${kotlinType?.let { + IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_NO_ANNOTATIONS.renderType(it) + } ?: ""}" + } + + val shortenParameterString = StringUtil.shortenTextWithEllipsis(parametersString, 30, 5) + return QuickFixBundle.message("change.method.parameters.text", shortenParameterString) + } + + override fun getFamilyName(): String = QuickFixBundle.message("change.method.parameters.family") + + override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean = element != null && request.isValid + + private sealed class ParameterModification { + data class Keep(val ktParameter: KtParameter) : ParameterModification() + data class Remove(val ktParameter: KtParameter) : ParameterModification() + data class Add( + val name: String, + val ktType: KotlinType, + val beforeAnchor: KtParameter? + ) : ParameterModification() + } + + private tailrec fun getParametersModifications( + target: KtNamedFunction, + currentParameters: List, + expectedParameters: List, + index: Int = 0, + collected: List = ArrayList(expectedParameters.size) + ): List { + + val expectedHead = expectedParameters.firstOrNull() ?: return collected + currentParameters.map { ParameterModification.Remove(it) } + + if (expectedHead is ChangeParametersRequest.ExistingParameterWrapper) { + val expectedExistingParameter = expectedHead.existingKtParameter + if (expectedExistingParameter == null) { + LOG.error("can't find the kotlinOrigin for parameter ${expectedHead.existingParameter} at index $index") + return collected + } + + val existingInTail = currentParameters.indexOf(expectedExistingParameter) + if (existingInTail == -1) { + throw IllegalArgumentException("can't find existing for parameter ${expectedHead.existingParameter} at index $index") + } + + return getParametersModifications( + target, + currentParameters.subList(existingInTail + 1, currentParameters.size), + expectedParameters.subList(1, expectedParameters.size), + index, + collected + + currentParameters.subList(0, existingInTail).map { ParameterModification.Remove(it) } + + ParameterModification.Keep(expectedExistingParameter) + ) + } + + val helper = JvmPsiConversionHelper.getInstance(target.project) + + val theType = expectedHead.expectedTypes.firstOrNull()?.theType ?: return emptyList() + val kotlinType = helper.convertType(theType).resolveToKotlinType(target.getResolutionFacade()) ?: return emptyList() + + return getParametersModifications( + target, + currentParameters, + expectedParameters.subList(1, expectedParameters.size), + index + 1, + collected + ParameterModification.Add( + expectedHead.semanticNames.firstOrNull() ?: "param$index", + kotlinType, + currentParameters.firstOrNull { anchor -> + expectedParameters.any { + it is ChangeParametersRequest.ExistingParameterWrapper && it.existingKtParameter == anchor + } + }) + ) + + } + + private val ChangeParametersRequest.ExistingParameterWrapper.existingKtParameter + get() = (existingParameter as? KtLightElement<*, *>)?.kotlinOrigin as? KtParameter + + + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + if (!request.isValid) return + + val target = element ?: return + val functionDescriptor = target.resolveToDescriptorIfAny(BodyResolveMode.FULL) ?: return + + val parameterActions = getParametersModifications(target, target.valueParameters, request.expectedParameters) + + val parametersGenerated = parameterActions.filterIsInstance().let { + it zip generateParameterList(project, functionDescriptor, it).parameters + }.toMap() + + for (action in parameterActions) { + when (action) { + is ParameterModification.Add -> { + val parameter = parametersGenerated.getValue(action) + val anchor = action.beforeAnchor + if (anchor != null) { + target.valueParameterList!!.addParameterBefore(parameter, anchor) + } else { + target.valueParameterList!!.addParameter(parameter) + } + } + + is ParameterModification.Keep -> { + // Do nothing + } + + is ParameterModification.Remove -> { + target.valueParameterList!!.removeParameter(action.ktParameter) + } + } + } + + ShortenReferences.DEFAULT.process(target.valueParameterList!!) + } + + private fun generateParameterList( + project: Project, + functionDescriptor: FunctionDescriptor, + paramsToAdd: List + ): KtParameterList { + val newFunctionDescriptor = SimpleFunctionDescriptorImpl.create( + functionDescriptor.containingDeclaration, + functionDescriptor.annotations, + functionDescriptor.name, + functionDescriptor.kind, + SourceElement.NO_SOURCE + ).apply { + initialize( + functionDescriptor.extensionReceiverParameter?.copy(this), + functionDescriptor.dispatchReceiverParameter, + functionDescriptor.typeParameters, + paramsToAdd.mapIndexed { index, parameter -> + ValueParameterDescriptorImpl( + this, null, index, Annotations.EMPTY, + Name.identifier(parameter.name), + parameter.ktType, false, + false, false, null, SourceElement.NO_SOURCE + ) + }, + functionDescriptor.returnType, + functionDescriptor.modality, + functionDescriptor.visibility + ) + } + + + val renderer = IdeDescriptorRenderers.SOURCE_CODE.withOptions { + defaultParameterValueRenderer = null + } + + val newFunction = KtPsiFactory(project).createFunction(renderer.render(newFunctionDescriptor)).apply { + valueParameters.forEach { param -> + param.annotationEntries.forEach { a -> + a.typeReference?.run { + val fqName = FqName(this.text) + if (fqName in (NULLABLE_ANNOTATIONS + NOT_NULL_ANNOTATIONS)) a.delete() + } + } + } + } + + return newFunction.valueParameterList!! + } + + companion object { + fun create(ktNamedFunction: KtNamedFunction, request: ChangeParametersRequest): ChangeMethodParameters? = + ChangeMethodParameters(ktNamedFunction, request) + } + + +} + +private val LOG = Logger.getInstance(ChangeMethodParameters::class.java) \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/crossLanguage/KotlinElementActionsFactory.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/crossLanguage/KotlinElementActionsFactory.kt index 98b2130b6ea..e5d4cd5c4f0 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/crossLanguage/KotlinElementActionsFactory.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/crossLanguage/KotlinElementActionsFactory.kt @@ -192,54 +192,6 @@ class KotlinElementActionsFactory : JvmElementActionsFactory() { .toTypedArray() } - private fun PsiType.collectTypeParameters(): List { - val results = ArrayList() - accept( - object : PsiTypeVisitor() { - override fun visitArrayType(arrayType: PsiArrayType) { - arrayType.componentType.accept(this) - } - - override fun visitClassType(classType: PsiClassType) { - (classType.resolve() as? PsiTypeParameter)?.let { results += it } - classType.parameters.forEach { it.accept(this) } - } - - override fun visitWildcardType(wildcardType: PsiWildcardType) { - wildcardType.bound?.accept(this) - } - } - ) - return results - } - - private fun PsiType.resolveToKotlinType(resolutionFacade: ResolutionFacade): KotlinType? { - val typeParameters = collectTypeParameters() - val components = resolutionFacade.getFrontendService(JavaResolverComponents::class.java) - val rootContext = LazyJavaResolverContext(components, TypeParameterResolver.EMPTY) { null } - val dummyPackageDescriptor = MutablePackageFragmentDescriptor(resolutionFacade.moduleDescriptor, FqName("dummy")) - val dummyClassDescriptor = ClassDescriptorImpl( - dummyPackageDescriptor, - Name.identifier("Dummy"), - Modality.FINAL, - ClassKind.CLASS, - emptyList(), - SourceElement.NO_SOURCE, - false, - LockBasedStorageManager.NO_LOCKS - ) - val typeParameterResolver = object : TypeParameterResolver { - override fun resolveTypeParameter(javaTypeParameter: JavaTypeParameter): TypeParameterDescriptor? { - val psiTypeParameter = (javaTypeParameter as JavaTypeParameterImpl).psi - val index = typeParameters.indexOf(psiTypeParameter) - if (index < 0) return null - return LazyJavaTypeParameterDescriptor(rootContext.child(this), javaTypeParameter, index, dummyClassDescriptor) - } - } - val typeResolver = JavaTypeResolver(rootContext, typeParameterResolver) - val attributes = JavaTypeAttributes(TypeUsage.COMMON) - return typeResolver.transformJavaType(JavaTypeImpl.create(this), attributes).approximateFlexibleTypes(preferNotNull = true) - } private fun ExpectedTypes.toKotlinTypeInfo(resolutionFacade: ResolutionFacade): TypeInfo { val candidateTypes = flatMapTo(LinkedHashSet()) { @@ -532,20 +484,60 @@ class KotlinElementActionsFactory : JvmElementActionsFactory() { override fun createChangeParametersActions(target: JvmMethod, request: ChangeParametersRequest): List { val ktNamedFunction = (target as? KtLightElement<*, *>)?.kotlinOrigin as? KtNamedFunction ?: return emptyList() - - val helper = JvmPsiConversionHelper.getInstance(target.project) - - val params = request.expectedParameters.map { ep -> - val name = ep.semanticNames.singleOrNull() ?: return emptyList() - val expectedType = ep.expectedTypes.singleOrNull() ?: return emptyList() - - val kotlinType = - helper.convertType(expectedType.theType).resolveToKotlinType(ktNamedFunction.getResolutionFacade()) ?: return emptyList() - Name.identifier(name) to kotlinType - } - return listOf(ChangeMethodParameters(ktNamedFunction, params, { request.isValid })) + return listOfNotNull(ChangeMethodParameters.create(ktNamedFunction,request )) } } +private fun PsiType.collectTypeParameters(): List { + val results = ArrayList() + accept( + object : PsiTypeVisitor() { + override fun visitArrayType(arrayType: PsiArrayType) { + arrayType.componentType.accept(this) + } + + override fun visitClassType(classType: PsiClassType) { + (classType.resolve() as? PsiTypeParameter)?.let { results += it } + classType.parameters.forEach { it.accept(this) } + } + + override fun visitWildcardType(wildcardType: PsiWildcardType) { + wildcardType.bound?.accept(this) + } + } + ) + return results +} + + +internal fun PsiType.resolveToKotlinType(resolutionFacade: ResolutionFacade): KotlinType? { + val typeParameters = collectTypeParameters() + val components = resolutionFacade.getFrontendService(JavaResolverComponents::class.java) + val rootContext = LazyJavaResolverContext(components, TypeParameterResolver.EMPTY) { null } + val dummyPackageDescriptor = MutablePackageFragmentDescriptor(resolutionFacade.moduleDescriptor, FqName("dummy")) + val dummyClassDescriptor = ClassDescriptorImpl( + dummyPackageDescriptor, + Name.identifier("Dummy"), + Modality.FINAL, + ClassKind.CLASS, + emptyList(), + SourceElement.NO_SOURCE, + false, + LockBasedStorageManager.NO_LOCKS + ) + val typeParameterResolver = object : TypeParameterResolver { + override fun resolveTypeParameter(javaTypeParameter: JavaTypeParameter): TypeParameterDescriptor? { + val psiTypeParameter = (javaTypeParameter as JavaTypeParameterImpl).psi + val index = typeParameters.indexOf(psiTypeParameter) + if (index < 0) return null + return LazyJavaTypeParameterDescriptor(rootContext.child(this), javaTypeParameter, index, dummyClassDescriptor) + } + } + val typeResolver = JavaTypeResolver(rootContext, typeParameterResolver) + val attributes = JavaTypeAttributes(TypeUsage.COMMON) + return typeResolver.transformJavaType(JavaTypeImpl.create(this), attributes).approximateFlexibleTypes(preferNotNull = true) +} + + private fun JvmPsiConversionHelper.asPsiType(param: Pair>): PsiType? = param.second.firstOrNull()?.theType?.let { convertType(it) } diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonIntentionActionsParametersTest.kt b/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonIntentionActionsParametersTest.kt new file mode 100644 index 00000000000..98c00be35bb --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonIntentionActionsParametersTest.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2010-2019 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.lang.jvm.actions.* +import com.intellij.lang.jvm.types.JvmType +import com.intellij.psi.PsiType +import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase +import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor +import org.jetbrains.uast.UMethod + +class CommonIntentionActionsParametersTest : LightPlatformCodeInsightFixtureTestCase() { + + override fun getProjectDescriptor() = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE_FULL_JDK + + fun testSetParameters() { + myFixture.configureByText( + "foo.kt", + """ + class Foo { + fun bar() {} + } + """.trimIndent() + ) + + myFixture.launchAction( + createChangeParametersActions( + myFixture.atCaret().javaPsi, + setMethodParametersRequest( + linkedMapOf( + "i" to PsiType.INT, + "file" to PsiType.getTypeByName("java.io.File", project, myFixture.file.resolveScope) + ).entries + ) + ).findWithText("Change method parameters to '(i: Int, file: File)'") + ) + myFixture.checkResult( + """ + import java.io.File + + class Foo { + fun bar(i: Int, file: File) {} + } + """.trimIndent(), + true + ) + } + +} + diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonIntentionActionsParametersTest.kt.182 b/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonIntentionActionsParametersTest.kt.182 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonIntentionActionsParametersTest.kt.192 b/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonIntentionActionsParametersTest.kt.192 new file mode 100644 index 00000000000..f8c0c52cf65 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonIntentionActionsParametersTest.kt.192 @@ -0,0 +1,166 @@ +/* + * Copyright 2010-2019 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.lang.jvm.actions.* +import com.intellij.lang.jvm.types.JvmType +import com.intellij.psi.PsiType +import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase +import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor +import org.jetbrains.uast.UMethod + +class CommonIntentionActionsParametersTest : LightPlatformCodeInsightFixtureTestCase() { + + override fun getProjectDescriptor() = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE_FULL_JDK + + override fun setUp() { + super.setUp() + myFixture.configureByText("Anno.kt", "annotation class Anno(val i: Int)") + } + + fun testSetParameters() { + myFixture.configureByText( + "foo.kt", + """ + class Foo { + fun bar() {} + } + """.trimIndent() + ) + + myFixture.launchAction( + createChangeParametersActions( + myFixture.atCaret().javaPsi, + setMethodParametersRequest( + linkedMapOf( + "i" to PsiType.INT, + "file" to PsiType.getTypeByName("java.io.File", project, myFixture.file.resolveScope) + ).entries + ) + ).findWithText("Change method parameters to '(i: Int, file: File)'") + ) + myFixture.checkResult( + """ + import java.io.File + + class Foo { + fun bar(i: Int, file: File) {} + } + """.trimIndent(), + true + ) + } + + + fun testAddParameterToTheEnd() { + myFixture.configureByText( + "foo.kt", + """ + class Foo { + fun bar(@Anno(3) a: Int) {} + } + """.trimIndent() + ) + + runParametersTransformation("Change method parameters to '(a: Int, file: File)'") { currentParameters -> + currentParameters + expectedParameter( + PsiType.getTypeByName("java.io.File", project, myFixture.file.resolveScope), "file" + ) + } + + myFixture.checkResult( + """ + import java.io.File + + class Foo { + fun bar(@Anno(3) a: Int, file: File) {} + } + """.trimIndent(), true + ) + } + + + fun testAddParameterToTheBeginning() { + myFixture.configureByText( + "foo.kt", + """ + class Foo { + fun bar(@Anno(3) a: Int) {} + } + """.trimIndent() + ) + + runParametersTransformation("Change method parameters to '(file: File, a: Int)'") { currentParameters -> + listOf( + expectedParameter( + PsiType.getTypeByName("java.io.File", project, myFixture.file.resolveScope), "file" + ) + ) + currentParameters + } + + myFixture.checkResult( + """ + import java.io.File + + class Foo { + fun bar(file: File, @Anno(3) a: Int) {} + } + """.trimIndent(), true + ) + } + + fun testReplaceInTheMiddle() { + myFixture.configureByText( + "foo.kt", + """ + class Foo { + fun bar(@Anno(1) a: Int, @Anno(2) b: Int, @Anno(3) c: Int) {} + } + """.trimIndent() + ) + + runParametersTransformation("Change method parameters to '(a: Int, file: File, c: Int)'") { currentParameters -> + ArrayList(currentParameters).apply { + this[1] = expectedParameter( + PsiType.getTypeByName("java.io.File", project, myFixture.file.resolveScope), "file" + ) + } + } + + myFixture.checkResult( + """ + import java.io.File + + class Foo { + fun bar(@Anno(1) a: Int, file: File, @Anno(3) c: Int) {} + } + """.trimIndent(), true + ) + } + + private fun runParametersTransformation( + actionName: String, + transformation: (List) -> List + ) { + val psiMethod = myFixture.atCaret().javaPsi + val currentParameters = psiMethod.parameters.map { ChangeParametersRequest.ExistingParameterWrapper(it) } + myFixture.launchAction( + createChangeParametersActions( + psiMethod, + SimpleChangeParametersRequest( + transformation(currentParameters) + ) + ).findWithText(actionName) + ) + } + +} + +private class SimpleChangeParametersRequest(private val list: List) : ChangeParametersRequest { + override fun getExpectedParameters(): List = list + + override fun isValid(): Boolean = true +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonIntentionActionsTest.kt b/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonIntentionActionsTest.kt index d36e7a8f882..0f1c907871f 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonIntentionActionsTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonIntentionActionsTest.kt @@ -11,11 +11,9 @@ 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.lang.jvm.types.JvmType import com.intellij.openapi.project.Project import com.intellij.openapi.util.Pair.pair import com.intellij.psi.* -import com.intellij.psi.codeStyle.SuggestedNameInfo import com.intellij.testFramework.fixtures.CodeInsightTestFixture import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase import junit.framework.TestCase @@ -23,9 +21,6 @@ import org.jetbrains.kotlin.asJava.toLightElements import org.jetbrains.kotlin.idea.search.allScope import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor import org.jetbrains.kotlin.psi.KtModifierListOwner -import org.jetbrains.uast.UMethod -import org.jetbrains.uast.UParameter -import org.jetbrains.uast.UastContext import org.jetbrains.uast.toUElement import org.junit.Assert @@ -57,8 +52,6 @@ class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() { } - private class NameInfo(vararg names: String) : SuggestedNameInfo(names) - override fun getProjectDescriptor() = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE_FULL_JDK fun testMakeNotFinal() { @@ -632,57 +625,12 @@ class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() { """.trim().trimMargin(), true) } - fun testSetParameters() { - myFixture.configureByText( - "foo.kt", """ - |class Foo { - | fun bar() {} - |} - """.trim().trimMargin() - ) - - - myFixture.launchAction( - com.intellij.lang.jvm.actions.createChangeParametersActions( - myFixture.atCaret().javaPsi, - setMethodParametersRequest( - linkedMapOf( - "i" to PsiType.INT, - "file" to PsiType.getTypeByName("java.io.File", project, myFixture.file.resolveScope) - ).entries - ) - ).findWithText("Change method parameters to '(i: Int, file: File)'") - ) - myFixture.checkResult( - """ - import java.io.File - - class Foo { - fun bar(i: Int, file: File) {} - } - """.trimIndent(), true - ) - } - - private fun makeParams(vararg psyTypes: PsiType): List { - 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 } - } private fun expectedTypes(vararg psiTypes: PsiType) = psiTypes.map { expectedType(it) } private fun expectedParams(vararg psyTypes: PsiType) = psyTypes.mapIndexed { index, psiType -> expectedParameter(expectedTypes(psiType), "param$index") } - 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 - class FieldRequest( private val project: Project, val modifiers: List, @@ -703,7 +651,14 @@ class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() { override fun isValid(): Boolean = true } - } +internal inline fun CodeInsightTestFixture.atCaret() = elementAtCaret.toUElement() as T + +@Suppress("CAST_NEVER_SUCCEEDS") +internal 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 + +