Extract Function: Allow to choose between function and property extraction
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
+6
-3
@@ -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
|
||||
|
||||
+16
-5
@@ -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() {
|
||||
|
||||
+14
-4
@@ -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
|
||||
}
|
||||
+13
-6
@@ -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)
|
||||
}
|
||||
+28
-5
@@ -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>
|
||||
|
||||
+18
-2
@@ -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
|
||||
+24
-10
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
-1
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user