Introduce Parameter: Initial support
This commit is contained in:
@@ -676,6 +676,7 @@ fun main(args: Array<String>) {
|
||||
model("refactoring/introduceVariable", extension = "kt", testMethod = "doIntroduceVariableTest")
|
||||
model("refactoring/extractFunction", extension = "kt", testMethod = "doExtractFunctionTest")
|
||||
model("refactoring/introduceProperty", extension = "kt", testMethod = "doIntroducePropertyTest")
|
||||
model("refactoring/introduceParameter", extension = "kt", testMethod = "doIntroduceParameterTest")
|
||||
}
|
||||
|
||||
testClass(javaClass<AbstractSelectExpressionForDebuggerTest>()) {
|
||||
|
||||
@@ -110,7 +110,7 @@ public abstract class CallableRefactoring<T: CallableDescriptor>(
|
||||
|
||||
protected abstract fun performRefactoring(descriptorsForChange: Collection<CallableDescriptor>)
|
||||
|
||||
public fun run() {
|
||||
public fun run(): Boolean {
|
||||
fun buttonPressed(code: Int, dialogButtons: List<String>, button: String): Boolean {
|
||||
return code == dialogButtons indexOf button && button in dialogButtons
|
||||
}
|
||||
@@ -134,7 +134,7 @@ public abstract class CallableRefactoring<T: CallableDescriptor>(
|
||||
|
||||
if (kind == SYNTHESIZED) {
|
||||
LOG.error("Change signature refactoring should not be called for synthesized member " + callableDescriptor)
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
val closestModifiableDescriptors = getClosestModifiableDescriptors()
|
||||
@@ -144,12 +144,12 @@ public abstract class CallableRefactoring<T: CallableDescriptor>(
|
||||
?: Collections.singletonList(callableDescriptor)
|
||||
if (ApplicationManager.getApplication()!!.isUnitTestMode()) {
|
||||
performRefactoring(deepestSuperDeclarations)
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
if (closestModifiableDescriptors.size() == 1 && deepestSuperDeclarations == closestModifiableDescriptors) {
|
||||
performRefactoring(closestModifiableDescriptors)
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
val isSingleFunctionSelected = closestModifiableDescriptors.size() == 1
|
||||
@@ -165,8 +165,10 @@ public abstract class CallableRefactoring<T: CallableDescriptor>(
|
||||
}
|
||||
else -> {
|
||||
//do nothing
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ cannot.refactor.not.expression.to.extract=Cannot find an expression to extract
|
||||
expressions.title=Expressions
|
||||
introduce.variable=Introduce Variable
|
||||
introduce.property=Introduce Property
|
||||
introduce.parameter=Introduce Parameter
|
||||
cannot.refactor.no.container=Cannot refactor in this place
|
||||
cannot.refactor.no.expression=Cannot perform refactoring without an expression
|
||||
cannot.refactor.expression.has.unit.type=Cannot introduce expression of unit type
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.idea.refactoring.changeSignature.JetChangeSignatureHandler;
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandler;
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.KotlinIntroduceParameterHandler;
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.KotlinIntroducePropertyHandler;
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler;
|
||||
import org.jetbrains.kotlin.idea.refactoring.safeDelete.SafeDeletePackage;
|
||||
@@ -40,6 +41,12 @@ public class JetRefactoringSupportProvider extends RefactoringSupportProvider {
|
||||
return new KotlinIntroduceVariableHandler();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public RefactoringActionHandler getIntroduceParameterHandler() {
|
||||
return new KotlinIntroduceParameterHandler();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public RefactoringActionHandler getIntroducePropertyHandler() {
|
||||
return new KotlinIntroducePropertyHandler();
|
||||
|
||||
+2
-2
@@ -56,8 +56,8 @@ public fun runChangeSignature(project: Project,
|
||||
configuration: JetChangeSignatureConfiguration,
|
||||
bindingContext: BindingContext,
|
||||
defaultValueContext: PsiElement,
|
||||
commandName: String? = null) {
|
||||
JetChangeSignature(project, functionDescriptor, configuration, bindingContext, defaultValueContext, commandName).run()
|
||||
commandName: String? = null): Boolean {
|
||||
return JetChangeSignature(project, functionDescriptor, configuration, bindingContext, defaultValueContext, commandName).run()
|
||||
}
|
||||
|
||||
public class JetChangeSignature(project: Project,
|
||||
|
||||
+4
@@ -33,6 +33,10 @@ public class JetMutableMethodDescriptor(val original: JetMethodDescriptor): JetM
|
||||
parameters.add(parameter)
|
||||
}
|
||||
|
||||
public fun setParameter(index: Int, parameter: JetParameterInfo) {
|
||||
parameters[index] = parameter
|
||||
}
|
||||
|
||||
public fun removeParameter(index: Int) {
|
||||
parameters.remove(index)
|
||||
}
|
||||
|
||||
+9
-2
@@ -156,8 +156,15 @@ public class JetFunctionCallUsage extends JetUsageInfo<JetCallElement> {
|
||||
? psiFactory.createArgument(oldArgument.getArgumentExpression())
|
||||
: oldArgument.asElement());
|
||||
}
|
||||
else if (parameterInfo.getDefaultValueForCall().isEmpty())
|
||||
newArgument.delete();
|
||||
// TODO: process default arguments in the middle
|
||||
else if (parameterInfo.getDefaultValueForCall().isEmpty()) {
|
||||
if (parameterInfo.getDefaultValueForParameter() != null) {
|
||||
JetPsiUtil.deleteElementWithDelimiters(newArgument);
|
||||
}
|
||||
else {
|
||||
newArgument.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<JetFunctionLiteralArgument> lambdaArguments = element.getFunctionLiteralArguments();
|
||||
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.refactoring.introduce.introduceParameter
|
||||
|
||||
import com.intellij.codeInsight.template.impl.TemplateManagerImpl
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.EditorFactory
|
||||
import com.intellij.openapi.editor.colors.EditorColors
|
||||
import com.intellij.openapi.editor.event.DocumentAdapter
|
||||
import com.intellij.openapi.editor.event.DocumentEvent
|
||||
import com.intellij.openapi.editor.ex.EditorEx
|
||||
import com.intellij.openapi.editor.impl.DocumentMarkupModel
|
||||
import com.intellij.openapi.editor.markup.EffectType
|
||||
import com.intellij.openapi.editor.markup.HighlighterTargetArea
|
||||
import com.intellij.openapi.editor.markup.MarkupModel
|
||||
import com.intellij.openapi.editor.markup.TextAttributes
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.refactoring.rename.inplace.InplaceRefactoring
|
||||
import com.intellij.ui.DottedBorder
|
||||
import com.intellij.ui.JBColor
|
||||
import org.jetbrains.kotlin.idea.JetFileType
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.refactoring.changeSignature.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinInplaceVariableIntroducer
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.idea.util.application.executeCommand
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.psi.JetExpression
|
||||
import org.jetbrains.kotlin.psi.JetParameter
|
||||
import org.jetbrains.kotlin.psi.JetPsiFactory
|
||||
import org.jetbrains.kotlin.psi.JetPsiUtil
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getValueParameterList
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getValueParameters
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.types.JetType
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Color
|
||||
import java.util.ArrayList
|
||||
import java.util.LinkedHashSet
|
||||
import javax.swing.BorderFactory
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.border.EmptyBorder
|
||||
import javax.swing.border.LineBorder
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
public class KotlinInplaceParameterIntroducer(
|
||||
val descriptor: IntroduceParameterDescriptor,
|
||||
editor: Editor,
|
||||
project: Project
|
||||
): KotlinInplaceVariableIntroducer<JetParameter>(
|
||||
descriptor.addedParameter,
|
||||
editor,
|
||||
project,
|
||||
INTRODUCE_PARAMETER,
|
||||
JetExpression.EMPTY_ARRAY,
|
||||
null,
|
||||
false,
|
||||
descriptor.addedParameter,
|
||||
false,
|
||||
true,
|
||||
descriptor.parameterType,
|
||||
false
|
||||
) {
|
||||
companion object {
|
||||
private val LOG = Logger.getInstance(javaClass<KotlinInplaceParameterIntroducer>())
|
||||
}
|
||||
|
||||
object PreviewDecorator {
|
||||
protected val textAttributes: TextAttributes = with(TextAttributes()) {
|
||||
setEffectType(EffectType.ROUNDED_BOX)
|
||||
setEffectColor(JBColor.RED)
|
||||
this
|
||||
}
|
||||
|
||||
fun applyToRange(range: TextRange, markupModel: MarkupModel) {
|
||||
markupModel.addRangeHighlighter(range.getStartOffset(),
|
||||
range.getEndOffset(),
|
||||
0,
|
||||
textAttributes,
|
||||
HighlighterTargetArea.EXACT_RANGE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private var previewer: EditorEx? = null
|
||||
|
||||
private fun updatePreview(currentName: String?, currentType: String?) {
|
||||
with (descriptor) {
|
||||
var addedRange: TextRange? = null
|
||||
val builder = StringBuilder()
|
||||
|
||||
builder.append(callable.getName())
|
||||
|
||||
val parameters = callable.getValueParameters()
|
||||
builder.append("(")
|
||||
for (i in parameters.indices) {
|
||||
val parameter = parameters[i]
|
||||
|
||||
val parameterText = if (parameter == addedParameter){
|
||||
val parameterName = currentName ?: parameter.getName()
|
||||
val parameterType = currentType ?: parameter.getTypeReference()!!.getText()
|
||||
val modifier = if (valVar != JetValVar.None) "${valVar.name} " else ""
|
||||
|
||||
"$modifier$parameterName: $parameterType"
|
||||
}
|
||||
else parameter.getText()
|
||||
|
||||
builder.append(parameterText)
|
||||
|
||||
val range = TextRange(builder.length() - parameterText.length(), builder.length())
|
||||
if (parameter == addedParameter) {
|
||||
addedRange = range
|
||||
}
|
||||
|
||||
if (i < parameters.lastIndex) {
|
||||
builder.append(", ")
|
||||
}
|
||||
}
|
||||
builder.append(")")
|
||||
|
||||
if (addedRange == null) {
|
||||
LOG.error("Added parameter not found: ${JetPsiUtil.getElementTextWithContext(callable)}")
|
||||
}
|
||||
|
||||
val document = previewer!!.getDocument()
|
||||
runWriteAction { document.setText(builder.toString()) }
|
||||
|
||||
val markupModel = DocumentMarkupModel.forDocument(document, myProject, true)
|
||||
markupModel.removeAllHighlighters()
|
||||
addedRange?.let { PreviewDecorator.applyToRange(it, markupModel) }
|
||||
}
|
||||
revalidate()
|
||||
}
|
||||
|
||||
override fun initPanelControls() {
|
||||
addPanelControl {
|
||||
val previewer = EditorFactory.getInstance().createEditor(EditorFactory.getInstance().createDocument(""),
|
||||
myProject,
|
||||
JetFileType.INSTANCE,
|
||||
true) as EditorEx
|
||||
this.previewer = previewer
|
||||
previewer.setOneLineMode(true)
|
||||
|
||||
with(previewer.getSettings()) {
|
||||
setAdditionalLinesCount(0)
|
||||
setAdditionalColumnsCount(1)
|
||||
setRightMarginShown(false)
|
||||
setFoldingOutlineShown(false)
|
||||
setLineNumbersShown(false)
|
||||
setLineMarkerAreaShown(false)
|
||||
setIndentGuidesShown(false)
|
||||
setVirtualSpace(false)
|
||||
setLineCursorWidth(1)
|
||||
}
|
||||
|
||||
previewer.setHorizontalScrollbarVisible(false)
|
||||
previewer.setVerticalScrollbarVisible(false)
|
||||
previewer.setCaretEnabled(false)
|
||||
|
||||
|
||||
val bg = previewer.getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR)
|
||||
previewer.setBackgroundColor(bg)
|
||||
previewer.setBorder(BorderFactory.createCompoundBorder(DottedBorder(Color.gray), LineBorder(bg, 2)))
|
||||
|
||||
updatePreview(null, null)
|
||||
|
||||
val previewerPanel = JPanel(BorderLayout())
|
||||
previewerPanel.add(previewer.getComponent(), BorderLayout.CENTER)
|
||||
previewerPanel.setBorder(EmptyBorder(2, 2, 6, 2))
|
||||
|
||||
previewerPanel
|
||||
}
|
||||
}
|
||||
|
||||
private var myDocumentAdapter: DocumentAdapter? = null
|
||||
|
||||
fun startRefactoring(suggestedNames: LinkedHashSet<String>): Boolean {
|
||||
if (!performInplaceRefactoring(suggestedNames)) return false
|
||||
|
||||
myDocumentAdapter = object : DocumentAdapter() {
|
||||
override fun documentChanged(e: DocumentEvent?) {
|
||||
if (previewer == null) return
|
||||
val templateState = TemplateManagerImpl.getTemplateState(myEditor)
|
||||
if (templateState != null) {
|
||||
val name = templateState.getVariableValue(KotlinInplaceVariableIntroducer.PRIMARY_VARIABLE_NAME)?.getText()
|
||||
val typeRefText = templateState.getVariableValue(KotlinInplaceVariableIntroducer.TYPE_REFERENCE_VARIABLE_NAME)?.getText()
|
||||
updatePreview(name, typeRefText)
|
||||
}
|
||||
}
|
||||
}
|
||||
myEditor.getDocument().addDocumentListener(myDocumentAdapter!!)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun finish(success: Boolean) {
|
||||
super.finish(success)
|
||||
myDocumentAdapter?.let { myEditor.getDocument().removeDocumentListener(it) }
|
||||
}
|
||||
|
||||
override fun checkLocalScope(): PsiElement? {
|
||||
return descriptor.callable
|
||||
}
|
||||
|
||||
override fun performRefactoring(): Boolean {
|
||||
descriptor.performRefactoring()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun performCleanup() {
|
||||
runWriteAction { JetPsiUtil.deleteElementWithDelimiters(descriptor.addedParameter) }
|
||||
}
|
||||
|
||||
override fun releaseResources() {
|
||||
super.releaseResources()
|
||||
previewer?.let {
|
||||
EditorFactory.getInstance().releaseEditor(it)
|
||||
previewer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.refactoring.introduce.introduceParameter
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.JetNameSuggester
|
||||
import org.jetbrains.kotlin.idea.refactoring.JetNameValidatorImpl
|
||||
import org.jetbrains.kotlin.idea.refactoring.JetRefactoringBundle
|
||||
import org.jetbrains.kotlin.idea.refactoring.changeSignature.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.KotlinIntroduceHandlerBase
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.selectElementsWithTargetParent
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHintByKey
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.idea.util.application.executeCommand
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.idea.util.approximateWithResolvableType
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getValueParameterList
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.scopes.JetScopeUtils
|
||||
import org.jetbrains.kotlin.types.JetType
|
||||
import kotlin.test.fail
|
||||
|
||||
public data class IntroduceParameterDescriptor(
|
||||
val originalExpression: JetExpression,
|
||||
val callable: JetNamedDeclaration,
|
||||
val callableDescriptor: FunctionDescriptor,
|
||||
val addedParameter: JetParameter,
|
||||
val parameterType: JetType
|
||||
) {
|
||||
val valVar: JetValVar
|
||||
|
||||
init {
|
||||
valVar = if (callable is JetClass) {
|
||||
val modifierIsUnnecessary: (PsiElement) -> Boolean = {
|
||||
when {
|
||||
it.getParent() != callable.getBody() ->
|
||||
false
|
||||
it is JetClassInitializer ->
|
||||
true
|
||||
it is JetProperty && it.getInitializer()?.getTextRange()?.intersects(originalExpression.getTextRange()) ?: false ->
|
||||
true
|
||||
else ->
|
||||
false
|
||||
}
|
||||
}
|
||||
if (originalExpression.parents().any(modifierIsUnnecessary)) JetValVar.None else JetValVar.Val
|
||||
}
|
||||
else JetValVar.None
|
||||
}
|
||||
}
|
||||
|
||||
fun IntroduceParameterDescriptor.performRefactoring() {
|
||||
runWriteAction {
|
||||
JetPsiUtil.deleteElementWithDelimiters(addedParameter)
|
||||
|
||||
val config = object: JetChangeSignatureConfiguration {
|
||||
override fun configure(originalDescriptor: JetMethodDescriptor, bindingContext: BindingContext): JetMethodDescriptor {
|
||||
return originalDescriptor.modify {
|
||||
val parameterInfo = JetParameterInfo(name = addedParameter.getName()!!,
|
||||
type = parameterType,
|
||||
defaultValueForParameter = JetPsiUtil.deparenthesize(originalExpression),
|
||||
valOrVar = valVar)
|
||||
parameterInfo.currentTypeText = addedParameter.getTypeReference()?.getText() ?: "Any"
|
||||
addParameter(parameterInfo)
|
||||
}
|
||||
}
|
||||
|
||||
override fun performSilently(affectedFunctions: Collection<PsiElement>): Boolean = true
|
||||
}
|
||||
if (runChangeSignature(callable.getProject(), callableDescriptor, config, callable.analyze(), callable, INTRODUCE_PARAMETER)) {
|
||||
originalExpression.replace(JetPsiFactory(callable).createSimpleName(addedParameter.getName()!!))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase() {
|
||||
public fun invoke(project: Project, editor: Editor, expression: JetExpression, targetParent: JetNamedDeclaration) {
|
||||
val psiFactory = JetPsiFactory(project)
|
||||
|
||||
val parameterList = targetParent.getValueParameterList()
|
||||
|
||||
val context = expression.analyze()
|
||||
val descriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, targetParent]
|
||||
val functionDescriptor: FunctionDescriptor =
|
||||
when (descriptor) {
|
||||
is FunctionDescriptor -> descriptor : FunctionDescriptor
|
||||
is ClassDescriptor -> descriptor.getUnsubstitutedPrimaryConstructor()
|
||||
else -> null
|
||||
} ?: throw AssertionError("Unexpected element type: ${JetPsiUtil.getElementTextWithContext(targetParent)}")
|
||||
val expressionType = context[BindingContext.EXPRESSION_TYPE, expression] ?: KotlinBuiltIns.getInstance().getAnyType()
|
||||
val parameterType = expressionType.approximateWithResolvableType(JetScopeUtils.getResolutionScope(targetParent, context), false)
|
||||
|
||||
val validatorContainer =
|
||||
when (targetParent) {
|
||||
is JetFunction -> targetParent.getBodyExpression()
|
||||
is JetClass -> targetParent.getBody()
|
||||
else -> null
|
||||
} ?: throw AssertionError("Body element is not found: ${JetPsiUtil.getElementTextWithContext(targetParent)}")
|
||||
val nameValidator = JetNameValidatorImpl(validatorContainer, null, JetNameValidatorImpl.Target.PROPERTIES)
|
||||
val suggestedNames = linkedSetOf(*JetNameSuggester.suggestNames(parameterType, nameValidator, "p"))
|
||||
|
||||
project.executeCommand(INTRODUCE_PARAMETER) {
|
||||
val renderedType = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(parameterType)
|
||||
val newParameter = psiFactory.createParameter("${suggestedNames.first()}: $renderedType")
|
||||
|
||||
val addedParameter = runWriteAction {
|
||||
val newParameterList =
|
||||
if (parameterList == null) {
|
||||
val klass = targetParent as? JetClass
|
||||
val anchor = klass?.getTypeParameterList() ?: klass?.getNameIdentifier()
|
||||
assert(anchor != null, "Invalid declaration: ${JetPsiUtil.getElementTextWithContext(targetParent)}")
|
||||
|
||||
val constructor = targetParent.addAfter(psiFactory.createPrimaryConstructor(), anchor) as JetPrimaryConstructor
|
||||
constructor.getValueParameterList()!!
|
||||
}
|
||||
else parameterList
|
||||
|
||||
val lastParameter = newParameterList.getChildren().lastOrNull { it is JetParameter } as? JetParameter
|
||||
if (lastParameter != null) {
|
||||
val comma = newParameterList.addAfter(psiFactory.createComma(), lastParameter)
|
||||
newParameterList.addAfter(newParameter, comma) as JetParameter
|
||||
}
|
||||
else {
|
||||
val singleParameterList = psiFactory.createParameterList("(${newParameter.getText()})")
|
||||
(newParameterList.replace(singleParameterList) as JetParameterList).getParameters().first()
|
||||
}
|
||||
}
|
||||
|
||||
val introduceParameterDescriptor =
|
||||
IntroduceParameterDescriptor(JetPsiUtil.deparenthesize(expression)!!,
|
||||
targetParent,
|
||||
functionDescriptor,
|
||||
addedParameter,
|
||||
parameterType)
|
||||
if (editor.getSettings().isVariableInplaceRenameEnabled() && !ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
with(PsiDocumentManager.getInstance(project)) {
|
||||
commitDocument(editor.getDocument())
|
||||
doPostponedOperationsAndUnblockDocument(editor.getDocument())
|
||||
}
|
||||
|
||||
if (!KotlinInplaceParameterIntroducer(introduceParameterDescriptor, editor, project).startRefactoring(suggestedNames)) {
|
||||
introduceParameterDescriptor.performRefactoring()
|
||||
}
|
||||
}
|
||||
else {
|
||||
introduceParameterDescriptor.performRefactoring()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext?) {
|
||||
if (file !is JetFile) return
|
||||
selectElementsWithTargetParent(
|
||||
operationName = INTRODUCE_PARAMETER,
|
||||
editor = editor,
|
||||
file = file,
|
||||
getContainers = { elements, parent ->
|
||||
parent.parents(withItself = false)
|
||||
.filter {
|
||||
((it is JetClass && !it.isTrait() && it !is JetEnumEntry) || it is JetNamedFunction || it is JetSecondaryConstructor) &&
|
||||
((it as JetNamedDeclaration).getValueParameterList() != null || it.getNameIdentifier() != null)
|
||||
}
|
||||
.toList()
|
||||
},
|
||||
continuation = { elements, targetParent ->
|
||||
val expression = ((elements.singleOrNull() as? JetBlockExpression)?.getStatements() ?: elements).singleOrNull()
|
||||
if (expression is JetExpression) {
|
||||
invoke(project, editor, expression, targetParent as JetNamedDeclaration)
|
||||
}
|
||||
else {
|
||||
showErrorHintByKey(project, editor, "cannot.refactor.no.expression", INTRODUCE_PARAMETER)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun invoke(project: Project, elements: Array<out PsiElement>, dataContext: DataContext?) {
|
||||
fail("$INTRODUCE_PARAMETER can only be invoked from editor")
|
||||
}
|
||||
}
|
||||
|
||||
val INTRODUCE_PARAMETER: String = JetRefactoringBundle.message("introduce.property")
|
||||
+16
-1
@@ -26,6 +26,7 @@ import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.markup.RangeHighlighter;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.popup.Balloon;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.Pass;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
@@ -35,6 +36,7 @@ import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
|
||||
import com.intellij.psi.search.SearchScope;
|
||||
import com.intellij.refactoring.introduce.inplace.InplaceVariableIntroducer;
|
||||
import com.intellij.ui.NonFocusableCheckBox;
|
||||
import com.intellij.util.ui.PositionTracker;
|
||||
import kotlin.Function0;
|
||||
import kotlin.Function1;
|
||||
import kotlin.KotlinPackage;
|
||||
@@ -56,6 +58,9 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
public class KotlinInplaceVariableIntroducer<D extends JetCallableDeclaration> extends InplaceVariableIntroducer<JetExpression> {
|
||||
public static final String TYPE_REFERENCE_VARIABLE_NAME = "TypeReferenceVariable";
|
||||
public static final String PRIMARY_VARIABLE_NAME = "PrimaryVariable";
|
||||
|
||||
private static final Function0<Boolean> TRUE = new Function0<Boolean>() {
|
||||
@Override
|
||||
public Boolean invoke() {
|
||||
@@ -329,10 +334,20 @@ public class KotlinInplaceVariableIntroducer<D extends JetCallableDeclaration> e
|
||||
return nameSuggestions;
|
||||
}
|
||||
|
||||
protected void revalidate() {
|
||||
getContentPanel().revalidate();
|
||||
if (myTarget != null) {
|
||||
myBalloon.revalidate(new PositionTracker.Static<Balloon>(myTarget));
|
||||
}
|
||||
}
|
||||
|
||||
protected void addTypeReferenceVariable(TemplateBuilderImpl builder) {
|
||||
JetTypeReference typeReference = myDeclaration.getTypeReference();
|
||||
if (typeReference != null) {
|
||||
builder.replaceElement(typeReference, SpecifyTypeExplicitlyAction.createTypeExpressionForTemplate(myExprType));
|
||||
builder.replaceElement(typeReference,
|
||||
TYPE_REFERENCE_VARIABLE_NAME,
|
||||
SpecifyTypeExplicitlyAction.createTypeExpressionForTemplate(myExprType),
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
class A(a: Int) {
|
||||
init {
|
||||
val t = <selection>1 + 2</selection>
|
||||
}
|
||||
}
|
||||
|
||||
fun test() {
|
||||
A(1)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
class A(a: Int, i: Int = 1 + 2) {
|
||||
init {
|
||||
val t = i
|
||||
}
|
||||
}
|
||||
|
||||
fun test() {
|
||||
A(1)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
class A(a: Int) {
|
||||
val x = <selection>1 + 2</selection>
|
||||
}
|
||||
|
||||
fun test() {
|
||||
A(1)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
class A(a: Int, i: Int = 1 + 2) {
|
||||
val x = i
|
||||
}
|
||||
|
||||
fun test() {
|
||||
A(1)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
class A {
|
||||
val a = <selection>1 + 2</selection>
|
||||
}
|
||||
|
||||
fun test() {
|
||||
A()
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
class A(i: Int = 1 + 2) {
|
||||
val a = i
|
||||
}
|
||||
|
||||
fun test() {
|
||||
A()
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// TARGET:
|
||||
class A(a: Int) {
|
||||
fun foo() = <selection>1 + 2</selection>
|
||||
}
|
||||
|
||||
fun test() {
|
||||
A(1)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// TARGET:
|
||||
class A(a: Int, val i: Int = 1 + 2) {
|
||||
fun foo() = i
|
||||
}
|
||||
|
||||
fun test() {
|
||||
A(1)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fun foo(a: Int): Int {
|
||||
val b = <selection>(a + 1)</selection> * 2
|
||||
return a + b
|
||||
}
|
||||
|
||||
fun test() {
|
||||
foo(1)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fun foo(a: Int, i: Int = a + 1): Int {
|
||||
val b = (i) * 2
|
||||
return a + b
|
||||
}
|
||||
|
||||
fun test() {
|
||||
foo(1)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
trait T
|
||||
|
||||
fun foo() {
|
||||
val a = <selection>object: T {}</selection>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
trait T
|
||||
|
||||
fun foo(t: T = object : T {}) {
|
||||
val a = t
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fun foo(a: Int): Int {
|
||||
val b = (<selection>a + 1</selection>) * 2
|
||||
return a + b
|
||||
}
|
||||
|
||||
fun test() {
|
||||
foo(1)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fun foo(a: Int, i: Int = a + 1): Int {
|
||||
val b = (i) * 2
|
||||
return a + b
|
||||
}
|
||||
|
||||
fun test() {
|
||||
foo(1)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
var foo: Int = 1
|
||||
set(m: Int) {
|
||||
val t = <selection>m + 1</selection>
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Cannot refactor in this place
|
||||
@@ -0,0 +1,10 @@
|
||||
class A {
|
||||
constructor(a: Int) {
|
||||
val b = (<selection>a + 1</selection>) * 2
|
||||
val t = a + b
|
||||
}
|
||||
}
|
||||
|
||||
fun test() {
|
||||
A(1)
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
class A {
|
||||
constructor(a: Int, i: Int = a + 1) {
|
||||
val b = (i) * 2
|
||||
val t = a + b
|
||||
}
|
||||
}
|
||||
|
||||
fun test() {
|
||||
A(1)
|
||||
}
|
||||
+22
-4
@@ -25,16 +25,18 @@ import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
|
||||
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
|
||||
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.idea.JetLightCodeInsightFixtureTestCase
|
||||
import org.jetbrains.kotlin.idea.PluginTestCaseBase
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.refactoring.JetRefactoringUtil
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandler
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.KotlinIntroduceParameterHandler
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.KotlinIntroducePropertyHandler
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler
|
||||
import org.jetbrains.kotlin.psi.JetDeclaration
|
||||
import org.jetbrains.kotlin.psi.JetFile
|
||||
import org.jetbrains.kotlin.psi.JetPackageDirective
|
||||
import org.jetbrains.kotlin.psi.JetTreeVisitorVoid
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||
import org.jetbrains.kotlin.test.ConfigLibraryUtil
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
@@ -61,6 +63,22 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
|
||||
}
|
||||
}
|
||||
|
||||
protected fun doIntroduceParameterTest(path: String) {
|
||||
doTest(path) { file ->
|
||||
with (KotlinIntroduceParameterHandler()) {
|
||||
val target = file.findElementByComment("// TARGET:") as? JetNamedDeclaration
|
||||
if (target != null) {
|
||||
JetRefactoringUtil.selectExpression(fixture.getEditor(), file, true) { expression ->
|
||||
invoke(fixture.getProject(), fixture.getEditor(), expression!!, target)
|
||||
}
|
||||
}
|
||||
else {
|
||||
invoke(fixture.getProject(), fixture.getEditor(), file, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun doIntroducePropertyTest(path: String) {
|
||||
doTest(path) { file ->
|
||||
val extractionTarget = propertyTargets.single {
|
||||
|
||||
+64
@@ -32,6 +32,7 @@ import java.util.regex.Pattern;
|
||||
JetExtractionTestGenerated.IntroduceVariable.class,
|
||||
JetExtractionTestGenerated.ExtractFunction.class,
|
||||
JetExtractionTestGenerated.IntroduceProperty.class,
|
||||
JetExtractionTestGenerated.IntroduceParameter.class,
|
||||
})
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
|
||||
@@ -2181,4 +2182,67 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/refactoring/introduceParameter")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class IntroduceParameter extends AbstractJetExtractionTest {
|
||||
public void testAllFilesPresentInIntroduceParameter() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/introduceParameter"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("classInAnonymousInitializer.kt")
|
||||
public void testClassInAnonymousInitializer() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classInAnonymousInitializer.kt");
|
||||
doIntroduceParameterTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("classInPropertyInitializer.kt")
|
||||
public void testClassInPropertyInitializer() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classInPropertyInitializer.kt");
|
||||
doIntroduceParameterTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("classNoParams.kt")
|
||||
public void testClassNoParams() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classNoParams.kt");
|
||||
doIntroduceParameterTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("classParameterInFunctionBody.kt")
|
||||
public void testClassParameterInFunctionBody() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classParameterInFunctionBody.kt");
|
||||
doIntroduceParameterTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("defaultValueInParens.kt")
|
||||
public void testDefaultValueInParens() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/defaultValueInParens.kt");
|
||||
doIntroduceParameterTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("functionWithApproximatedType.kt")
|
||||
public void testFunctionWithApproximatedType() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/functionWithApproximatedType.kt");
|
||||
doIntroduceParameterTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("functionWithDefaultValue.kt")
|
||||
public void testFunctionWithDefaultValue() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/functionWithDefaultValue.kt");
|
||||
doIntroduceParameterTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("propertyAccessor.kt")
|
||||
public void testPropertyAccessor() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/propertyAccessor.kt");
|
||||
doIntroduceParameterTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("secondaryConstructorWithDefaultValue.kt")
|
||||
public void testSecondaryConstructorWithDefaultValue() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/secondaryConstructorWithDefaultValue.kt");
|
||||
doIntroduceParameterTest(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user