192: KotlinElementActionsFactory: changing parameters could add annotations

This commit is contained in:
Nicolay Mitropolsky
2019-03-19 15:36:32 +03:00
parent 6b91b7cce5
commit 1669b6aa9f
3 changed files with 66 additions and 47 deletions
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.idea.quickfix.crossLanguage package org.jetbrains.kotlin.idea.quickfix.crossLanguage
import com.intellij.codeInsight.daemon.QuickFixBundle import com.intellij.codeInsight.daemon.QuickFixBundle
import com.intellij.lang.jvm.actions.AnnotationRequest
import com.intellij.lang.jvm.actions.ChangeParametersRequest import com.intellij.lang.jvm.actions.ChangeParametersRequest
import com.intellij.lang.jvm.actions.ExpectedParameter import com.intellij.lang.jvm.actions.ExpectedParameter
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
@@ -65,6 +66,7 @@ internal class ChangeMethodParameters(
data class Add( data class Add(
val name: String, val name: String,
val ktType: KotlinType, val ktType: KotlinType,
val expectedAnnotations: Collection<AnnotationRequest>,
val beforeAnchor: KtParameter? val beforeAnchor: KtParameter?
) : ParameterModification() ) : ParameterModification()
} }
@@ -115,6 +117,7 @@ internal class ChangeMethodParameters(
collected + ParameterModification.Add( collected + ParameterModification.Add(
expectedHead.semanticNames.firstOrNull() ?: "param$index", expectedHead.semanticNames.firstOrNull() ?: "param$index",
kotlinType, kotlinType,
expectedHead.expectedAnnotations,
currentParameters.firstOrNull { anchor -> currentParameters.firstOrNull { anchor ->
expectedParameters.any { expectedParameters.any {
it is ChangeParametersRequest.ExistingParameterWrapper && it.existingKtParameter == anchor it is ChangeParametersRequest.ExistingParameterWrapper && it.existingKtParameter == anchor
@@ -144,6 +147,9 @@ internal class ChangeMethodParameters(
when (action) { when (action) {
is ParameterModification.Add -> { is ParameterModification.Add -> {
val parameter = parametersGenerated.getValue(action) val parameter = parametersGenerated.getValue(action)
for (expectedAnnotation in action.expectedAnnotations) {
addAnnotationEntry(parameter, expectedAnnotation, null)
}
val anchor = action.beforeAnchor val anchor = action.beforeAnchor
if (anchor != null) { if (anchor != null) {
target.valueParameterList!!.addParameterBefore(parameter, anchor) target.valueParameterList!!.addParameterBefore(parameter, anchor)
@@ -437,57 +437,67 @@ class KotlinElementActionsFactory : JvmElementActionsFactory() {
override fun invoke(project: Project, editor: Editor?, file: PsiFile?) { override fun invoke(project: Project, editor: Editor?, file: PsiFile?) {
val target = pointer.element ?: return val target = pointer.element ?: return
val annotationClass = JavaPsiFacade.getInstance(project).findClass(request.qualifiedName, target.resolveScope) val entry = addAnnotationEntry(target, request, annotationTarget)
val kotlinAnnotation = annotationClass?.language == KotlinLanguage.INSTANCE
val annotationUseSiteTargetPrefix = run prefixEvaluation@{
if (annotationTarget == null) return@prefixEvaluation ""
val moduleDescriptor = (target as? KtDeclaration)?.resolveToDescriptorIfAny()?.module ?: return@prefixEvaluation ""
val annotationClassDescriptor = moduleDescriptor.resolveClassByFqName(
FqName(request.qualifiedName), NoLookupLocation.FROM_IDE
) ?: return@prefixEvaluation ""
val applicableTargetSet =
AnnotationChecker.applicableTargetSet(annotationClassDescriptor) ?: KotlinTarget.DEFAULT_TARGET_SET
if (KotlinTarget.PROPERTY !in applicableTargetSet) return@prefixEvaluation ""
"${annotationTarget.renderName}:"
}
val entry = target.addAnnotationEntry(
KtPsiFactory(target)
.createAnnotationEntry(
"@$annotationUseSiteTargetPrefix${request.qualifiedName}${
request.attributes.mapIndexed { i, p ->
if (!kotlinAnnotation && i == 0 && p.name == "value")
renderAttributeValue(p.value).toString()
else
"${p.name} = ${renderAttributeValue(p.value)}"
}.joinToString(", ", "(", ")")
}"
)
)
ShortenReferences.DEFAULT.process(entry) ShortenReferences.DEFAULT.process(entry)
} }
private fun renderAttributeValue(annotationAttributeRequest: AnnotationAttributeValueRequest) =
when (annotationAttributeRequest) {
is AnnotationAttributeValueRequest.PrimitiveValue -> annotationAttributeRequest.value
is AnnotationAttributeValueRequest.StringValue -> "\"" + annotationAttributeRequest.value + "\""
}
} }
override fun createChangeParametersActions(target: JvmMethod, request: ChangeParametersRequest): List<IntentionAction> { override fun createChangeParametersActions(target: JvmMethod, request: ChangeParametersRequest): List<IntentionAction> {
val ktNamedFunction = (target as? KtLightElement<*, *>)?.kotlinOrigin as? KtNamedFunction ?: return emptyList() val ktNamedFunction = (target as? KtLightElement<*, *>)?.kotlinOrigin as? KtNamedFunction ?: return emptyList()
return listOfNotNull(ChangeMethodParameters.create(ktNamedFunction,request )) return listOfNotNull(ChangeMethodParameters.create(ktNamedFunction, request))
} }
} }
internal fun addAnnotationEntry(
target: KtModifierListOwner,
request: AnnotationRequest,
annotationTarget: AnnotationUseSiteTarget?
): KtAnnotationEntry {
val annotationClass = JavaPsiFacade.getInstance(target.project).findClass(request.qualifiedName, target.resolveScope)
val kotlinAnnotation = annotationClass?.language == KotlinLanguage.INSTANCE
val annotationUseSiteTargetPrefix = run prefixEvaluation@{
if (annotationTarget == null) return@prefixEvaluation ""
val moduleDescriptor = (target as? KtDeclaration)?.resolveToDescriptorIfAny()?.module ?: return@prefixEvaluation ""
val annotationClassDescriptor = moduleDescriptor.resolveClassByFqName(
FqName(request.qualifiedName), NoLookupLocation.FROM_IDE
) ?: return@prefixEvaluation ""
val applicableTargetSet =
AnnotationChecker.applicableTargetSet(annotationClassDescriptor) ?: KotlinTarget.DEFAULT_TARGET_SET
if (KotlinTarget.PROPERTY !in applicableTargetSet) return@prefixEvaluation ""
"${annotationTarget.renderName}:"
}
// could be generated via descriptor when KT-30478 is fixed
val entry = target.addAnnotationEntry(
KtPsiFactory(target)
.createAnnotationEntry(
"@$annotationUseSiteTargetPrefix${request.qualifiedName}${
request.attributes.mapIndexed { i, p ->
if (!kotlinAnnotation && i == 0 && p.name == "value")
renderAttributeValue(p.value).toString()
else
"${p.name} = ${renderAttributeValue(p.value)}"
}.joinToString(", ", "(", ")")
}"
)
)
return entry
}
private fun renderAttributeValue(annotationAttributeRequest: AnnotationAttributeValueRequest) =
when (annotationAttributeRequest) {
is AnnotationAttributeValueRequest.PrimitiveValue -> annotationAttributeRequest.value
is AnnotationAttributeValueRequest.StringValue -> "\"" + annotationAttributeRequest.value + "\""
}
private fun PsiType.collectTypeParameters(): List<PsiTypeParameter> { private fun PsiType.collectTypeParameters(): List<PsiTypeParameter> {
val results = ArrayList<PsiTypeParameter>() val results = ArrayList<PsiTypeParameter>()
accept( accept(
@@ -67,7 +67,8 @@ class CommonIntentionActionsParametersTest : LightPlatformCodeInsightFixtureTest
runParametersTransformation("Change method parameters to '(a: Int, file: File)'") { currentParameters -> runParametersTransformation("Change method parameters to '(a: Int, file: File)'") { currentParameters ->
currentParameters + expectedParameter( currentParameters + expectedParameter(
PsiType.getTypeByName("java.io.File", project, myFixture.file.resolveScope), "file" PsiType.getTypeByName("java.io.File", project, myFixture.file.resolveScope), "file",
listOf(annotationRequest("Anno", intAttribute("i", 8)))
) )
} }
@@ -76,7 +77,7 @@ class CommonIntentionActionsParametersTest : LightPlatformCodeInsightFixtureTest
import java.io.File import java.io.File
class Foo { class Foo {
fun bar(@Anno(3) a: Int, file: File) {} fun bar(@Anno(3) a: Int, @Anno(i = 8) file: File) {}
} }
""".trimIndent(), true """.trimIndent(), true
) )
@@ -96,7 +97,8 @@ class CommonIntentionActionsParametersTest : LightPlatformCodeInsightFixtureTest
runParametersTransformation("Change method parameters to '(file: File, a: Int)'") { currentParameters -> runParametersTransformation("Change method parameters to '(file: File, a: Int)'") { currentParameters ->
listOf( listOf(
expectedParameter( expectedParameter(
PsiType.getTypeByName("java.io.File", project, myFixture.file.resolveScope), "file" PsiType.getTypeByName("java.io.File", project, myFixture.file.resolveScope), "file",
listOf(annotationRequest("Anno", intAttribute("i", 8)))
) )
) + currentParameters ) + currentParameters
} }
@@ -106,7 +108,7 @@ class CommonIntentionActionsParametersTest : LightPlatformCodeInsightFixtureTest
import java.io.File import java.io.File
class Foo { class Foo {
fun bar(file: File, @Anno(3) a: Int) {} fun bar(@Anno(i = 8) file: File, @Anno(3) a: Int) {}
} }
""".trimIndent(), true """.trimIndent(), true
) )
@@ -125,7 +127,8 @@ class CommonIntentionActionsParametersTest : LightPlatformCodeInsightFixtureTest
runParametersTransformation("Change method parameters to '(a: Int, file: File, c: Int)'") { currentParameters -> runParametersTransformation("Change method parameters to '(a: Int, file: File, c: Int)'") { currentParameters ->
ArrayList<ExpectedParameter>(currentParameters).apply { ArrayList<ExpectedParameter>(currentParameters).apply {
this[1] = expectedParameter( this[1] = expectedParameter(
PsiType.getTypeByName("java.io.File", project, myFixture.file.resolveScope), "file" PsiType.getTypeByName("java.io.File", project, myFixture.file.resolveScope), "file",
listOf(annotationRequest("Anno", intAttribute("i", 8)))
) )
} }
} }
@@ -135,7 +138,7 @@ class CommonIntentionActionsParametersTest : LightPlatformCodeInsightFixtureTest
import java.io.File import java.io.File
class Foo { class Foo {
fun bar(@Anno(1) a: Int, file: File, @Anno(3) c: Int) {} fun bar(@Anno(1) a: Int, @Anno(i = 8) file: File, @Anno(3) c: Int) {}
} }
""".trimIndent(), true """.trimIndent(), true
) )