Extract Function: Allow to choose between function and property extraction

This commit is contained in:
Alexey Sedunov
2014-08-11 12:45:52 +04:00
parent 87de6cff23
commit 96dae07276
19 changed files with 314 additions and 100 deletions
@@ -25,6 +25,7 @@ import com.intellij.util.LocalTimeCounter
import org.jetbrains.jet.lang.resolve.ImportPath
import org.jetbrains.jet.lexer.JetKeywordToken
import org.jetbrains.jet.plugin.JetFileType
import org.jetbrains.jet.lang.psi.JetPsiFactory.CallableBuilder.Target
public fun JetPsiFactory(project: Project?): JetPsiFactory = JetPsiFactory(project!!)
public fun JetPsiFactory(contextElement: JetElement): JetPsiFactory = JetPsiFactory(contextElement.getProject())
@@ -424,7 +425,12 @@ public class JetPsiFactory(private val project: Project) {
return WhenBuilder(subject?.getText())
}
public class FunctionBuilder() {
public class CallableBuilder(private val target: Target) {
public enum class Target {
FUNCTION
READ_ONLY_PROPERTY
}
enum class State {
MODIFIERS
NAME
@@ -440,25 +446,35 @@ public class JetPsiFactory(private val project: Project) {
private var state = State.MODIFIERS
private fun closeParams() {
assert(state == State.FIRST_PARAM || state == State.REST_PARAMS)
sb.append(")")
if (target == Target.FUNCTION) {
assert(state == State.FIRST_PARAM || state == State.REST_PARAMS)
sb.append(")")
}
state = State.TYPE_CONSTRAINTS
}
private fun placeFun() {
private fun placeKeyword() {
assert(state == State.MODIFIERS)
if (sb.length() != 0) {
sb.append(" ")
}
sb.append("fun ")
val keyword = when (target) {
Target.FUNCTION -> "fun"
Target.READ_ONLY_PROPERTY -> "val"
}
sb.append("$keyword ")
state = State.RECEIVER
}
public fun modifier(modifier: String): FunctionBuilder {
private fun blockPrefix() = when (target) {
Target.FUNCTION -> ""
Target.READ_ONLY_PROPERTY -> "\nget()"
}
public fun modifier(modifier: String): CallableBuilder {
assert(state == State.MODIFIERS)
sb.append(modifier)
@@ -466,8 +482,8 @@ public class JetPsiFactory(private val project: Project) {
return this
}
public fun typeParams(values: Collection<String>): FunctionBuilder {
placeFun()
public fun typeParams(values: Collection<String>): CallableBuilder {
placeKeyword()
if (!values.isEmpty()) {
sb.append(values.joinToString(", ", "<", "> ", -1, ""))
}
@@ -475,7 +491,7 @@ public class JetPsiFactory(private val project: Project) {
return this
}
public fun receiver(receiverType: String): FunctionBuilder {
public fun receiver(receiverType: String): CallableBuilder {
assert(state == State.RECEIVER)
sb.append(receiverType).append(".")
@@ -484,16 +500,24 @@ public class JetPsiFactory(private val project: Project) {
return this
}
public fun name(name: String): FunctionBuilder {
public fun name(name: String): CallableBuilder {
assert(state == State.NAME || state == State.RECEIVER)
sb.append(name).append("(")
state = State.FIRST_PARAM
sb.append(name)
when (target) {
Target.FUNCTION -> {
sb.append("(")
state = State.FIRST_PARAM
}
else ->
state = State.TYPE_CONSTRAINTS
}
return this
}
public fun param(name: String, `type`: String): FunctionBuilder {
public fun param(name: String, `type`: String): CallableBuilder {
assert(target == Target.FUNCTION)
assert(state == State.FIRST_PARAM || state == State.REST_PARAMS)
if (state == State.REST_PARAMS) {
@@ -507,20 +531,20 @@ public class JetPsiFactory(private val project: Project) {
return this
}
public fun returnType(`type`: String): FunctionBuilder {
public fun returnType(`type`: String): CallableBuilder {
closeParams()
sb.append(": ").append(`type`)
return this
}
public fun noReturnType(): FunctionBuilder {
public fun noReturnType(): CallableBuilder {
closeParams()
return this
}
public fun typeConstraints(values: Collection<String>): FunctionBuilder {
public fun typeConstraints(values: Collection<String>): CallableBuilder {
assert(state == State.TYPE_CONSTRAINTS)
if (!values.isEmpty()) {
@@ -531,25 +555,25 @@ public class JetPsiFactory(private val project: Project) {
return this
}
public fun simpleBody(body: String): FunctionBuilder {
public fun simpleBody(body: String): CallableBuilder {
assert(state == State.BODY || state == State.TYPE_CONSTRAINTS)
sb.append(" = ").append(body)
sb.append(blockPrefix()).append(" = ").append(body)
state = State.DONE
return this
}
public fun blockBody(body: String): FunctionBuilder {
public fun blockBody(body: String): CallableBuilder {
assert(state == State.BODY || state == State.TYPE_CONSTRAINTS)
sb.append(" {\n").append(body).append("\n}")
sb.append(blockPrefix()).append(" {\n").append(body).append("\n}")
state = State.DONE
return this
}
public fun toFunctionText(): String {
public fun asString(): String {
if (state != State.DONE) {
state = State.DONE
}
@@ -30,7 +30,9 @@ import org.jetbrains.jet.plugin.refactoring.extractFunction.performAnalysis
import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult.Status
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
import org.jetbrains.jet.plugin.refactoring.extractFunction.validate
import org.jetbrains.jet.plugin.refactoring.extractFunction.generateFunction
import org.jetbrains.jet.lang.psi.JetImportList
import org.jetbrains.jet.lang.psi.JetPsiFactory
import org.jetbrains.jet.lang.psi.JetExpression
import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractionOptions
import org.jetbrains.jet.plugin.refactoring.runReadAction
import com.intellij.psi.PsiManager
@@ -39,6 +41,7 @@ import org.jetbrains.jet.lang.psi.*
import org.jetbrains.jet.plugin.intentions.InsertExplicitTypeArguments
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractionGeneratorOptions
import org.jetbrains.jet.plugin.refactoring.extractFunction.generateDeclaration
fun getFunctionForExtractedFragment(
codeFragment: JetCodeFragment,
@@ -101,8 +104,8 @@ fun getFunctionForExtractedFragment(
}
return validationResult.descriptor
.generateFunction(ExtractionGeneratorOptions(inTempFile = true))
.function
.generateDeclaration(ExtractionGeneratorOptions(inTempFile = true))
.declaration as JetNamedFunction
}
return runReadAction { generateFunction() }
@@ -25,6 +25,7 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.plugin.refactoring.changeSignature.JetChangeSignatureHandler;
import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractKotlinFunctionHandler;
import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractKotlinFunctionHandlerHelper;
import org.jetbrains.jet.plugin.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler;
import org.jetbrains.jet.plugin.refactoring.safeDelete.SafeDeletePackage;
@@ -46,7 +47,7 @@ public class JetRefactoringSupportProvider extends RefactoringSupportProvider {
@NotNull
public RefactoringActionHandler getExtractFunctionToScopeHandler() {
return new ExtractKotlinFunctionHandler(true);
return new ExtractKotlinFunctionHandler(true, ExtractKotlinFunctionHandlerHelper.DEFAULT);
}
@Override
@@ -67,13 +67,23 @@ import org.jetbrains.jet.renderer.DescriptorRenderer
import org.jetbrains.jet.lang.psi.JetPropertyAccessor
import org.jetbrains.jet.lang.psi.JetClassOrObject
public class ExtractKotlinFunctionHandler(public val allContainersEnabled: Boolean = false) : RefactoringActionHandler {
public open class ExtractKotlinFunctionHandlerHelper {
open fun adjustGeneratorOptions(options: ExtractionGeneratorOptions): ExtractionGeneratorOptions = options
open fun adjustDescriptor(descriptor: ExtractableCodeDescriptor): ExtractableCodeDescriptor = descriptor
class object {
public val DEFAULT: ExtractKotlinFunctionHandlerHelper = ExtractKotlinFunctionHandlerHelper()
}
}
public class ExtractKotlinFunctionHandler(
public val allContainersEnabled: Boolean = false,
private val helper: ExtractKotlinFunctionHandlerHelper = ExtractKotlinFunctionHandlerHelper.DEFAULT) : RefactoringActionHandler {
fun doInvoke(
editor: Editor,
file: JetFile,
elements: List<PsiElement>,
targetSibling: PsiElement,
preprocessor: ((ExtractableCodeDescriptor) -> Unit)? = null
targetSibling: PsiElement
) {
val project = file.getProject()
@@ -84,8 +94,9 @@ public class ExtractKotlinFunctionHandler(public val allContainersEnabled: Boole
}
fun doRefactor(descriptor: ExtractableCodeDescriptor, generatorOptions: ExtractionGeneratorOptions) {
preprocessor?.invoke(descriptor)
project.executeWriteCommand(EXTRACT_FUNCTION) { descriptor.generateFunction(generatorOptions) }
val adjustedDescriptor = helper.adjustDescriptor(descriptor)
val adjustedGeneratorOptions = helper.adjustGeneratorOptions(generatorOptions)
project.executeWriteCommand(EXTRACT_FUNCTION) { adjustedDescriptor.generateDeclaration(adjustedGeneratorOptions) }
}
fun validateAndRefactor() {
@@ -38,7 +38,9 @@ import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor
import org.jetbrains.jet.lang.psi.JetProperty
import org.jetbrains.jet.lang.psi.JetDeclaration
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.jet.lang.psi.JetNamedFunction
import org.jetbrains.jet.lang.psi.JetClassBody
import org.jetbrains.jet.lang.psi.JetFile
import org.jetbrains.jet.lang.psi.JetNamedDeclaration
trait Parameter {
val argumentText: String
@@ -164,8 +166,9 @@ data class ExtractableCodeDescriptor(
val controlFlow: ControlFlow
)
class ExtractionGeneratorOptions(
val inTempFile: Boolean = false
data class ExtractionGeneratorOptions(
val inTempFile: Boolean = false,
val extractAsProperty: Boolean = false
) {
class object {
val DEFAULT = ExtractionGeneratorOptions()
@@ -173,7 +176,7 @@ class ExtractionGeneratorOptions(
}
data class ExtractionResult(
val function: JetNamedFunction,
val declaration: JetNamedDeclaration,
val nameByOffset: Map<Int, JetElement>
)
@@ -232,3 +235,10 @@ class ExtractableCodeDescriptorWithConflicts(
val descriptor: ExtractableCodeDescriptor,
val conflicts: MultiMap<PsiElement, String>
)
fun ExtractableCodeDescriptor.canGenerateProperty(): Boolean {
if (!parameters.empty) return false
val parent = extractionData.targetSibling.getParent()
return parent is JetFile || parent is JetClassBody
}
@@ -274,7 +274,7 @@ private fun ExtractionData.analyzeControlFlow(
return Pair(defaultControlFlow, null)
}
fun ExtractionData.createTemporaryFunction(functionText: String): JetNamedFunction {
fun ExtractionData.createTemporaryDeclaration(functionText: String): JetNamedDeclaration {
val textRange = targetSibling.getTextRange()!!
val insertText: String
@@ -294,11 +294,11 @@ fun ExtractionData.createTemporaryFunction(functionText: String): JetNamedFuncti
val tmpFile = originalFile.createTempCopy { text ->
StringBuilder(text).insert(insertPosition, insertText).toString()
}
return tmpFile.findElementAt(lookupPosition)?.getParentByType(javaClass<JetNamedFunction>())!!
return tmpFile.findElementAt(lookupPosition)?.getParentByType(javaClass<JetNamedDeclaration>())!!
}
private fun ExtractionData.createTemporaryCodeBlock(): JetBlockExpression =
createTemporaryFunction("fun() {\n${getCodeFragmentText()}\n}\n").getBodyExpression() as JetBlockExpression
(createTemporaryDeclaration("fun() {\n${getCodeFragmentText()}\n}\n") as JetNamedFunction).getBodyExpression() as JetBlockExpression
private fun JetType.collectReferencedTypes(processTypeArguments: Boolean): List<JetType> {
if (!processTypeArguments) return Collections.singletonList(this)
@@ -683,12 +683,19 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
)
}
private fun JetNamedDeclaration.getGeneratedBlockBody() =
when (this) {
is JetNamedFunction -> getBodyExpression()
else -> (this as JetProperty).getGetter()!!.getBodyExpression()
} as JetBlockExpression
fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts {
val conflicts = MultiMap<PsiElement, String>()
val result = generateFunction(ExtractionGeneratorOptions(inTempFile = true))
val result = generateDeclaration(ExtractionGeneratorOptions(inTempFile = true))
val bindingContext = AnalyzerFacadeWithCache.getContextForElement(result.function.getBodyExpression()!!)
val valueParameterList = (result.declaration as? JetNamedFunction)?.getValueParameterList()
val bindingContext = AnalyzerFacadeWithCache.getContextForElement(result.declaration.getGeneratedBlockBody())
for ((originalOffset, resolveResult) in extractionData.refOffsetToDeclaration) {
if (resolveResult.declaration.isInsideOf(extractionData.originalElements)) continue
@@ -703,7 +710,7 @@ fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts
val currentDescriptor = bindingContext[BindingContext.REFERENCE_TARGET, currentRefExpr]
val currentTarget =
currentDescriptor?.let { DescriptorToDeclarationUtil.getDeclaration(extractionData.project, it) } as? PsiNamedElement
if (currentTarget is JetParameter && currentTarget.getParent() == result.function.getValueParameterList()) continue
if (currentTarget is JetParameter && currentTarget.getParent() == valueParameterList) continue
if (currentDescriptor is LocalVariableDescriptor
&& parameters.any { it.mirrorVarName == currentDescriptor.getName().asString() }) continue
@@ -17,39 +17,44 @@
package org.jetbrains.jet.plugin.refactoring.extractFunction
import org.jetbrains.jet.renderer.DescriptorRenderer
import org.jetbrains.jet.lang.psi.JetPsiFactory.FunctionBuilder
import org.jetbrains.jet.lang.psi.JetElement
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
import java.util.HashMap
import org.jetbrains.jet.lang.psi.JetTreeVisitorVoid
import org.jetbrains.jet.lang.psi.psiUtil.getParentByType
import org.jetbrains.jet.lang.psi.JetNamedFunction
import org.jetbrains.jet.lang.psi.JetPsiFactory
import org.jetbrains.jet.lang.psi.JetBlockExpression
import java.util.LinkedHashMap
import org.jetbrains.jet.lang.psi.JetExpression
import java.util.Collections
import org.jetbrains.jet.lang.psi.psiUtil.prependElement
import org.jetbrains.jet.lang.psi.psiUtil.appendElement
import org.jetbrains.jet.lang.psi.psiUtil.replaced
import com.intellij.psi.PsiElement
import org.jetbrains.jet.lang.psi.JetCallExpression
import com.intellij.psi.PsiWhiteSpace
import org.jetbrains.jet.lang.psi.JetDeclaration
import org.jetbrains.jet.lang.psi.JetProperty
import org.jetbrains.jet.plugin.intentions.declarations.DeclarationUtils
import org.jetbrains.jet.lang.psi.JetPsiFactory.CallableBuilder
import org.jetbrains.jet.lang.psi.JetPsiFactory.CallableBuilder.Target
import org.jetbrains.jet.lang.psi.JetNamedDeclaration
import org.jetbrains.jet.lang.psi.JetPsiFactory
import java.util.LinkedHashMap
import java.util.Collections
import org.jetbrains.jet.plugin.codeInsight.ShortenReferences
import org.jetbrains.jet.lang.psi.psiUtil.isFunctionLiteralOutsideParentheses
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.jet.lang.psi.JetFunctionLiteralArgument
import org.jetbrains.jet.lang.resolve.BindingContext
import org.jetbrains.jet.plugin.util.psiModificationUtil.moveInsideParenthesesAndReplaceWith
import org.jetbrains.jet.lang.psi.psiUtil.prependElement
import org.jetbrains.jet.lang.psi.psiUtil.appendElement
import org.jetbrains.jet.lang.psi.psiUtil.replaced
import org.jetbrains.jet.plugin.intentions.declarations.DeclarationUtils
fun ExtractableCodeDescriptor.getFunctionText(
fun ExtractableCodeDescriptor.getDeclarationText(
options: ExtractionGeneratorOptions = ExtractionGeneratorOptions.DEFAULT,
withBody: Boolean = true,
descriptorRenderer: DescriptorRenderer = DescriptorRenderer.FQ_NAMES_IN_TYPES
): String {
return FunctionBuilder().let { builder ->
if (!canGenerateProperty() && options.extractAsProperty) {
throw IllegalArgumentException("Can't generate property: ${extractionData.getCodeFragmentText()}")
}
val builderTarget = if (options.extractAsProperty) Target.READ_ONLY_PROPERTY else Target.FUNCTION
return CallableBuilder(builderTarget).let { builder ->
builder.modifier(visibility)
builder.typeParams(typeParameters.map { it.originalDeclaration.getText()!! })
@@ -72,7 +77,7 @@ fun ExtractableCodeDescriptor.getFunctionText(
builder.blockBody(extractionData.getCodeFragmentText())
}
builder.toFunctionText()
builder.asString()
}
}
@@ -95,23 +100,23 @@ fun createNameCounterpartMap(from: JetElement, to: JetElement): Map<JetSimpleNam
return map
}
fun ExtractableCodeDescriptor.generateFunction(options: ExtractionGeneratorOptions): ExtractionResult {
fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOptions): ExtractionResult{
val psiFactory = JetPsiFactory(extractionData.originalFile)
val nameByOffset = HashMap<Int, JetElement>()
fun createFunction(): JetNamedFunction {
fun createDeclaration(): JetNamedDeclaration {
return with(extractionData) {
if (options.inTempFile) {
createTemporaryFunction("${getFunctionText()}\n")
createTemporaryDeclaration("${getDeclarationText()}\n")
}
else {
psiFactory.createFunction(getFunctionText())
psiFactory.createDeclaration(getDeclarationText(options))
}
}
}
fun adjustFunctionBody(function: JetNamedFunction) {
val body = function.getBodyExpression() as JetBlockExpression
fun adjustDeclarationBody(declaration: JetNamedDeclaration) {
val body = declaration.getGeneratedBlockBody()
val exprReplacementMap = HashMap<JetElement, (JetElement) -> JetElement>()
val originalOffsetByExpr = LinkedHashMap<JetElement, Int>()
@@ -183,28 +188,32 @@ fun ExtractableCodeDescriptor.generateFunction(options: ExtractionGeneratorOptio
is ExpressionEvaluation ->
body.getStatements().last?.let {
val newExpr = it.replaced(psiFactory.createReturn(it.getText() ?: throw AssertionError("Return expression shouldn't be empty: code fragment = ${body.getText()}"))).getReturnedExpression()!!
val newExpr = it.replaced(
psiFactory.createReturn(
it.getText() ?: throw AssertionError("Return expression shouldn't be empty: code fragment = ${body.getText()}")
)
).getReturnedExpression()!!
val counterpartMap = createNameCounterpartMap(it, newExpr)
nameByOffset.entrySet().forEach { e -> counterpartMap[e.getValue()]?.let { e.setValue(it) } }
}
}
}
fun insertFunction(function: JetNamedFunction): JetNamedFunction {
fun insertDeclaration(declaration: JetNamedDeclaration): JetNamedDeclaration {
return with(extractionData) {
val targetContainer = targetSibling.getParent()!!
val emptyLines = psiFactory.createWhiteSpace("\n\n")
if (insertBefore) {
val functionInFile = targetContainer.addBefore(function, targetSibling) as JetNamedFunction
val declarationInFile = targetContainer.addBefore(declaration, targetSibling) as JetNamedDeclaration
targetContainer.addBefore(emptyLines, targetSibling)
functionInFile
declarationInFile
}
else {
val functionInFile = targetContainer.addAfter(function, targetSibling) as JetNamedFunction
val declarationInFile = targetContainer.addAfter(declaration, targetSibling) as JetNamedDeclaration
targetContainer.addAfter(emptyLines, targetSibling)
functionInFile
declarationInFile
}
}
}
@@ -225,9 +234,9 @@ fun ExtractableCodeDescriptor.generateFunction(options: ExtractionGeneratorOptio
anchor.replace(wrappedCall)
}
fun makeCall(function: JetNamedFunction): JetNamedFunction {
fun makeCall(declaration: JetNamedDeclaration): JetNamedDeclaration {
val anchor = extractionData.originalElements.first
if (anchor == null) return function
if (anchor == null) return declaration
val anchorParent = anchor.getParent()!!
@@ -238,9 +247,13 @@ fun ExtractableCodeDescriptor.generateFunction(options: ExtractionGeneratorOptio
}
}
val callText = parameters
.map { it.argumentText }
.joinToString(separator = ", ", prefix = "${name}(", postfix = ")")
val callText = when (declaration) {
is JetNamedFunction ->
parameters
.map { it.argumentText }
.joinToString(separator = ", ", prefix = "${name}(", postfix = ")")
else -> name
}
val copiedDeclarations = HashMap<JetDeclaration, JetDeclaration>()
for (decl in controlFlow.declarationsToCopy) {
@@ -280,17 +293,15 @@ fun ExtractableCodeDescriptor.generateFunction(options: ExtractionGeneratorOptio
}
insertCall(anchor, wrappedCall)
return function
return declaration
}
val function = createFunction()
adjustFunctionBody(function)
if (options.inTempFile) return ExtractionResult(function, nameByOffset)
val functionInPlace = makeCall(insertFunction(function))
ShortenReferences.process(functionInPlace)
return ExtractionResult(functionInPlace, nameByOffset)
}
val declaration = createDeclaration()
adjustDeclarationBody(declaration)
if (options.inTempFile) return ExtractionResult(declaration, nameByOffset)
val declarationInPlace = makeCall(insertDeclaration(declaration))
ShortenReferences.process(declarationInPlace)
return ExtractionResult(declaration, nameByOffset)
}
@@ -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.jet.plugin.refactoring.extractFunction.ui.KotlinExtractFunctionDialog">
<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">
<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">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="522" height="318"/>
<xy x="20" y="20" width="522" height="351"/>
</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="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"/>
<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"/>
</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="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">
<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">
<preferred-size width="-1" height="200"/>
</grid>
</constraints>
@@ -127,9 +127,32 @@
</grid>
<vspacer id="cd10f">
<constraints>
<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"/>
<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"/>
</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>
@@ -36,6 +36,8 @@ import org.jetbrains.jet.plugin.refactoring.extractFunction.*;
import org.jetbrains.jet.renderer.DescriptorRenderer;
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.List;
@@ -48,6 +50,7 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
private KotlinFunctionSignatureComponent signaturePreviewField;
private EditorTextField functionNameField;
private JLabel functionNameLabel;
private JCheckBox propertyCheckBox;
private KotlinParameterTablePanel parameterTablePanel;
private final Project project;
@@ -106,7 +109,8 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
setOKActionEnabled(checkNames());
signaturePreviewField.setText(
ExtractFunctionPackage.getFunctionText(currentDescriptor, false, DescriptorRenderer.SOURCE_CODE_SHORT_NAMES_IN_TYPES)
ExtractFunctionPackage.getDeclarationText(currentDescriptor, getGeneratorOptions(), false,
DescriptorRenderer.SOURCE_CODE_SHORT_NAMES_IN_TYPES)
);
}
@@ -140,6 +144,18 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
}
);
propertyCheckBox.setEnabled(ExtractFunctionPackage.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() {
@@ -256,6 +272,6 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
@NotNull
public ExtractionGeneratorOptions getGeneratorOptions() {
return ExtractionGeneratorOptions.DEFAULT;
return new ExtractionGeneratorOptions(false, propertyCheckBox.isSelected());
}
}
@@ -0,0 +1,10 @@
// EXTRACT_AS_PROPERTY
class A(val n: Int = 1) {
val m: Int = 2
// SIBLING:
fun foo(): Int {
return <selection>m + n + 1</selection>
}
}
@@ -0,0 +1,15 @@
// EXTRACT_AS_PROPERTY
class A(val n: Int = 1) {
val m: Int = 2
// SIBLING:
fun foo(): Int {
return i
}
private val i: Int
get() {
return m + n + 1
}
}
@@ -0,0 +1,8 @@
// EXTRACT_AS_PROPERTY
val n: Int = 1
// SIBLING:
fun foo(): Int {
return <selection>n + 1</selection>
}
@@ -0,0 +1,13 @@
// EXTRACT_AS_PROPERTY
val n: Int = 1
// SIBLING:
fun foo(): Int {
return i
}
private val i: Int
get() {
return n + 1
}
@@ -0,0 +1,6 @@
// EXTRACT_AS_PROPERTY
fun foo(n: Int): Int {
// SIBLING:
return {<selection>n + 1</selection>}()
}
@@ -0,0 +1 @@
Can't generate property: n + 1
@@ -0,0 +1,11 @@
// 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>
}
@@ -0,0 +1 @@
Can't generate property: n + m + 1
@@ -38,6 +38,9 @@ import com.intellij.util.containers.ContainerUtil
import kotlin.test.assertEquals
import org.jetbrains.jet.plugin.JetLightCodeInsightFixtureTestCase
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractKotlinFunctionHandlerHelper
import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractionGeneratorOptions
import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractableCodeDescriptor
public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTestCase() {
override fun getProjectDescriptor() = LightCodeInsightFixtureTestCase.JAVA_LATEST
@@ -78,21 +81,32 @@ 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 renderer = DescriptorRenderer.DEBUG_TEXT
val editor = fixture.getEditor()
selectElements(editor, file) { (elements, previousSibling) ->
ExtractKotlinFunctionHandler().doInvoke(editor, file, elements, explicitPreviousSibling ?: previousSibling) {
val allParameters = ContainerUtil.createMaybeSingletonList(it.receiverParameter) + it.parameters
val actualDescriptors = allParameters.map { renderer.render(it.originalDescriptor) }.joinToString()
val actualTypes = allParameters.map {
it.parameterTypeCandidates.map { renderer.renderType(it) }.joinToString(", ", "[", "]")
}.joinToString()
selectElements(editor, file) {(elements, previousSibling) ->
ExtractKotlinFunctionHandler(
helper = object : ExtractKotlinFunctionHandlerHelper() {
override fun adjustGeneratorOptions(options: ExtractionGeneratorOptions): ExtractionGeneratorOptions {
return options.copy(extractAsProperty = extractAsProperty)
}
assertEquals(expectedDescriptors, actualDescriptors, "Expected descriptors mismatch.")
assertEquals(expectedTypes, actualTypes, "Expected types mismatch.")
}
override fun adjustDescriptor(descriptor: ExtractableCodeDescriptor): ExtractableCodeDescriptor {
val allParameters = ContainerUtil.createMaybeSingletonList(descriptor.receiverParameter) + descriptor.parameters
val actualDescriptors = allParameters.map { renderer.render(it.originalDescriptor) }.joinToString()
val actualTypes = allParameters.map {
it.parameterTypeCandidates.map { renderer.renderType(it) }.joinToString(", ", "[", "]")
}.joinToString()
assertEquals(expectedDescriptors, actualDescriptors, "Expected descriptors mismatch.")
assertEquals(expectedTypes, actualTypes, "Expected types mismatch.")
return descriptor
}
}
).doInvoke(editor, file, elements, explicitPreviousSibling ?: previousSibling)
}
}
}
@@ -211,12 +211,40 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
}
@TestMetadata("idea/testData/refactoring/extractFunction")
@InnerTestClasses({ExtractFunction.Basic.class, ExtractFunction.ControlFlow.class, ExtractFunction.DefaultContainer.class, ExtractFunction.Delegation.class, ExtractFunction.Initializers.class, ExtractFunction.Parameters.class, ExtractFunction.TypeParameters.class})
@InnerTestClasses({ExtractFunction.AsProperty.class, ExtractFunction.Basic.class, ExtractFunction.ControlFlow.class, ExtractFunction.DefaultContainer.class, ExtractFunction.Delegation.class, ExtractFunction.Initializers.class, ExtractFunction.Parameters.class, ExtractFunction.TypeParameters.class})
public static class ExtractFunction extends AbstractJetExtractionTest {
public void testAllFilesPresentInExtractFunction() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/refactoring/extractFunction"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("idea/testData/refactoring/extractFunction/asProperty")
public static class AsProperty extends AbstractJetExtractionTest {
public void testAllFilesPresentInAsProperty() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/refactoring/extractFunction/asProperty"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("extractToClass.kt")
public void testExtractToClass() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/asProperty/extractToClass.kt");
}
@TestMetadata("extractToFile.kt")
public void testExtractToFile() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/asProperty/extractToFile.kt");
}
@TestMetadata("extractToFunction.kt")
public void testExtractToFunction() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/asProperty/extractToFunction.kt");
}
@TestMetadata("extractWithParams.kt")
public void testExtractWithParams() throws Exception {
doExtractFunctionTest("idea/testData/refactoring/extractFunction/asProperty/extractWithParams.kt");
}
}
@TestMetadata("idea/testData/refactoring/extractFunction/basic")
public static class Basic extends AbstractJetExtractionTest {
public void testAllFilesPresentInBasic() throws Exception {
@@ -1311,6 +1339,7 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
public static Test innerSuite() {
TestSuite suite = new TestSuite("ExtractFunction");
suite.addTestSuite(ExtractFunction.class);
suite.addTestSuite(AsProperty.class);
suite.addTestSuite(Basic.class);
suite.addTest(ControlFlow.innerSuite());
suite.addTestSuite(DefaultContainer.class);