Refactoring: Introduce ExtractionEngine class

This commit is contained in:
Alexey Sedunov
2015-02-25 11:26:33 +03:00
parent b99c191bb2
commit 48914e70ff
9 changed files with 210 additions and 153 deletions
@@ -17,28 +17,22 @@
package org.jetbrains.kotlin.idea.debugger.evaluate package org.jetbrains.kotlin.idea.debugger.evaluate
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.ErrorMessage import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.ErrorMessage
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.refactoring.createTempCopy import org.jetbrains.kotlin.idea.refactoring.createTempCopy
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionData
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.performAnalysis
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.Status import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.Status
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.validate
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionOptions
import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runReadAction
import com.intellij.psi.PsiManager import com.intellij.psi.PsiManager
import com.intellij.psi.impl.PsiModificationTrackerImpl import com.intellij.psi.impl.PsiModificationTrackerImpl
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.idea.intentions.InsertExplicitTypeArguments import org.jetbrains.kotlin.idea.intentions.InsertExplicitTypeArguments
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionGeneratorOptions
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.generateDeclaration
import org.jetbrains.kotlin.idea.util.psi.patternMatching.toRange import org.jetbrains.kotlin.idea.util.psi.patternMatching.toRange
import org.jetbrains.kotlin.idea.actions.internal.KotlinInternalMode import org.jetbrains.kotlin.idea.actions.internal.KotlinInternalMode
import org.jetbrains.kotlin.psi.psiUtil.replaced import org.jetbrains.kotlin.psi.psiUtil.replaced
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
fun getFunctionForExtractedFragment( fun getFunctionForExtractedFragment(
codeFragment: JetCodeFragment, codeFragment: JetCodeFragment,
@@ -106,9 +100,11 @@ fun getFunctionForExtractedFragment(
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() }.joinToString(",")}")
} }
return validationResult.descriptor val config = ExtractionGeneratorConfiguration(
.generateDeclaration(ExtractionGeneratorOptions(inTempFile = true, flexibleTypesAllowed = true)) validationResult.descriptor,
.declaration as JetNamedFunction ExtractionGeneratorOptions(inTempFile = true, flexibleTypesAllowed = true)
)
return config.generateDeclaration().declaration as JetNamedFunction
} }
return runReadAction { generateFunction() } return runReadAction { generateFunction() }
@@ -24,7 +24,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.idea.refactoring.changeSignature.JetChangeSignatureHandler; import org.jetbrains.kotlin.idea.refactoring.changeSignature.JetChangeSignatureHandler;
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandler; import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandler;
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandlerHelper;
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler; import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler;
import org.jetbrains.kotlin.idea.refactoring.safeDelete.SafeDeletePackage; import org.jetbrains.kotlin.idea.refactoring.safeDelete.SafeDeletePackage;
import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.*;
@@ -47,7 +46,7 @@ public class JetRefactoringSupportProvider extends RefactoringSupportProvider {
@NotNull @NotNull
public RefactoringActionHandler getExtractFunctionToScopeHandler() { public RefactoringActionHandler getExtractFunctionToScopeHandler() {
return new ExtractKotlinFunctionHandler(true, ExtractKotlinFunctionHandlerHelper.DEFAULT); return new ExtractKotlinFunctionHandler(true, ExtractKotlinFunctionHandler.InteractiveExtractionHelper.INSTANCE$);
} }
@Override @Override
@@ -25,44 +25,29 @@ import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.refactoring.JetRefactoringBundle import org.jetbrains.kotlin.idea.refactoring.JetRefactoringBundle
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ui.KotlinExtractFunctionDialog import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ui.KotlinExtractFunctionDialog
import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.psi.JetFile
import com.intellij.openapi.application.ApplicationManager
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.psi.JetBlockExpression import org.jetbrains.kotlin.psi.JetBlockExpression
import kotlin.test.fail import kotlin.test.fail
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.Status
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.ui.MessageType
import javax.swing.event.HyperlinkEvent
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
import com.intellij.ui.awt.RelativePoint
import com.intellij.openapi.ui.popup.Balloon.Position
import org.jetbrains.kotlin.psi.JetFunctionLiteral import org.jetbrains.kotlin.psi.JetFunctionLiteral
import org.jetbrains.kotlin.idea.util.psi.patternMatching.toRange import org.jetbrains.kotlin.idea.util.psi.patternMatching.toRange
import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.* import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
import org.jetbrains.kotlin.idea.refactoring.introduce.* import org.jetbrains.kotlin.idea.refactoring.introduce.*
public open class ExtractKotlinFunctionHandlerHelper {
open fun adjustExtractionData(data: ExtractionData): ExtractionData = data
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 class ExtractKotlinFunctionHandler(
public val allContainersEnabled: Boolean = false, public val allContainersEnabled: Boolean = false,
private val helper: ExtractKotlinFunctionHandlerHelper = ExtractKotlinFunctionHandlerHelper.DEFAULT) : RefactoringActionHandler { private val helper: ExtractionEngineHelper = ExtractKotlinFunctionHandler.InteractiveExtractionHelper) : RefactoringActionHandler {
private fun adjustElements(elements: List<PsiElement>): List<PsiElement> {
if (elements.size() != 1) return elements
val e = elements.first() object InteractiveExtractionHelper : ExtractionEngineHelper() {
if (e is JetBlockExpression && e.getParent() is JetFunctionLiteral) return e.getStatements() override fun configureInteractively(
project: Project,
return elements editor: Editor,
descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts,
continuation: (ExtractionGeneratorConfiguration) -> Unit
) {
KotlinExtractFunctionDialog(descriptorWithConflicts.descriptor.extractionData.project, descriptorWithConflicts) {
continuation(it.getCurrentConfiguration())
}.show()
}
} }
fun doInvoke( fun doInvoke(
@@ -71,66 +56,18 @@ public class ExtractKotlinFunctionHandler(
elements: List<PsiElement>, elements: List<PsiElement>,
targetSibling: PsiElement targetSibling: PsiElement
) { ) {
val project = file.getProject() fun adjustElements(elements: List<PsiElement>): List<PsiElement> {
if (elements.size() != 1) return elements
val analysisResult = helper.adjustExtractionData( val e = elements.first()
ExtractionData(file, adjustElements(elements).toRange(false), targetSibling) if (e is JetBlockExpression && e.getParent() is JetFunctionLiteral) return e.getStatements()
).performAnalysis()
if (ApplicationManager.getApplication()!!.isUnitTestMode() && analysisResult.status != Status.SUCCESS) { return elements
throw ConflictsInTestsException(analysisResult.messages.map { it.renderMessage() })
} }
fun doRefactor(descriptor: ExtractableCodeDescriptor, generatorOptions: ExtractionGeneratorOptions) { val extractionData = ExtractionData(file, adjustElements(elements).toRange(false), targetSibling)
val adjustedDescriptor = helper.adjustDescriptor(descriptor) ExtractionEngine(EXTRACT_FUNCTION, helper).run(editor, extractionData) {
val adjustedGeneratorOptions = helper.adjustGeneratorOptions(generatorOptions) processDuplicates(it.duplicateReplacers, file.getProject(), editor)
val result = project.executeWriteCommand<ExtractionResult>(EXTRACT_FUNCTION) { adjustedDescriptor.generateDeclaration(adjustedGeneratorOptions) }
processDuplicates(result.duplicateReplacers, project, editor)
}
fun validateAndRefactor() {
val validationResult = analysisResult.descriptor!!.validate()
project.checkConflictsInteractively(validationResult.conflicts) {
if (ApplicationManager.getApplication()!!.isUnitTestMode()) {
doRefactor(validationResult.descriptor, ExtractionGeneratorOptions.DEFAULT)
}
else {
KotlinExtractFunctionDialog(project, validationResult) {
doRefactor(it.getCurrentDescriptor(), it.getGeneratorOptions())
}.show()
}
}
}
val message = analysisResult.messages.map { it.renderMessage() }.joinToString("\n")
when (analysisResult.status) {
Status.CRITICAL_ERROR -> {
showErrorHint(project, editor, message, EXTRACT_FUNCTION)
}
Status.NON_CRITICAL_ERROR -> {
val anchorPoint = RelativePoint(
editor.getContentComponent(),
editor.visualPositionToXY(editor.getSelectionModel().getSelectionStartPosition()!!)
)
JBPopupFactory.getInstance()!!
.createHtmlTextBalloonBuilder(
"$message<br/><br/><a href=\"EXTRACT\">Proceed with extraction</a>",
MessageType.WARNING,
{ event ->
if (event?.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
validateAndRefactor()
}
}
)
.setHideOnClickOutside(true)
.setHideOnFrameResize(false)
.setHideOnLinkClick(true)
.createBalloon()
.show(anchorPoint, Position.below)
}
Status.SUCCESS -> validateAndRefactor()
} }
} }
@@ -110,8 +110,9 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
setOKActionEnabled(checkNames()); setOKActionEnabled(checkNames());
signaturePreviewField.setText( signaturePreviewField.setText(
ExtractionEnginePackage.getDeclarationText(currentDescriptor, getGeneratorOptions(), false, ExtractionEnginePackage.getDeclarationText(getCurrentConfiguration(),
IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES) false,
IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES)
); );
} }
@@ -270,8 +271,8 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
} }
@NotNull @NotNull
public ExtractableCodeDescriptor getCurrentDescriptor() { public ExtractionGeneratorConfiguration getCurrentConfiguration() {
return currentDescriptor; return new ExtractionGeneratorConfiguration(currentDescriptor, getGeneratorOptions());
} }
@NotNull @NotNull
@@ -324,7 +324,13 @@ data class ExtractionGeneratorOptions(
} }
} }
data class ExtractionGeneratorConfiguration(
val descriptor: ExtractableCodeDescriptor,
val generatorOptions: ExtractionGeneratorOptions
)
data class ExtractionResult( data class ExtractionResult(
val config: ExtractionGeneratorConfiguration,
val declaration: JetNamedDeclaration, val declaration: JetNamedDeclaration,
val duplicateReplacers: Map<JetPsiRange, () -> Unit>, val duplicateReplacers: Map<JetPsiRange, () -> Unit>,
val nameByOffset: Map<Int, JetElement> val nameByOffset: Map<Int, JetElement>
@@ -0,0 +1,121 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
import com.intellij.psi.*
import org.jetbrains.kotlin.psi.*
import com.intellij.openapi.editor.*
import org.jetbrains.kotlin.idea.util.psi.patternMatching.*
import com.intellij.openapi.application.*
import com.intellij.refactoring.*
import org.jetbrains.kotlin.idea.util.application.*
import org.jetbrains.kotlin.idea.refactoring.*
import org.jetbrains.kotlin.idea.refactoring.introduce.*
import com.intellij.ui.awt.*
import com.intellij.openapi.ui.popup.*
import com.intellij.openapi.ui.*
import javax.swing.event.*
import com.intellij.openapi.project.*
public open class ExtractionEngineHelper {
open fun adjustExtractionData(data: ExtractionData): ExtractionData = data
open fun configure(
descriptor: ExtractableCodeDescriptor,
generatorOptions: ExtractionGeneratorOptions
): ExtractionGeneratorConfiguration {
return ExtractionGeneratorConfiguration(descriptor, generatorOptions)
}
open fun configureInteractively(
project: Project,
editor: Editor,
descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts,
continuation: (ExtractionGeneratorConfiguration) -> Unit
) {
continuation(ExtractionGeneratorConfiguration(descriptorWithConflicts.descriptor, ExtractionGeneratorOptions.DEFAULT))
}
class object {
public val DEFAULT: ExtractionEngineHelper = ExtractionEngineHelper()
}
}
public class ExtractionEngine(
val operationName: String,
val helper: ExtractionEngineHelper
) {
fun run(editor: Editor,
extractionData: ExtractionData,
onFinish: (ExtractionResult) -> Unit = {}
) {
val project = extractionData.project
val analysisResult = helper.adjustExtractionData(extractionData).performAnalysis()
if (ApplicationManager.getApplication()!!.isUnitTestMode() && analysisResult.status != AnalysisResult.Status.SUCCESS) {
throw BaseRefactoringProcessor.ConflictsInTestsException(analysisResult.messages.map { it.renderMessage() })
}
fun doRefactor(config: ExtractionGeneratorConfiguration) {
onFinish(project.executeWriteCommand<ExtractionResult>(operationName) { config.generateDeclaration() })
}
fun validateAndRefactor() {
val validationResult = analysisResult.descriptor!!.validate()
project.checkConflictsInteractively(validationResult.conflicts) {
if (ApplicationManager.getApplication()!!.isUnitTestMode()) {
doRefactor(helper.configure(validationResult.descriptor, ExtractionGeneratorOptions.DEFAULT))
}
else {
helper.configureInteractively(project, editor, validationResult, ::doRefactor)
}
}
}
val message = analysisResult.messages.map { it.renderMessage() }.joinToString("\n")
when (analysisResult.status) {
AnalysisResult.Status.CRITICAL_ERROR -> {
showErrorHint(project, editor, message, operationName)
}
AnalysisResult.Status.NON_CRITICAL_ERROR -> {
val anchorPoint = RelativePoint(
editor.getContentComponent(),
editor.visualPositionToXY(editor.getSelectionModel().getSelectionStartPosition()!!)
)
JBPopupFactory.getInstance()!!
.createHtmlTextBalloonBuilder(
"$message<br/><br/><a href=\"EXTRACT\">Proceed with extraction</a>",
MessageType.WARNING,
{ event ->
if (event?.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
validateAndRefactor()
}
}
)
.setHideOnClickOutside(true)
.setHideOnFrameResize(false)
.setHideOnLinkClick(true)
.createBalloon()
.show(anchorPoint, Balloon.Position.below)
}
AnalysisResult.Status.SUCCESS -> validateAndRefactor()
}
}
}
@@ -841,7 +841,7 @@ private fun JetNamedDeclaration.getGeneratedBlockBody() =
fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts { fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts {
val conflicts = MultiMap<PsiElement, String>() val conflicts = MultiMap<PsiElement, String>()
val result = generateDeclaration(ExtractionGeneratorOptions(inTempFile = true)) val result = ExtractionGeneratorConfiguration(this, ExtractionGeneratorOptions(inTempFile = true)).generateDeclaration()
val valueParameterList = (result.declaration as? JetNamedFunction)?.getValueParameterList() val valueParameterList = (result.declaration as? JetNamedFunction)?.getValueParameterList()
val bindingContext = result.declaration.getGeneratedBlockBody().analyze() val bindingContext = result.declaration.getGeneratedBlockBody().analyze()
@@ -46,22 +46,21 @@ import java.util.Collections
import java.util.HashMap import java.util.HashMap
import java.util.LinkedHashMap import java.util.LinkedHashMap
fun ExtractableCodeDescriptor.getDeclarationText( fun ExtractionGeneratorConfiguration.getDeclarationText(
options: ExtractionGeneratorOptions = ExtractionGeneratorOptions.DEFAULT,
withBody: Boolean = true, withBody: Boolean = true,
descriptorRenderer: DescriptorRenderer = if (options.flexibleTypesAllowed) descriptorRenderer: DescriptorRenderer = if (generatorOptions.flexibleTypesAllowed)
DescriptorRenderer.FLEXIBLE_TYPES_FOR_CODE DescriptorRenderer.FLEXIBLE_TYPES_FOR_CODE
else IdeDescriptorRenderers.SOURCE_CODE else IdeDescriptorRenderers.SOURCE_CODE
): String { ): String {
if (!canGenerateProperty() && options.extractAsProperty) { if (!descriptor.canGenerateProperty() && generatorOptions.extractAsProperty) {
throw IllegalArgumentException("Can't generate property: ${extractionData.getCodeFragmentText()}") throw IllegalArgumentException("Can't generate property: ${descriptor.extractionData.getCodeFragmentText()}")
} }
val builderTarget = if (options.extractAsProperty) Target.READ_ONLY_PROPERTY else Target.FUNCTION val builderTarget = if (generatorOptions.extractAsProperty) Target.READ_ONLY_PROPERTY else Target.FUNCTION
return CallableBuilder(builderTarget).let { builder -> return CallableBuilder(builderTarget).let { builder ->
builder.modifier(visibility) builder.modifier(descriptor.visibility)
builder.typeParams(typeParameters.map { it.originalDeclaration.getText()!! }) builder.typeParams(descriptor.typeParameters.map { it.originalDeclaration.getText()!! })
fun JetType.typeAsString(): String { fun JetType.typeAsString(): String {
return if (isSpecial()) DEBUG_TYPE_REFERENCE_STRING else descriptorRenderer.renderType(this) return if (isSpecial()) DEBUG_TYPE_REFERENCE_STRING else descriptorRenderer.renderType(this)
@@ -79,10 +78,10 @@ fun ExtractableCodeDescriptor.getDeclarationText(
if (isDefault() || isError()) builder.noReturnType() else builder.returnType(this.typeAsString()) if (isDefault() || isError()) builder.noReturnType() else builder.returnType(this.typeAsString())
} }
builder.typeConstraints(typeParameters.flatMap { it.originalConstraints }.map { it.getText()!! }) builder.typeConstraints(descriptor.typeParameters.flatMap { it.originalConstraints }.map { it.getText()!! })
if (withBody) { if (withBody) {
builder.blockBody(extractionData.getCodeFragmentText()) builder.blockBody(descriptor.extractionData.getCodeFragmentText())
} }
builder.asString() builder.asString()
@@ -335,23 +334,23 @@ private fun makeCall(
} }
} }
fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOptions): ExtractionResult{ fun ExtractionGeneratorConfiguration.generateDeclaration(): ExtractionResult{
val psiFactory = JetPsiFactory(extractionData.originalFile) val psiFactory = JetPsiFactory(descriptor.extractionData.originalFile)
val nameByOffset = HashMap<Int, JetElement>() val nameByOffset = HashMap<Int, JetElement>()
fun createDeclaration(): JetNamedDeclaration { fun createDeclaration(): JetNamedDeclaration {
return with(extractionData) { return with(descriptor.extractionData) {
if (options.inTempFile) { if (generatorOptions.inTempFile) {
createTemporaryDeclaration("${getDeclarationText(options)}\n") createTemporaryDeclaration("${getDeclarationText()}\n")
} }
else { else {
psiFactory.createDeclaration(getDeclarationText(options)) psiFactory.createDeclaration(getDeclarationText())
} }
} }
} }
fun getReturnArguments(resultExpression: JetExpression?): List<String> { fun getReturnArguments(resultExpression: JetExpression?): List<String> {
return controlFlow.outputValues return descriptor.controlFlow.outputValues
.map { .map {
when (it) { when (it) {
is ExpressionValue -> resultExpression?.getText() is ExpressionValue -> resultExpression?.getText()
@@ -373,9 +372,9 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp
if (originalExpression is JetReturnExpression) originalExpression.getReturnedExpression() else originalExpression if (originalExpression is JetReturnExpression) originalExpression.getReturnedExpression() else originalExpression
if (currentResultExpression == null) return if (currentResultExpression == null) return
val newResultExpression = controlFlow.defaultOutputValue?.let { val newResultExpression = descriptor.controlFlow.defaultOutputValue?.let {
val boxedExpression = originalExpression.replaced(replacingExpression).getReturnedExpression()!! val boxedExpression = originalExpression.replaced(replacingExpression).getReturnedExpression()!!
controlFlow.outputValueBoxer.extractExpressionByValue(boxedExpression, it) descriptor.controlFlow.outputValueBoxer.extractExpressionByValue(boxedExpression, it)
} }
if (newResultExpression == null) { if (newResultExpression == null) {
throw AssertionError("Can' replace '${originalExpression.getText()}' with '${replacingExpression.getText()}'") throw AssertionError("Can' replace '${originalExpression.getText()}' with '${replacingExpression.getText()}'")
@@ -398,14 +397,14 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp
* Sort by descending position so that internals of value/type arguments in calls and qualified types are replaced * Sort by descending position so that internals of value/type arguments in calls and qualified types are replaced
* before calls/types themselves * before calls/types themselves
*/ */
for ((offsetInBody, resolveResult) in extractionData.refOffsetToDeclaration.entrySet().sortDescendingBy { it.key }) { for ((offsetInBody, resolveResult) in descriptor.extractionData.refOffsetToDeclaration.entrySet().sortDescendingBy { it.key }) {
val expr = file.findElementAt(bodyOffset + offsetInBody)?.getNonStrictParentOfType<JetSimpleNameExpression>() val expr = file.findElementAt(bodyOffset + offsetInBody)?.getNonStrictParentOfType<JetSimpleNameExpression>()
assert(expr != null, "Couldn't find expression at $offsetInBody in '${body.getText()}'") assert(expr != null, "Couldn't find expression at $offsetInBody in '${body.getText()}'")
originalOffsetByExpr[expr!!] = offsetInBody originalOffsetByExpr[expr!!] = offsetInBody
replacementMap[offsetInBody]?.let { replacement -> descriptor.replacementMap[offsetInBody]?.let { replacement ->
if (replacement !is ParameterReplacement || replacement.parameter != receiverParameter) { if (replacement !is ParameterReplacement || replacement.parameter != descriptor.receiverParameter) {
exprReplacementMap[expr] = replacement exprReplacementMap[expr] = replacement
} }
} }
@@ -414,11 +413,11 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp
val replacingReturn: JetExpression? val replacingReturn: JetExpression?
val expressionsToReplaceWithReturn: List<JetElement> val expressionsToReplaceWithReturn: List<JetElement>
val jumpValue = controlFlow.jumpOutputValue val jumpValue = descriptor.controlFlow.jumpOutputValue
if (jumpValue != null) { if (jumpValue != null) {
replacingReturn = psiFactory.createExpression(if (jumpValue.conditional) "return true" else "return") replacingReturn = psiFactory.createExpression(if (jumpValue.conditional) "return true" else "return")
expressionsToReplaceWithReturn = jumpValue.elementsToReplace.map { jumpElement -> expressionsToReplaceWithReturn = jumpValue.elementsToReplace.map { jumpElement ->
val offsetInBody = jumpElement.getTextRange()!!.getStartOffset() - extractionData.originalStartOffset!! val offsetInBody = jumpElement.getTextRange()!!.getStartOffset() - descriptor.extractionData.originalStartOffset!!
val expr = file.findElementAt(bodyOffset + offsetInBody)?.getNonStrictParentOfType(jumpElement.javaClass) val expr = file.findElementAt(bodyOffset + offsetInBody)?.getNonStrictParentOfType(jumpElement.javaClass)
assert(expr != null, "Couldn't find expression at $offsetInBody in '${body.getText()}'") assert(expr != null, "Couldn't find expression at $offsetInBody in '${body.getText()}'")
@@ -444,7 +443,7 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp
val firstExpression = body.getStatements().firstOrNull() val firstExpression = body.getStatements().firstOrNull()
if (firstExpression != null) { if (firstExpression != null) {
for (param in parameters) { for (param in descriptor.parameters) {
param.mirrorVarName?.let { varName -> param.mirrorVarName?.let { varName ->
body.addBefore(psiFactory.createProperty(varName, null, true, param.name), firstExpression) body.addBefore(psiFactory.createProperty(varName, null, true, param.name), firstExpression)
body.addBefore(psiFactory.createNewLine(), firstExpression) body.addBefore(psiFactory.createNewLine(), firstExpression)
@@ -452,13 +451,13 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp
} }
} }
val defaultValue = controlFlow.defaultOutputValue val defaultValue = descriptor.controlFlow.defaultOutputValue
val lastExpression = body.getStatements().lastOrNull() as? JetExpression val lastExpression = body.getStatements().lastOrNull() as? JetExpression
if (lastExpression is JetReturnExpression) return if (lastExpression is JetReturnExpression) return
val (defaultExpression, expressionToUnifyWith) = val (defaultExpression, expressionToUnifyWith) =
if (!options.inTempFile && defaultValue != null && controlFlow.outputValueBoxer.boxingRequired && lastExpression!!.isMultiLine()) { if (!generatorOptions.inTempFile && defaultValue != null && descriptor.controlFlow.outputValueBoxer.boxingRequired && lastExpression!!.isMultiLine()) {
val varNameValidator = JetNameValidatorImpl(body, lastExpression, JetNameValidatorImpl.Target.PROPERTIES) val varNameValidator = JetNameValidatorImpl(body, lastExpression, JetNameValidatorImpl.Target.PROPERTIES)
val resultVal = JetNameSuggester.suggestNames(defaultValue.valueType, varNameValidator, null).first() val resultVal = JetNameSuggester.suggestNames(defaultValue.valueType, varNameValidator, null).first()
val newDecl = body.addBefore(psiFactory.createDeclaration("val $resultVal = ${lastExpression!!.getText()}"), lastExpression) as JetProperty val newDecl = body.addBefore(psiFactory.createDeclaration("val $resultVal = ${lastExpression!!.getText()}"), lastExpression) as JetProperty
@@ -469,7 +468,7 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp
lastExpression to null lastExpression to null
} }
val returnExpression = controlFlow.outputValueBoxer.getReturnExpression(getReturnArguments(defaultExpression), psiFactory) val returnExpression = descriptor.controlFlow.outputValueBoxer.getReturnExpression(getReturnArguments(defaultExpression), psiFactory)
if (returnExpression == null) return if (returnExpression == null) return
when { when {
@@ -482,7 +481,7 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp
} }
fun insertDeclaration(declaration: JetNamedDeclaration, anchor: PsiElement): JetNamedDeclaration { fun insertDeclaration(declaration: JetNamedDeclaration, anchor: PsiElement): JetNamedDeclaration {
return with(extractionData) { return with(descriptor.extractionData) {
val targetContainer = anchor.getParent()!! val targetContainer = anchor.getParent()!!
val emptyLines = psiFactory.createWhiteSpace("\n\n") val emptyLines = psiFactory.createWhiteSpace("\n\n")
if (insertBefore) { if (insertBefore) {
@@ -500,9 +499,9 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp
} }
} }
val duplicates = if (options.inTempFile) Collections.emptyList() else findDuplicates() val duplicates = if (generatorOptions.inTempFile) Collections.emptyList() else descriptor.findDuplicates()
val anchor = with(extractionData) { val anchor = with(descriptor.extractionData) {
val anchorCandidates = duplicates.mapTo(ArrayList<PsiElement>()) { it.range.elements.first() } val anchorCandidates = duplicates.mapTo(ArrayList<PsiElement>()) { it.range.elements.first() }
anchorCandidates.add(targetSibling) anchorCandidates.add(targetSibling)
@@ -518,7 +517,7 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp
marginalCandidate.parents().first { it.getParent() == targetParent } marginalCandidate.parents().first { it.getParent() == targetParent }
} }
val declaration = createDeclaration().let { if (options.inTempFile) it else insertDeclaration(it, anchor) } val declaration = createDeclaration().let { if (generatorOptions.inTempFile) it else insertDeclaration(it, anchor) }
adjustDeclarationBody(declaration) adjustDeclarationBody(declaration)
if (declaration is JetNamedFunction && declaration.getContainingJetFile().suppressDiagnosticsInDebugMode) { if (declaration is JetNamedFunction && declaration.getContainingJetFile().suppressDiagnosticsInDebugMode) {
@@ -533,11 +532,11 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp
} }
} }
if (options.inTempFile) return ExtractionResult(declaration, Collections.emptyMap(), nameByOffset) if (generatorOptions.inTempFile) return ExtractionResult(this, declaration, Collections.emptyMap(), nameByOffset)
makeCall(this, declaration, controlFlow, extractionData.originalRange, parameters.map { it.argumentText }) makeCall(descriptor, declaration, descriptor.controlFlow, descriptor.extractionData.originalRange, descriptor.parameters.map { it.argumentText })
ShortenReferences.DEFAULT.process(declaration) ShortenReferences.DEFAULT.process(declaration)
val duplicateReplacers = duplicates.map { it.range to { makeCall(this, declaration, it.controlFlow, it.range, it.arguments) } }.toMap() val duplicateReplacers = duplicates.map { it.range to { makeCall(descriptor, declaration, it.controlFlow, it.range, it.arguments) } }.toMap()
return ExtractionResult(declaration, duplicateReplacers, nameByOffset) return ExtractionResult(this, declaration, duplicateReplacers, nameByOffset)
} }
@@ -31,18 +31,14 @@ import org.jetbrains.kotlin.renderer.DescriptorRenderer
import kotlin.test.assertEquals import kotlin.test.assertEquals
import org.jetbrains.kotlin.idea.JetLightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.JetLightCodeInsightFixtureTestCase
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandlerHelper
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionGeneratorOptions
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractableCodeDescriptor
import org.jetbrains.kotlin.test.ConfigLibraryUtil import org.jetbrains.kotlin.test.ConfigLibraryUtil
import org.jetbrains.kotlin.idea.PluginTestCaseBase import org.jetbrains.kotlin.idea.PluginTestCaseBase
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionData
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionOptions
import org.jetbrains.kotlin.psi.JetDeclaration import org.jetbrains.kotlin.psi.JetDeclaration
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.psi.JetPackageDirective import org.jetbrains.kotlin.psi.JetPackageDirective
import org.jetbrains.kotlin.utils.emptyOrSingletonList import org.jetbrains.kotlin.utils.emptyOrSingletonList
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
import kotlin.test.assertTrue import kotlin.test.assertTrue
public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTestCase() { public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTestCase() {
@@ -106,26 +102,28 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
val editor = fixture.getEditor() val editor = fixture.getEditor()
val handler = ExtractKotlinFunctionHandler( val handler = ExtractKotlinFunctionHandler(
helper = object : ExtractKotlinFunctionHandlerHelper() { helper = object : ExtractionEngineHelper() {
override fun adjustExtractionData(data: ExtractionData): ExtractionData { override fun adjustExtractionData(data: ExtractionData): ExtractionData {
return data.copy(options = extractionOptions) return data.copy(options = extractionOptions)
} }
override fun adjustGeneratorOptions(options: ExtractionGeneratorOptions): ExtractionGeneratorOptions { override fun configure(
return options.copy(extractAsProperty = extractAsProperty) descriptor: ExtractableCodeDescriptor,
} generatorOptions: ExtractionGeneratorOptions
): ExtractionGeneratorConfiguration {
override fun adjustDescriptor(descriptor: ExtractableCodeDescriptor): ExtractableCodeDescriptor { val allParameters = emptyOrSingletonList(descriptor.receiverParameter) + descriptor.parameters
val allParameters = emptyOrSingletonList(descriptor.receiverParameter) + descriptor.parameters val actualDescriptors = allParameters.map { renderer.render(it.originalDescriptor) }.joinToString()
val actualDescriptors = allParameters.map { renderer.render(it.originalDescriptor) }.joinToString() val actualTypes = allParameters.map {
val actualTypes = allParameters.map { it.parameterTypeCandidates.map { renderer.renderType(it) }.joinToString(", ", "[", "]")
it.getParameterTypeCandidates(extractionOptions.allowSpecialClassNames).map { renderer.renderType(it) }.joinToString(", ", "[", "]") }.joinToString()
}.joinToString()
assertEquals(expectedDescriptors, actualDescriptors, "Expected descriptors mismatch.") assertEquals(expectedDescriptors, actualDescriptors, "Expected descriptors mismatch.")
assertEquals(expectedTypes, actualTypes, "Expected types mismatch.") assertEquals(expectedTypes, actualTypes, "Expected types mismatch.")
return if (descriptor.name == "") descriptor.copy(name = "__dummyTestFun__") else descriptor return ExtractionGeneratorConfiguration(
if (descriptor.name == "") descriptor.copy(name = "__dummyTestFun__") else descriptor,
generatorOptions.copy(extractAsProperty = extractAsProperty)
)
} }
} }
) )