Extraction Engine: Suggest function names based on the name of the result variable (if applicable)

#KT-6291 Fixed
This commit is contained in:
Alexey Sedunov
2015-03-11 18:26:50 +03:00
parent 8e5b960e3d
commit 9d8cfd04d9
27 changed files with 75 additions and 31 deletions
@@ -103,7 +103,7 @@ fun getFunctionForExtractedFragment(
val config = ExtractionGeneratorConfiguration(
validationResult.descriptor,
ExtractionGeneratorOptions(inTempFile = true, flexibleTypesAllowed = true)
ExtractionGeneratorOptions(inTempFile = true, flexibleTypesAllowed = true, allowDummyName = true)
)
return config.generateDeclaration()
}
@@ -99,14 +99,16 @@
<visible value="true"/>
</properties>
</component>
<component id="bee3c" class="com.intellij.ui.EditorTextField" binding="functionNameField">
<grid id="d0f58" binding="functionNamePanel" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<border type="none"/>
<children/>
</grid>
<component id="71ee7" class="javax.swing.JComboBox" binding="visibilityBox">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="0" indent="0" use-parent-layout="false">
@@ -16,19 +16,19 @@
package org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ui;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.psi.PsiElement;
import com.intellij.ui.EditorTextField;
import com.intellij.refactoring.ui.NameSuggestionsField;
import com.intellij.ui.TitledSeparator;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import kotlin.Function0;
import kotlin.Function1;
import kotlin.Unit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.JetFileType;
import org.jetbrains.kotlin.idea.refactoring.JetNameSuggester;
import org.jetbrains.kotlin.idea.refactoring.JetRefactoringBundle;
import org.jetbrains.kotlin.idea.refactoring.RefactoringPackage;
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*;
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
@@ -48,7 +49,8 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
private TitledSeparator inputParametersPanel;
private JComboBox visibilityBox;
private KotlinFunctionSignatureComponent signaturePreviewField;
private EditorTextField functionNameField;
private JPanel functionNamePanel;
private NameSuggestionsField functionNameField;
private JLabel functionNameLabel;
private KotlinParameterTablePanel parameterTablePanel;
@@ -85,7 +87,7 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
}
private String getFunctionName() {
return functionNameField.getText();
return functionNameField.getEnteredName();
}
private String getVisibility() {
@@ -118,17 +120,21 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
protected void init() {
super.init();
functionNameLabel.setLabelFor(functionNameField);
functionNameField.setText(originalDescriptor.getDescriptor().getName());
functionNameField.addDocumentListener(
new DocumentAdapter() {
functionNameField = new NameSuggestionsField(
ArrayUtil.toStringArray(originalDescriptor.getDescriptor().getSuggestedNames()),
project,
JetFileType.INSTANCE
);
functionNameField.addDataChangedListener(
new NameSuggestionsField.DataChanged() {
@Override
public void documentChanged(DocumentEvent event) {
public void dataChanged() {
update();
}
}
);
functionNamePanel.add(functionNameField, BorderLayout.CENTER);
functionNameLabel.setLabelFor(functionNameField);
boolean enableVisibility = isVisibilitySectionAvailable();
visibilityBox.setEnabled(enableVisibility);
@@ -363,7 +363,8 @@ val propertyTargets: List<ExtractionTarget> = listOf(ExtractionTarget.PROPERTY_W
data class ExtractionGeneratorOptions(
val inTempFile: Boolean = false,
val target: ExtractionTarget = ExtractionTarget.FUNCTION,
val flexibleTypesAllowed: Boolean = false
val flexibleTypesAllowed: Boolean = false,
val allowDummyName: Boolean = false
) {
default object {
val DEFAULT = ExtractionGeneratorOptions()
@@ -72,8 +72,8 @@ import org.jetbrains.kotlin.resolve.calls.callUtil.*
import org.jetbrains.kotlin.diagnostics.*
import java.util.logging.*
import com.intellij.openapi.diagnostic.Logger
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
private val DEFAULT_FUNCTION_NAME = "myFun"
private val DEFAULT_RETURN_TYPE = KotlinBuiltIns.getInstance().getUnitType()
private val DEFAULT_PARAMETER_TYPE = KotlinBuiltIns.getInstance().getNullableAnyType()
@@ -804,19 +804,6 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
val enclosingDeclaration = commonParent.getStrictParentOfType<JetDeclaration>()!!
checkDeclarationsMovingOutOfScope(enclosingDeclaration, controlFlow, bindingContext)?.let { messages.add(it) }
val functionNameValidator =
JetNameValidatorImpl(
targetSibling.getParent(),
if (targetSibling is JetClassInitializer) targetSibling.getParent() else targetSibling,
if (options.extractAsProperty) JetNameValidatorImpl.Target.PROPERTIES else JetNameValidatorImpl.Target.FUNCTIONS_AND_CLASSES
)
val functionNames = if (returnType.isDefault()) {
Collections.emptyList<String>()
}
else {
JetNameSuggester.suggestNames(returnType, functionNameValidator, DEFAULT_FUNCTION_NAME).toList()
}
controlFlow.jumpOutputValue?.elementToInsertAfterCall?.accept(
object : JetTreeVisitorVoid() {
override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) {
@@ -834,7 +821,7 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
ExtractableCodeDescriptor(
this,
bindingContext,
functionNames,
suggestFunctionNames(returnType),
getDefaultVisibility(),
adjustedParameters.sortBy { it.name },
receiverParameter,
@@ -847,6 +834,29 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
)
}
private fun ExtractionData.suggestFunctionNames(returnType: JetType): List<String> {
val functionNames = LinkedHashSet<String>()
val validator =
JetNameValidatorImpl(
targetSibling.getParent(),
if (targetSibling is JetClassInitializer) targetSibling.getParent() else targetSibling,
if (options.extractAsProperty) JetNameValidatorImpl.Target.PROPERTIES else JetNameValidatorImpl.Target.FUNCTIONS_AND_CLASSES
)
if (!returnType.isDefault()) {
functionNames.addAll(JetNameSuggester.suggestNamesForType(returnType, validator))
}
getExpressions().singleOrNull()?.let { expr ->
val property = expr.getStrictParentOfType<JetProperty>()
if (property?.getInitializer() == expr) {
property?.getName()?.let { functionNames.add(validator.validateName("get" + it.capitalize())) }
}
}
return functionNames.toList()
}
private fun JetNamedDeclaration.getGeneratedBody() =
when (this) {
is JetNamedFunction -> getBodyExpression()
@@ -90,7 +90,7 @@ fun ExtractionGeneratorConfiguration.getDeclarationText(
builder.receiver(it.getParameterType(descriptor.extractionData.options.allowSpecialClassNames).typeAsString())
}
builder.name(if (descriptor.name != "") descriptor.name else DEFAULT_FUNCTION_NAME)
builder.name(if (descriptor.name == "" && generatorOptions.allowDummyName) "myFun" else descriptor.name)
descriptor.parameters.forEach { parameter ->
builder.param(parameter.name,
@@ -1,4 +1,5 @@
// WITH_RUNTIME
// SUGGESTED_NAMES: pair, getT
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo
@@ -1,4 +1,5 @@
// WITH_RUNTIME
// SUGGESTED_NAMES: pair, getT
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo
@@ -1,3 +1,4 @@
// SUGGESTED_NAMES: pair, getT
// WITH_RUNTIME
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
@@ -1,3 +1,4 @@
// SUGGESTED_NAMES: pair, getT
// WITH_RUNTIME
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
@@ -1,4 +1,5 @@
// WITH_RUNTIME
// SUGGESTED_NAMES: triple, getT
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
@@ -1,4 +1,5 @@
// WITH_RUNTIME
// SUGGESTED_NAMES: triple, getT
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
@@ -1,3 +1,4 @@
// SUGGESTED_NAMES: i, getT
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo
@@ -1,3 +1,4 @@
// SUGGESTED_NAMES: i, getT
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo
@@ -1,4 +1,5 @@
// WITH_RUNTIME
// SUGGESTED_NAMES: i, getT
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in test
@@ -1,4 +1,5 @@
// WITH_RUNTIME
// SUGGESTED_NAMES: i, getT
// PARAM_TYPES: kotlin.Int
// PARAM_TYPES: kotlin.Int
// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in test
@@ -1,3 +1,4 @@
// SUGGESTED_NAMES: i, getX
class A {
val x = <selection>1 + 1</selection>
val y = 1 + 1
@@ -1,3 +1,4 @@
// SUGGESTED_NAMES: i, getX
class A {
val x = i()
val y = i()
@@ -1,3 +1,4 @@
// SUGGESTED_NAMES: i, getY
fun foo() {
val x = 1 + 1
val y = <selection>1 + 1</selection>
@@ -1,3 +1,4 @@
// SUGGESTED_NAMES: i, getY
fun foo() {
val x = i()
val y = i()
@@ -1,3 +1,4 @@
// SUGGESTED_NAMES: i, getY
// SIBLING:
fun main(args: Array<String>) {
val x = 1 + 1
@@ -1,3 +1,4 @@
// SUGGESTED_NAMES: i, getY
// SIBLING:
fun main(args: Array<String>) {
val x = i()
@@ -1,4 +1,5 @@
// WITH_RUNTIME
// SUGGESTED_NAMES: i, getN
// PARAM_TYPES: String?, String, kotlin.CharSequence?, CharSequence
// PARAM_DESCRIPTOR: val property: (String..String?) defined in test
fun test() {
@@ -1,4 +1,5 @@
// WITH_RUNTIME
// SUGGESTED_NAMES: i, getN
// PARAM_TYPES: String?, String, kotlin.CharSequence?, CharSequence
// PARAM_DESCRIPTOR: val property: (String..String?) defined in test
fun test() {
@@ -1,4 +1,5 @@
// WITH_RUNTIME
// SUGGESTED_NAMES: i, getN
// PARAM_TYPES: String, CharSequence
// PARAM_DESCRIPTOR: val property: (String..String?) defined in test
fun test() {
@@ -1,4 +1,5 @@
// WITH_RUNTIME
// SUGGESTED_NAMES: i, getN
// PARAM_TYPES: String, CharSequence
// PARAM_DESCRIPTOR: val property: (String..String?) defined in test
fun test() {
@@ -110,6 +110,7 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
)
val fileText = file.getText() ?: ""
val expectedNames = InTextDirectivesUtils.findListWithPrefixes(fileText, "// SUGGESTED_NAMES: ")
val expectedDescriptors =
InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PARAM_DESCRIPTOR: ").joinToString()
val expectedTypes =
@@ -136,12 +137,16 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
descriptor: ExtractableCodeDescriptor,
generatorOptions: ExtractionGeneratorOptions
): ExtractionGeneratorConfiguration {
val actualNames = descriptor.suggestedNames
val allParameters = emptyOrSingletonList(descriptor.receiverParameter) + descriptor.parameters
val actualDescriptors = allParameters.map { renderer.render(it.originalDescriptor) }.joinToString()
val actualTypes = allParameters.map {
it.getParameterTypeCandidates(false).map { renderer.renderType(it) }.joinToString(", ", "[", "]")
}.joinToString()
if (actualNames.size() != 1 || expectedNames.isNotEmpty()) {
assertEquals(expectedNames, actualNames, "Expected names mismatch.")
}
assertEquals(expectedDescriptors, actualDescriptors, "Expected descriptors mismatch.")
assertEquals(expectedTypes, actualTypes, "Expected types mismatch.")