Introduce Property: Initial support (properties with getter)
This commit is contained in:
@@ -681,6 +681,7 @@ fun main(args: Array<String>) {
|
||||
testClass(javaClass<AbstractJetExtractionTest>()) {
|
||||
model("refactoring/introduceVariable", extension = "kt", testMethod = "doIntroduceVariableTest")
|
||||
model("refactoring/extractFunction", extension = "kt", testMethod = "doExtractFunctionTest")
|
||||
model("refactoring/introduceProperty", extension = "kt", testMethod = "doIntroducePropertyTest")
|
||||
}
|
||||
|
||||
testClass(javaClass<AbstractSelectExpressionForDebuggerTest>()) {
|
||||
|
||||
@@ -101,6 +101,11 @@
|
||||
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="ExtractMethod"/>
|
||||
</action>
|
||||
|
||||
<action id="IntroduceProperty" class="org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.IntroducePropertyAction"
|
||||
text="Introduce _Property..." use-shortcut-of="IntroduceField">
|
||||
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="IntroduceField"/>
|
||||
</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"/>
|
||||
|
||||
+4
-3
@@ -88,9 +88,10 @@ fun getFunctionForExtractedFragment(
|
||||
val targetSibling = tmpFile.getDeclarations().firstOrNull()
|
||||
if (targetSibling == null) return null
|
||||
|
||||
val analysisResult = ExtractionData(
|
||||
tmpFile, newDebugExpression.toRange(), targetSibling, ExtractionOptions(false, true, true)
|
||||
).performAnalysis()
|
||||
val options = ExtractionOptions(inferUnitTypeForUnusedValues = false,
|
||||
enableListBoxing = true,
|
||||
allowSpecialClassNames = true)
|
||||
val analysisResult = ExtractionData(tmpFile, newDebugExpression.toRange(), targetSibling, options).performAnalysis()
|
||||
if (analysisResult.status != Status.SUCCESS) {
|
||||
throw EvaluateExceptionUtil.createEvaluateException(getErrorMessageForExtractFunctionResult(analysisResult))
|
||||
}
|
||||
|
||||
@@ -19,11 +19,15 @@ package org.jetbrains.kotlin.idea.refactoring;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.analyzer.AnalysisResult;
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes;
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor;
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage;
|
||||
import org.jetbrains.kotlin.name.Name;
|
||||
import org.jetbrains.kotlin.psi.JetElement;
|
||||
import org.jetbrains.kotlin.psi.JetExpression;
|
||||
import org.jetbrains.kotlin.psi.JetVisitorVoid;
|
||||
import org.jetbrains.kotlin.psi.*;
|
||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||
import org.jetbrains.kotlin.resolve.scopes.JetScope;
|
||||
|
||||
@@ -72,7 +76,9 @@ public class JetNameValidatorImpl extends JetNameValidator {
|
||||
private boolean checkElement(String name, PsiElement sibling, final Set<JetScope> visitedScopes) {
|
||||
if (!(sibling instanceof JetElement)) return true;
|
||||
|
||||
final BindingContext bindingContext = ResolvePackage.analyze((JetElement) sibling);
|
||||
AnalysisResult analysisResult = ResolvePackage.analyzeAndGetResult((JetElement) sibling);
|
||||
final BindingContext bindingContext = analysisResult.getBindingContext();
|
||||
final ModuleDescriptor module = analysisResult.getModuleDescriptor();
|
||||
final Name identifier = Name.identifier(name);
|
||||
|
||||
final Ref<Boolean> result = new Ref<Boolean>(true);
|
||||
@@ -84,12 +90,33 @@ public class JetNameValidatorImpl extends JetNameValidator {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JetScope getScope(@NotNull JetExpression expression) {
|
||||
PsiElement parent = expression.getParent();
|
||||
|
||||
if (parent instanceof JetClassBody) {
|
||||
JetClassOrObject classOrObject = (JetClassOrObject) parent.getParent();
|
||||
ClassDescriptor classDescriptor = bindingContext.get(BindingContext.CLASS, classOrObject);
|
||||
return classDescriptor instanceof ClassDescriptorWithResolutionScopes
|
||||
? ((ClassDescriptorWithResolutionScopes) classDescriptor).getScopeForMemberDeclarationResolution()
|
||||
: null;
|
||||
}
|
||||
|
||||
if (parent instanceof JetFile) {
|
||||
PackageViewDescriptor packageViewDescriptor = module.getPackage(((JetFile) parent).getPackageFqName());
|
||||
return packageViewDescriptor != null ? packageViewDescriptor.getMemberScope() : null;
|
||||
}
|
||||
|
||||
return bindingContext.get(BindingContext.RESOLUTION_SCOPE, expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitExpression(@NotNull JetExpression expression) {
|
||||
JetScope resolutionScope = bindingContext.get(BindingContext.RESOLUTION_SCOPE, expression);
|
||||
if (!visitedScopes.add(resolutionScope)) return;
|
||||
JetScope resolutionScope = getScope(expression);
|
||||
|
||||
if (resolutionScope != null) {
|
||||
if (!visitedScopes.add(resolutionScope)) return;
|
||||
|
||||
boolean noConflict;
|
||||
if (myTarget == Target.PROPERTIES) {
|
||||
noConflict = resolutionScope.getProperties(identifier).isEmpty()
|
||||
|
||||
@@ -2,6 +2,7 @@ cannot.refactor.not.expression=Cannot find an expression to introduce
|
||||
cannot.refactor.not.expression.to.extract=Cannot find an expression to extract
|
||||
expressions.title=Expressions
|
||||
introduce.variable=Introduce Variable
|
||||
introduce.property=Introduce Property
|
||||
cannot.refactor.no.container=Cannot refactor in this place
|
||||
cannot.refactor.no.expression=Cannot perform refactoring without an expression
|
||||
cannot.refactor.expression.has.unit.type=Cannot introduce expression of unit type
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.idea.refactoring.changeSignature.JetChangeSignatureHandler;
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandler;
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.KotlinIntroducePropertyHandler;
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler;
|
||||
import org.jetbrains.kotlin.idea.refactoring.safeDelete.SafeDeletePackage;
|
||||
import org.jetbrains.kotlin.psi.*;
|
||||
@@ -39,6 +40,11 @@ public class JetRefactoringSupportProvider extends RefactoringSupportProvider {
|
||||
return new KotlinIntroduceVariableHandler();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public RefactoringActionHandler getIntroducePropertyHandler() {
|
||||
return new KotlinIntroducePropertyHandler();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public RefactoringActionHandler getExtractFunctionHandler() {
|
||||
return new ExtractKotlinFunctionHandler();
|
||||
|
||||
+5
-28
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ui.KotlinExtractFunctionDialog">
|
||||
<grid id="27dc6" binding="contentPane" layout-manager="GridLayoutManager" row-count="5" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<grid id="27dc6" binding="contentPane" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="522" height="351"/>
|
||||
<xy x="20" y="20" width="522" height="318"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
@@ -11,7 +11,7 @@
|
||||
<grid id="34b30" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
@@ -38,7 +38,7 @@
|
||||
<grid id="b45d1" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="-1" height="200"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
@@ -127,32 +127,9 @@
|
||||
</grid>
|
||||
<vspacer id="cd10f">
|
||||
<constraints>
|
||||
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
<grid id="ad40b" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="1b96b" class="javax.swing.JCheckBox" binding="propertyCheckBox">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Extract as property"/>
|
||||
</properties>
|
||||
</component>
|
||||
<hspacer id="df17e">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</hspacer>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
|
||||
+3
-22
@@ -36,11 +36,10 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*;
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -51,7 +50,6 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
|
||||
private KotlinFunctionSignatureComponent signaturePreviewField;
|
||||
private EditorTextField functionNameField;
|
||||
private JLabel functionNameLabel;
|
||||
private JCheckBox propertyCheckBox;
|
||||
private KotlinParameterTablePanel parameterTablePanel;
|
||||
|
||||
private final Project project;
|
||||
@@ -146,18 +144,6 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
|
||||
}
|
||||
);
|
||||
|
||||
propertyCheckBox.setEnabled(ExtractionEnginePackage.canGenerateProperty(originalDescriptor.getDescriptor()));
|
||||
if (propertyCheckBox.isEnabled()) {
|
||||
propertyCheckBox.addActionListener(
|
||||
new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull ActionEvent e) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
parameterTablePanel = new KotlinParameterTablePanel() {
|
||||
@Override
|
||||
protected void updateSignature() {
|
||||
@@ -260,7 +246,7 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
|
||||
return new ExtractableCodeDescriptor(
|
||||
descriptor.getExtractionData(),
|
||||
descriptor.getOriginalContext(),
|
||||
getFunctionName(),
|
||||
Collections.singletonList(getFunctionName()),
|
||||
getVisibility(),
|
||||
ContainerUtil.newArrayList(oldToNewParameters.values()),
|
||||
descriptor.getReceiverParameter(),
|
||||
@@ -272,11 +258,6 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
|
||||
|
||||
@NotNull
|
||||
public ExtractionGeneratorConfiguration getCurrentConfiguration() {
|
||||
return new ExtractionGeneratorConfiguration(currentDescriptor, getGeneratorOptions());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ExtractionGeneratorOptions getGeneratorOptions() {
|
||||
return new ExtractionGeneratorOptions(false, propertyCheckBox.isSelected(), false);
|
||||
return new ExtractionGeneratorConfiguration(currentDescriptor, ExtractionGeneratorOptions.DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -305,14 +305,16 @@ fun ControlFlow.toDefault(): ControlFlow =
|
||||
data class ExtractableCodeDescriptor(
|
||||
val extractionData: ExtractionData,
|
||||
val originalContext: BindingContext,
|
||||
val name: String,
|
||||
val suggestedNames: List<String>,
|
||||
val visibility: String,
|
||||
val parameters: List<Parameter>,
|
||||
val receiverParameter: Parameter?,
|
||||
val typeParameters: List<TypeParameter>,
|
||||
val replacementMap: Map<Int, Replacement>,
|
||||
val controlFlow: ControlFlow
|
||||
)
|
||||
) {
|
||||
val name: String get() = suggestedNames.firstOrNull() ?: ""
|
||||
}
|
||||
|
||||
data class ExtractionGeneratorOptions(
|
||||
val inTempFile: Boolean = false,
|
||||
|
||||
+5
-4
@@ -54,12 +54,13 @@ import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.idea.refactoring.compareDescriptors
|
||||
|
||||
data class ExtractionOptions(
|
||||
val inferUnitTypeForUnusedValues: Boolean,
|
||||
val enableListBoxing: Boolean,
|
||||
val allowSpecialClassNames: Boolean
|
||||
val inferUnitTypeForUnusedValues: Boolean = true,
|
||||
val enableListBoxing: Boolean = false,
|
||||
val extractAsProperty: Boolean = false,
|
||||
val allowSpecialClassNames: Boolean = false
|
||||
) {
|
||||
class object {
|
||||
val DEFAULT = ExtractionOptions(true, false, false)
|
||||
val DEFAULT = ExtractionOptions()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-4
@@ -796,10 +796,13 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
|
||||
JetNameValidatorImpl(
|
||||
targetSibling.getParent(),
|
||||
if (targetSibling is JetClassInitializer) targetSibling.getParent() else targetSibling,
|
||||
JetNameValidatorImpl.Target.FUNCTIONS_AND_CLASSES
|
||||
if (options.extractAsProperty) JetNameValidatorImpl.Target.PROPERTIES else JetNameValidatorImpl.Target.FUNCTIONS_AND_CLASSES
|
||||
)
|
||||
val functionName = if (returnType.isDefault()) "" else {
|
||||
JetNameSuggester.suggestNames(returnType, functionNameValidator, DEFAULT_FUNCTION_NAME).first()
|
||||
val functionNames = if (returnType.isDefault()) {
|
||||
Collections.emptyList<String>()
|
||||
}
|
||||
else {
|
||||
JetNameSuggester.suggestNames(returnType, functionNameValidator, DEFAULT_FUNCTION_NAME).toList()
|
||||
}
|
||||
|
||||
controlFlow.jumpOutputValue?.elementToInsertAfterCall?.accept(
|
||||
@@ -819,7 +822,7 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
|
||||
ExtractableCodeDescriptor(
|
||||
this,
|
||||
bindingContext,
|
||||
functionName,
|
||||
functionNames,
|
||||
if (isVisibilityApplicable()) "private" else "",
|
||||
adjustedParameters.sortBy { it.name },
|
||||
receiverParameter,
|
||||
|
||||
+12
-9
@@ -66,15 +66,18 @@ fun ExtractionGeneratorConfiguration.getDeclarationText(
|
||||
return if (isSpecial()) DEBUG_TYPE_REFERENCE_STRING else descriptorRenderer.renderType(this)
|
||||
}
|
||||
|
||||
receiverParameter?.let { builder.receiver(it.getParameterType(extractionData.options.allowSpecialClassNames).typeAsString()) }
|
||||
|
||||
builder.name(if (name != "") name else DEFAULT_FUNCTION_NAME)
|
||||
|
||||
parameters.forEach { parameter ->
|
||||
builder.param(parameter.name, parameter.getParameterType(extractionData.options.allowSpecialClassNames).typeAsString())
|
||||
descriptor.receiverParameter?.let {
|
||||
builder.receiver(it.getParameterType(descriptor.extractionData.options.allowSpecialClassNames).typeAsString())
|
||||
}
|
||||
|
||||
with(controlFlow.outputValueBoxer.returnType) {
|
||||
builder.name(if (descriptor.name != "") descriptor.name else DEFAULT_FUNCTION_NAME)
|
||||
|
||||
descriptor.parameters.forEach { parameter ->
|
||||
builder.param(parameter.name,
|
||||
parameter.getParameterType(descriptor.extractionData.options.allowSpecialClassNames).typeAsString())
|
||||
}
|
||||
|
||||
with(descriptor.controlFlow.outputValueBoxer.returnType) {
|
||||
if (isDefault() || isError()) builder.noReturnType() else builder.returnType(this.typeAsString())
|
||||
}
|
||||
|
||||
@@ -521,10 +524,10 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(): ExtractionResult{
|
||||
adjustDeclarationBody(declaration)
|
||||
|
||||
if (declaration is JetNamedFunction && declaration.getContainingJetFile().suppressDiagnosticsInDebugMode) {
|
||||
declaration.getReceiverTypeReference()?.debugTypeInfo = receiverParameter?.getParameterType(true)
|
||||
declaration.getReceiverTypeReference()?.debugTypeInfo = descriptor.receiverParameter?.getParameterType(true)
|
||||
|
||||
for ((i, param) in declaration.getValueParameters().withIndex()) {
|
||||
param.getTypeReference()?.debugTypeInfo = parameters[i].getParameterType(true)
|
||||
param.getTypeReference()?.debugTypeInfo = descriptor.parameters[i].getParameterType(true)
|
||||
}
|
||||
|
||||
if (declaration.getTypeReference() != null) {
|
||||
|
||||
+27
@@ -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.introduceVariable
|
||||
|
||||
import com.intellij.refactoring.RefactoringActionHandler
|
||||
import com.intellij.lang.refactoring.RefactoringSupportProvider
|
||||
import org.jetbrains.kotlin.idea.refactoring.JetRefactoringSupportProvider
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.*
|
||||
|
||||
public class IntroducePropertyAction : AbstractIntroduceAction() {
|
||||
override fun getRefactoringHandler(provider: RefactoringSupportProvider): RefactoringActionHandler? =
|
||||
(provider as? JetRefactoringSupportProvider)?.getIntroducePropertyHandler()
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.introduceProperty
|
||||
|
||||
import com.intellij.openapi.project.*
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.openapi.actionSystem.*
|
||||
import com.intellij.openapi.editor.*
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
|
||||
import org.jetbrains.kotlin.idea.util.psi.patternMatching.*
|
||||
import kotlin.test.*
|
||||
import com.intellij.openapi.application.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.*
|
||||
import java.util.*
|
||||
|
||||
public class KotlinIntroducePropertyHandler(
|
||||
val helper: ExtractionEngineHelper = KotlinIntroducePropertyHandler.InteractiveExtractionHelper
|
||||
): KotlinIntroduceHandlerBase() {
|
||||
object InteractiveExtractionHelper : ExtractionEngineHelper() {
|
||||
override fun configureInteractively(
|
||||
project: Project,
|
||||
editor: Editor,
|
||||
descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts,
|
||||
continuation: (ExtractionGeneratorConfiguration) -> Unit
|
||||
) {
|
||||
val descriptor = descriptorWithConflicts.descriptor
|
||||
if (descriptor.canGenerateProperty()) {
|
||||
continuation(ExtractionGeneratorConfiguration(descriptor, ExtractionGeneratorOptions.DEFAULT.copy(extractAsProperty = true)))
|
||||
}
|
||||
else {
|
||||
showErrorHint(project, editor, "Can't introduce property for this expression", INTRODUCE_PROPERTY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext?) {
|
||||
if (file !is JetFile) return
|
||||
selectElements(
|
||||
operationName = INTRODUCE_PROPERTY,
|
||||
editor = editor,
|
||||
file = file,
|
||||
getContainers = {(elements, parent) ->
|
||||
parent.getExtractionContainers(strict = true, includeAll = true).filter { it is JetClassBody || it is JetFile }
|
||||
}
|
||||
) { (elements, targetSibling) ->
|
||||
val adjustedElements = (elements.singleOrNull() as? JetBlockExpression)?.getStatements() ?: elements
|
||||
if (adjustedElements.isNotEmpty()) {
|
||||
val options = ExtractionOptions(extractAsProperty = true)
|
||||
val extractionData = ExtractionData(file, adjustedElements.toRange(), targetSibling, options)
|
||||
ExtractionEngine(INTRODUCE_PROPERTY, helper).run(editor, extractionData) {
|
||||
val property = it.declaration as JetProperty
|
||||
val descriptor = it.config.descriptor
|
||||
|
||||
editor.getCaretModel().moveToOffset(property.getTextOffset())
|
||||
editor.getSelectionModel().removeSelection()
|
||||
if (editor.getSettings().isVariableInplaceRenameEnabled() && !ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
with(PsiDocumentManager.getInstance(project)) {
|
||||
commitDocument(editor.getDocument())
|
||||
doPostponedOperationsAndUnblockDocument(editor.getDocument())
|
||||
}
|
||||
|
||||
val introducer = KotlinInplaceVariableIntroducer(
|
||||
property,
|
||||
editor,
|
||||
project,
|
||||
INTRODUCE_PROPERTY, // title
|
||||
JetExpression.EMPTY_ARRAY, // occurrences
|
||||
null, // expr
|
||||
false, // replaceOccurrence
|
||||
property,
|
||||
false, // isVar
|
||||
true, // doNotChangeVar
|
||||
descriptor.controlFlow.outputValueBoxer.returnType,
|
||||
true // noTypeInference
|
||||
)
|
||||
introducer.performInplaceRefactoring(LinkedHashSet(descriptor.suggestedNames))
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
showErrorHintByKey(project, editor, "cannot.refactor.no.expression", INTRODUCE_PROPERTY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun invoke(project: Project, elements: Array<out PsiElement>, dataContext: DataContext?) {
|
||||
fail("$INTRODUCE_PROPERTY can only be invoked from editor")
|
||||
}
|
||||
}
|
||||
|
||||
private val INTRODUCE_PROPERTY: String = JetRefactoringBundle.message("introduce.property")
|
||||
@@ -28,6 +28,10 @@ fun showErrorHint(project: Project, editor: Editor, message: String, title: Stri
|
||||
CodeInsightUtils.showErrorHint(project, editor, message, title, null)
|
||||
}
|
||||
|
||||
fun showErrorHintByKey(project: Project, editor: Editor, messageKey: String, title: String) {
|
||||
showErrorHint(project, editor, JetRefactoringBundle.message(messageKey), title)
|
||||
}
|
||||
|
||||
fun selectElements(
|
||||
operationName: String,
|
||||
editor: Editor,
|
||||
@@ -36,7 +40,7 @@ fun selectElements(
|
||||
continuation: (elements: List<PsiElement>, targetSibling: PsiElement) -> Unit
|
||||
) {
|
||||
fun showErrorHintByKey(key: String) {
|
||||
showErrorHint(file.getProject(), editor, JetRefactoringBundle.message(key), operationName)
|
||||
showErrorHintByKey(file.getProject(), editor, key, operationName)
|
||||
}
|
||||
|
||||
fun onSelectionComplete(parent: PsiElement, elements: List<PsiElement>, targetContainer: PsiElement) {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_DESCRIPTOR: val m: kotlin.Int defined in foo
|
||||
// EXTRACT_AS_PROPERTY
|
||||
|
||||
val n: Int = 1
|
||||
|
||||
// SIBLING:
|
||||
fun foo(): Int {
|
||||
val m = 1
|
||||
return <selection>n + m + 1</selection>
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
// OPTIONS: true, true, false
|
||||
// OPTIONS: true, true, false, false
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
// OPTIONS: true, true, false
|
||||
// OPTIONS: true, true, false, false
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
// PARAM_TYPES: kotlin.Int
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// EXTRACTION_TARGET: property with initializer
|
||||
class A {
|
||||
val i = 1
|
||||
|
||||
fun foo(): Int {
|
||||
return <selection>1 + 2</selection>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// EXTRACTION_TARGET: property with initializer
|
||||
class A {
|
||||
val i = 1
|
||||
|
||||
fun foo(): Int {
|
||||
return i1
|
||||
}
|
||||
|
||||
private val i1 = 1 + 2
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// EXTRACTION_TARGET: property with initializer
|
||||
val i = 1
|
||||
|
||||
fun foo(): Int {
|
||||
return <selection>1 + 2</selection>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// EXTRACTION_TARGET: property with initializer
|
||||
val i = 1
|
||||
|
||||
fun foo(): Int {
|
||||
return i1
|
||||
}
|
||||
|
||||
private val i1 = 1 + 2
|
||||
|
||||
+1
-3
@@ -1,6 +1,4 @@
|
||||
// EXTRACT_AS_PROPERTY
|
||||
|
||||
// EXTRACTION_TARGET: property with getter
|
||||
fun foo(n: Int): Int {
|
||||
// SIBLING:
|
||||
return {<selection>n + 1</selection>}()
|
||||
}
|
||||
+1
-2
@@ -1,5 +1,4 @@
|
||||
// EXTRACT_AS_PROPERTY
|
||||
// SIBLING:
|
||||
// EXTRACTION_TARGET: property with getter
|
||||
fun foo() {
|
||||
<selection>1 + 1</selection>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// EXTRACTION_TARGET: property with getter
|
||||
class A {
|
||||
fun foo(): Int {
|
||||
<selection>val a = 1 + 2
|
||||
val b = a*2</selection>
|
||||
val c = b - 1
|
||||
}
|
||||
}
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// EXTRACTION_TARGET: property with getter
|
||||
class A {
|
||||
fun foo(): Int {
|
||||
val b = i
|
||||
val c = b - 1
|
||||
}
|
||||
|
||||
private val i: Int
|
||||
get() {
|
||||
val a = 1 + 2
|
||||
val b = a * 2
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -1,8 +1,7 @@
|
||||
// EXTRACT_AS_PROPERTY
|
||||
|
||||
// EXTRACTION_TARGET: property with getter
|
||||
class A(val n: Int = 1) {
|
||||
val m: Int = 2
|
||||
// SIBLING:
|
||||
|
||||
fun foo(): Int {
|
||||
return <selection>m + n + 1</selection>
|
||||
}
|
||||
+2
-3
@@ -1,8 +1,7 @@
|
||||
// EXTRACT_AS_PROPERTY
|
||||
|
||||
// EXTRACTION_TARGET: property with getter
|
||||
class A(val n: Int = 1) {
|
||||
val m: Int = 2
|
||||
// SIBLING:
|
||||
|
||||
fun foo(): Int {
|
||||
return i
|
||||
}
|
||||
+1
-3
@@ -1,8 +1,6 @@
|
||||
// EXTRACT_AS_PROPERTY
|
||||
|
||||
// EXTRACTION_TARGET: property with getter
|
||||
val n: Int = 1
|
||||
|
||||
// SIBLING:
|
||||
fun foo(): Int {
|
||||
return <selection>n + 1</selection>
|
||||
}
|
||||
+1
-3
@@ -1,8 +1,6 @@
|
||||
// EXTRACT_AS_PROPERTY
|
||||
|
||||
// EXTRACTION_TARGET: property with getter
|
||||
val n: Int = 1
|
||||
|
||||
// SIBLING:
|
||||
fun foo(): Int {
|
||||
return i
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// EXTRACTION_TARGET: property with getter
|
||||
val n: Int = 1
|
||||
|
||||
fun foo(): Int {
|
||||
val m = 1
|
||||
return <selection>n + m + 1</selection>
|
||||
}
|
||||
+33
-9
@@ -39,6 +39,8 @@ import com.intellij.psi.PsiWhiteSpace
|
||||
import org.jetbrains.kotlin.psi.JetPackageDirective
|
||||
import org.jetbrains.kotlin.utils.emptyOrSingletonList
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.KotlinIntroducePropertyHandler
|
||||
import java.util.*
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTestCase() {
|
||||
@@ -57,6 +59,28 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
|
||||
}
|
||||
}
|
||||
|
||||
protected fun doIntroducePropertyTest(path: String) {
|
||||
doTest(path) { file ->
|
||||
val helper = object : ExtractionEngineHelper() {
|
||||
override fun configure(
|
||||
descriptor: ExtractableCodeDescriptor,
|
||||
generatorOptions: ExtractionGeneratorOptions
|
||||
): ExtractionGeneratorConfiguration {
|
||||
return ExtractionGeneratorConfiguration(
|
||||
descriptor,
|
||||
generatorOptions.copy(extractAsProperty = true)
|
||||
)
|
||||
}
|
||||
}
|
||||
KotlinIntroducePropertyHandler(helper).invoke(
|
||||
fixture.getProject(),
|
||||
fixture.getEditor(),
|
||||
file,
|
||||
DataManager.getInstance().getDataContext(fixture.getEditor().getComponent())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun doExtractFunctionTest(path: String) {
|
||||
doTest(path) { file ->
|
||||
var explicitPreviousSibling: PsiElement? = null
|
||||
@@ -86,15 +110,12 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
|
||||
InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PARAM_DESCRIPTOR: ").joinToString()
|
||||
val expectedTypes =
|
||||
InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// PARAM_TYPES: ").map { "[$it]" }.joinToString()
|
||||
val extractAsProperty = InTextDirectivesUtils.isDirectiveDefined(fileText, "// EXTRACT_AS_PROPERTY")
|
||||
|
||||
val extractionOptions = InTextDirectivesUtils.findListWithPrefixes(fileText, "// OPTIONS: ").let {
|
||||
if (it.isNotEmpty()) {
|
||||
[suppress("CAST_NEVER_SUCCEEDS")]
|
||||
val args = it.map { it.toBoolean() }.copyToArray() as Array<Any?>
|
||||
val constructor = javaClass<ExtractionOptions>().getConstructors()[0]
|
||||
assertTrue(constructor.getParameterTypes().size() == args.size(), "Wrong number of parameters was passed for ExtractOptions constructor: expected = ${constructor.getParameterTypes().size()}, actual = ${args.size()}. \nTest directive: // OPTIONS: $it")
|
||||
constructor.newInstance(*args) as ExtractionOptions
|
||||
javaClass<ExtractionOptions>().getConstructors().first { it.getParameterTypes().size() == args.size() }.newInstance(*args) as ExtractionOptions
|
||||
} else ExtractionOptions.DEFAULT
|
||||
}
|
||||
|
||||
@@ -114,16 +135,19 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
|
||||
val allParameters = emptyOrSingletonList(descriptor.receiverParameter) + descriptor.parameters
|
||||
val actualDescriptors = allParameters.map { renderer.render(it.originalDescriptor) }.joinToString()
|
||||
val actualTypes = allParameters.map {
|
||||
it.parameterTypeCandidates.map { renderer.renderType(it) }.joinToString(", ", "[", "]")
|
||||
it.getParameterTypeCandidates(false).map { renderer.renderType(it) }.joinToString(", ", "[", "]")
|
||||
}.joinToString()
|
||||
|
||||
assertEquals(expectedDescriptors, actualDescriptors, "Expected descriptors mismatch.")
|
||||
assertEquals(expectedTypes, actualTypes, "Expected types mismatch.")
|
||||
|
||||
return ExtractionGeneratorConfiguration(
|
||||
if (descriptor.name == "") descriptor.copy(name = "__dummyTestFun__") else descriptor,
|
||||
generatorOptions.copy(extractAsProperty = extractAsProperty)
|
||||
)
|
||||
val newDescriptor = if (descriptor.name == "") {
|
||||
descriptor.copy(suggestedNames = Collections.singletonList("__dummyTestFun__"))
|
||||
}
|
||||
else {
|
||||
descriptor
|
||||
}
|
||||
return ExtractionGeneratorConfiguration(newDescriptor, generatorOptions)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
+59
-40
@@ -31,6 +31,7 @@ import java.util.regex.Pattern;
|
||||
@InnerTestClasses({
|
||||
JetExtractionTestGenerated.IntroduceVariable.class,
|
||||
JetExtractionTestGenerated.ExtractFunction.class,
|
||||
JetExtractionTestGenerated.IntroduceProperty.class,
|
||||
})
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
|
||||
@@ -316,7 +317,6 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
|
||||
@TestMetadata("idea/testData/refactoring/extractFunction")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@InnerTestClasses({
|
||||
ExtractFunction.AsProperty.class,
|
||||
ExtractFunction.Basic.class,
|
||||
ExtractFunction.ControlFlow.class,
|
||||
ExtractFunction.DefaultContainer.class,
|
||||
@@ -332,45 +332,6 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/extractFunction"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/refactoring/extractFunction/asProperty")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class AsProperty extends AbstractJetExtractionTest {
|
||||
public void testAllFilesPresentInAsProperty() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/extractFunction/asProperty"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("extractToClass.kt")
|
||||
public void testExtractToClass() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/asProperty/extractToClass.kt");
|
||||
doExtractFunctionTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractToFile.kt")
|
||||
public void testExtractToFile() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/asProperty/extractToFile.kt");
|
||||
doExtractFunctionTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractToFunction.kt")
|
||||
public void testExtractToFunction() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/asProperty/extractToFunction.kt");
|
||||
doExtractFunctionTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractUnit.kt")
|
||||
public void testExtractUnit() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/asProperty/extractUnit.kt");
|
||||
doExtractFunctionTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractWithParams.kt")
|
||||
public void testExtractWithParams() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/asProperty/extractWithParams.kt");
|
||||
doExtractFunctionTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/refactoring/extractFunction/basic")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
@@ -1953,4 +1914,62 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/refactoring/introduceProperty")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class IntroduceProperty extends AbstractJetExtractionTest {
|
||||
public void testAllFilesPresentInIntroduceProperty() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/introduceProperty"),
|
||||
Pattern.compile("^(.+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("extractToClassWithNameClash.kt")
|
||||
public void testExtractToClassWithNameClash() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractToClassWithNameClash.kt");
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractToFileWithNameClash.kt")
|
||||
public void testExtractToFileWithNameClash() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractToFileWithNameClash.kt");
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractToFunction.kt")
|
||||
public void testExtractToFunction() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractToFunction.kt");
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractUnit.kt")
|
||||
public void testExtractUnit() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractUnit.kt");
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractWithGetterMultipleExpressions.kt")
|
||||
public void testExtractWithGetterMultipleExpressions() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractWithGetterMultipleExpressions.kt");
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractWithGetterToClass.kt")
|
||||
public void testExtractWithGetterToClass() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractWithGetterToClass.kt");
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractWithGetterToFile.kt")
|
||||
public void testExtractWithGetterToFile() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractWithGetterToFile.kt");
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractWithParams.kt")
|
||||
public void testExtractWithParams() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractWithParams.kt");
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user