Extract Function: Refactoring: Introduce ExtractionGeneratorOptions and

ExtractionResult
This commit is contained in:
Alexey Sedunov
2014-07-28 16:18:46 +04:00
parent 6a42317fd1
commit 8af173bd4b
6 changed files with 345 additions and 281 deletions
@@ -38,6 +38,7 @@ import com.intellij.psi.impl.PsiModificationTrackerImpl
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
fun getFunctionForExtractedFragment(
codeFragment: JetCodeFragment,
@@ -96,10 +97,12 @@ fun getFunctionForExtractedFragment(
val validationResult = analysisResult.descriptor!!.validate()
if (!validationResult.conflicts.isEmpty()) {
throw EvaluateExceptionUtil.createEvaluateException("Following declarations are unavailable in debug scope: ${validationResult.conflicts.keySet().map { it.getText() }.joinToString(",")}")
throw EvaluateExceptionUtil.createEvaluateException("Following declarations are unavailable in debug scope: ${validationResult.conflicts.keySet()?.map { it.getText() }?.makeString(",")}")
}
return validationResult.descriptor.generateFunction(true)
return validationResult.descriptor
.generateFunction(ExtractionGeneratorOptions(inTempFile = true))
.function
}
return runReadAction { generateFunction() }
@@ -73,7 +73,7 @@ public class ExtractKotlinFunctionHandler(public val allContainersEnabled: Boole
file: JetFile,
elements: List<PsiElement>,
targetSibling: PsiElement,
preprocessor: ((ExtractionDescriptor) -> Unit)? = null
preprocessor: ((ExtractableCodeDescriptor) -> Unit)? = null
) {
val project = file.getProject()
@@ -83,19 +83,21 @@ public class ExtractKotlinFunctionHandler(public val allContainersEnabled: Boole
throw ConflictsInTestsException(analysisResult.messages.map { it.renderMessage() })
}
fun doRefactor(descriptor: ExtractionDescriptor) {
fun doRefactor(descriptor: ExtractableCodeDescriptor, generatorOptions: ExtractionGeneratorOptions) {
preprocessor?.invoke(descriptor)
project.executeWriteCommand(EXTRACT_FUNCTION) { descriptor.generateFunction() }
project.executeWriteCommand(EXTRACT_FUNCTION) { descriptor.generateFunction(generatorOptions) }
}
fun validateAndRefactor() {
val validationResult = analysisResult.descriptor!!.validate()
project.checkConflictsInteractively(validationResult.conflicts) {
if (ApplicationManager.getApplication()!!.isUnitTestMode()) {
doRefactor(validationResult.descriptor)
doRefactor(validationResult.descriptor, ExtractionGeneratorOptions.DEFAULT)
}
else {
KotlinExtractFunctionDialog(project, validationResult) { doRefactor(it.getCurrentDescriptor()) }.show()
KotlinExtractFunctionDialog(project, validationResult) {
doRefactor(it.getCurrentDescriptor(), it.getGeneratorOptions())
}.show()
}
}
}
@@ -38,6 +38,7 @@ 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
trait Parameter {
val argumentText: String
@@ -152,7 +153,7 @@ class Initializer(
override val declarationsToCopy: List<JetDeclaration>
): ControlFlow
data class ExtractionDescriptor(
data class ExtractableCodeDescriptor(
val extractionData: ExtractionData,
val name: String,
val visibility: String,
@@ -163,8 +164,21 @@ data class ExtractionDescriptor(
val controlFlow: ControlFlow
)
class ExtractionGeneratorOptions(
val inTempFile: Boolean = false
) {
class object {
val DEFAULT = ExtractionGeneratorOptions()
}
}
data class ExtractionResult(
val function: JetNamedFunction,
val nameByOffset: Map<Int, JetElement>
)
class AnalysisResult (
val descriptor: ExtractionDescriptor?,
val descriptor: ExtractableCodeDescriptor?,
val status: Status,
val messages: List<ErrorMessage>
) {
@@ -214,7 +228,7 @@ class AnalysisResult (
}
}
class ExtractionDescriptorWithConflicts(
val descriptor: ExtractionDescriptor,
class ExtractableCodeDescriptorWithConflicts(
val descriptor: ExtractableCodeDescriptor,
val conflicts: MultiMap<PsiElement, String>
)
@@ -34,7 +34,6 @@ import org.jetbrains.jet.lang.resolve.scopes.receivers.ThisReceiver
import org.jetbrains.jet.lang.cfg.pseudocode.*
import org.jetbrains.jet.lang.resolve.BindingContext
import org.jetbrains.jet.lang.cfg.Label
import org.jetbrains.jet.lang.psi.JetPsiFactory.FunctionBuilder
import org.jetbrains.jet.plugin.refactoring.JetNameValidatorImpl
import org.jetbrains.jet.plugin.codeInsight.DescriptorToDeclarationUtil
import org.jetbrains.jet.plugin.imports.canBeReferencedViaImport
@@ -44,14 +43,10 @@ import org.jetbrains.jet.lang.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.jet.utils.DFS
import org.jetbrains.jet.utils.DFS.*
import org.jetbrains.jet.plugin.caches.resolve.getLazyResolveSession
import org.jetbrains.jet.lang.psi.psiUtil.prependElement
import org.jetbrains.jet.lang.psi.psiUtil.appendElement
import org.jetbrains.jet.plugin.codeInsight.ShortenReferences
import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
import org.jetbrains.jet.lang.diagnostics.Errors
import org.jetbrains.jet.lang.psi.psiUtil.replaced
import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult.Status
import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult.ErrorMessage
import org.jetbrains.jet.lang.cfg.pseudocode.instructions.special.LocalFunctionDeclarationInstruction
@@ -62,11 +57,9 @@ import kotlin.properties.Delegates
import org.jetbrains.jet.lang.cfg.pseudocodeTraverser.traverse
import org.jetbrains.jet.lang.cfg.pseudocodeTraverser.TraversalOrder
import org.jetbrains.jet.lang.resolve.bindingContextUtil.getTargetFunctionDescriptor
import com.intellij.psi.PsiWhiteSpace
import org.jetbrains.jet.lang.resolve.OverridingUtil
import org.jetbrains.jet.lang.resolve.bindingContextUtil.isUsedAsStatement
import org.jetbrains.jet.lang.psi.psiUtil.isAncestor
import org.jetbrains.jet.plugin.intentions.declarations.DeclarationUtils
import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils
import org.jetbrains.jet.lang.psi.psiUtil.isFunctionLiteralOutsideParentheses
import org.jetbrains.jet.plugin.util.psiModificationUtil.moveInsideParenthesesAndReplaceWith
@@ -668,7 +661,7 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
receiverParameter?.let { adjustedParameters.remove(it) }
return AnalysisResult(
ExtractionDescriptor(
ExtractableCodeDescriptor(
this,
functionName,
"",
@@ -683,18 +676,17 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
)
}
fun ExtractionDescriptor.validate(): ExtractionDescriptorWithConflicts {
fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts {
val conflicts = MultiMap<PsiElement, String>()
val nameByOffset = HashMap<Int, JetElement>()
val function = generateFunction(true, nameByOffset)
val result = generateFunction(ExtractionGeneratorOptions(inTempFile = true))
val bindingContext = AnalyzerFacadeWithCache.getContextForElement(function.getBodyExpression()!!)
val bindingContext = AnalyzerFacadeWithCache.getContextForElement(result.function.getBodyExpression()!!)
for ((originalOffset, resolveResult) in extractionData.refOffsetToDeclaration) {
if (resolveResult.declaration.isInsideOf(extractionData.originalElements)) continue
val currentRefExpr = nameByOffset[originalOffset] as JetSimpleNameExpression?
val currentRefExpr = result.nameByOffset[originalOffset] as JetSimpleNameExpression?
if (currentRefExpr == null) continue
if (currentRefExpr.getParent() is JetThisExpression) continue
@@ -704,7 +696,7 @@ fun ExtractionDescriptor.validate(): ExtractionDescriptorWithConflicts {
val currentDescriptor = bindingContext[BindingContext.REFERENCE_TARGET, currentRefExpr]
val currentTarget =
currentDescriptor?.let { DescriptorToDeclarationUtil.getDeclaration(extractionData.project, it) } as? PsiNamedElement
if (currentTarget is JetParameter && currentTarget.getParent() == function.getValueParameterList()) continue
if (currentTarget is JetParameter && currentTarget.getParent() == result.function.getValueParameterList()) continue
if (currentDescriptor is LocalVariableDescriptor
&& parameters.any { it.mirrorVarName == currentDescriptor.getName().asString() }) continue
@@ -733,7 +725,7 @@ fun ExtractionDescriptor.validate(): ExtractionDescriptorWithConflicts {
}
}
return ExtractionDescriptorWithConflicts(this, conflicts)
return ExtractableCodeDescriptorWithConflicts(this, conflicts)
}
private fun comparePossiblyOverridingDescriptors(currentDescriptor: DeclarationDescriptor?, originalDescriptor: DeclarationDescriptor?): Boolean {
@@ -745,251 +737,3 @@ private fun comparePossiblyOverridingDescriptors(currentDescriptor: DeclarationD
return false
}
fun ExtractionDescriptor.getFunctionText(
withBody: Boolean = true,
descriptorRenderer: DescriptorRenderer = DescriptorRenderer.FQ_NAMES_IN_TYPES
): String {
return FunctionBuilder().let { builder ->
builder.modifier(visibility)
builder.typeParams(typeParameters.map { it.originalDeclaration.getText()!! })
receiverParameter?.let { builder.receiver(descriptorRenderer.renderType(it.parameterType)) }
builder.name(name)
parameters.forEach { parameter ->
builder.param(parameter.name, descriptorRenderer.renderType(parameter.parameterType))
}
with(controlFlow.returnType) {
if (isDefault() || isError()) builder.noReturnType() else builder.returnType(descriptorRenderer.renderType(this))
}
builder.typeConstraints(typeParameters.flatMap { it.originalConstraints }.map { it.getText()!! })
if (withBody) {
builder.blockBody(extractionData.getCodeFragmentText())
}
builder.toFunctionText()
}
}
fun createNameCounterpartMap(from: JetElement, to: JetElement): Map<JetSimpleNameExpression, JetSimpleNameExpression> {
val map = HashMap<JetSimpleNameExpression, JetSimpleNameExpression>()
val fromOffset = from.getTextRange()!!.getStartOffset()
from.accept(
object: JetTreeVisitorVoid() {
override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) {
val offset = expression.getTextRange()!!.getStartOffset() - fromOffset
val newExpression = to.findElementAt(offset)?.getParentByType(javaClass<JetSimpleNameExpression>())
assert(newExpression!= null, "Couldn't find expression at $offset in '${to.getText()}'")
map[expression] = newExpression!!
}
}
)
return map
}
fun ExtractionDescriptor.generateFunction(
inTempFile: Boolean = false,
nameByOffset: MutableMap<Int, JetElement> = HashMap()
): JetNamedFunction {
val psiFactory = JetPsiFactory(extractionData.originalFile)
fun createFunction(): JetNamedFunction {
return with(extractionData) {
if (inTempFile) {
createTemporaryFunction("${getFunctionText()}\n")
}
else {
psiFactory.createFunction(getFunctionText())
}
}
}
fun adjustFunctionBody(function: JetNamedFunction) {
val body = function.getBodyExpression() as JetBlockExpression
val exprReplacementMap = HashMap<JetElement, (JetElement) -> JetElement>()
val originalOffsetByExpr = LinkedHashMap<JetElement, Int>()
val bodyOffset = body.getBlockContentOffset()
val file = body.getContainingFile()!!
/*
* Sort by descending position so that internals of value/type arguments in calls and qualified types are replaced
* before calls/types themselves
*/
for ((offsetInBody, resolveResult) in extractionData.refOffsetToDeclaration.entrySet().sortDescendingBy { it.key }) {
val expr = file.findElementAt(bodyOffset + offsetInBody)?.getParentByType(javaClass<JetSimpleNameExpression>())
assert(expr != null, "Couldn't find expression at $offsetInBody in '${body.getText()}'")
originalOffsetByExpr[expr!!] = offsetInBody
replacementMap[offsetInBody]?.let { replacement ->
if (replacement !is ParameterReplacement || replacement.parameter != receiverParameter) {
exprReplacementMap[expr] = replacement
}
}
}
val replacingReturn: JetExpression?
val expressionsToReplaceWithReturn: List<JetElement>
if (controlFlow is JumpBasedControlFlow) {
replacingReturn = psiFactory.createExpression(if (controlFlow is ConditionalJump) "return true" else "return")
expressionsToReplaceWithReturn = controlFlow.elementsToReplace.map { jumpElement ->
val offsetInBody = jumpElement.getTextRange()!!.getStartOffset() - extractionData.originalStartOffset!!
val expr = file.findElementAt(bodyOffset + offsetInBody)?.getParentByType(jumpElement.javaClass)
assert(expr != null, "Couldn't find expression at $offsetInBody in '${body.getText()}'")
expr!!
}
}
else {
replacingReturn = null
expressionsToReplaceWithReturn = Collections.emptyList()
}
if (replacingReturn != null) {
for (expr in expressionsToReplaceWithReturn) {
expr.replace(replacingReturn)
}
}
for ((expr, originalOffset) in originalOffsetByExpr) {
if (expr.isValid()) {
nameByOffset.put(originalOffset, exprReplacementMap[expr]?.invoke(expr) ?: expr)
}
}
for (param in parameters) {
param.mirrorVarName?.let { varName ->
body.prependElement(psiFactory.createProperty(varName, null, true, param.name))
}
}
when (controlFlow) {
is ParameterUpdate ->
body.appendElement(psiFactory.createReturn(controlFlow.parameter.nameForRef))
is Initializer ->
body.appendElement(psiFactory.createReturn(controlFlow.initializedDeclaration.getName()!!))
is ConditionalJump ->
body.appendElement(psiFactory.createReturn("false"))
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 counterpartMap = createNameCounterpartMap(it, newExpr)
nameByOffset.entrySet().forEach { e -> counterpartMap[e.getValue()]?.let { e.setValue(it) } }
}
}
}
fun insertFunction(function: JetNamedFunction): JetNamedFunction {
return with(extractionData) {
val targetContainer = targetSibling.getParent()!!
val emptyLines = psiFactory.createWhiteSpace("\n\n")
if (insertBefore) {
val functionInFile = targetContainer.addBefore(function, targetSibling) as JetNamedFunction
targetContainer.addBefore(emptyLines, targetSibling)
functionInFile
}
else {
val functionInFile = targetContainer.addAfter(function, targetSibling) as JetNamedFunction
targetContainer.addAfter(emptyLines, targetSibling)
functionInFile
}
}
}
fun insertCall(anchor: PsiElement, wrappedCall: JetExpression?) {
if (wrappedCall == null) {
anchor.delete()
return
}
val firstExpression = extractionData.getExpressions().firstOrNull()
if (firstExpression?.isFunctionLiteralOutsideParentheses() ?: false) {
val functionLiteralArgument = PsiTreeUtil.getParentOfType(firstExpression, javaClass<JetFunctionLiteralArgument>())!!
//todo use the right binding context
functionLiteralArgument.moveInsideParenthesesAndReplaceWith(wrappedCall, BindingContext.EMPTY)
return
}
anchor.replace(wrappedCall)
}
fun makeCall(function: JetNamedFunction): JetNamedFunction {
val anchor = extractionData.originalElements.first
if (anchor == null) return function
val anchorParent = anchor.getParent()!!
anchor.getNextSibling()?.let { from ->
val to = extractionData.originalElements.last
if (to != anchor) {
anchorParent.deleteChildRange(from, to);
}
}
val callText = parameters
.map { it.argumentText }
.joinToString(separator = ", ", prefix = "$name(", postfix = ")")
val copiedDeclarations = HashMap<JetDeclaration, JetDeclaration>()
for (decl in controlFlow.declarationsToCopy) {
val declCopy = psiFactory.createDeclaration<JetDeclaration>(decl.getText()!!)
copiedDeclarations[decl] = anchorParent.addBefore(declCopy, anchor) as JetDeclaration
anchorParent.addBefore(psiFactory.createNewLine(), anchor)
}
val wrappedCall = when (controlFlow) {
is ExpressionEvaluationWithCallSiteReturn ->
psiFactory.createReturn(callText)
is ParameterUpdate ->
psiFactory.createExpression("${controlFlow.parameter.argumentText} = $callText")
is Initializer -> {
val newDecl = copiedDeclarations[controlFlow.initializedDeclaration] as JetProperty
newDecl.replace(DeclarationUtils.changePropertyInitializer(newDecl, psiFactory.createExpression(callText)))
null
}
is ConditionalJump ->
psiFactory.createExpression("if ($callText) ${controlFlow.elementToInsertAfterCall.getText()}")
is UnconditionalJump -> {
anchorParent.addAfter(
psiFactory.createExpression(controlFlow.elementToInsertAfterCall.getText()!!),
anchor
)
anchorParent.addAfter(psiFactory.createNewLine(), anchor)
psiFactory.createExpression(callText)
}
else ->
psiFactory.createExpression(callText)
}
insertCall(anchor, wrappedCall)
return function
}
val function = createFunction()
adjustFunctionBody(function)
if (inTempFile) return function
val functionInPlace = makeCall(insertFunction(function))
ShortenReferences.process(functionInPlace)
return functionInPlace
}
@@ -0,0 +1,296 @@
/*
* Copyright 2010-2014 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.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.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
fun ExtractableCodeDescriptor.getFunctionText(
withBody: Boolean = true,
descriptorRenderer: DescriptorRenderer = DescriptorRenderer.FQ_NAMES_IN_TYPES
): String {
return FunctionBuilder().let { builder ->
builder.modifier(visibility)
builder.typeParams(typeParameters.map { it.originalDeclaration.getText()!! })
receiverParameter?.let { builder.receiver(descriptorRenderer.renderType(it.parameterType)) }
builder.name(name)
parameters.forEach { parameter ->
builder.param(parameter.name, descriptorRenderer.renderType(parameter.parameterType))
}
with(controlFlow.returnType) {
if (isDefault() || isError()) builder.noReturnType() else builder.returnType(descriptorRenderer.renderType(this))
}
builder.typeConstraints(typeParameters.flatMap { it.originalConstraints }.map { it.getText()!! })
if (withBody) {
builder.blockBody(extractionData.getCodeFragmentText())
}
builder.toFunctionText()
}
}
fun createNameCounterpartMap(from: JetElement, to: JetElement): Map<JetSimpleNameExpression, JetSimpleNameExpression> {
val map = HashMap<JetSimpleNameExpression, JetSimpleNameExpression>()
val fromOffset = from.getTextRange()!!.getStartOffset()
from.accept(
object: JetTreeVisitorVoid() {
override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) {
val offset = expression.getTextRange()!!.getStartOffset() - fromOffset
val newExpression = to.findElementAt(offset)?.getParentByType(javaClass<JetSimpleNameExpression>())
assert(newExpression!= null, "Couldn't find expression at $offset in '${to.getText()}'")
map[expression] = newExpression!!
}
}
)
return map
}
fun ExtractableCodeDescriptor.generateFunction(options: ExtractionGeneratorOptions): ExtractionResult {
val psiFactory = JetPsiFactory(extractionData.originalFile)
val nameByOffset = HashMap<Int, JetElement>()
fun createFunction(): JetNamedFunction {
return with(extractionData) {
if (options.inTempFile) {
createTemporaryFunction("${getFunctionText()}\n")
}
else {
psiFactory.createFunction(getFunctionText())
}
}
}
fun adjustFunctionBody(function: JetNamedFunction) {
val body = function.getBodyExpression() as JetBlockExpression
val exprReplacementMap = HashMap<JetElement, (JetElement) -> JetElement>()
val originalOffsetByExpr = LinkedHashMap<JetElement, Int>()
val bodyOffset = body.getBlockContentOffset()
val file = body.getContainingFile()!!
/*
* Sort by descending position so that internals of value/type arguments in calls and qualified types are replaced
* before calls/types themselves
*/
for ((offsetInBody, resolveResult) in extractionData.refOffsetToDeclaration.entrySet().sortDescendingBy { it.key }) {
val expr = file.findElementAt(bodyOffset + offsetInBody)?.getParentByType(javaClass<JetSimpleNameExpression>())
assert(expr != null, "Couldn't find expression at $offsetInBody in '${body.getText()}'")
originalOffsetByExpr[expr!!] = offsetInBody
replacementMap[offsetInBody]?.let { replacement ->
if (replacement !is ParameterReplacement || replacement.parameter != receiverParameter) {
exprReplacementMap[expr] = replacement
}
}
}
val replacingReturn: JetExpression?
val expressionsToReplaceWithReturn: List<JetElement>
if (controlFlow is JumpBasedControlFlow) {
replacingReturn = psiFactory.createExpression(if (controlFlow is ConditionalJump) "return true" else "return")
expressionsToReplaceWithReturn = controlFlow.elementsToReplace.map { jumpElement ->
val offsetInBody = jumpElement.getTextRange()!!.getStartOffset() - extractionData.originalStartOffset!!
val expr = file.findElementAt(bodyOffset + offsetInBody)?.getParentByType(jumpElement.javaClass)
assert(expr != null, "Couldn't find expression at $offsetInBody in '${body.getText()}'")
expr!!
}
}
else {
replacingReturn = null
expressionsToReplaceWithReturn = Collections.emptyList()
}
if (replacingReturn != null) {
for (expr in expressionsToReplaceWithReturn) {
expr.replace(replacingReturn)
}
}
for ((expr, originalOffset) in originalOffsetByExpr) {
if (expr.isValid()) {
nameByOffset.put(originalOffset, exprReplacementMap[expr]?.invoke(expr) ?: expr)
}
}
for (param in parameters) {
param.mirrorVarName?.let { varName ->
body.prependElement(psiFactory.createProperty(varName, null, true, param.name))
}
}
when (controlFlow) {
is ParameterUpdate ->
body.appendElement(psiFactory.createReturn(controlFlow.parameter.nameForRef))
is Initializer ->
body.appendElement(psiFactory.createReturn(controlFlow.initializedDeclaration.getName()!!))
is ConditionalJump ->
body.appendElement(psiFactory.createReturn("false"))
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 counterpartMap = createNameCounterpartMap(it, newExpr)
nameByOffset.entrySet().forEach { e -> counterpartMap[e.getValue()]?.let { e.setValue(it) } }
}
}
}
fun insertFunction(function: JetNamedFunction): JetNamedFunction {
return with(extractionData) {
val targetContainer = targetSibling.getParent()!!
val emptyLines = psiFactory.createWhiteSpace("\n\n")
if (insertBefore) {
val functionInFile = targetContainer.addBefore(function, targetSibling) as JetNamedFunction
targetContainer.addBefore(emptyLines, targetSibling)
functionInFile
}
else {
val functionInFile = targetContainer.addAfter(function, targetSibling) as JetNamedFunction
targetContainer.addAfter(emptyLines, targetSibling)
functionInFile
}
}
}
fun insertCall(anchor: PsiElement, wrappedCall: JetExpression?) {
if (wrappedCall == null) {
anchor.delete()
return
}
val firstExpression = extractionData.getExpressions().firstOrNull()
if (firstExpression?.isFunctionLiteralOutsideParentheses() ?: false) {
val functionLiteralArgument = PsiTreeUtil.getParentOfType(firstExpression, javaClass<JetFunctionLiteralArgument>())!!
//todo use the right binding context
functionLiteralArgument.moveInsideParenthesesAndReplaceWith(wrappedCall, BindingContext.EMPTY)
return
}
anchor.replace(wrappedCall)
}
fun makeCall(function: JetNamedFunction): JetNamedFunction {
val anchor = extractionData.originalElements.first
if (anchor == null) return function
val anchorParent = anchor.getParent()!!
anchor.getNextSibling()?.let { from ->
val to = extractionData.originalElements.last
if (to != anchor) {
anchorParent.deleteChildRange(from, to);
}
}
val callText = parameters
.map { it.argumentText }
.joinToString(separator = ", ", prefix = "${name}(", postfix = ")")
val copiedDeclarations = HashMap<JetDeclaration, JetDeclaration>()
for (decl in controlFlow.declarationsToCopy) {
val declCopy = psiFactory.createDeclaration<JetDeclaration>(decl.getText()!!)
copiedDeclarations[decl] = anchorParent.addBefore(declCopy, anchor) as JetDeclaration
anchorParent.addBefore(psiFactory.createNewLine(), anchor)
}
val wrappedCall = when (controlFlow) {
is ExpressionEvaluationWithCallSiteReturn ->
psiFactory.createReturn(callText)
is ParameterUpdate ->
psiFactory.createExpression("${controlFlow.parameter.argumentText} = $callText")
is Initializer -> {
val newDecl = copiedDeclarations[controlFlow.initializedDeclaration] as JetProperty
newDecl.replace(DeclarationUtils.changePropertyInitializer(newDecl, psiFactory.createExpression(callText)))
null
}
is ConditionalJump ->
psiFactory.createExpression("if ($callText) ${controlFlow.elementToInsertAfterCall.getText()}")
is UnconditionalJump -> {
anchorParent.addAfter(
psiFactory.createExpression(controlFlow.elementToInsertAfterCall.getText()!!),
anchor
)
anchorParent.addAfter(psiFactory.createNewLine(), anchor)
psiFactory.createExpression(callText)
}
else ->
psiFactory.createExpression(callText)
}
insertCall(anchor, wrappedCall)
return function
}
val function = createFunction()
adjustFunctionBody(function)
if (options.inTempFile) return ExtractionResult(function, nameByOffset)
val functionInPlace = makeCall(insertFunction(function))
ShortenReferences.process(functionInPlace)
return ExtractionResult(functionInPlace, nameByOffset)
}
@@ -54,14 +54,14 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
private final Project project;
private final ExtractionDescriptorWithConflicts originalDescriptor;
private ExtractionDescriptor currentDescriptor;
private final ExtractableCodeDescriptorWithConflicts originalDescriptor;
private ExtractableCodeDescriptor currentDescriptor;
private final Function1<KotlinExtractFunctionDialog, Unit> onAccept;
public KotlinExtractFunctionDialog(
@NotNull Project project,
@NotNull ExtractionDescriptorWithConflicts originalDescriptor,
@NotNull ExtractableCodeDescriptorWithConflicts originalDescriptor,
@NotNull Function1<KotlinExtractFunctionDialog, Unit> onAccept) {
super(project, true);
@@ -202,8 +202,8 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
}
@NotNull
private ExtractionDescriptor createDescriptor() {
ExtractionDescriptor descriptor = originalDescriptor.getDescriptor();
private ExtractableCodeDescriptor createDescriptor() {
ExtractableCodeDescriptor descriptor = originalDescriptor.getDescriptor();
List<KotlinParameterTablePanel.ParameterInfo> parameterInfos = parameterTablePanel.getParameterInfos();
@@ -240,7 +240,7 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
}
}
return new ExtractionDescriptor(
return new ExtractableCodeDescriptor(
descriptor.getExtractionData(),
getFunctionName(),
getVisibility(),
@@ -253,7 +253,12 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
}
@NotNull
public ExtractionDescriptor getCurrentDescriptor() {
public ExtractableCodeDescriptor getCurrentDescriptor() {
return currentDescriptor;
}
@NotNull
public ExtractionGeneratorOptions getGeneratorOptions() {
return ExtractionGeneratorOptions.DEFAULT;
}
}