diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddNameToArgumentFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddNameToArgumentFix.kt index 16ac7c79be1..0fbb9fc06da 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddNameToArgumentFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddNameToArgumentFix.kt @@ -23,7 +23,7 @@ import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.ui.popup.ListPopupStep import com.intellij.openapi.ui.popup.PopupStep import com.intellij.openapi.ui.popup.util.BaseListPopupStep -import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiFile import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.idea.JetIcons import org.jetbrains.kotlin.idea.caches.resolve.analyze @@ -38,23 +38,72 @@ import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode -public class AddNameToArgumentFix(argument: JetValueArgument, private val possibleNames: List) : JetIntentionAction(argument) { +public class AddNameToArgumentFix(argument: JetValueArgument) : JetIntentionAction(argument) { + + override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean { + if (!super.isAvailable(project, editor, file)) return false + if (element.getArgumentExpression() == null) return false + return calculatePossibleArgumentNames().isNotEmpty() + } override fun invoke(project: Project, editor: Editor?, file: JetFile) { + val possibleNames = calculatePossibleArgumentNames() + assert(possibleNames.isNotEmpty(), "isAvailable() should be checked before invoke()") if (possibleNames.size() == 1 || editor == null || !editor.component.isShowing) { addName(project, element, possibleNames.first()) } else { - chooseNameAndAdd(project, editor) + chooseNameAndAdd(project, editor, possibleNames) } } - private fun chooseNameAndAdd(project: Project, editor: Editor) { - JBPopupFactory.getInstance().createListPopup(getNamePopup(project)).showInBestPositionFor(editor) + override fun getText(): String { + return calculatePossibleArgumentNames() + .singleOrNull() + ?.let { "Add name to argument: '${createArgumentWithName(it).text}'" } + ?: "Add name to argument..." } - private fun getNamePopup(project: Project): ListPopupStep { - return object : BaseListPopupStep("Choose parameter name", possibleNames) { + override fun getFamilyName() = "Add name to argument" + + private fun calculatePossibleArgumentNames(): List { + val callElement = element.getParentOfType(true) ?: return emptyList() + + val context = element.analyze(BodyResolveMode.PARTIAL) + val resolvedCall = callElement.getResolvedCall(context) ?: return emptyList() + + val argumentType = element.getArgumentExpression()?.let { context.getType(it) } + + val usedParameters = resolvedCall.call.valueArguments + .map { resolvedCall.getArgumentMapping(it) } + .filterIsInstance() + .filter { argumentMatch -> argumentType == null || argumentType.isError || !argumentMatch.isError() } + .map { it.valueParameter } + .toSet() + + return resolvedCall.resultingDescriptor.valueParameters + .filter { it !in usedParameters } + .map { it.name } + } + + private fun addName(project: Project, argument: JetValueArgument, name: Name) { + project.executeWriteCommand("Add name to argument...") { + argument.replace(createArgumentWithName(name)) + } + } + + private fun createArgumentWithName(name: Name): JetValueArgument { + val argumentExpression = element.getArgumentExpression()!! + val newArgument = JetPsiFactory(element).createArgument(argumentExpression, name, element.getSpreadElement() != null) + return newArgument + } + + private fun chooseNameAndAdd(project: Project, editor: Editor, names: List) { + JBPopupFactory.getInstance().createListPopup(getNamePopup(project, names)).showInBestPositionFor(editor) + } + + private fun getNamePopup(project: Project, names: List): ListPopupStep { + return object : BaseListPopupStep("Choose parameter name", names) { override fun onChosen(selectedValue: Name, finalChoice: Boolean): PopupStep<*>? { if (finalChoice) { addName(project, element, selectedValue) @@ -64,60 +113,15 @@ public class AddNameToArgumentFix(argument: JetValueArgument, private val possib override fun getIconFor(name: Name) = JetIcons.PARAMETER - override fun getTextFor(name: Name) = getParsedArgumentWithName(name, element).text + override fun getTextFor(name: Name) = createArgumentWithName(name).text } } - override fun getText(): String { - return possibleNames - .singleOrNull() - ?.let { "Add name to argument: '${getParsedArgumentWithName(it, element).text}'" } - ?: "Add name to argument..." - } - - override fun getFamilyName() = "Add Name to Argument" - companion object : JetSingleIntentionActionFactory() { override fun createAction(diagnostic: Diagnostic): IntentionAction? { val argument = diagnostic.psiElement.getParentOfType(false) ?: return null - val possibleNames = generatePossibleNames(argument) - if (possibleNames.isEmpty()) return null - return AddNameToArgumentFix(argument, possibleNames) + return AddNameToArgumentFix(argument) } - private fun generatePossibleNames(argument: JetValueArgument): List { - val callElement = argument.getParentOfType(true) ?: return emptyList() - - val context = argument.analyze(BodyResolveMode.PARTIAL) - val resolvedCall = callElement.getResolvedCall(context) ?: return emptyList() - - val argumentType = argument.getArgumentExpression()?.let { context.getType(it) } - - val usedParameters = resolvedCall.call.valueArguments - .map { resolvedCall.getArgumentMapping(it) } - .filterIsInstance() - .filter { argumentMatch -> argumentType == null || argumentType.isError || !argumentMatch.isError() } - .map { it.valueParameter } - .toSet() - - return resolvedCall.resultingDescriptor.valueParameters - .filter { it !in usedParameters } - .map { it.name } - } - - private fun addName(project: Project, argument: JetValueArgument, name: Name) { - PsiDocumentManager.getInstance(project).commitAllDocuments() - - project.executeWriteCommand("Add name to argument...") { - val newArgument = getParsedArgumentWithName(name, argument) - argument.replace(newArgument) - } - } - - private fun getParsedArgumentWithName(name: Name, argument: JetValueArgument): JetValueArgument { - val argumentExpression = argument.getArgumentExpression() - ?: error("Argument should be already parsed.") - return JetPsiFactory(argument).createArgument(argumentExpression, name, argument.getSpreadElement() != null) - } } }