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
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.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.refactoring.createTempCopy
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
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 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 com.intellij.psi.PsiManager
import com.intellij.psi.impl.PsiModificationTrackerImpl
import org.jetbrains.kotlin.psi.*
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.actions.internal.KotlinInternalMode
import org.jetbrains.kotlin.psi.psiUtil.replaced
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
fun getFunctionForExtractedFragment(
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(",")}")
}
return validationResult.descriptor
.generateDeclaration(ExtractionGeneratorOptions(inTempFile = true, flexibleTypesAllowed = true))
.declaration as JetNamedFunction
val config = ExtractionGeneratorConfiguration(
validationResult.descriptor,
ExtractionGeneratorOptions(inTempFile = true, flexibleTypesAllowed = true)
)
return config.generateDeclaration().declaration as JetNamedFunction
}
return runReadAction { generateFunction() }
@@ -24,7 +24,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.idea.refactoring.changeSignature.JetChangeSignatureHandler;
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandler;
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandlerHelper;
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler;
import org.jetbrains.kotlin.idea.refactoring.safeDelete.SafeDeletePackage;
import org.jetbrains.kotlin.psi.*;
@@ -47,7 +46,7 @@ public class JetRefactoringSupportProvider extends RefactoringSupportProvider {
@NotNull
public RefactoringActionHandler getExtractFunctionToScopeHandler() {
return new ExtractKotlinFunctionHandler(true, ExtractKotlinFunctionHandlerHelper.DEFAULT);
return new ExtractKotlinFunctionHandler(true, ExtractKotlinFunctionHandler.InteractiveExtractionHelper.INSTANCE$);
}
@Override
@@ -25,44 +25,29 @@ import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.refactoring.JetRefactoringBundle
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ui.KotlinExtractFunctionDialog
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 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.idea.util.psi.patternMatching.toRange
import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
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 val allContainersEnabled: Boolean = false,
private val helper: ExtractKotlinFunctionHandlerHelper = ExtractKotlinFunctionHandlerHelper.DEFAULT) : RefactoringActionHandler {
private fun adjustElements(elements: List<PsiElement>): List<PsiElement> {
if (elements.size() != 1) return elements
private val helper: ExtractionEngineHelper = ExtractKotlinFunctionHandler.InteractiveExtractionHelper) : RefactoringActionHandler {
val e = elements.first()
if (e is JetBlockExpression && e.getParent() is JetFunctionLiteral) return e.getStatements()
return elements
object InteractiveExtractionHelper : ExtractionEngineHelper() {
override fun configureInteractively(
project: Project,
editor: Editor,
descriptorWithConflicts: ExtractableCodeDescriptorWithConflicts,
continuation: (ExtractionGeneratorConfiguration) -> Unit
) {
KotlinExtractFunctionDialog(descriptorWithConflicts.descriptor.extractionData.project, descriptorWithConflicts) {
continuation(it.getCurrentConfiguration())
}.show()
}
}
fun doInvoke(
@@ -71,66 +56,18 @@ public class ExtractKotlinFunctionHandler(
elements: List<PsiElement>,
targetSibling: PsiElement
) {
val project = file.getProject()
fun adjustElements(elements: List<PsiElement>): List<PsiElement> {
if (elements.size() != 1) return elements
val analysisResult = helper.adjustExtractionData(
ExtractionData(file, adjustElements(elements).toRange(false), targetSibling)
).performAnalysis()
val e = elements.first()
if (e is JetBlockExpression && e.getParent() is JetFunctionLiteral) return e.getStatements()
if (ApplicationManager.getApplication()!!.isUnitTestMode() && analysisResult.status != Status.SUCCESS) {
throw ConflictsInTestsException(analysisResult.messages.map { it.renderMessage() })
return elements
}
fun doRefactor(descriptor: ExtractableCodeDescriptor, generatorOptions: ExtractionGeneratorOptions) {
val adjustedDescriptor = helper.adjustDescriptor(descriptor)
val adjustedGeneratorOptions = helper.adjustGeneratorOptions(generatorOptions)
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()
val extractionData = ExtractionData(file, adjustElements(elements).toRange(false), targetSibling)
ExtractionEngine(EXTRACT_FUNCTION, helper).run(editor, extractionData) {
processDuplicates(it.duplicateReplacers, file.getProject(), editor)
}
}
@@ -110,8 +110,9 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
setOKActionEnabled(checkNames());
signaturePreviewField.setText(
ExtractionEnginePackage.getDeclarationText(currentDescriptor, getGeneratorOptions(), false,
IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES)
ExtractionEnginePackage.getDeclarationText(getCurrentConfiguration(),
false,
IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES)
);
}
@@ -270,8 +271,8 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
}
@NotNull
public ExtractableCodeDescriptor getCurrentDescriptor() {
return currentDescriptor;
public ExtractionGeneratorConfiguration getCurrentConfiguration() {
return new ExtractionGeneratorConfiguration(currentDescriptor, getGeneratorOptions());
}
@NotNull
@@ -324,7 +324,13 @@ data class ExtractionGeneratorOptions(
}
}
data class ExtractionGeneratorConfiguration(
val descriptor: ExtractableCodeDescriptor,
val generatorOptions: ExtractionGeneratorOptions
)
data class ExtractionResult(
val config: ExtractionGeneratorConfiguration,
val declaration: JetNamedDeclaration,
val duplicateReplacers: Map<JetPsiRange, () -> Unit>,
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 {
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 bindingContext = result.declaration.getGeneratedBlockBody().analyze()
@@ -46,22 +46,21 @@ import java.util.Collections
import java.util.HashMap
import java.util.LinkedHashMap
fun ExtractableCodeDescriptor.getDeclarationText(
options: ExtractionGeneratorOptions = ExtractionGeneratorOptions.DEFAULT,
fun ExtractionGeneratorConfiguration.getDeclarationText(
withBody: Boolean = true,
descriptorRenderer: DescriptorRenderer = if (options.flexibleTypesAllowed)
descriptorRenderer: DescriptorRenderer = if (generatorOptions.flexibleTypesAllowed)
DescriptorRenderer.FLEXIBLE_TYPES_FOR_CODE
else IdeDescriptorRenderers.SOURCE_CODE
): String {
if (!canGenerateProperty() && options.extractAsProperty) {
throw IllegalArgumentException("Can't generate property: ${extractionData.getCodeFragmentText()}")
if (!descriptor.canGenerateProperty() && generatorOptions.extractAsProperty) {
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 ->
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 {
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())
}
builder.typeConstraints(typeParameters.flatMap { it.originalConstraints }.map { it.getText()!! })
builder.typeConstraints(descriptor.typeParameters.flatMap { it.originalConstraints }.map { it.getText()!! })
if (withBody) {
builder.blockBody(extractionData.getCodeFragmentText())
builder.blockBody(descriptor.extractionData.getCodeFragmentText())
}
builder.asString()
@@ -335,23 +334,23 @@ private fun makeCall(
}
}
fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOptions): ExtractionResult{
val psiFactory = JetPsiFactory(extractionData.originalFile)
fun ExtractionGeneratorConfiguration.generateDeclaration(): ExtractionResult{
val psiFactory = JetPsiFactory(descriptor.extractionData.originalFile)
val nameByOffset = HashMap<Int, JetElement>()
fun createDeclaration(): JetNamedDeclaration {
return with(extractionData) {
if (options.inTempFile) {
createTemporaryDeclaration("${getDeclarationText(options)}\n")
return with(descriptor.extractionData) {
if (generatorOptions.inTempFile) {
createTemporaryDeclaration("${getDeclarationText()}\n")
}
else {
psiFactory.createDeclaration(getDeclarationText(options))
psiFactory.createDeclaration(getDeclarationText())
}
}
}
fun getReturnArguments(resultExpression: JetExpression?): List<String> {
return controlFlow.outputValues
return descriptor.controlFlow.outputValues
.map {
when (it) {
is ExpressionValue -> resultExpression?.getText()
@@ -373,9 +372,9 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp
if (originalExpression is JetReturnExpression) originalExpression.getReturnedExpression() else originalExpression
if (currentResultExpression == null) return
val newResultExpression = controlFlow.defaultOutputValue?.let {
val newResultExpression = descriptor.controlFlow.defaultOutputValue?.let {
val boxedExpression = originalExpression.replaced(replacingExpression).getReturnedExpression()!!
controlFlow.outputValueBoxer.extractExpressionByValue(boxedExpression, it)
descriptor.controlFlow.outputValueBoxer.extractExpressionByValue(boxedExpression, it)
}
if (newResultExpression == null) {
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
* 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>()
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) {
descriptor.replacementMap[offsetInBody]?.let { replacement ->
if (replacement !is ParameterReplacement || replacement.parameter != descriptor.receiverParameter) {
exprReplacementMap[expr] = replacement
}
}
@@ -414,11 +413,11 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp
val replacingReturn: JetExpression?
val expressionsToReplaceWithReturn: List<JetElement>
val jumpValue = controlFlow.jumpOutputValue
val jumpValue = descriptor.controlFlow.jumpOutputValue
if (jumpValue != null) {
replacingReturn = psiFactory.createExpression(if (jumpValue.conditional) "return true" else "return")
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)
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()
if (firstExpression != null) {
for (param in parameters) {
for (param in descriptor.parameters) {
param.mirrorVarName?.let { varName ->
body.addBefore(psiFactory.createProperty(varName, null, true, param.name), 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
if (lastExpression is JetReturnExpression) return
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 resultVal = JetNameSuggester.suggestNames(defaultValue.valueType, varNameValidator, null).first()
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
}
val returnExpression = controlFlow.outputValueBoxer.getReturnExpression(getReturnArguments(defaultExpression), psiFactory)
val returnExpression = descriptor.controlFlow.outputValueBoxer.getReturnExpression(getReturnArguments(defaultExpression), psiFactory)
if (returnExpression == null) return
when {
@@ -482,7 +481,7 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp
}
fun insertDeclaration(declaration: JetNamedDeclaration, anchor: PsiElement): JetNamedDeclaration {
return with(extractionData) {
return with(descriptor.extractionData) {
val targetContainer = anchor.getParent()!!
val emptyLines = psiFactory.createWhiteSpace("\n\n")
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() }
anchorCandidates.add(targetSibling)
@@ -518,7 +517,7 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp
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)
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)
val duplicateReplacers = duplicates.map { it.range to { makeCall(this, declaration, it.controlFlow, it.range, it.arguments) } }.toMap()
return ExtractionResult(declaration, duplicateReplacers, nameByOffset)
val duplicateReplacers = duplicates.map { it.range to { makeCall(descriptor, declaration, it.controlFlow, it.range, it.arguments) } }.toMap()
return ExtractionResult(this, declaration, duplicateReplacers, nameByOffset)
}
@@ -31,18 +31,14 @@ import org.jetbrains.kotlin.renderer.DescriptorRenderer
import kotlin.test.assertEquals
import org.jetbrains.kotlin.idea.JetLightCodeInsightFixtureTestCase
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.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 com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.psi.JetPackageDirective
import org.jetbrains.kotlin.utils.emptyOrSingletonList
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
import kotlin.test.assertTrue
public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTestCase() {
@@ -106,26 +102,28 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
val editor = fixture.getEditor()
val handler = ExtractKotlinFunctionHandler(
helper = object : ExtractKotlinFunctionHandlerHelper() {
helper = object : ExtractionEngineHelper() {
override fun adjustExtractionData(data: ExtractionData): ExtractionData {
return data.copy(options = extractionOptions)
}
override fun adjustGeneratorOptions(options: ExtractionGeneratorOptions): ExtractionGeneratorOptions {
return options.copy(extractAsProperty = extractAsProperty)
}
override fun adjustDescriptor(descriptor: ExtractableCodeDescriptor): ExtractableCodeDescriptor {
val allParameters = emptyOrSingletonList(descriptor.receiverParameter) + descriptor.parameters
val actualDescriptors = allParameters.map { renderer.render(it.originalDescriptor) }.joinToString()
val actualTypes = allParameters.map {
it.getParameterTypeCandidates(extractionOptions.allowSpecialClassNames).map { renderer.renderType(it) }.joinToString(", ", "[", "]")
}.joinToString()
override fun configure(
descriptor: ExtractableCodeDescriptor,
generatorOptions: ExtractionGeneratorOptions
): ExtractionGeneratorConfiguration {
val allParameters = emptyOrSingletonList(descriptor.receiverParameter) + descriptor.parameters
val actualDescriptors = allParameters.map { renderer.render(it.originalDescriptor) }.joinToString()
val actualTypes = allParameters.map {
it.parameterTypeCandidates.map { renderer.renderType(it) }.joinToString(", ", "[", "]")
}.joinToString()
assertEquals(expectedDescriptors, actualDescriptors, "Expected descriptors 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)
)
}
}
)