Introduce Parameter: Implement "Introduce lambda parameter"

This commit is contained in:
Alexey Sedunov
2015-04-21 20:55:06 +03:00
parent 2cc39995bb
commit 298dfa545f
31 changed files with 747 additions and 208 deletions
@@ -617,7 +617,8 @@ 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")
model("refactoring/introduceParameter", extension = "kt", testMethod = "doIntroduceSimpleParameterTest")
model("refactoring/introduceLambdaParameter", extension = "kt", testMethod = "doIntroduceLambdaParameterTest")
}
testClass(javaClass<AbstractSelectExpressionForDebuggerTest>()) {
+7
View File
@@ -106,6 +106,13 @@
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="IntroduceField"/>
</action>
<action id="IntroduceLambdaParameter"
class="org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.IntroduceLambdaParameterAction"
text="Introduce Lambda Parameter...">
<keyboard-shortcut keymap="$default" first-keystroke="control shift P"/>
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="IntroduceParameter"/>
</action>
<action id="CopyAsDiagnosticTest" class="org.jetbrains.kotlin.idea.actions.internal.CopyAsDiagnosticTestAction"
text="Copy Current File As Diagnostic Test">
<keyboard-shortcut keymap="$default" first-keystroke="control alt shift T"/>
@@ -91,7 +91,7 @@ fun getFunctionForExtractedFragment(
val options = ExtractionOptions(inferUnitTypeForUnusedValues = false,
enableListBoxing = true,
allowSpecialClassNames = true)
val analysisResult = ExtractionData(tmpFile, newDebugExpression.toRange(), targetSibling, options).performAnalysis()
val analysisResult = ExtractionData(tmpFile, newDebugExpression.toRange(), targetSibling, null, options).performAnalysis()
if (analysisResult.status != Status.SUCCESS) {
throw EvaluateExceptionUtil.createEvaluateException(getErrorMessageForExtractFunctionResult(analysisResult))
}
@@ -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.KotlinIntroduceLambdaParameterHandler;
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;
@@ -47,6 +48,11 @@ public class JetRefactoringSupportProvider extends RefactoringSupportProvider {
return new KotlinIntroduceParameterHandler();
}
@NotNull
public RefactoringActionHandler getIntroduceLambdaParameterHandler() {
return new KotlinIntroduceLambdaParameterHandler();
}
@NotNull
public RefactoringActionHandler getIntroducePropertyHandler() {
return new KotlinIntroducePropertyHandler();
@@ -210,16 +210,29 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
@NotNull
private ExtractableCodeDescriptor createDescriptor() {
ExtractableCodeDescriptor descriptor = originalDescriptor.getDescriptor();
return createNewDescriptor(originalDescriptor.getDescriptor(),
getFunctionName(),
getVisibility(),
parameterTablePanel.getParameterInfos());
}
List<KotlinParameterTablePanel.ParameterInfo> parameterInfos = parameterTablePanel.getParameterInfos();
@NotNull
public ExtractionGeneratorConfiguration getCurrentConfiguration() {
return new ExtractionGeneratorConfiguration(currentDescriptor, ExtractionGeneratorOptions.DEFAULT);
}
public static ExtractableCodeDescriptor createNewDescriptor(
@NotNull ExtractableCodeDescriptor originalDescriptor,
@NotNull String newName,
@NotNull String newVisibility,
@NotNull List<KotlinParameterTablePanel.ParameterInfo> newParameterInfos
) {
Map<Parameter, Parameter> oldToNewParameters = ContainerUtil.newLinkedHashMap();
for (KotlinParameterTablePanel.ParameterInfo parameterInfo : parameterInfos) {
for (KotlinParameterTablePanel.ParameterInfo parameterInfo : newParameterInfos) {
oldToNewParameters.put(parameterInfo.getOriginalParameter(), parameterInfo.toParameter());
}
ControlFlow controlFlow = descriptor.getControlFlow();
ControlFlow controlFlow = originalDescriptor.getControlFlow();
List<OutputValue> outputValues = new ArrayList<OutputValue>(controlFlow.getOutputValues());
for (int i = 0; i < outputValues.size(); i++) {
OutputValue outputValue = outputValues.get(i);
@@ -231,7 +244,7 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
controlFlow = new ControlFlow(outputValues, controlFlow.getBoxerFactory(), controlFlow.getDeclarationsToCopy());
Map<Integer, Replacement> replacementMap = ContainerUtil.newHashMap();
for (Map.Entry<Integer, Replacement> e : descriptor.getReplacementMap().entrySet()) {
for (Map.Entry<Integer, Replacement> e : originalDescriptor.getReplacementMap().entrySet()) {
Integer offset = e.getKey();
Replacement replacement = e.getValue();
@@ -250,20 +263,15 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
}
return new ExtractableCodeDescriptor(
descriptor.getExtractionData(),
descriptor.getOriginalContext(),
Collections.singletonList(getFunctionName()),
getVisibility(),
originalDescriptor.getExtractionData(),
originalDescriptor.getOriginalContext(),
Collections.singletonList(newName),
newVisibility,
ContainerUtil.newArrayList(oldToNewParameters.values()),
descriptor.getReceiverParameter(),
descriptor.getTypeParameters(),
originalDescriptor.getReceiverParameter(),
originalDescriptor.getTypeParameters(),
replacementMap,
controlFlow
);
}
@NotNull
public ExtractionGeneratorConfiguration getCurrentConfiguration() {
return new ExtractionGeneratorConfiguration(currentDescriptor, ExtractionGeneratorOptions.DEFAULT);
}
}
@@ -313,6 +313,12 @@ enum class ExtractionTarget(val name: String) {
override fun isAvailable(descriptor: ExtractableCodeDescriptor) = true
}
FAKE_LAMBDALIKE_FUNCTION : ExtractionTarget("lambda parameter") {
override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean {
return checkSimpleControlFlow(descriptor) || descriptor.controlFlow.outputValues.isEmpty()
}
}
PROPERTY_WITH_INITIALIZER : ExtractionTarget("property with initializer") {
override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean {
return checkSignatureAndParent(descriptor)
@@ -72,6 +72,7 @@ data class ExtractionData(
val originalFile: JetFile,
val originalRange: JetPsiRange,
val targetSibling: PsiElement,
val duplicateContainer: PsiElement? = null,
val options: ExtractionOptions = ExtractionOptions.DEFAULT
) {
val project: Project = originalFile.getProject()
@@ -61,7 +61,10 @@ fun ExtractionGeneratorConfiguration.getDeclarationText(
throw IllegalArgumentException("Can't generate ${extractionTarget.name}: ${descriptor.extractionData.codeFragmentText}")
}
val builderTarget = if (extractionTarget == ExtractionTarget.FUNCTION) CallableBuilder.Target.FUNCTION else CallableBuilder.Target.READ_ONLY_PROPERTY
val builderTarget = when (extractionTarget) {
ExtractionTarget.FUNCTION, ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION -> CallableBuilder.Target.FUNCTION
else -> CallableBuilder.Target.READ_ONLY_PROPERTY
}
return CallableBuilder(builderTarget).let { builder ->
builder.modifier(descriptor.visibility)
@@ -95,7 +98,9 @@ fun ExtractionGeneratorConfiguration.getDeclarationText(
if (withBody) {
val bodyText = descriptor.extractionData.codeFragmentText
when (extractionTarget) {
ExtractionTarget.FUNCTION, ExtractionTarget.PROPERTY_WITH_GETTER -> builder.blockBody(bodyText)
ExtractionTarget.FUNCTION,
ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION,
ExtractionTarget.PROPERTY_WITH_GETTER -> builder.blockBody(bodyText)
ExtractionTarget.PROPERTY_WITH_INITIALIZER -> builder.initializer(bodyText)
ExtractionTarget.LAZY_PROPERTY -> builder.lazyBody(bodyText)
}
@@ -186,7 +191,7 @@ fun ExtractableCodeDescriptor.findDuplicates(): List<DuplicateInfo> {
val unifier = JetPsiUnifier(unifierParameters, true)
val scopeElement = extractionData.targetSibling.getParent() ?: return Collections.emptyList()
val scopeElement = extractionData.duplicateContainer ?: extractionData.targetSibling.getParent() ?: return Collections.emptyList()
val originalTextRange = extractionData.originalRange.getTextRange()
return extractionData
.originalRange
@@ -513,13 +518,15 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
val returnExpression = descriptor.controlFlow.outputValueBoxer.getReturnExpression(getReturnArguments(defaultExpression), psiFactory)
if (returnExpression == null) return
if (generatorOptions.target == ExtractionTarget.LAZY_PROPERTY) {
// In the case of lazy property absence of default value means that output values are of OutputValue.Initializer type
// We just add resulting expressions without return, since returns are prohibited in the body of lazy property
if (defaultValue == null) {
body.appendElement(returnExpression.getReturnedExpression()!!)
when(generatorOptions.target) {
ExtractionTarget.LAZY_PROPERTY, ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION -> {
// In the case of lazy property absence of default value means that output values are of OutputValue.Initializer type
// We just add resulting expressions without return, since returns are prohibited in the body of lazy property
if (defaultValue == null) {
body.appendElement(returnExpression.getReturnedExpression()!!)
}
return
}
return
}
when {
@@ -579,7 +586,8 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
marginalCandidate.parents().first { it.getParent() == targetParent }
}
val declaration = createDeclaration().let { if (generatorOptions.inTempFile) it else insertDeclaration(it, anchor) }
val shouldInsert = !(generatorOptions.inTempFile || generatorOptions.target == ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION)
val declaration = createDeclaration().let { if (shouldInsert) insertDeclaration(it, anchor) else it }
adjustDeclarationBody(declaration)
if (declaration is JetNamedFunction && declaration.getContainingJetFile().suppressDiagnosticsInDebugMode) {
@@ -597,7 +605,9 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
if (generatorOptions.inTempFile) return ExtractionResult(this, declaration, Collections.emptyMap(), nameByOffset)
makeCall(descriptor, declaration, descriptor.controlFlow, descriptor.extractionData.originalRange, descriptor.parameters.map { it.argumentText })
ShortenReferences.DEFAULT.process(declaration)
if (shouldInsert) {
ShortenReferences.DEFAULT.process(declaration)
}
val duplicateReplacers = duplicates.map { it.range to { makeCall(descriptor, declaration, it.controlFlow, it.range, it.arguments) } }.toMap()
return ExtractionResult(this, declaration, duplicateReplacers, nameByOffset)
@@ -0,0 +1,27 @@
/*
* 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.lang.refactoring.RefactoringSupportProvider
import com.intellij.refactoring.RefactoringActionHandler
import org.jetbrains.kotlin.idea.refactoring.JetRefactoringSupportProvider
import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractIntroduceAction
public class IntroduceLambdaParameterAction : AbstractIntroduceAction() {
override fun getRefactoringHandler(provider: RefactoringSupportProvider): RefactoringActionHandler? =
(provider as? JetRefactoringSupportProvider)?.getIntroduceLambdaParameterHandler()
}
@@ -68,20 +68,22 @@ import kotlin.properties.Delegates
public class KotlinInplaceParameterIntroducer(
val originalDescriptor: IntroduceParameterDescriptor,
val addedParameter: JetParameter,
val parameterType: JetType,
editor: Editor,
project: Project
): KotlinInplaceVariableIntroducer<JetParameter>(
originalDescriptor.addedParameter,
addedParameter,
editor,
project,
INTRODUCE_PARAMETER,
JetExpression.EMPTY_ARRAY,
null,
false,
originalDescriptor.addedParameter,
addedParameter,
false,
true,
originalDescriptor.parameterType,
parameterType,
false
) {
companion object {
@@ -137,7 +139,7 @@ public class KotlinInplaceParameterIntroducer(
val parameterName = currentName ?: parameter.getName()
val parameterType = currentType ?: parameter.getTypeReference()!!.getText()
val modifier = if (valVar != JetValVar.None) "${valVar.name} " else ""
val defaultValue = if (withDefaultValue) " = ${originalExpression.getText()}" else ""
val defaultValue = if (withDefaultValue) " = ${newArgumentValue.getText()}" else ""
"$modifier$parameterName: $parameterType$defaultValue"
}
@@ -280,13 +282,18 @@ public class KotlinInplaceParameterIntroducer(
return descriptor.callable
}
private fun removeAddedParameter() {
runWriteAction { JetPsiUtil.deleteElementWithDelimiters(addedParameter) }
}
override fun performRefactoring(): Boolean {
removeAddedParameter()
descriptor.performRefactoring()
return true
}
override fun performCleanup() {
runWriteAction { JetPsiUtil.deleteElementWithDelimiters(descriptor.addedParameter) }
removeAddedParameter()
}
override fun releaseResources() {
@@ -301,9 +308,11 @@ public class KotlinInplaceParameterIntroducer(
stopIntroduce()
with (originalDescriptor) {
KotlinIntroduceParameterDialog(myProject,
myEditor,
this,
myNameSuggestions.copyToArray(),
listOf(parameterType) + parameterType.supertypes()).show()
listOf(parameterType) + parameterType.supertypes(),
KotlinIntroduceParameterHelper.Default).show()
}
}
}
@@ -16,31 +16,29 @@
package org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.options.ConfigurationException
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNameHelper
import com.intellij.refactoring.BaseRefactoringProcessor
import com.intellij.refactoring.ui.NameSuggestionsField
import com.intellij.refactoring.ui.RefactoringDialog
import com.intellij.ui.NonFocusableCheckBox
import com.intellij.ui.TitledSeparator
import com.intellij.usageView.BaseUsageViewDescriptor
import com.intellij.usageView.UsageInfo
import com.intellij.usageView.UsageViewDescriptor
import org.jetbrains.kotlin.idea.JetFileType
import org.jetbrains.kotlin.idea.core.refactoring.isMultiLine
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ui.KotlinExtractFunctionDialog
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ui.KotlinParameterTablePanel
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
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.JetParameter
import org.jetbrains.kotlin.psi.JetPsiFactory
import org.jetbrains.kotlin.psi.JetPsiUtil
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.AnalyzingUtils
import org.jetbrains.kotlin.types.JetType
import java.awt.GridBagConstraints
import java.awt.GridBagLayout
import java.awt.Insets
import java.util.ArrayList
import java.util.Collections
import java.util.LinkedHashMap
import javax.swing.JCheckBox
@@ -48,12 +46,39 @@ import javax.swing.JComponent
import javax.swing.JLabel
import javax.swing.JPanel
public class KotlinIntroduceParameterDialog(
public class KotlinIntroduceParameterDialog private (
project: Project,
val editor: Editor,
val descriptor: IntroduceParameterDescriptor,
val lambdaExtractionDescriptor: ExtractableCodeDescriptor?,
nameSuggestions: Array<String>,
val typeSuggestions: List<JetType>
val typeSuggestions: List<JetType>,
val helper: KotlinIntroduceParameterHelper
): RefactoringDialog(project, true) {
constructor(
project: Project,
editor: Editor,
descriptor: IntroduceParameterDescriptor,
nameSuggestions: Array<String>,
typeSuggestions: List<JetType>,
helper: KotlinIntroduceParameterHelper
): this(project, editor, descriptor, null, nameSuggestions, typeSuggestions, helper)
constructor(project: Project,
editor: Editor,
introduceParameterDescriptor: IntroduceParameterDescriptor,
lambdaExtractionDescriptor: ExtractableCodeDescriptor,
helper: KotlinIntroduceParameterHelper
) : this(
project,
editor,
introduceParameterDescriptor,
lambdaExtractionDescriptor,
lambdaExtractionDescriptor.suggestedNames.copyToArray(),
listOf(lambdaExtractionDescriptor.controlFlow.outputValueBoxer.returnType),
helper
)
private val typeNameSuggestions = typeSuggestions
.map { IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(it) }
.copyToArray()
@@ -62,9 +87,10 @@ public class KotlinIntroduceParameterDialog(
private val typeField = NameSuggestionsField(typeNameSuggestions, project, JetFileType.INSTANCE)
private var replaceAllCheckBox: JCheckBox? = null
private val removeParamsCheckBoxes = LinkedHashMap<JCheckBox, JetParameter>(descriptor.parametersToRemove.size())
private var parameterTablePanel: KotlinParameterTablePanel? = null
init {
setTitle(INTRODUCE_PARAMETER)
setTitle(if (lambdaExtractionDescriptor != null) INTRODUCE_LAMBDA_PARAMETER else INTRODUCE_PARAMETER)
init()
nameField.addDataChangedListener { validateButtons() }
@@ -87,7 +113,7 @@ public class KotlinIntroduceParameterDialog(
gbConstraints.weightx = 0.0
gbConstraints.weighty = 0.0
gbConstraints.gridy = 0
val nameLabel = JLabel("Parameter name:")
val nameLabel = JLabel("Parameter name: ")
nameLabel.setDisplayedMnemonic('n')
nameLabel.setLabelFor(nameField)
panel.add(nameLabel, gbConstraints)
@@ -102,9 +128,9 @@ public class KotlinIntroduceParameterDialog(
gbConstraints.gridwidth = 1
gbConstraints.weightx = 0.0
gbConstraints.gridx = 0
gbConstraints.gridy = 1
gbConstraints.gridy++
gbConstraints.fill = GridBagConstraints.NONE
val typeLabel = JLabel("Parameter type: ")
val typeLabel = JLabel(if (lambdaExtractionDescriptor != null) "Lambda return type" else "Parameter type: ")
typeLabel.setDisplayedMnemonic('t')
typeLabel.setLabelFor(typeField)
panel.add(typeLabel, gbConstraints)
@@ -115,6 +141,39 @@ public class KotlinIntroduceParameterDialog(
gbConstraints.fill = GridBagConstraints.BOTH
panel.add(typeField, gbConstraints)
if (lambdaExtractionDescriptor != null && lambdaExtractionDescriptor.parameters.isNotEmpty()) {
val parameterTablePanel = object : KotlinParameterTablePanel() {
override fun onEnterAction() {
doOKAction()
}
override fun onCancelAction() {
doCancelAction()
}
}
parameterTablePanel.init(lambdaExtractionDescriptor!!.parameters)
gbConstraints.insets = Insets(4, 4, 4, 8)
gbConstraints.gridwidth = 1
gbConstraints.weightx = 0.0
gbConstraints.gridx = 0
gbConstraints.gridy++
gbConstraints.fill = GridBagConstraints.NONE
val parametersLabel = JLabel("Lambda parameters: ")
parametersLabel.setDisplayedMnemonic('p')
parametersLabel.setLabelFor(parameterTablePanel)
panel.add(parametersLabel, gbConstraints)
gbConstraints.gridx++
gbConstraints.insets = Insets(4, 4, 4, 8)
gbConstraints.weightx = 1.0
gbConstraints.fill = GridBagConstraints.BOTH
panel.add(parameterTablePanel, gbConstraints)
this.parameterTablePanel = parameterTablePanel
}
gbConstraints.fill = GridBagConstraints.HORIZONTAL
gbConstraints.gridx = 0
gbConstraints.insets = Insets(4, 0, 4, 8)
gbConstraints.gridwidth = 2
@@ -169,31 +228,82 @@ public class KotlinIntroduceParameterDialog(
}
override fun doAction() {
val descriptorToRefactor = descriptor.copy(
addedParameter = JetPsiFactory(myProject).createParameter("${nameField.getEnteredName()}: ${typeField.getEnteredName()}"),
occurrencesToReplace = with(descriptor) {
if (replaceAllCheckBox?.isSelected() ?: true) {
occurrencesToReplace
}
else {
Collections.singletonList(originalOccurrence)
}
}
)
performRefactoring()
}
public fun performRefactoring() {
invokeRefactoring(
object: BaseRefactoringProcessor(myProject) {
object : BaseRefactoringProcessor(myProject) {
override fun findUsages() = UsageInfo.EMPTY_ARRAY
override fun performRefactoring(usages: Array<out UsageInfo>) {
descriptorToRefactor.performRefactoring(
parametersToRemove =
removeParamsCheckBoxes.filter { it.key.isEnabled() && it.key.isSelected() }.map { it.value }
fun createLambdaForArgument(function: JetFunction): JetExpression {
val statement = (function.getBodyExpression() as JetBlockExpression).getStatements().single()
val space = if (statement.isMultiLine()) "\n" else " "
val parameters = function.getValueParameters()
val parametersText = if (parameters.isNotEmpty()) {
" " + parameters.map { it.getName() }.joinToString() + " ->"
} else ""
val text = "{$parametersText$space${statement.getText()}$space}"
return JetPsiFactory(myProject).createExpression(text)
}
val chosenName = nameField.getEnteredName()
var chosenType = typeField.getEnteredName()
var newArgumentValue = descriptor.newArgumentValue
var newReplacer = descriptor.occurrenceReplacer
lambdaExtractionDescriptor?.let { oldDescriptor ->
val newDescriptor = KotlinExtractFunctionDialog.createNewDescriptor(
oldDescriptor,
chosenName,
"",
parameterTablePanel?.getParameterInfos() ?: listOf()
)
val options = ExtractionGeneratorOptions.DEFAULT.copy(
target = ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION,
allowExpressionBody = false
)
with (ExtractionGeneratorConfiguration(newDescriptor, options).generateDeclaration()) {
val function = declaration as JetFunction
val receiverType = function.getReceiverTypeReference()?.getText()
val parameterTypes = function
.getValueParameters()
.map { it.getTypeReference()!!.getText() }
.joinToString()
val returnType = function.getTypeReference()?.getText() ?: "Unit"
chosenType = (receiverType?.let { "$it." } ?: "") + "($parameterTypes) -> $returnType"
newArgumentValue = createLambdaForArgument(function)
newReplacer = { }
processDuplicates(duplicateReplacers, myProject, editor)
}
}
val descriptorToRefactor = descriptor.copy(
newParameterName = chosenName,
newParameterTypeText = chosenType,
newArgumentValue = newArgumentValue,
occurrencesToReplace = with(descriptor) {
if (replaceAllCheckBox?.isSelected() ?: true) {
occurrencesToReplace
}
else {
Collections.singletonList(originalOccurrence)
}
},
parametersToRemove = removeParamsCheckBoxes.filter { it.key.isEnabled() && it.key.isSelected() }.map { it.value },
occurrenceReplacer = newReplacer
)
helper.configure(descriptorToRefactor).performRefactoring()
}
override fun createUsageViewDescriptor(usages: Array<out UsageInfo>) = BaseUsageViewDescriptor()
override fun getCommandName() = INTRODUCE_PARAMETER
override fun getCommandName() = if (lambdaExtractionDescriptor != null) INTRODUCE_LAMBDA_PARAMETER else INTRODUCE_PARAMETER
}
)
}
@@ -20,21 +20,21 @@ 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 com.intellij.psi.PsiReference
import com.intellij.psi.search.LocalSearchScope
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import com.intellij.psi.util.PsiTreeUtil
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.caches.resolve.resolveToDescriptor
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.extractionEngine.*
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinInplaceVariableIntroducer
import org.jetbrains.kotlin.idea.refactoring.introduce.selectElementsWithTargetParent
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint
@@ -45,31 +45,32 @@ import org.jetbrains.kotlin.idea.search.usagesSearch.search
import org.jetbrains.kotlin.idea.util.*
import org.jetbrains.kotlin.idea.util.application.executeCommand
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.idea.util.psi.patternMatching.JetPsiRange
import org.jetbrains.kotlin.idea.util.psi.patternMatching.JetPsiUnifier
import org.jetbrains.kotlin.idea.util.psi.patternMatching.toRange
import org.jetbrains.kotlin.idea.util.supertypes
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.scopes.JetScopeUtils
import org.jetbrains.kotlin.types.JetType
import java.util.Collections
import kotlin.test.fail
public data class IntroduceParameterDescriptor(
val originalExpression: JetExpression,
val originalRange: JetPsiRange,
val callable: JetNamedDeclaration,
val callableDescriptor: FunctionDescriptor,
val addedParameter: JetParameter,
val parameterType: JetType,
val newParameterName: String,
val newParameterTypeText: String,
val newArgumentValue: JetExpression,
val withDefaultValue: Boolean,
val parametersUsages: Map<JetParameter, List<PsiReference>>,
val occurrencesToReplace: List<JetExpression>
val occurrencesToReplace: List<JetPsiRange>,
val parametersToRemove: List<JetParameter> = getParametersToRemove(withDefaultValue, parametersUsages, occurrencesToReplace),
val occurrenceReplacer: (JetPsiRange) -> Unit = {}
) {
val originalOccurrence: JetExpression
get() = occurrencesToReplace.first { it.isAncestor(originalExpression) }
val originalOccurrence: JetPsiRange
get() = occurrencesToReplace.first { it.getTextRange().intersects(originalRange.getTextRange()) }
val valVar: JetValVar
val parametersToRemove: List<JetParameter>
init {
valVar = if (callable is JetClass) {
@@ -79,47 +80,52 @@ public data class IntroduceParameterDescriptor(
false
it is JetClassInitializer ->
true
it is JetProperty && it.getInitializer()?.getTextRange()?.intersects(originalExpression.getTextRange()) ?: false ->
it is JetProperty && it.getInitializer()?.getTextRange()?.intersects(originalRange.getTextRange()) ?: false ->
true
else ->
false
}
}
if (occurrencesToReplace.all { it.parents().any(modifierIsUnnecessary) }) JetValVar.None else JetValVar.Val
if (occurrencesToReplace.all {
PsiTreeUtil.findCommonParent(it.elements)?.parents()?.any(modifierIsUnnecessary) ?: false
}) JetValVar.None else JetValVar.Val
}
else JetValVar.None
parametersToRemove =
if (withDefaultValue) Collections.emptyList()
else {
val occurrenceRanges = occurrencesToReplace.map { it.getTextRange() }
parametersUsages.entrySet()
.filter {
it.value.all { paramRef ->
occurrenceRanges.any { occurrenceRange -> occurrenceRange.contains(paramRef.getElement().getTextRange()) }
}
}
.map { it.key }
}
}
}
fun IntroduceParameterDescriptor.performRefactoring(parametersToRemove: List<JetParameter> = this.parametersToRemove) {
fun getParametersToRemove(
withDefaultValue: Boolean,
parametersUsages: Map<JetParameter, List<PsiReference>>,
occurrencesToReplace: List<JetPsiRange>
): List<JetParameter> {
if (withDefaultValue) return Collections.emptyList()
val occurrenceRanges = occurrencesToReplace.map { it.getTextRange() }
return parametersUsages.entrySet()
.filter {
it.value.all { paramRef ->
occurrenceRanges.any { occurrenceRange -> occurrenceRange.contains(paramRef.getElement().getTextRange()) }
}
}
.map { it.key }
}
fun IntroduceParameterDescriptor.performRefactoring() {
runWriteAction {
JetPsiUtil.deleteElementWithDelimiters(addedParameter)
val config = object: JetChangeSignatureConfiguration {
val config = object : JetChangeSignatureConfiguration {
override fun configure(originalDescriptor: JetMethodDescriptor, bindingContext: BindingContext): JetMethodDescriptor {
return originalDescriptor.modify {
val parameters = callable.getValueParameters()
parametersToRemove.map { parameters.indexOf(it) }.sortDescending().forEach { removeParameter(it) }
val parameterInfo = JetParameterInfo(name = addedParameter.getName()!!,
type = parameterType,
defaultValueForCall = if (withDefaultValue) "" else originalExpression.getText(),
defaultValueForParameter = if (withDefaultValue) originalExpression else null,
if (!withDefaultValue) {
val parameters = callable.getValueParameters()
parametersToRemove.map { parameters.indexOf(it) }.sortDescending().forEach { removeParameter(it) }
}
val parameterInfo = JetParameterInfo(name = newParameterName,
defaultValueForCall = if (withDefaultValue) "" else newArgumentValue.getText(),
defaultValueForParameter = if (withDefaultValue) newArgumentValue else null,
valOrVar = valVar)
parameterInfo.currentTypeText = addedParameter.getTypeReference()?.getText() ?: "Any"
parameterInfo.currentTypeText = newParameterTypeText
addParameter(parameterInfo)
}
}
@@ -127,19 +133,49 @@ fun IntroduceParameterDescriptor.performRefactoring(parametersToRemove: List<Jet
override fun performSilently(affectedFunctions: Collection<PsiElement>): Boolean = true
}
if (runChangeSignature(callable.getProject(), callableDescriptor, config, callable.analyze(), callable, INTRODUCE_PARAMETER)) {
val paramRef = JetPsiFactory(callable).createSimpleName(addedParameter.getName()!!)
occurrencesToReplace.forEach { it.replace(paramRef) }
occurrencesToReplace.forEach(occurrenceReplacer)
}
}
}
public open class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase() {
open fun configure(descriptor: IntroduceParameterDescriptor): IntroduceParameterDescriptor = descriptor
private fun isObjectOrNonInnerClass(e: PsiElement): Boolean = e is JetObjectDeclaration || (e is JetClass && !e.isInner())
private fun isObjectOrNonInnerClass(e: PsiElement): Boolean =
e is JetObjectDeclaration || (e is JetClass && !e.isInner())
fun selectNewParameterContext(
editor: Editor,
file: PsiFile,
continuation: (elements: List<PsiElement>, targetParent: PsiElement) -> Unit
) {
selectElementsWithTargetParent(
operationName = INTRODUCE_PARAMETER,
editor = editor,
file = file,
getContainers = { elements, parent ->
val parents = parent.parents(withItself = false)
val stopAt = (parent.parents(withItself = false) zip parent.parents(withItself = false).drop(1))
.firstOrNull { isObjectOrNonInnerClass(it.first) }
?.second
fun invoke(project: Project, editor: Editor, expression: JetExpression, targetParent: JetNamedDeclaration) {
(if (stopAt != null) parent.parents(withItself = false).takeWhile { it != stopAt } else parents)
.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 = continuation
)
}
public trait KotlinIntroduceParameterHelper {
object Default: KotlinIntroduceParameterHelper
fun configure(descriptor: IntroduceParameterDescriptor): IntroduceParameterDescriptor = descriptor
}
public open class KotlinIntroduceParameterHandler(
val helper: KotlinIntroduceParameterHelper = KotlinIntroduceParameterHelper.Default
): KotlinIntroduceHandlerBase() {
open fun invoke(project: Project, editor: Editor, expression: JetExpression, targetParent: JetNamedDeclaration) {
val context = expression.analyze()
val expressionType = context.getType(expression)
@@ -161,7 +197,7 @@ public open class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase()
is ClassDescriptor -> descriptor.getUnsubstitutedPrimaryConstructor()
else -> null
} ?: throw AssertionError("Unexpected element type: ${targetParent.getElementTextWithContext()}")
val parameterType = expressionType.approximateWithResolvableType(JetScopeUtils.getResolutionScope(targetParent, context), false)
val replacementType = expressionType.approximateWithResolvableType(JetScopeUtils.getResolutionScope(targetParent, context), false)
val body = when (targetParent) {
is JetFunction -> targetParent.getBodyExpression()
@@ -169,18 +205,13 @@ public open class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase()
else -> null
} ?: throw AssertionError("Body element is not found: ${targetParent.getElementTextWithContext()}")
val nameValidator = JetNameValidatorImpl(body, null, JetNameValidatorImpl.Target.PROPERTIES)
val suggestedNames = linkedSetOf(*JetNameSuggester.suggestNames(parameterType, nameValidator, "p"))
val suggestedNames = linkedSetOf(*JetNameSuggester.suggestNames(replacementType, nameValidator, "p"))
val parametersUsages = targetParent.getValueParameters()
.filter { !it.hasValOrVarNode() }
.map {
it to DefaultSearchHelper<JetParameter>()
.newRequest(UsagesSearchTarget(element = it))
.search()
.toList()
}
.filter { it.second.isNotEmpty() }
.toMap()
val parametersUsages = findInternalParameterUsages(targetParent)
val psiFactory = JetPsiFactory(project)
val renderedType = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(replacementType)
val newParameter = psiFactory.createParameter("${suggestedNames.first()}: $renderedType")
val forbiddenRanges =
if (targetParent is JetClass) {
@@ -204,16 +235,12 @@ public open class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase()
} as? JetExpression
}
.filterNotNull()
.map { it.toRange() }
project.executeCommand(
INTRODUCE_PARAMETER,
null,
fun() {
val psiFactory = JetPsiFactory(project)
val renderedType = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(parameterType)
val newParameter = psiFactory.createParameter("${suggestedNames.first()}: $renderedType")
val isTestMode = ApplicationManager.getApplication().isUnitTestMode()
val inplaceIsAvailable = editor.getSettings().isVariableInplaceRenameEnabled() && !isTestMode
@@ -243,15 +270,24 @@ public open class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase()
}
else newParameter
val originalExpression = JetPsiUtil.safeDeparenthesize(expression)
val newParameterName = addedParameter.getName()!!
val replacement = psiFactory.createExpression(newParameterName)
val introduceParameterDescriptor =
configure(IntroduceParameterDescriptor(JetPsiUtil.deparenthesize(expression)!!,
targetParent,
functionDescriptor,
addedParameter,
parameterType,
false,
parametersUsages,
occurrencesToReplace))
helper.configure(
IntroduceParameterDescriptor(
originalRange = originalExpression.toRange(),
callable = targetParent,
callableDescriptor = functionDescriptor,
newParameterName = newParameterName,
newParameterTypeText = renderedType,
newArgumentValue = originalExpression,
withDefaultValue = false,
parametersUsages = parametersUsages,
occurrencesToReplace = occurrencesToReplace,
occurrenceReplacer = { it.elements.single().replace(replacement) }
)
)
if (isTestMode) {
introduceParameterDescriptor.performRefactoring()
return
@@ -263,14 +299,20 @@ public open class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase()
doPostponedOperationsAndUnblockDocument(editor.getDocument())
}
if (KotlinInplaceParameterIntroducer(introduceParameterDescriptor, editor, project)
.startRefactoring(suggestedNames)) return
val introducer = KotlinInplaceParameterIntroducer(introduceParameterDescriptor,
addedParameter,
replacementType,
editor,
project)
if (introducer.startRefactoring(suggestedNames)) return
}
KotlinIntroduceParameterDialog(project,
editor,
introduceParameterDescriptor,
suggestedNames.copyToArray(),
listOf(parameterType) + parameterType.supertypes()).show()
listOf(replacementType) + replacementType.supertypes(),
helper).show()
}
)
}
@@ -282,33 +324,15 @@ public open class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase()
}
if (file !is JetFile) return
selectElementsWithTargetParent(
operationName = INTRODUCE_PARAMETER,
editor = editor,
file = file,
getContainers = { elements, parent ->
val parents = parent.parents(withItself = false)
val stopAt = (parent.parents(withItself = false) zip parent.parents(withItself = false).drop(1))
.firstOrNull { isObjectOrNonInnerClass(it.first) }
?.second
(if (stopAt != null) parent.parents(withItself = false).takeWhile { it != stopAt } else parents)
.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)
}
}
)
selectNewParameterContext(editor, file) { 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?) {
@@ -316,4 +340,96 @@ public open class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase()
}
}
val INTRODUCE_PARAMETER: String = JetRefactoringBundle.message("introduce.parameter")
private fun findInternalParameterUsages(targetParent: JetNamedDeclaration): Map<JetParameter, List<PsiReference>> {
return targetParent.getValueParameters()
.filter { !it.hasValOrVarNode() }
.map {
it to DefaultSearchHelper<JetParameter>()
.newRequest(UsagesSearchTarget(element = it))
.search()
.toList()
}
.filter { it.second.isNotEmpty() }
.toMap()
}
trait KotlinIntroduceLambdaParameterHelper: KotlinIntroduceParameterHelper {
object Default: KotlinIntroduceLambdaParameterHelper
fun configureExtractLambda(descriptor: ExtractableCodeDescriptor): ExtractableCodeDescriptor = descriptor
}
public open class KotlinIntroduceLambdaParameterHandler(
helper: KotlinIntroduceLambdaParameterHelper = KotlinIntroduceLambdaParameterHelper.Default
): KotlinIntroduceParameterHandler(helper) {
val extractLambdaHelper = object: ExtractionEngineHelper(INTRODUCE_LAMBDA_PARAMETER) {
private fun createDialog(
project: Project,
editor: Editor,
lambdaExtractionDescriptor: ExtractableCodeDescriptor
): KotlinIntroduceParameterDialog {
val callable = lambdaExtractionDescriptor.extractionData.targetSibling as JetNamedDeclaration
val descriptor = callable.resolveToDescriptor()
val callableDescriptor: FunctionDescriptor =
when (descriptor) {
is FunctionDescriptor -> descriptor : FunctionDescriptor
is ClassDescriptor -> descriptor.getUnsubstitutedPrimaryConstructor()
else -> null
} ?: throw AssertionError("Unexpected element type: ${callable.getElementTextWithContext()}")
val originalRange = lambdaExtractionDescriptor.extractionData.originalRange
val introduceParameterDescriptor = IntroduceParameterDescriptor(
originalRange = originalRange,
callable = callable,
callableDescriptor = callableDescriptor,
newParameterName = "", // to be chosen in the dialog
newParameterTypeText = "", // to be chosen in the dialog
newArgumentValue = JetPsiFactory(project).createExpression("{}"), // substituted later
withDefaultValue = false,
parametersUsages = findInternalParameterUsages(callable),
occurrencesToReplace = listOf(originalRange),
parametersToRemove = listOf()
)
return KotlinIntroduceParameterDialog(project, editor, introduceParameterDescriptor, lambdaExtractionDescriptor, helper)
}
override fun configureAndRun(
project: Project,
editor: Editor,
descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts,
onFinish: (ExtractionResult) -> Unit
) {
val lambdaExtractionDescriptor = helper.configureExtractLambda(descriptorWithConflicts.descriptor)
if (!ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION.isAvailable(lambdaExtractionDescriptor)) {
showErrorHint(project, editor, "Can't introduce lambda parameter for this expression", INTRODUCE_LAMBDA_PARAMETER)
return
}
val dialog = createDialog(project, editor, lambdaExtractionDescriptor)
if (ApplicationManager.getApplication()!!.isUnitTestMode()) {
dialog.performRefactoring()
}
else {
dialog.show()
}
}
}
override fun invoke(project: Project, editor: Editor, expression: JetExpression, targetParent: JetNamedDeclaration) {
val duplicateContainer =
when (targetParent) {
is JetFunction -> targetParent.getBodyExpression()
is JetClass -> targetParent.getBody()
else -> null
} ?: throw AssertionError("Body element is not found: ${targetParent.getElementTextWithContext()}")
val extractionData = ExtractionData(expression.getContainingJetFile(),
expression.toRange(),
targetParent,
duplicateContainer,
ExtractionOptions.DEFAULT)
ExtractionEngine(extractLambdaHelper).run(editor, extractionData)
}
}
val INTRODUCE_PARAMETER: String = "Introduce Parameter"
val INTRODUCE_LAMBDA_PARAMETER: String = "Introduce Lambda Parameter"
@@ -65,7 +65,7 @@ public class KotlinIntroducePropertyHandler(
val adjustedElements = (elements.singleOrNull() as? JetBlockExpression)?.getStatements() ?: elements
if (adjustedElements.isNotEmpty()) {
val options = ExtractionOptions(extractAsProperty = true)
val extractionData = ExtractionData(file, adjustedElements.toRange(), targetSibling, options)
val extractionData = ExtractionData(file, adjustedElements.toRange(), targetSibling, null, options)
ExtractionEngine(helper).run(editor, extractionData) {
val property = it.declaration as JetProperty
val descriptor = it.config.descriptor
@@ -0,0 +1,12 @@
// WITH_DEFAULT_VALUE: false
// TARGET:
class Foo(val a: Int) {
fun foo(n: Int): Int {
val t = a + n + 1
return <selection>a - n</selection> - t
}
}
fun test() {
Foo(1).foo(2)
}
@@ -0,0 +1,12 @@
// WITH_DEFAULT_VALUE: false
// TARGET:
class Foo(val a: Int, val i: Foo.(Int) -> Int) {
fun foo(n: Int): Int {
val t = a + n + 1
return i(n) - t
}
}
fun test() {
Foo(1) { n -> a - n }.foo(2)
}
@@ -0,0 +1,11 @@
// WITH_DEFAULT_VALUE: false
fun foo(a: Int, s: String): Int {
val t = a + 1 + 2
return <selection>a
.plus(1)
.plus(2)</selection> - t
}
fun test() {
foo(1, "2")
}
@@ -0,0 +1,13 @@
// WITH_DEFAULT_VALUE: false
fun foo(a: Int, s: String, i: (Int) -> Int): Int {
val t = i(a)
return i(a) - t
}
fun test() {
foo(1, "2") { a ->
a
.plus(1)
.plus(2)
}
}
@@ -0,0 +1,9 @@
// WITH_DEFAULT_VALUE: false
fun foo(a: Int, s: String): Int {
val t = (a + 1) * 2
return <selection>a + 1</selection> - t
}
fun test() {
foo(1, "2")
}
@@ -0,0 +1,9 @@
// WITH_DEFAULT_VALUE: false
fun foo(a: Int, s: String, i: (Int) -> Int): Int {
val t = i(a) * 2
return i(a) - t
}
fun test() {
foo(1, "2") { a -> a + 1 }
}
@@ -0,0 +1,9 @@
// WITH_DEFAULT_VALUE: false
fun foo(a: Int, s: () -> String): Int {
val t = (a + 1) * 2
return <selection>a + 1</selection> - t
}
fun test() {
foo(1) { "2" }
}
@@ -0,0 +1,9 @@
// WITH_DEFAULT_VALUE: false
fun foo(a: Int, s: () -> String, i: (Int) -> Int): Int {
val t = i(a) * 2
return i(a) - t
}
fun test() {
foo(1, { "2" }) { a -> a + 1 }
}
@@ -0,0 +1,9 @@
// WITH_DEFAULT_VALUE: false
fun foo(a: Int, s: String): Int {
val t = (a + 1) * 2
return <selection>1 + 2</selection> - t
}
fun test() {
foo(1, "2")
}
@@ -0,0 +1,9 @@
// WITH_DEFAULT_VALUE: false
fun foo(a: Int, s: String, i: () -> Int): Int {
val t = (a + 1) * 2
return i() - t
}
fun test() {
foo(1, "2") { 1 + 2 }
}
@@ -0,0 +1,8 @@
// WITH_DEFAULT_VALUE: false
fun foo(a: Int) {
<selection>throw Exception("Error: $a")</selection>
}
fun test() {
foo(1)
}
@@ -0,0 +1,8 @@
// WITH_DEFAULT_VALUE: false
fun foo(a: Int, __dummyTestFun__: (Int) -> Unit) {
__dummyTestFun__(a)
}
fun test() {
foo(1) { a -> throw Exception("Error: $a") }
}
@@ -0,0 +1,12 @@
// WITH_DEFAULT_VALUE: false
fun foo(a: Int) {
}
fun bar(a: Int) {
<selection>foo(a + 1)</selection>
}
fun test() {
bar(1)
}
@@ -0,0 +1,12 @@
// WITH_DEFAULT_VALUE: false
fun foo(a: Int) {
}
fun bar(a: Int, __dummyTestFun__: (Int) -> Unit) {
__dummyTestFun__(a)
}
fun test() {
bar(1) { a -> foo(a + 1) }
}
@@ -0,0 +1,8 @@
fun foo(a: Int, s: String): Int {
val t = (a + 1) * 2
return <selection>a + 1</selection> - t
}
fun test() {
foo(1, "2")
}
@@ -0,0 +1,8 @@
fun foo(a: Int, s: String, i: (Int) -> Int = { a -> a + 1 }): Int {
val t = i(a) * 2
return i(a) - t
}
fun test() {
foo(1, "2")
}
@@ -27,8 +27,7 @@ import org.jetbrains.kotlin.idea.refactoring.JetRefactoringUtil
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.EXTRACT_FUNCTION
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.IntroduceParameterDescriptor
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.KotlinIntroduceParameterHandler
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.*
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.INTRODUCE_PROPERTY
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.KotlinIntroducePropertyHandler
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler
@@ -62,21 +61,37 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
}
}
protected fun doIntroduceParameterTest(path: String) {
private fun doIntroduceParameterTest(path: String, asLambda: Boolean) {
doTest(path) { file ->
val handler = object: KotlinIntroduceParameterHandler() {
val fileText = file.getText()
open class HelperImpl: KotlinIntroduceParameterHelper {
override fun configure(descriptor: IntroduceParameterDescriptor): IntroduceParameterDescriptor {
val fileText = file.getText()
val singleReplace = InTextDirectivesUtils.isDirectiveDefined(fileText, "// SINGLE_REPLACE")
val withDefaultValue = InTextDirectivesUtils.getPrefixedBoolean(fileText, "// WITH_DEFAULT_VALUE:") ?: true
return with (descriptor) {
val singleReplace = InTextDirectivesUtils.isDirectiveDefined(fileText, "// SINGLE_REPLACE")
val withDefaultValue = InTextDirectivesUtils.getPrefixedBoolean(fileText, "// WITH_DEFAULT_VALUE:") ?: true
copy(occurrencesToReplace = if (singleReplace) Collections.singletonList(originalOccurrence) else occurrencesToReplace,
withDefaultValue = withDefaultValue)
}
}
}
class LambdaHelperImpl: HelperImpl(), KotlinIntroduceLambdaParameterHelper {
override fun configureExtractLambda(descriptor: ExtractableCodeDescriptor): ExtractableCodeDescriptor {
return with(descriptor) {
if (name.isNullOrEmpty()) copy(suggestedNames = listOf("__dummyTestFun__")) else this
}
}
}
val handler = if (asLambda) {
KotlinIntroduceLambdaParameterHandler(LambdaHelperImpl())
} else {
KotlinIntroduceParameterHandler(HelperImpl())
}
with (handler) {
val target = file.findElementByComment("// TARGET:") as? JetNamedDeclaration
val target = (file as JetFile).findElementByComment("// TARGET:") as? JetNamedDeclaration
if (target != null) {
JetRefactoringUtil.selectExpression(fixture.getEditor(), file, true) { expression ->
invoke(fixture.getProject(), fixture.getEditor(), expression!!, target)
@@ -89,6 +104,14 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
}
}
protected fun doIntroduceSimpleParameterTest(path: String) {
doIntroduceParameterTest(path, false)
}
protected fun doIntroduceLambdaParameterTest(path: String) {
doIntroduceParameterTest(path, true)
}
protected fun doIntroducePropertyTest(path: String) {
doTest(path) { file ->
val extractionTarget = propertyTargets.single {
@@ -2203,187 +2203,244 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
@TestMetadata("assignment.kt")
public void testAssignment() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/assignment.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("classInAnonymousInitializer.kt")
public void testClassInAnonymousInitializer() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classInAnonymousInitializer.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("classInPropertyInitializer.kt")
public void testClassInPropertyInitializer() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classInPropertyInitializer.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("classMultipleUsages1.kt")
public void testClassMultipleUsages1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classMultipleUsages1.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("classMultipleUsages2.kt")
public void testClassMultipleUsages2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classMultipleUsages2.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("classMultipleUsagesOutsideScope.kt")
public void testClassMultipleUsagesOutsideScope() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classMultipleUsagesOutsideScope.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("classMultipleUsagesSingleReplace1.kt")
public void testClassMultipleUsagesSingleReplace1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classMultipleUsagesSingleReplace1.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("classMultipleUsagesSingleReplace2.kt")
public void testClassMultipleUsagesSingleReplace2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classMultipleUsagesSingleReplace2.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("classNoParams.kt")
public void testClassNoParams() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classNoParams.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("classParameterInFunctionBody.kt")
public void testClassParameterInFunctionBody() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classParameterInFunctionBody.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("classParameterUsedOutside.kt")
public void testClassParameterUsedOutside() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classParameterUsedOutside.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("classUnusedParameter.kt")
public void testClassUnusedParameter() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classUnusedParameter.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("classUsedParameter.kt")
public void testClassUsedParameter() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classUsedParameter.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("defaultValueInParens.kt")
public void testDefaultValueInParens() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/defaultValueInParens.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("forbiddenUsages.kt")
public void testForbiddenUsages() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/forbiddenUsages.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("fun.kt")
public void testFun() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/fun.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("functionMultipleUnusedParameters.kt")
public void testFunctionMultipleUnusedParameters() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/functionMultipleUnusedParameters.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("functionMultipleUsages.kt")
public void testFunctionMultipleUsages() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/functionMultipleUsages.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("functionMultipleUsagesSingleReplace.kt")
public void testFunctionMultipleUsagesSingleReplace() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/functionMultipleUsagesSingleReplace.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("functionUnusedParameter.kt")
public void testFunctionUnusedParameter() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/functionUnusedParameter.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("functionUsedParameter.kt")
public void testFunctionUsedParameter() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/functionUsedParameter.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("functionWithApproximatedType.kt")
public void testFunctionWithApproximatedType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/functionWithApproximatedType.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("functionWithDefaultValue.kt")
public void testFunctionWithDefaultValue() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/functionWithDefaultValue.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("localVar.kt")
public void testLocalVar() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/localVar.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("propertyAccessor.kt")
public void testPropertyAccessor() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/propertyAccessor.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("return.kt")
public void testReturn() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/return.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("secondaryConstructorWithDefaultValue.kt")
public void testSecondaryConstructorWithDefaultValue() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/secondaryConstructorWithDefaultValue.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("throw.kt")
public void testThrow() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/throw.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("valueAtCallSite.kt")
public void testValueAtCallSite() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/valueAtCallSite.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("valueAtCallSiteMultipleUsages.kt")
public void testValueAtCallSiteMultipleUsages() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/valueAtCallSiteMultipleUsages.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("while.kt")
public void testWhile() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/while.kt");
doIntroduceParameterTest(fileName);
doIntroduceSimpleParameterTest(fileName);
}
}
@TestMetadata("idea/testData/refactoring/introduceLambdaParameter")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class IntroduceLambdaParameter extends AbstractJetExtractionTest {
public void testAllFilesPresentInIntroduceLambdaParameter() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/introduceLambdaParameter"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("lambdaParamInPrimaryConstructor.kt")
public void testLambdaParamInPrimaryConstructor() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceLambdaParameter/lambdaParamInPrimaryConstructor.kt");
doIntroduceLambdaParameterTest(fileName);
}
@TestMetadata("lambdaParamMultiline.kt")
public void testLambdaParamMultiline() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceLambdaParameter/lambdaParamMultiline.kt");
doIntroduceLambdaParameterTest(fileName);
}
@TestMetadata("lambdaParamNoDefaultValue.kt")
public void testLambdaParamNoDefaultValue() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoDefaultValue.kt");
doIntroduceLambdaParameterTest(fileName);
}
@TestMetadata("lambdaParamNoDefaultValueSecondLambda.kt")
public void testLambdaParamNoDefaultValueSecondLambda() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoDefaultValueSecondLambda.kt");
doIntroduceLambdaParameterTest(fileName);
}
@TestMetadata("lambdaParamNoParams.kt")
public void testLambdaParamNoParams() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoParams.kt");
doIntroduceLambdaParameterTest(fileName);
}
@TestMetadata("lambdaParamOfNothing.kt")
public void testLambdaParamOfNothing() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceLambdaParameter/lambdaParamOfNothing.kt");
doIntroduceLambdaParameterTest(fileName);
}
@TestMetadata("lambdaParamOfUnit.kt")
public void testLambdaParamOfUnit() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceLambdaParameter/lambdaParamOfUnit.kt");
doIntroduceLambdaParameterTest(fileName);
}
@TestMetadata("lambdaParamWithDefaultValue.kt")
public void testLambdaParamWithDefaultValue() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceLambdaParameter/lambdaParamWithDefaultValue.kt");
doIntroduceLambdaParameterTest(fileName);
}
}
}