From 298dfa545fa723b6c1bfbc734956c8345de0cfb6 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Tue, 21 Apr 2015 20:55:06 +0300 Subject: [PATCH] Introduce Parameter: Implement "Introduce lambda parameter" --- .../kotlin/generators/tests/GenerateTests.kt | 3 +- idea/src/META-INF/plugin.xml | 7 + .../extractFunctionForDebuggerUtil.kt | 2 +- .../JetRefactoringSupportProvider.java | 6 + .../ui/KotlinExtractFunctionDialog.java | 40 ++- .../ExtractableCodeDescriptor.kt | 6 + .../extractionEngine/ExtractionData.kt | 1 + .../extractionEngine/extractorUtil.kt | 32 +- .../IntroduceLambdaParameterAction.kt | 27 ++ .../KotlinInplaceParameterIntroducer.kt | 21 +- .../KotlinIntroduceParameterDialog.kt | 172 ++++++++-- .../KotlinIntroduceParameterHandler.kt | 320 ++++++++++++------ .../KotlinIntroducePropertyHandler.kt | 2 +- .../lambdaParamInPrimaryConstructor.kt | 12 + .../lambdaParamInPrimaryConstructor.kt.after | 12 + .../lambdaParamMultiline.kt | 11 + .../lambdaParamMultiline.kt.after | 13 + .../lambdaParamNoDefaultValue.kt | 9 + .../lambdaParamNoDefaultValue.kt.after | 9 + .../lambdaParamNoDefaultValueSecondLambda.kt | 9 + ...daParamNoDefaultValueSecondLambda.kt.after | 9 + .../lambdaParamNoParams.kt | 9 + .../lambdaParamNoParams.kt.after | 9 + .../lambdaParamOfNothing.kt | 8 + .../lambdaParamOfNothing.kt.after | 8 + .../lambdaParamOfUnit.kt | 12 + .../lambdaParamOfUnit.kt.after | 12 + .../lambdaParamWithDefaultValue.kt | 8 + .../lambdaParamWithDefaultValue.kt.after | 8 + .../introduce/AbstractJetExtractionTest.kt | 39 ++- .../introduce/JetExtractionTestGenerated.java | 119 +++++-- 31 files changed, 747 insertions(+), 208 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/IntroduceLambdaParameterAction.kt create mode 100644 idea/testData/refactoring/introduceLambdaParameter/lambdaParamInPrimaryConstructor.kt create mode 100644 idea/testData/refactoring/introduceLambdaParameter/lambdaParamInPrimaryConstructor.kt.after create mode 100644 idea/testData/refactoring/introduceLambdaParameter/lambdaParamMultiline.kt create mode 100644 idea/testData/refactoring/introduceLambdaParameter/lambdaParamMultiline.kt.after create mode 100644 idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoDefaultValue.kt create mode 100644 idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoDefaultValue.kt.after create mode 100644 idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoDefaultValueSecondLambda.kt create mode 100644 idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoDefaultValueSecondLambda.kt.after create mode 100644 idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoParams.kt create mode 100644 idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoParams.kt.after create mode 100644 idea/testData/refactoring/introduceLambdaParameter/lambdaParamOfNothing.kt create mode 100644 idea/testData/refactoring/introduceLambdaParameter/lambdaParamOfNothing.kt.after create mode 100644 idea/testData/refactoring/introduceLambdaParameter/lambdaParamOfUnit.kt create mode 100644 idea/testData/refactoring/introduceLambdaParameter/lambdaParamOfUnit.kt.after create mode 100644 idea/testData/refactoring/introduceLambdaParameter/lambdaParamWithDefaultValue.kt create mode 100644 idea/testData/refactoring/introduceLambdaParameter/lambdaParamWithDefaultValue.kt.after diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index b0aa3910f3f..7ef47f29280 100644 --- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -617,7 +617,8 @@ fun main(args: Array) { 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()) { diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 55650195fbd..c506d8d2221 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -106,6 +106,13 @@ + + + + + diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt index bbfba8fb175..1ba8a1d97c2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt @@ -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)) } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/JetRefactoringSupportProvider.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/JetRefactoringSupportProvider.java index 3dd1281cfc4..d43dd1637a7 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/JetRefactoringSupportProvider.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/JetRefactoringSupportProvider.java @@ -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(); diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.java index c00d6133a64..68e65689239 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.java @@ -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 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 newParameterInfos + ) { Map 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 outputValues = new ArrayList(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 replacementMap = ContainerUtil.newHashMap(); - for (Map.Entry e : descriptor.getReplacementMap().entrySet()) { + for (Map.Entry 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); - } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt index 5e1e9d12e63..f13acfa2df4 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt @@ -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) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt index bbacba30557..20ac2e993b2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt @@ -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() diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt index c694a44d2df..754e6fcc969 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt @@ -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 { 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) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/IntroduceLambdaParameterAction.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/IntroduceLambdaParameterAction.kt new file mode 100644 index 00000000000..7d388efd195 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/IntroduceLambdaParameterAction.kt @@ -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() +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt index d85375ab973..f48e235215e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt @@ -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( - 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() } } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterDialog.kt index 2ed584bc116..5a7590fff12 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterDialog.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterDialog.kt @@ -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, - val typeSuggestions: List + val typeSuggestions: List, + val helper: KotlinIntroduceParameterHelper ): RefactoringDialog(project, true) { + constructor( + project: Project, + editor: Editor, + descriptor: IntroduceParameterDescriptor, + nameSuggestions: Array, + typeSuggestions: List, + 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(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) { - 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) = BaseUsageViewDescriptor() - override fun getCommandName() = INTRODUCE_PARAMETER + override fun getCommandName() = if (lambdaExtractionDescriptor != null) INTRODUCE_LAMBDA_PARAMETER else INTRODUCE_PARAMETER } ) } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt index e414aa141b4..d17071df203 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt @@ -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>, - val occurrencesToReplace: List + val occurrencesToReplace: List, + val parametersToRemove: List = 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 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 = this.parametersToRemove) { +fun getParametersToRemove( + withDefaultValue: Boolean, + parametersUsages: Map>, + occurrencesToReplace: List +): List { + 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): 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, 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() - .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, 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> { + return targetParent.getValueParameters() + .filter { !it.hasValOrVarNode() } + .map { + it to DefaultSearchHelper() + .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" diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceProperty/KotlinIntroducePropertyHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceProperty/KotlinIntroducePropertyHandler.kt index 0d750b2d76c..f2a2a16c05b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceProperty/KotlinIntroducePropertyHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceProperty/KotlinIntroducePropertyHandler.kt @@ -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 diff --git a/idea/testData/refactoring/introduceLambdaParameter/lambdaParamInPrimaryConstructor.kt b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamInPrimaryConstructor.kt new file mode 100644 index 00000000000..c37d6d48330 --- /dev/null +++ b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamInPrimaryConstructor.kt @@ -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 a - n - t + } +} + +fun test() { + Foo(1).foo(2) +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceLambdaParameter/lambdaParamInPrimaryConstructor.kt.after b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamInPrimaryConstructor.kt.after new file mode 100644 index 00000000000..616c6b5cdc4 --- /dev/null +++ b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamInPrimaryConstructor.kt.after @@ -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) +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceLambdaParameter/lambdaParamMultiline.kt b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamMultiline.kt new file mode 100644 index 00000000000..4dbc36daf71 --- /dev/null +++ b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamMultiline.kt @@ -0,0 +1,11 @@ +// WITH_DEFAULT_VALUE: false +fun foo(a: Int, s: String): Int { + val t = a + 1 + 2 + return a + .plus(1) + .plus(2) - t +} + +fun test() { + foo(1, "2") +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceLambdaParameter/lambdaParamMultiline.kt.after b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamMultiline.kt.after new file mode 100644 index 00000000000..b390649e584 --- /dev/null +++ b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamMultiline.kt.after @@ -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) + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoDefaultValue.kt b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoDefaultValue.kt new file mode 100644 index 00000000000..8d95e845d7f --- /dev/null +++ b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoDefaultValue.kt @@ -0,0 +1,9 @@ +// WITH_DEFAULT_VALUE: false +fun foo(a: Int, s: String): Int { + val t = (a + 1) * 2 + return a + 1 - t +} + +fun test() { + foo(1, "2") +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoDefaultValue.kt.after b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoDefaultValue.kt.after new file mode 100644 index 00000000000..084cf1e143b --- /dev/null +++ b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoDefaultValue.kt.after @@ -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 } +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoDefaultValueSecondLambda.kt b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoDefaultValueSecondLambda.kt new file mode 100644 index 00000000000..c7bb87b0ebf --- /dev/null +++ b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoDefaultValueSecondLambda.kt @@ -0,0 +1,9 @@ +// WITH_DEFAULT_VALUE: false +fun foo(a: Int, s: () -> String): Int { + val t = (a + 1) * 2 + return a + 1 - t +} + +fun test() { + foo(1) { "2" } +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoDefaultValueSecondLambda.kt.after b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoDefaultValueSecondLambda.kt.after new file mode 100644 index 00000000000..b905b9cafee --- /dev/null +++ b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoDefaultValueSecondLambda.kt.after @@ -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 } +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoParams.kt b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoParams.kt new file mode 100644 index 00000000000..9caffd9bb22 --- /dev/null +++ b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoParams.kt @@ -0,0 +1,9 @@ +// WITH_DEFAULT_VALUE: false +fun foo(a: Int, s: String): Int { + val t = (a + 1) * 2 + return 1 + 2 - t +} + +fun test() { + foo(1, "2") +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoParams.kt.after b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoParams.kt.after new file mode 100644 index 00000000000..ba8dc167eed --- /dev/null +++ b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamNoParams.kt.after @@ -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 } +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceLambdaParameter/lambdaParamOfNothing.kt b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamOfNothing.kt new file mode 100644 index 00000000000..377a741d2f8 --- /dev/null +++ b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamOfNothing.kt @@ -0,0 +1,8 @@ +// WITH_DEFAULT_VALUE: false +fun foo(a: Int) { + throw Exception("Error: $a") +} + +fun test() { + foo(1) +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceLambdaParameter/lambdaParamOfNothing.kt.after b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamOfNothing.kt.after new file mode 100644 index 00000000000..35e07fa84cf --- /dev/null +++ b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamOfNothing.kt.after @@ -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") } +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceLambdaParameter/lambdaParamOfUnit.kt b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamOfUnit.kt new file mode 100644 index 00000000000..f105671d7eb --- /dev/null +++ b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamOfUnit.kt @@ -0,0 +1,12 @@ +// WITH_DEFAULT_VALUE: false +fun foo(a: Int) { + +} + +fun bar(a: Int) { + foo(a + 1) +} + +fun test() { + bar(1) +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceLambdaParameter/lambdaParamOfUnit.kt.after b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamOfUnit.kt.after new file mode 100644 index 00000000000..77d54ea0b70 --- /dev/null +++ b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamOfUnit.kt.after @@ -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) } +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceLambdaParameter/lambdaParamWithDefaultValue.kt b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamWithDefaultValue.kt new file mode 100644 index 00000000000..50927cc9697 --- /dev/null +++ b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamWithDefaultValue.kt @@ -0,0 +1,8 @@ +fun foo(a: Int, s: String): Int { + val t = (a + 1) * 2 + return a + 1 - t +} + +fun test() { + foo(1, "2") +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceLambdaParameter/lambdaParamWithDefaultValue.kt.after b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamWithDefaultValue.kt.after new file mode 100644 index 00000000000..898643960cf --- /dev/null +++ b/idea/testData/refactoring/introduceLambdaParameter/lambdaParamWithDefaultValue.kt.after @@ -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") +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractJetExtractionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractJetExtractionTest.kt index 5e9b8500827..716b19d4c70 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractJetExtractionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractJetExtractionTest.kt @@ -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 { diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java index 4925256fca4..0ad6dd177f5 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java @@ -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); } } }