Introduce Parameter: Support conversion of local variables

#KT-9170 Fixed
This commit is contained in:
Alexey Sedunov
2015-12-10 19:35:37 +03:00
parent fff011f60a
commit d0b9b6a3b4
12 changed files with 117 additions and 46 deletions
@@ -425,6 +425,7 @@ public class KotlinRefactoringUtil {
if (expressions.size() == 0) { if (expressions.size() == 0) {
if (failOnEmptySuggestion) throw new IntroduceRefactoringException( if (failOnEmptySuggestion) throw new IntroduceRefactoringException(
KotlinRefactoringBundle.message("cannot.refactor.not.expression")); KotlinRefactoringBundle.message("cannot.refactor.not.expression"));
callback.run(null);
return; return;
} }
@@ -19,21 +19,16 @@ package org.jetbrains.kotlin.idea.refactoring.introduce
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.RangeMarker import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.refactoring.introduce.inplace.AbstractInplaceIntroducer import com.intellij.refactoring.introduce.inplace.AbstractInplaceIntroducer
import com.intellij.refactoring.rename.inplace.InplaceRefactoring import com.intellij.refactoring.rename.inplace.InplaceRefactoring
import com.intellij.ui.NonFocusableCheckBox
import com.intellij.util.ui.FormBuilder import com.intellij.util.ui.FormBuilder
import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import java.awt.BorderLayout import java.awt.BorderLayout
public abstract class AbstractKotlinInplaceIntroducer<D: KtNamedDeclaration>( public abstract class AbstractKotlinInplaceIntroducer<D: KtNamedDeclaration>(
@@ -77,10 +72,16 @@ public abstract class AbstractKotlinInplaceIntroducer<D: KtNamedDeclaration>(
): KtExpression? { ): KtExpression? {
if (exprText == null || !declaration.isValid()) return null if (exprText == null || !declaration.isValid()) return null
return containingFile val leaf = containingFile.findElementAt(marker.startOffset) ?: return null
.findElementAt(marker.getStartOffset())
?.getNonStrictParentOfType<KtSimpleNameExpression>() leaf.getParentOfTypeAndBranch<KtProperty> { nameIdentifier }?.let {
?.replaced(KtPsiFactory(myProject).createExpression(exprText)) return it.replaced(KtPsiFactory(myProject).createDeclaration(exprText))
}
val occurrenceExprText = (myExpr as? KtProperty)?.name ?: exprText
return leaf
.getNonStrictParentOfType<KtSimpleNameExpression>()
?.replaced(KtPsiFactory(myProject).createExpression(occurrenceExprText))
} }
override fun updateTitle(declaration: D?) = updateTitle(declaration, null) override fun updateTitle(declaration: D?) = updateTitle(declaration, null)
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter
import com.intellij.codeInsight.template.impl.TemplateManagerImpl import com.intellij.codeInsight.template.impl.TemplateManagerImpl
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.editor.impl.DocumentMarkupModel import com.intellij.openapi.editor.impl.DocumentMarkupModel
import com.intellij.openapi.editor.markup.EffectType import com.intellij.openapi.editor.markup.EffectType
import com.intellij.openapi.editor.markup.HighlighterTargetArea import com.intellij.openapi.editor.markup.HighlighterTargetArea
@@ -26,6 +27,7 @@ import com.intellij.openapi.editor.markup.MarkupModel
import com.intellij.openapi.editor.markup.TextAttributes import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.ui.JBColor import com.intellij.ui.JBColor
import com.intellij.ui.NonFocusableCheckBox import com.intellij.ui.NonFocusableCheckBox
import org.jetbrains.kotlin.idea.core.refactoring.createPrimaryConstructorParameterListIfAbsent import org.jetbrains.kotlin.idea.core.refactoring.createPrimaryConstructorParameterListIfAbsent
@@ -38,6 +40,7 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.psi.psiUtil.getValueParameterList import org.jetbrains.kotlin.psi.psiUtil.getValueParameterList
import org.jetbrains.kotlin.psi.psiUtil.getValueParameters import org.jetbrains.kotlin.psi.psiUtil.getValueParameters
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.supertypes import org.jetbrains.kotlin.types.typeUtil.supertypes
import org.jetbrains.kotlin.utils.addToStdlib.singletonList import org.jetbrains.kotlin.utils.addToStdlib.singletonList
@@ -135,7 +138,9 @@ public class KotlinInplaceParameterIntroducer(
val parameterType = currentType ?: parameter.getTypeReference()!!.getText() val parameterType = currentType ?: parameter.getTypeReference()!!.getText()
descriptor = descriptor.copy(newParameterName = parameterName!!, newParameterTypeText = parameterType) descriptor = descriptor.copy(newParameterName = parameterName!!, newParameterTypeText = parameterType)
val modifier = if (valVar != KotlinValVar.None) "${valVar.keywordName} " else "" val modifier = if (valVar != KotlinValVar.None) "${valVar.keywordName} " else ""
val defaultValue = if (withDefaultValue) " = ${newArgumentValue.getText()}" else "" val defaultValue = if (withDefaultValue) {
" = ${if (newArgumentValue is KtProperty) newArgumentValue.name else newArgumentValue.text}"
} else ""
"$modifier$parameterName: $parameterType$defaultValue" "$modifier$parameterName: $parameterType$defaultValue"
} }
@@ -239,6 +244,16 @@ public class KotlinInplaceParameterIntroducer(
revalidate() revalidate()
} }
override fun getRangeToRename(element: PsiElement): TextRange {
if (element is KtProperty) return element.nameIdentifier!!.textRange.shiftRight(-element.startOffset)
return super.getRangeToRename(element)
}
override fun createMarker(element: PsiElement): RangeMarker {
if (element is KtProperty) return super.createMarker(element.nameIdentifier)
return super.createMarker(element)
}
override fun performIntroduce() { override fun performIntroduce() {
getDescriptorToRefactor(isReplaceAllOccurrences()).performRefactoring() getDescriptorToRefactor(isReplaceAllOccurrences()).performRefactoring()
} }
@@ -27,6 +27,7 @@ import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
import com.intellij.refactoring.introduce.inplace.AbstractInplaceIntroducer import com.intellij.refactoring.introduce.inplace.AbstractInplaceIntroducer
import com.intellij.refactoring.listeners.RefactoringEventListener import com.intellij.refactoring.listeners.RefactoringEventListener
import com.intellij.util.SmartList
import com.intellij.util.containers.MultiMap import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
@@ -36,7 +37,6 @@ import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.idea.core.moveInsideParenthesesAndReplaceWith import org.jetbrains.kotlin.idea.core.moveInsideParenthesesAndReplaceWith
import org.jetbrains.kotlin.idea.core.refactoring.removeTemplateEntryBracesIfPossible import org.jetbrains.kotlin.idea.core.refactoring.removeTemplateEntryBracesIfPossible
import org.jetbrains.kotlin.idea.core.refactoring.runRefactoringWithPostprocessing import org.jetbrains.kotlin.idea.core.refactoring.runRefactoringWithPostprocessing
@@ -50,6 +50,7 @@ import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.application.executeCommand import org.jetbrains.kotlin.idea.util.application.executeCommand
import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.idea.util.approximateWithResolvableType import org.jetbrains.kotlin.idea.util.approximateWithResolvableType
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiRange import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiRange
import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiUnifier import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiUnifier
import org.jetbrains.kotlin.idea.util.psi.patternMatching.toRange import org.jetbrains.kotlin.idea.util.psi.patternMatching.toRange
@@ -61,6 +62,7 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
import org.jetbrains.kotlin.types.typeUtil.isNothing import org.jetbrains.kotlin.types.typeUtil.isNothing
import org.jetbrains.kotlin.types.typeUtil.isUnit import org.jetbrains.kotlin.types.typeUtil.isUnit
import org.jetbrains.kotlin.types.typeUtil.supertypes import org.jetbrains.kotlin.types.typeUtil.supertypes
import org.jetbrains.kotlin.utils.addIfNotNull
import java.util.* import java.util.*
import kotlin.test.fail import kotlin.test.fail
@@ -147,10 +149,11 @@ fun IntroduceParameterDescriptor.performRefactoring() {
.forEach { methodDescriptor.removeParameter(it) } .forEach { methodDescriptor.removeParameter(it) }
} }
val defaultValue = if (newArgumentValue is KtProperty) (newArgumentValue as KtProperty).initializer else newArgumentValue
val parameterInfo = KotlinParameterInfo(callableDescriptor = callableDescriptor, val parameterInfo = KotlinParameterInfo(callableDescriptor = callableDescriptor,
name = newParameterName, name = newParameterName,
defaultValueForCall = if (withDefaultValue) null else newArgumentValue, defaultValueForCall = if (withDefaultValue) null else defaultValue,
defaultValueForParameter = if (withDefaultValue) newArgumentValue else null, defaultValueForParameter = if (withDefaultValue) defaultValue else null,
valOrVar = valVar) valOrVar = valVar)
parameterInfo.currentTypeText = newParameterTypeText parameterInfo.currentTypeText = newParameterTypeText
methodDescriptor.addParameter(parameterInfo) methodDescriptor.addParameter(parameterInfo)
@@ -214,9 +217,20 @@ public open class KotlinIntroduceParameterHandler(
): KotlinIntroduceHandlerBase() { ): KotlinIntroduceHandlerBase() {
open fun invoke(project: Project, editor: Editor, expression: KtExpression, targetParent: KtNamedDeclaration) { open fun invoke(project: Project, editor: Editor, expression: KtExpression, targetParent: KtNamedDeclaration) {
val physicalExpression = expression.substringContextOrThis val physicalExpression = expression.substringContextOrThis
if (physicalExpression is KtProperty && physicalExpression.isLocal && physicalExpression.nameIdentifier == null) {
showErrorHintByKey(project, editor, "cannot.refactor.no.expression", INTRODUCE_PARAMETER)
return
}
val context = physicalExpression.analyze() val context = physicalExpression.analyze()
val expressionType = expression.extractableSubstringInfo?.type ?: context.getType(physicalExpression) val expressionType = if (physicalExpression is KtProperty && physicalExpression.isLocal) {
context[BindingContext.VARIABLE, physicalExpression]?.type
}
else {
expression.extractableSubstringInfo?.type ?: context.getType(physicalExpression)
}
if (expressionType == null) { if (expressionType == null) {
showErrorHint(project, editor, "Expression has no type", INTRODUCE_PARAMETER) showErrorHint(project, editor, "Expression has no type", INTRODUCE_PARAMETER)
return return
@@ -241,7 +255,13 @@ public open class KotlinIntroduceParameterHandler(
else -> null else -> null
} ?: throw AssertionError("Body element is not found: ${targetParent.getElementTextWithContext()}") } ?: throw AssertionError("Body element is not found: ${targetParent.getElementTextWithContext()}")
val nameValidator = NewDeclarationNameValidator(body, sequenceOf(body), NewDeclarationNameValidator.Target.VARIABLES) val nameValidator = NewDeclarationNameValidator(body, sequenceOf(body), NewDeclarationNameValidator.Target.VARIABLES)
val suggestedNames = KotlinNameSuggester.suggestNamesByType(replacementType, nameValidator, "p")
val suggestedNames = SmartList<String>().apply {
if (physicalExpression is KtProperty && !ApplicationManager.getApplication().isUnitTestMode) {
addIfNotNull(physicalExpression.name)
}
addAll(KotlinNameSuggester.suggestNamesByType(replacementType, nameValidator, "p"))
}
val parametersUsages = findInternalUsagesOfParametersAndReceiver(targetParent, functionDescriptor) val parametersUsages = findInternalUsagesOfParametersAndReceiver(targetParent, functionDescriptor)
@@ -252,21 +272,27 @@ public open class KotlinIntroduceParameterHandler(
else { else {
Collections.emptyList() Collections.emptyList()
} }
val occurrencesToReplace = expression.toRange()
.match(body, KotlinPsiUnifier.DEFAULT) val occurrencesToReplace = if (expression is KtProperty) {
.filterNot { ReferencesSearch.search(expression).mapNotNullTo(SmartList(expression.toRange())) { it.element?.toRange() }
val textRange = it.range.getPhysicalTextRange() }
forbiddenRanges.any { it.intersects(textRange) } else {
} expression.toRange()
.mapNotNull { .match(body, KotlinPsiUnifier.DEFAULT)
val matchedElement = it.range.elements.singleOrNull() .filterNot {
when (matchedElement) { val textRange = it.range.getPhysicalTextRange()
is KtExpression -> matchedElement forbiddenRanges.any { it.intersects(textRange) }
is KtStringTemplateEntryWithExpression -> matchedElement.getExpression() }
else -> null .mapNotNull {
} as? KtExpression val matchedElement = it.range.elements.singleOrNull()
} val matchedExpr = when (matchedElement) {
.map { it.toRange() } is KtExpression -> matchedElement
is KtStringTemplateEntryWithExpression -> matchedElement.expression
else -> null
} as? KtExpression
matchedExpr?.toRange()
}
}
project.executeCommand( project.executeCommand(
INTRODUCE_PARAMETER, INTRODUCE_PARAMETER,
@@ -296,11 +322,12 @@ public open class KotlinIntroduceParameterHandler(
withDefaultValue = false, withDefaultValue = false,
parametersUsages = parametersUsages, parametersUsages = parametersUsages,
occurrencesToReplace = occurrencesToReplace, occurrencesToReplace = occurrencesToReplace,
occurrenceReplacer = { occurrenceReplacer = replacer@ {
val expressionToReplace = it.elements.single() as KtExpression val expressionToReplace = it.elements.single() as KtExpression
val replacingExpression = psiFactory.createExpression(newParameterName) val replacingExpression = psiFactory.createExpression(newParameterName)
val substringInfo = expressionToReplace.extractableSubstringInfo val substringInfo = expressionToReplace.extractableSubstringInfo
val result = when { val result = when {
expressionToReplace is KtProperty -> return@replacer expressionToReplace.delete()
expressionToReplace.isLambdaOutsideParentheses() -> { expressionToReplace.isLambdaOutsideParentheses() -> {
expressionToReplace expressionToReplace
.getStrictParentOfType<KtLambdaArgument>()!! .getStrictParentOfType<KtLambdaArgument>()!!
@@ -21,7 +21,6 @@ import com.intellij.openapi.editor.ScrollType
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
@@ -122,6 +121,11 @@ fun selectElementsWithTargetParent(
} }
else { else {
if (!editor.getSelectionModel().hasSelection()) { if (!editor.getSelectionModel().hasSelection()) {
val elementAtCaret = file.findElementAt(editor.caretModel.offset)
elementAtCaret?.getParentOfTypeAndBranch<KtProperty> { nameIdentifier }?.let {
return@selectExpression selectTargetContainer(listOf(it))
}
editor.getSelectionModel().selectLineAtCaret() editor.getSelectionModel().selectLineAtCaret()
} }
selectMultipleExpressions() selectMultipleExpressions()
@@ -1,5 +0,0 @@
fun foo(): Int {
<selection>val x = 1</selection>
return 1 + x
}
@@ -1 +0,0 @@
Cannot introduce parameter of type 'Unit'
@@ -0,0 +1,4 @@
fun foo(i: Int) {
val <caret>t = 1
val u = t + i
}
@@ -0,0 +1,3 @@
fun foo(i: Int, i1: Int = 1) {
val u = i1 + i
}
@@ -0,0 +1,4 @@
fun foo(i: Int) {
<selection>val t = 1</selection>
val u = t + i
}
@@ -0,0 +1,3 @@
fun foo(i: Int, i1: Int = 1) {
val u = i1 + i
}
@@ -3370,12 +3370,6 @@ public class ExtractionTestGenerated extends AbstractExtractionTest {
doIntroduceSimpleParameterTest(fileName); doIntroduceSimpleParameterTest(fileName);
} }
@TestMetadata("localVar.kt")
public void testLocalVar() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/localVar.kt");
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("partialSubstitution.kt") @TestMetadata("partialSubstitution.kt")
public void testPartialSubstitution() throws Exception { public void testPartialSubstitution() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/partialSubstitution.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/partialSubstitution.kt");
@@ -3666,6 +3660,27 @@ public class ExtractionTestGenerated extends AbstractExtractionTest {
doIntroduceSimpleParameterTest(fileName); doIntroduceSimpleParameterTest(fileName);
} }
} }
@TestMetadata("idea/testData/refactoring/introduceParameter/variableConversion")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class VariableConversion extends AbstractExtractionTest {
public void testAllFilesPresentInVariableConversion() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/introduceParameter/variableConversion"), Pattern.compile("^(.+)\\.(kt|kts)$"), true);
}
@TestMetadata("caretAtIdentifier.kt")
public void testCaretAtIdentifier() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/variableConversion/caretAtIdentifier.kt");
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("fullSelection.kt")
public void testFullSelection() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/variableConversion/fullSelection.kt");
doIntroduceSimpleParameterTest(fileName);
}
}
} }
@TestMetadata("idea/testData/refactoring/introduceLambdaParameter") @TestMetadata("idea/testData/refactoring/introduceLambdaParameter")