From 48914e70ffca4bafb37edb732e6dfbe498f4ac1d Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Wed, 25 Feb 2015 11:26:33 +0300 Subject: [PATCH] Refactoring: Introduce ExtractionEngine class --- .../extractFunctionForDebuggerUtil.kt | 16 +-- .../JetRefactoringSupportProvider.java | 3 +- .../ExtractKotlinFunctionHandler.kt | 103 +++------------ .../ui/KotlinExtractFunctionDialog.java | 9 +- .../ExtractableCodeDescriptor.kt | 6 + .../extractionEngine/ExtractionEngine.kt | 121 ++++++++++++++++++ .../extractableAnalysisUtil.kt | 2 +- .../extractionEngine/extractorUtil.kt | 71 +++++----- .../AbstractJetExtractionTest.kt | 32 +++-- 9 files changed, 210 insertions(+), 153 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt index 5ac8b893205..497ac09bc55 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt @@ -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() } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/JetRefactoringSupportProvider.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/JetRefactoringSupportProvider.java index 747f5bd6ca9..438547b96c8 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/JetRefactoringSupportProvider.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/JetRefactoringSupportProvider.java @@ -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 diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ExtractKotlinFunctionHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ExtractKotlinFunctionHandler.kt index e0ab956c2ff..71a0047a736 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ExtractKotlinFunctionHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ExtractKotlinFunctionHandler.kt @@ -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): List { - 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, targetSibling: PsiElement ) { - val project = file.getProject() + fun adjustElements(elements: List): List { + 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(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

Proceed with extraction", - 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) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.java index e1c8bca4dc4..2c029482692 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractFunction/ui/KotlinExtractFunctionDialog.java @@ -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 diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt index 3c3b066199f..c28e5e44a75 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt @@ -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 Unit>, val nameByOffset: Map diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt new file mode 100644 index 00000000000..c137aaa474c --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionEngine.kt @@ -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(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

Proceed with extraction", + 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() + } + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt index bde32bb9938..c5bdc7bc1f1 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt @@ -841,7 +841,7 @@ private fun JetNamedDeclaration.getGeneratedBlockBody() = fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts { val conflicts = MultiMap() - 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() diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt index bce1a6f0e76..18feb36f6cc 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt @@ -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() 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 { - 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() 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 - 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()) { 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) } diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/AbstractJetExtractionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/AbstractJetExtractionTest.kt index 2e0b6014076..7bfb31fe59d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/AbstractJetExtractionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/AbstractJetExtractionTest.kt @@ -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) + ) } } )