diff --git a/annotations/com/intellij/codeInsight/folding/annotations.xml b/annotations/com/intellij/codeInsight/folding/annotations.xml
new file mode 100644
index 00000000000..8a507eaaf30
--- /dev/null
+++ b/annotations/com/intellij/codeInsight/folding/annotations.xml
@@ -0,0 +1,10 @@
+
+ -
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/annotations/com/intellij/codeInsight/folding/impl/annotations.xml b/annotations/com/intellij/codeInsight/folding/impl/annotations.xml
new file mode 100644
index 00000000000..9c7706fb5ee
--- /dev/null
+++ b/annotations/com/intellij/codeInsight/folding/impl/annotations.xml
@@ -0,0 +1,10 @@
+
+ -
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/annotations/com/intellij/codeInsight/highlighting/annotations.xml b/annotations/com/intellij/codeInsight/highlighting/annotations.xml
new file mode 100644
index 00000000000..55381ff35c0
--- /dev/null
+++ b/annotations/com/intellij/codeInsight/highlighting/annotations.xml
@@ -0,0 +1,6 @@
+
+ -
+
+
+
\ No newline at end of file
diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/pseudocode/instructions/jumps/ReturnValueInstruction.kt b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/pseudocode/instructions/jumps/ReturnValueInstruction.kt
index 57bf94d911d..9950f6ef136 100644
--- a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/pseudocode/instructions/jumps/ReturnValueInstruction.kt
+++ b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/pseudocode/instructions/jumps/ReturnValueInstruction.kt
@@ -49,7 +49,5 @@ public class ReturnValueInstruction(
return ReturnValueInstruction((element as JetExpression), lexicalScope, newLabel, returnedValue)
}
- public val resultExpression: JetExpression =
- element.let{ if (it is JetReturnExpression) it.getReturnedExpression()!! else element as JetExpression }
public val returnExpressionIfAny: JetReturnExpression? = element as? JetReturnExpression
}
diff --git a/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt b/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt
index a2dbff11f3e..bc957846a64 100644
--- a/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt
+++ b/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/extractFunctionForDebuggerUtil.kt
@@ -24,7 +24,6 @@ import org.jetbrains.jet.plugin.refactoring.createTempCopy
import org.jetbrains.jet.lang.psi.codeFragmentUtil.skipVisibilityCheck
import com.intellij.psi.PsiElement
import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractionData
-import java.util.Collections
import org.jetbrains.jet.plugin.refactoring.extractFunction.performAnalysis
import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult.Status
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
@@ -37,6 +36,7 @@ import org.jetbrains.jet.lang.psi.*
import org.jetbrains.jet.plugin.intentions.InsertExplicitTypeArguments
import org.jetbrains.jet.plugin.refactoring.extractFunction.ExtractionGeneratorOptions
import org.jetbrains.jet.plugin.refactoring.extractFunction.generateDeclaration
+import org.jetbrains.jet.plugin.util.psi.patternMatching.toRange
import org.jetbrains.jet.plugin.actions.internal.KotlinInternalMode
import org.jetbrains.jet.lang.psi.psiUtil.replaced
@@ -97,7 +97,7 @@ fun getFunctionForExtractedFragment(
if (targetSibling == null) return null
val analysisResult = ExtractionData(
- tmpFile, Collections.singletonList(newDebugExpression), targetSibling, ExtractionOptions(false, true)
+ tmpFile, newDebugExpression.toRange(), targetSibling, ExtractionOptions(false, true)
).performAnalysis()
if (analysisResult.status != Status.SUCCESS) {
throw EvaluateExceptionUtil.createEvaluateException(getErrorMessageForExtractFunctionResult(analysisResult))
diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties
index eb2f1500af0..9339afa88e0 100644
--- a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties
+++ b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties
@@ -20,6 +20,8 @@ cannot.refactor.expression.should.have.inferred.type=Expression should have infe
cannot.refactor.synthesized.function=Cannot refactor synthesized function ''{0}''
error.types.in.generated.function=Cannot generate function with erroneous return type
+0.has.detected.1.code.fragments.in.this.file.that.can.be.replaced.with.a.call.to.extracted.declaration={0} has detected {1} code {1,choice,1#fragment|2#fragments} in this file that can be replaced with a call to extracted declaration. Would you like to review and replace {1,choice,1#it|2#them}?
+
error.wrong.caret.position.function.or.constructor.name=The caret should be positioned at the name of the function or constructor to be refactored.
error.cant.refactor.vararg.functions=Can't refactor the function with variable arguments
function.name.is.invalid=Function name is invalid
diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt
index c57bd7d30b5..2ff228e1180 100644
--- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt
+++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractKotlinFunctionHandler.kt
@@ -66,6 +66,7 @@ import org.jetbrains.jet.lang.descriptors.FunctionDescriptor
import org.jetbrains.jet.renderer.DescriptorRenderer
import org.jetbrains.jet.lang.psi.JetPropertyAccessor
import org.jetbrains.jet.lang.psi.JetClassOrObject
+import org.jetbrains.jet.plugin.util.psi.patternMatching.toRange
import org.jetbrains.jet.lang.psi.JetMultiDeclaration
public open class ExtractKotlinFunctionHandlerHelper {
@@ -81,6 +82,15 @@ public open class 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
+
+ val e = elements.first()
+ if (e is JetBlockExpression && e.getParent() is JetFunctionLiteral) return e.getStatements()
+
+ return elements
+ }
+
fun doInvoke(
editor: Editor,
file: JetFile,
@@ -89,7 +99,9 @@ public class ExtractKotlinFunctionHandler(
) {
val project = file.getProject()
- val analysisResult = helper.adjustExtractionData(ExtractionData(file, elements, targetSibling)).performAnalysis()
+ val analysisResult = helper.adjustExtractionData(
+ ExtractionData(file, adjustElements(elements).toRange(false), targetSibling)
+ ).performAnalysis()
if (ApplicationManager.getApplication()!!.isUnitTestMode() && analysisResult.status != Status.SUCCESS) {
throw ConflictsInTestsException(analysisResult.messages.map { it.renderMessage() })
@@ -98,7 +110,8 @@ public class ExtractKotlinFunctionHandler(
fun doRefactor(descriptor: ExtractableCodeDescriptor, generatorOptions: ExtractionGeneratorOptions) {
val adjustedDescriptor = helper.adjustDescriptor(descriptor)
val adjustedGeneratorOptions = helper.adjustGeneratorOptions(generatorOptions)
- project.executeWriteCommand(EXTRACT_FUNCTION) { adjustedDescriptor.generateDeclaration(adjustedGeneratorOptions) }
+ val result = project.executeWriteCommand(EXTRACT_FUNCTION) { adjustedDescriptor.generateDeclaration(adjustedGeneratorOptions) }
+ processDuplicates(result.duplicateReplacers, project, editor)
}
fun validateAndRefactor() {
diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractableCodeDescriptor.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractableCodeDescriptor.kt
index 7e9f9399d0c..bb95f405865 100644
--- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractableCodeDescriptor.kt
+++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractableCodeDescriptor.kt
@@ -64,6 +64,8 @@ import kotlin.properties.Delegates
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.jet.lang.psi.JetCallElement
import org.jetbrains.jet.lang.psi.psiUtil.getQualifiedElementSelector
+import org.jetbrains.jet.plugin.util.psi.patternMatching.JetPsiRange
+import org.jetbrains.jet.lang.resolve.BindingContext
trait Parameter {
val argumentText: String
@@ -123,29 +125,37 @@ class FqNameReplacement(val fqName: FqName): Replacement {
}
trait OutputValue {
+ val originalExpressions: List
val valueType: JetType
class ExpressionValue(
val callSiteReturn: Boolean,
+ override val originalExpressions: List,
override val valueType: JetType
): OutputValue
class Jump(
- val elementsToReplace: List,
+ val elementsToReplace: List,
val elementToInsertAfterCall: JetElement,
val conditional: Boolean
): OutputValue {
+ override val originalExpressions: List get() = elementsToReplace
override val valueType: JetType = with(KotlinBuiltIns.getInstance()) { if (conditional) getBooleanType() else getUnitType() }
}
- class ParameterUpdate(val parameter: Parameter): OutputValue {
+ class ParameterUpdate(
+ val parameter: Parameter,
+ override val originalExpressions: List
+ ): OutputValue {
override val valueType: JetType get() = parameter.parameterType
}
class Initializer(
val initializedDeclaration: JetProperty,
override val valueType: JetType
- ): OutputValue
+ ): OutputValue {
+ override val originalExpressions: List get() = Collections.singletonList(initializedDeclaration)
+ }
}
abstract class OutputValueBoxer(val outputValues: List) {
@@ -315,6 +325,7 @@ data class ExtractionGeneratorOptions(
data class ExtractionResult(
val declaration: JetNamedDeclaration,
+ val duplicateReplacers: Map Unit>,
val nameByOffset: Map
)
diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt
index d510692b9fb..520514d4744 100644
--- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt
+++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ExtractionData.kt
@@ -51,6 +51,7 @@ import org.jetbrains.jet.lang.psi.JetFunctionLiteral
import org.jetbrains.jet.lang.psi.JetClassInitializer
import org.jetbrains.jet.lang.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils
+import org.jetbrains.jet.plugin.util.psi.patternMatching.JetPsiRange
data class ExtractionOptions(
val inferUnitTypeForUnusedValues: Boolean,
@@ -76,11 +77,12 @@ data class ResolvedReferenceInfo(
data class ExtractionData(
val originalFile: JetFile,
- val originalElements: List,
+ val originalRange: JetPsiRange,
val targetSibling: PsiElement,
val options: ExtractionOptions = ExtractionOptions.DEFAULT
) {
val project: Project = originalFile.getProject()
+ val originalElements: List = originalRange.elements
val insertBefore: Boolean = targetSibling.getParentByType(javaClass(), true)?.let {
it is JetDeclarationWithBody || it is JetClassInitializer
diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/duplicateUtil.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/duplicateUtil.kt
new file mode 100644
index 00000000000..9f8f8563951
--- /dev/null
+++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/duplicateUtil.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2010-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.jet.plugin.refactoring.extractFunction
+
+import org.jetbrains.jet.plugin.util.psi.patternMatching.JetPsiRange
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.ui.Messages
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.editor.Editor
+import java.util.ArrayList
+import com.intellij.openapi.editor.markup.RangeHighlighter
+import com.intellij.openapi.editor.ScrollType
+import org.jetbrains.jet.plugin.refactoring.JetRefactoringBundle
+import com.intellij.openapi.application.ApplicationNamesInfo
+import com.intellij.openapi.util.Ref
+import com.intellij.openapi.editor.colors.EditorColorsManager
+import com.intellij.openapi.editor.colors.EditorColors
+import com.intellij.codeInsight.highlighting.HighlightManager
+import com.intellij.codeInsight.folding.CodeFoldingManager
+import com.intellij.ui.ReplacePromptDialog
+import com.intellij.find.FindManager
+import org.jetbrains.jet.plugin.refactoring.executeWriteCommand
+import com.intellij.refactoring.util.duplicates.MethodDuplicatesHandler
+import com.intellij.refactoring.RefactoringBundle
+
+public fun JetPsiRange.highlight(project: Project, editor: Editor): RangeHighlighter? {
+ val textRange = getTextRange()
+ val highlighters = ArrayList()
+ val attributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES)!!
+ HighlightManager.getInstance(project).addRangeHighlight(
+ editor, textRange.getStartOffset(), textRange.getEndOffset(), attributes, true, highlighters
+ )
+ return highlighters.firstOrNull()
+}
+
+public fun JetPsiRange.preview(project: Project, editor: Editor): RangeHighlighter? {
+ return highlight(project, editor)?.let {
+ val startOffset = getTextRange().getStartOffset()
+ val foldedRegions =
+ CodeFoldingManager.getInstance(project)
+ .getFoldRegionsAtOffset(editor, startOffset)
+ .filter { !it.isExpanded() }
+ if (!foldedRegions.empty) {
+ editor.getFoldingModel().runBatchFoldingOperation { foldedRegions.forEach { it.setExpanded(true) } }
+ }
+ editor.getScrollingModel().scrollTo(editor.offsetToLogicalPosition(startOffset), ScrollType.MAKE_VISIBLE)
+
+ it
+ }
+}
+
+public fun processDuplicates(
+ duplicateReplacers: Map Unit>,
+ project: Project,
+ editor: Editor
+) {
+ val size = duplicateReplacers.size
+ if (size == 0) return
+
+ if (size == 1) {
+ duplicateReplacers.keySet().first().preview(project, editor)
+ }
+
+ val answer = if (ApplicationManager.getApplication()!!.isUnitTestMode())
+ Messages.YES
+ else
+ Messages.showYesNoDialog(
+ project,
+ JetRefactoringBundle.message(
+ "0.has.detected.1.code.fragments.in.this.file.that.can.be.replaced.with.a.call.to.extracted.declaration",
+ ApplicationNamesInfo.getInstance()!!.getProductName(),
+ duplicateReplacers.size()
+ ),
+ "Process Duplicates",
+ Messages.getQuestionIcon()
+ )
+ if (answer != Messages.YES) return
+
+ var showAll = false
+ for ((i, entry) in duplicateReplacers.entrySet().withIndices()) {
+ val (pattern, replacer) = entry
+ if (!pattern.isValid()) continue
+
+ val highlighter = pattern.preview(project, editor)
+ if (!ApplicationManager.getApplication()!!.isUnitTestMode()) {
+ if (size > 1 && !showAll) {
+ val promptDialog = ReplacePromptDialog(false, RefactoringBundle.message("process.duplicates.title", i + 1, size), project)
+ promptDialog.show()
+ when(promptDialog.getExitCode()) {
+ FindManager.PromptResult.ALL -> showAll = true
+ FindManager.PromptResult.SKIP -> continue
+ FindManager.PromptResult.CANCEL -> return
+ }
+ }
+ }
+ highlighter?.let { HighlightManager.getInstance(project).removeSegmentHighlighter(editor, it) }
+
+ project.executeWriteCommand(MethodDuplicatesHandler.REFACTORING_NAME, replacer)
+ }
+}
diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractableAnalysisUtil.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractableAnalysisUtil.kt
index 41af055c18c..2192a5cd920 100644
--- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractableAnalysisUtil.kt
+++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractableAnalysisUtil.kt
@@ -37,7 +37,6 @@ import org.jetbrains.jet.lang.cfg.Label
import org.jetbrains.jet.plugin.refactoring.JetNameValidatorImpl
import org.jetbrains.jet.plugin.codeInsight.DescriptorToDeclarationUtil
import org.jetbrains.jet.plugin.imports.canBeReferencedViaImport
-import org.jetbrains.jet.lang.resolve.DescriptorUtils
import com.intellij.psi.PsiNamedElement
import org.jetbrains.jet.lang.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.jet.utils.DFS
@@ -62,24 +61,12 @@ import org.jetbrains.jet.lang.resolve.bindingContextUtil.isUsedAsStatement
import org.jetbrains.jet.lang.psi.psiUtil.isAncestor
import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils
import org.jetbrains.jet.plugin.imports.importableFqNameSafe
-import org.jetbrains.jet.lang.psi.psiUtil.isFunctionLiteralOutsideParentheses
-import org.jetbrains.jet.plugin.util.psiModificationUtil.moveInsideParenthesesAndReplaceWith
-import org.jetbrains.jet.lang.resolve.name.Name
-import gnu.trove.THashSet
-import gnu.trove.TObjectHashingStrategy
-import gnu.trove.THashMap
-import org.jetbrains.jet.lang.resolve.name.FqName
-import org.jetbrains.jet.lang.resolve.lazy.KotlinCodeAnalyzer
-import org.jetbrains.jet.lang.resolve.lazy.ResolveSessionUtils
import org.jetbrains.jet.plugin.refactoring.extractFunction.OutputValue.Initializer
import org.jetbrains.jet.plugin.refactoring.extractFunction.OutputValue.ParameterUpdate
import org.jetbrains.jet.plugin.refactoring.extractFunction.OutputValue.ExpressionValue
import org.jetbrains.jet.plugin.refactoring.extractFunction.OutputValue.Jump
-import org.jetbrains.jet.lang.cfg.pseudocode.instructions.special.MarkInstruction
-import org.jetbrains.jet.lang.cfg.pseudocode.instructions.special.CompilationErrorInstruction
import org.jetbrains.jet.lang.cfg.pseudocodeTraverser.traverseFollowingInstructions
import org.jetbrains.jet.plugin.refactoring.extractFunction.OutputValueBoxer.AsList
-import org.jetbrains.jet.lang.cfg.pseudocodeTraverser.getStartInstruction
private val DEFAULT_FUNCTION_NAME = "myFun"
private val DEFAULT_RETURN_TYPE = KotlinBuiltIns.getInstance().getUnitType()
@@ -96,10 +83,17 @@ private fun JetDeclaration.renderForMessage(bindingContext: BindingContext): Str
private fun JetType.isDefault(): Boolean = KotlinBuiltIns.getInstance().isUnit(this)
-private fun List.getModifiedVarDescriptors(bindingContext: BindingContext): Set {
- return this
- .map {if (it is WriteValueInstruction) PseudocodeUtil.extractVariableDescriptorIfAny(it, false, bindingContext) else null}
- .filterNotNullTo(HashSet())
+private fun List.getModifiedVarDescriptors(bindingContext: BindingContext): Map> {
+ val result = HashMap>()
+ for (instruction in filterIsInstance(javaClass())) {
+ val expression = instruction.element as? JetExpression
+ val descriptor = PseudocodeUtil.extractVariableDescriptorIfAny(instruction, false, bindingContext)
+ if (expression != null && descriptor != null) {
+ result.getOrPut(descriptor) { ArrayList() }.add(expression)
+ }
+ }
+
+ return result
}
private fun List.getVarDescriptorsAccessedAfterwards(bindingContext: BindingContext): Set {
@@ -127,17 +121,21 @@ private fun List.getVarDescriptorsAccessedAfterwards(bindingContext
private fun List.getExitPoints(): List =
filter { localInstruction -> localInstruction.nextInstructions.any { it !in this } }
-private fun List.getResultType(
+private fun List.getResultTypeAndExpressions(
bindingContext: BindingContext,
- options: ExtractionOptions): JetType {
- fun instructionToType(instruction: Instruction): JetType? {
- val expression = when (instruction) {
+ options: ExtractionOptions): Pair> {
+ fun instructionToExpression(instruction: Instruction, unwrapReturn: Boolean): JetExpression? {
+ return when (instruction) {
is ReturnValueInstruction ->
- instruction.resultExpression
+ (if (unwrapReturn) null else instruction.returnExpressionIfAny) ?: instruction.returnedValue.element as? JetExpression
is InstructionWithValue ->
instruction.outputValue?.element as? JetExpression
else -> null
}
+ }
+
+ fun instructionToType(instruction: Instruction): JetType? {
+ val expression = instructionToExpression(instruction, true)
if (expression == null) return null
if (options.inferUnitTypeForUnusedValues && expression.isUsedAsStatement(bindingContext)) return null
@@ -146,7 +144,10 @@ private fun List.getResultType(
}
val resultTypes = map(::instructionToType).filterNotNull()
- return if (resultTypes.isNotEmpty()) CommonSupertypes.commonSupertype(resultTypes) else DEFAULT_RETURN_TYPE
+ val resultType = if (resultTypes.isNotEmpty()) CommonSupertypes.commonSupertype(resultTypes) else DEFAULT_RETURN_TYPE
+ val expressions = map { instructionToExpression(it, false) }.filterNotNull()
+
+ return resultType to expressions
}
private fun List.checkEquivalence(checkPsi: Boolean): Boolean {
@@ -182,7 +183,7 @@ private fun ExtractionData.analyzeControlFlow(
pseudocode: Pseudocode,
module: ModuleDescriptor,
bindingContext: BindingContext,
- modifiedVarDescriptors: Set,
+ modifiedVarDescriptors: Map>,
options: ExtractionOptions,
parameters: Set
): Pair {
@@ -213,7 +214,10 @@ private fun ExtractionData.analyzeControlFlow(
is ReturnValueInstruction -> {
val returnExpression = insn.returnExpressionIfAny
if (returnExpression == null) {
- defaultExits.add(insn)
+ val containingDeclaration = insn.returnedValue.element?.getParentByType(javaClass())
+ if (containingDeclaration == pseudocode.getCorrespondingElement()) {
+ defaultExits.add(insn)
+ }
}
else if (isCurrentFunctionReturn(returnExpression)) {
valuedReturnExits.add(insn)
@@ -241,8 +245,8 @@ private fun ExtractionData.analyzeControlFlow(
val nonLocallyUsedDeclarations = getLocalDeclarationsWithNonLocalUsages(pseudocode, localInstructions, bindingContext)
val (declarationsToCopy, declarationsToReport) = nonLocallyUsedDeclarations.partition { it is JetProperty && it.isLocal() }
- val typeOfDefaultFlow = defaultExits.getResultType(bindingContext, options)
- val returnValueType = valuedReturnExits.getResultType(bindingContext, options)
+ val (typeOfDefaultFlow, defaultResultExpressions) = defaultExits.getResultTypeAndExpressions(bindingContext, options)
+ val (returnValueType, valuedReturnExpressions) = valuedReturnExits.getResultTypeAndExpressions(bindingContext, options)
val emptyControlFlow =
ControlFlow(Collections.emptyList(), { OutputValueBoxer.AsTuple(it, module) }, declarationsToCopy)
@@ -251,7 +255,7 @@ private fun ExtractionData.analyzeControlFlow(
if (defaultReturnType.isError()) return emptyControlFlow to ErrorMessage.ERROR_TYPES
val controlFlow = if (defaultReturnType.isMeaningful()) {
- emptyControlFlow.copy(outputValues = Collections.singletonList(ExpressionValue(false, defaultReturnType)))
+ emptyControlFlow.copy(outputValues = Collections.singletonList(ExpressionValue(false, defaultResultExpressions, defaultReturnType)))
}
else {
emptyControlFlow
@@ -263,9 +267,9 @@ private fun ExtractionData.analyzeControlFlow(
}
val outParameters =
- parameters.filter { it.mirrorVarName != null && it.originalDescriptor in modifiedVarDescriptors }.sortBy { it.nameForRef }
+ parameters.filter { it.mirrorVarName != null && modifiedVarDescriptors[it.originalDescriptor] != null }.sortBy { it.nameForRef }
val outDeclarations =
- declarationsToCopy.filter { bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it] in modifiedVarDescriptors }
+ declarationsToCopy.filter { modifiedVarDescriptors[bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it]] != null }
val modifiedValueCount = outParameters.size + outDeclarations.size
val outputValues = ArrayList()
@@ -276,7 +280,7 @@ private fun ExtractionData.analyzeControlFlow(
if (typeOfDefaultFlow.isMeaningful()) {
if (valuedReturnExits.isNotEmpty() || jumpExits.isNotEmpty()) return multipleExitsError
- outputValues.add(ExpressionValue(false, typeOfDefaultFlow))
+ outputValues.add(ExpressionValue(false, defaultResultExpressions, typeOfDefaultFlow))
}
else if (valuedReturnExits.isNotEmpty()) {
if (jumpExits.isNotEmpty()) return multipleExitsError
@@ -285,19 +289,19 @@ private fun ExtractionData.analyzeControlFlow(
if (modifiedValueCount != 0) return outputAndExitsError
if (valuedReturnExits.size != 1) return multipleExitsError
- val element = valuedReturnExits.first!!.element
+ val element = valuedReturnExits.first!!.element as JetExpression
return controlFlow.copy(outputValues = Collections.singletonList(Jump(listOf(element), element, true))) to null
}
if (!valuedReturnExits.checkEquivalence(false)) return multipleExitsError
- outputValues.add(ExpressionValue(true, returnValueType))
+ outputValues.add(ExpressionValue(true, valuedReturnExpressions, returnValueType))
}
outDeclarations.mapTo(outputValues) {
val descriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it] as? CallableDescriptor
Initializer(it as JetProperty, descriptor?.getReturnType() ?: DEFAULT_PARAMETER_TYPE)
}
- outParameters.mapTo(outputValues) { ParameterUpdate(it) }
+ outParameters.mapTo(outputValues) { ParameterUpdate(it, modifiedVarDescriptors[it.originalDescriptor]!!) }
if (outputValues.isNotEmpty()) {
if (jumpExits.isNotEmpty()) return outputAndExitsError
@@ -322,7 +326,7 @@ private fun ExtractionData.analyzeControlFlow(
if (jumpExits.isNotEmpty()) {
if (!jumpExits.checkEquivalence(true)) return multipleExitsError
- val elements = jumpExits.map { it.element }
+ val elements = jumpExits.map { it.element as JetExpression }
return controlFlow.copy(
outputValues = Collections.singletonList(Jump(elements, elements.first(), defaultExits.isNotEmpty()))
) to null
@@ -674,22 +678,24 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
val pseudocode = PseudocodeUtil.generatePseudocode(pseudocodeDeclaration, bindingContext)
val localInstructions = getLocalInstructions(pseudocode)
- val modifiedVarDescriptors = localInstructions.getModifiedVarDescriptors(bindingContext)
+ val modifiedVarDescriptorsWithExpressions = localInstructions.getModifiedVarDescriptors(bindingContext)
- val paramsInfo = inferParametersInfo(commonParent, pseudocode, bindingContext, modifiedVarDescriptors)
+ val paramsInfo = inferParametersInfo(commonParent, pseudocode, bindingContext, modifiedVarDescriptorsWithExpressions.keySet())
if (paramsInfo.errorMessage != null) {
return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(paramsInfo.errorMessage!!))
}
val messages = ArrayList()
+ val modifiedVarDescriptorsForControlFlow = HashMap(modifiedVarDescriptorsWithExpressions)
+ modifiedVarDescriptorsForControlFlow.keySet().retainAll(localInstructions.getVarDescriptorsAccessedAfterwards(bindingContext))
val (controlFlow, controlFlowMessage) =
analyzeControlFlow(
localInstructions,
pseudocode,
resolveSession.getModuleDescriptor(),
bindingContext,
- modifiedVarDescriptors intersect localInstructions.getVarDescriptorsAccessedAfterwards(bindingContext),
+ modifiedVarDescriptorsForControlFlow,
options,
paramsInfo.parameters
)
diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractorUtil.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractorUtil.kt
index 3016be27a94..f58107623de 100644
--- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractorUtil.kt
+++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/extractorUtil.kt
@@ -33,7 +33,6 @@ import org.jetbrains.jet.lang.psi.JetNamedDeclaration
import org.jetbrains.jet.lang.psi.JetPsiFactory
import java.util.LinkedHashMap
import java.util.Collections
-import org.jetbrains.jet.plugin.codeInsight.ShortenReferences
import org.jetbrains.jet.lang.psi.psiUtil.isFunctionLiteralOutsideParentheses
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.jet.lang.psi.JetFunctionLiteralArgument
@@ -53,6 +52,16 @@ import org.jetbrains.jet.plugin.refactoring.JetNameValidatorImpl
import org.jetbrains.jet.plugin.refactoring.JetNameSuggester
import org.jetbrains.jet.plugin.refactoring.isMultiLine
import org.jetbrains.jet.plugin.refactoring.extractFunction.OutputValueBoxer.AsTuple
+import org.jetbrains.jet.plugin.util.psi.patternMatching.JetPsiUnifier
+import org.jetbrains.jet.plugin.util.psi.patternMatching.UnifierParameter
+import org.jetbrains.jet.plugin.util.psi.patternMatching.toRange
+import org.jetbrains.jet.plugin.codeInsight.ShortenReferences
+import org.jetbrains.jet.plugin.util.psi.patternMatching.JetPsiRange
+import org.jetbrains.jet.plugin.util.psi.patternMatching.UnificationResult
+import org.jetbrains.jet.plugin.util.psi.patternMatching.JetPsiRange.Match
+import org.jetbrains.jet.plugin.util.psi.patternMatching.UnificationResult.Status
+import org.jetbrains.jet.plugin.util.psi.patternMatching.UnificationResult.WeaklyMatched
+import org.jetbrains.jet.plugin.util.psi.patternMatching.UnificationResult.StronglyMatched
fun ExtractableCodeDescriptor.getDeclarationText(
options: ExtractionGeneratorOptions = ExtractionGeneratorOptions.DEFAULT,
@@ -110,6 +119,227 @@ fun createNameCounterpartMap(from: JetElement, to: JetElement): Map
+)
+
+fun ExtractableCodeDescriptor.findDuplicates(): List {
+ fun processWeakMatch(match: Match, newControlFlow: ControlFlow): Boolean {
+ val valueCount = controlFlow.outputValues.size
+
+ val weakMatches = HashMap((match.result as WeaklyMatched).weakMatches)
+ val currentValuesToNew = HashMap()
+
+ fun matchValues(currentValue: OutputValue, newValue: OutputValue): Boolean {
+ if ((currentValue is Jump) != (newValue is Jump)) return false
+ if (currentValue.originalExpressions.zip(newValue.originalExpressions).all { weakMatches[it.first] == it.second }) {
+ currentValuesToNew[currentValue] = newValue
+ weakMatches.keySet().removeAll(currentValue.originalExpressions)
+ return true
+ }
+ return false
+ }
+
+ if (valueCount == 1) {
+ matchValues(controlFlow.outputValues.first(), newControlFlow.outputValues.first())
+ } else {
+ @outer
+ for (currentValue in controlFlow.outputValues)
+ for (newValue in newControlFlow.outputValues) {
+ if ((currentValue is ExpressionValue) != (newValue is ExpressionValue)) continue
+ if (matchValues(currentValue, newValue)) continue @outer
+ }
+ }
+
+ return currentValuesToNew.size == valueCount && weakMatches.isEmpty()
+ }
+
+ fun getControlFlowIfMatched(match: Match): ControlFlow? {
+ val analysisResult = extractionData.copy(originalRange = match.range).performAnalysis()
+ if (analysisResult.status != AnalysisResult.Status.SUCCESS) return null
+
+ val newControlFlow = analysisResult.descriptor!!.controlFlow
+ if (newControlFlow.outputValues.isEmpty()) return newControlFlow
+ if (controlFlow.outputValues.size != newControlFlow.outputValues.size) return null
+
+ val matched = when (match.result) {
+ is StronglyMatched -> true
+ is WeaklyMatched -> processWeakMatch(match, newControlFlow)
+ else -> throw AssertionError("Unexpected unification result: ${match.result}")
+ }
+
+ return if (matched) newControlFlow else null
+ }
+
+ val unifierParameters = parameters.map { UnifierParameter(it.originalDescriptor, it.parameterType) }
+
+ val unifier = JetPsiUnifier(unifierParameters, true)
+
+ val scopeElement = extractionData.targetSibling.getParent() ?: return Collections.emptyList()
+ val originalTextRange = extractionData.originalRange.getTextRange()
+ return extractionData
+ .originalRange
+ .match(scopeElement, unifier)
+ .stream()
+ .filter { !(it.range.getTextRange() intersects originalTextRange) }
+ .map { match ->
+ val controlFlow = getControlFlowIfMatched(match)
+ controlFlow?.let { DuplicateInfo(match.range, it, unifierParameters.map { match.result.substitution[it]!!.getText()!! }) }
+ }
+ .filterNotNull()
+ .toList()
+}
+
+private fun makeCall(
+ name: String,
+ declaration: JetNamedDeclaration,
+ controlFlow: ControlFlow,
+ rangeToReplace: JetPsiRange,
+ arguments: List) {
+ fun insertCall(anchor: PsiElement, wrappedCall: JetExpression) {
+ val firstExpression = rangeToReplace.elements.firstOrNull { it is JetExpression } as? JetExpression
+ if (firstExpression?.isFunctionLiteralOutsideParentheses() ?: false) {
+ val functionLiteralArgument = PsiTreeUtil.getParentOfType(firstExpression, javaClass())!!
+ //todo use the right binding context
+ functionLiteralArgument.moveInsideParenthesesAndReplaceWith(wrappedCall, BindingContext.EMPTY)
+ return
+ }
+ anchor.replace(wrappedCall)
+ }
+
+ if (rangeToReplace !is JetPsiRange.ListRange) return
+
+ val anchor = rangeToReplace.startElement
+ val anchorParent = anchor.getParent()!!
+
+ anchor.getNextSibling()?.let { from ->
+ val to = rangeToReplace.endElement
+ if (to != anchor) {
+ anchorParent.deleteChildRange(from, to);
+ }
+ }
+
+ val callText = when (declaration) {
+ is JetNamedFunction ->
+ arguments.joinToString(separator = ", ", prefix = "${name}(", postfix = ")")
+ else -> name
+ }
+
+ val anchorInBlock = stream(anchor) { it.getParent() }.firstOrNull { it.getParent() is JetBlockExpression }
+ val block = (anchorInBlock?.getParent() as? JetBlockExpression) ?: anchorParent
+
+ val psiFactory = JetPsiFactory(anchor.getProject())
+ val newLine = psiFactory.createNewLine()
+
+ if (controlFlow.outputValueBoxer is AsTuple && controlFlow.outputValues.size > 1 && controlFlow.outputValues.all { it is Initializer }) {
+ val declarationsToMerge = controlFlow.outputValues.map { (it as Initializer).initializedDeclaration }
+ val isVar = declarationsToMerge.first().isVar()
+ if (declarationsToMerge.all { it.isVar() == isVar }) {
+ controlFlow.declarationsToCopy.subtract(declarationsToMerge).forEach {
+ block.addBefore(psiFactory.createDeclaration(it.getText()!!), anchorInBlock) as JetDeclaration
+ block.addBefore(newLine, anchorInBlock)
+ }
+
+ val entries = declarationsToMerge.map { p -> p.getName() + (p.getTypeRef()?.let { ": ${it.getText()}" } ?: "") }
+ anchorInBlock?.replace(
+ psiFactory.createDeclaration("${if (isVar) "var" else "val"} (${entries.joinToString()}) = $callText")
+ )
+
+ return
+ }
+ }
+
+ val inlinableCall = controlFlow.outputValues.size <= 1
+ val unboxingExpressions =
+ if (inlinableCall) {
+ controlFlow.outputValueBoxer.getUnboxingExpressions(callText)
+ }
+ else {
+ val varNameValidator = JetNameValidatorImpl(block, anchorInBlock, JetNameValidatorImpl.Target.PROPERTIES)
+ val resultVal = JetNameSuggester.suggestNames(controlFlow.outputValueBoxer.returnType, varNameValidator, null).first()
+ block.addBefore(psiFactory.createDeclaration("val $resultVal = $callText"), anchorInBlock)
+ block.addBefore(newLine, anchorInBlock)
+ controlFlow.outputValueBoxer.getUnboxingExpressions(resultVal)
+ }
+
+ val copiedDeclarations = HashMap()
+ for (decl in controlFlow.declarationsToCopy) {
+ val declCopy = psiFactory.createDeclaration(decl.getText()!!)
+ copiedDeclarations[decl] = block.addBefore(declCopy, anchorInBlock) as JetDeclaration
+ block.addBefore(newLine, anchorInBlock)
+ }
+
+ if (controlFlow.outputValues.isEmpty()) {
+ anchor.replace(psiFactory.createExpression(callText))
+ return
+ }
+
+ fun wrapCall(outputValue: OutputValue, callText: String): List {
+ return when (outputValue) {
+ is OutputValue.ExpressionValue ->
+ Collections.singletonList(
+ if (outputValue.callSiteReturn) psiFactory.createReturn(callText) else psiFactory.createExpression(callText)
+ )
+
+ is ParameterUpdate ->
+ Collections.singletonList(
+ psiFactory.createExpression("${outputValue.parameter.argumentText} = $callText")
+ )
+
+ is Jump -> {
+ if (outputValue.conditional) {
+ Collections.singletonList(
+ psiFactory.createExpression("if ($callText) ${outputValue.elementToInsertAfterCall.getText()}")
+ )
+ }
+ else {
+ listOf(
+ psiFactory.createExpression(callText),
+ newLine,
+ psiFactory.createExpression(outputValue.elementToInsertAfterCall.getText()!!)
+ )
+ }
+ }
+
+ is Initializer -> {
+ val newProperty = copiedDeclarations[outputValue.initializedDeclaration] as JetProperty
+ newProperty.replace(DeclarationUtils.changePropertyInitializer(newProperty, psiFactory.createExpression(callText)))
+ Collections.emptyList()
+ }
+
+ else -> throw IllegalArgumentException("Unknown output value: $outputValue")
+ }
+ }
+
+ val defaultValue = controlFlow.defaultOutputValue
+
+ controlFlow.outputValues
+ .filter { it != defaultValue }
+ .flatMap { wrapCall(it, unboxingExpressions[it]!!) }
+ .withIndices()
+ .forEach {
+ val (i, e) = it
+
+ if (i > 0) {
+ block.addBefore(newLine, anchorInBlock)
+ }
+ block.addBefore(e, anchorInBlock)
+ }
+
+ defaultValue?.let {
+ if (!inlinableCall) {
+ block.addBefore(newLine, anchorInBlock)
+ }
+ insertCall(anchor, wrapCall(it, unboxingExpressions[it]!!).first() as JetExpression)
+ }
+
+ if (anchor.isValid()) {
+ anchor.delete()
+ }
+}
+
fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOptions): ExtractionResult{
val psiFactory = JetPsiFactory(extractionData.originalFile)
val nameByOffset = HashMap()
@@ -275,156 +505,16 @@ fun ExtractableCodeDescriptor.generateDeclaration(options: ExtractionGeneratorOp
}
}
- fun insertCall(anchor: PsiElement, wrappedCall: JetExpression) {
- val firstExpression = extractionData.getExpressions().firstOrNull()
- if (firstExpression?.isFunctionLiteralOutsideParentheses() ?: false) {
- val functionLiteralArgument = PsiTreeUtil.getParentOfType(firstExpression, javaClass())!!
- //todo use the right binding context
- functionLiteralArgument.moveInsideParenthesesAndReplaceWith(wrappedCall, BindingContext.EMPTY)
- return
- }
- anchor.replace(wrappedCall)
- }
-
- fun makeCall(declaration: JetNamedDeclaration) {
- val anchor = extractionData.originalElements.first
- if (anchor == null) return
-
- val anchorParent = anchor.getParent()!!
-
- anchor.getNextSibling()?.let { from ->
- val to = extractionData.originalElements.last
- if (to != anchor) {
- anchorParent.deleteChildRange(from, to);
- }
- }
-
- val callText = when (declaration) {
- is JetNamedFunction ->
- parameters
- .map { it.argumentText }
- .joinToString(separator = ", ", prefix = "${name}(", postfix = ")")
- else -> name
- }
-
- val anchorInBlock = stream(anchor) { it.getParent() }.firstOrNull { it.getParent() is JetBlockExpression }
- val block = (anchorInBlock?.getParent() as? JetBlockExpression) ?: anchorParent
-
- val newLine = psiFactory.createNewLine()
-
- if (controlFlow.outputValueBoxer is AsTuple && controlFlow.outputValues.size > 1 && controlFlow.outputValues.all { it is Initializer }) {
- val declarationsToMerge = controlFlow.outputValues.map { (it as Initializer).initializedDeclaration }
- val isVar = declarationsToMerge.first().isVar()
- if (declarationsToMerge.all { it.isVar() == isVar }) {
- controlFlow.declarationsToCopy.subtract(declarationsToMerge).forEach {
- block.addBefore(psiFactory.createDeclaration(it.getText()!!), anchorInBlock) as JetDeclaration
- block.addBefore(newLine, anchorInBlock)
- }
-
- val entries = declarationsToMerge.map { p -> p.getName() + (p.getTypeRef()?.let { ": ${it.getText()}" } ?: "") }
- anchorInBlock?.replace(
- psiFactory.createDeclaration("${if (isVar) "var" else "val"} (${entries.joinToString()}) = $callText")
- )
-
- return
- }
- }
-
- val inlinableCall = controlFlow.outputValues.size <= 1
- val unboxingExpressions =
- if (inlinableCall) {
- controlFlow.outputValueBoxer.getUnboxingExpressions(callText)
- }
- else {
- val varNameValidator = JetNameValidatorImpl(block, anchorInBlock, JetNameValidatorImpl.Target.PROPERTIES)
- val resultVal = JetNameSuggester.suggestNames(controlFlow.outputValueBoxer.returnType, varNameValidator, null).first()
- block.addBefore(psiFactory.createDeclaration("val $resultVal = $callText"), anchorInBlock)
- block.addBefore(newLine, anchorInBlock)
- controlFlow.outputValueBoxer.getUnboxingExpressions(resultVal)
- }
-
- val copiedDeclarations = HashMap()
- for (decl in controlFlow.declarationsToCopy) {
- val declCopy = psiFactory.createDeclaration(decl.getText()!!)
- copiedDeclarations[decl] = block.addBefore(declCopy, anchorInBlock) as JetDeclaration
- block.addBefore(newLine, anchorInBlock)
- }
-
- if (controlFlow.outputValues.isEmpty()) {
- anchor.replace(psiFactory.createExpression(callText))
- return
- }
-
- fun wrapCall(outputValue: OutputValue, callText: String): List {
- return when (outputValue) {
- is OutputValue.ExpressionValue ->
- Collections.singletonList(
- if (outputValue.callSiteReturn) psiFactory.createReturn(callText) else psiFactory.createExpression(callText)
- )
-
- is ParameterUpdate ->
- Collections.singletonList(
- psiFactory.createExpression("${outputValue.parameter.argumentText} = $callText")
- )
-
- is Jump -> {
- if (outputValue.conditional) {
- Collections.singletonList(
- psiFactory.createExpression("if ($callText) ${outputValue.elementToInsertAfterCall.getText()}")
- )
- }
- else {
- listOf(
- psiFactory.createExpression(callText),
- newLine,
- psiFactory.createExpression(outputValue.elementToInsertAfterCall.getText()!!)
- )
- }
- }
-
- is Initializer -> {
- val newProperty = copiedDeclarations[outputValue.initializedDeclaration] as JetProperty
- newProperty.replace(DeclarationUtils.changePropertyInitializer(newProperty, psiFactory.createExpression(callText)))
- Collections.emptyList()
- }
-
- else -> throw IllegalArgumentException("Unknown output value: $outputValue")
- }
- }
-
- val defaultValue = controlFlow.defaultOutputValue
-
- controlFlow.outputValues
- .filter { it != defaultValue }
- .flatMap { wrapCall(it, unboxingExpressions[it]!!) }
- .withIndices()
- .forEach {
- val (i, e) = it
-
- if (i > 0) {
- block.addBefore(newLine, anchorInBlock)
- }
- block.addBefore(e, anchorInBlock)
- }
-
- defaultValue?.let {
- if (!inlinableCall) {
- block.addBefore(newLine, anchorInBlock)
- }
- insertCall(anchor, wrapCall(it, unboxingExpressions[it]!!).first() as JetExpression)
- }
-
- if (anchor.isValid()) {
- anchor.delete()
- }
- }
+ val duplicates = if (options.inTempFile) Collections.emptyList() else findDuplicates()
val declaration = createDeclaration().let { if (options.inTempFile) it else insertDeclaration(it) }
adjustDeclarationBody(declaration)
- if (options.inTempFile) return ExtractionResult(declaration, nameByOffset)
+ if (options.inTempFile) return ExtractionResult(declaration, Collections.emptyMap(), nameByOffset)
- makeCall(declaration)
- ShortenReferences.process(declaration)
- return ExtractionResult(declaration, nameByOffset)
+ makeCall(name, declaration, controlFlow, extractionData.originalRange, parameters.map { it.argumentText })
+ ShortenReferences.process(declaration)
+
+ val duplicateReplacers = duplicates.map { it.range to { makeCall(name, declaration, it.controlFlow, it.range, it.arguments) } }.toMap()
+ return ExtractionResult(declaration, duplicateReplacers, nameByOffset)
}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.java b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.java
index ceaec8cd83c..3425848233d 100644
--- a/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.java
+++ b/idea/src/org/jetbrains/jet/plugin/refactoring/extractFunction/ui/KotlinExtractFunctionDialog.java
@@ -232,7 +232,7 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
OutputValue outputValue = outputValues.get(i);
if (outputValue instanceof OutputValue.ParameterUpdate) {
OutputValue.ParameterUpdate parameterUpdate = (OutputValue.ParameterUpdate) outputValue;
- outputValues.set(i, new OutputValue.ParameterUpdate(oldToNewParameters.get(parameterUpdate.getParameter())));
+ outputValues.set(i, new OutputValue.ParameterUpdate(oldToNewParameters.get(parameterUpdate.getParameter()), parameterUpdate.getOriginalExpressions()));
}
}
controlFlow = new ControlFlow(outputValues, controlFlow.getBoxerFactory(), controlFlow.getDeclarationsToCopy());
diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt
index ce3c24218ad..f2acf9cf96b 100644
--- a/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt
+++ b/idea/src/org/jetbrains/jet/plugin/refactoring/jetRefactoringUtil.kt
@@ -66,6 +66,8 @@ import com.intellij.openapi.editor.Document
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiWhiteSpace
+import com.intellij.openapi.util.Computable
+import com.intellij.openapi.ui.DialogWrapper
/**
* Replace [[JetSimpleNameExpression]] (and its enclosing qualifier) with qualified element given by FqName
@@ -190,6 +192,12 @@ public fun Project.executeWriteCommand(name: String, command: () -> Unit) {
CommandProcessor.getInstance().executeCommand(this, { runWriteAction(command) }, name, null)
}
+public fun Project.executeWriteCommand(name: String, command: () -> T): T {
+ var result: T? = null
+ CommandProcessor.getInstance().executeCommand(this, { result = runWriteAction(command) }, name, null)
+ return result!!
+}
+
public fun getPsiElementPopup(
editor: Editor,
elements: Array,
diff --git a/idea/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfThen.kt.after b/idea/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfThen.kt.after
index 18a4e9d87bc..99db87af6e4 100644
--- a/idea/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfThen.kt.after
+++ b/idea/testData/refactoring/extractFunction/controlFlow/evaluateExpression/evalExprInIfThen.kt.after
@@ -1,6 +1,6 @@
// SIBLING:
fun foo(a: Int): Int {
- val b: Int = 1
+ val b: Int = i()
return if (a + b > 0) i() else if (a - b < 0) 2 else b
}
diff --git a/idea/testData/refactoring/extractFunction/duplicates/branchingMatch1.kt b/idea/testData/refactoring/extractFunction/duplicates/branchingMatch1.kt
new file mode 100644
index 00000000000..0283a436a13
--- /dev/null
+++ b/idea/testData/refactoring/extractFunction/duplicates/branchingMatch1.kt
@@ -0,0 +1,87 @@
+// WITH_RUNTIME
+// PARAM_TYPES: kotlin.Int
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in test
+// PARAM_DESCRIPTOR: var b: kotlin.Int defined in test
+
+// SIBLING:
+fun test(a: Int): Int {
+ var b: Int = 1
+ val t = if (a > 0) {
+ b++
+ b + a
+ }
+ else {
+ b--
+ b - a
+ }
+ return t
+}
+
+fun foo1() {
+ val x = 1
+ var y: Int = x
+ println(
+ if (x > 0) {
+ y++
+ y + x
+ }
+ else {
+ y--
+ y - x
+ }
+ )
+}
+
+fun foo2(x: Int) {
+ var p: Int = 1
+ var q: Int
+ if (x > 0) {
+ p++
+ q = p + x
+ }
+ else {
+ p--
+ q = p - x
+ }
+ println(q)
+}
+
+fun foo3(x: Int): Int {
+ var p: Int = 1
+ if (x > 0) {
+ p++
+ return p + x
+ }
+ else {
+ p--
+ return p - x
+ }
+}
+
+fun foo4() {
+ val t: (Int) -> (Int) = {
+ var n = it
+ if (it > 0) {
+ n++
+ n + it
+ }
+ else {
+ n--
+ n - it
+ }
+ }
+ println(t(1))
+}
+
+fun foo5(x: Int): Int {
+ var p: Int = 1
+ if (x > 0) {
+ p++
+ val t = p + x
+ }
+ else {
+ p--
+ val u = p - x
+ }
+}
diff --git a/idea/testData/refactoring/extractFunction/duplicates/branchingMatch1.kt.after b/idea/testData/refactoring/extractFunction/duplicates/branchingMatch1.kt.after
new file mode 100644
index 00000000000..c31c80b774d
--- /dev/null
+++ b/idea/testData/refactoring/extractFunction/duplicates/branchingMatch1.kt.after
@@ -0,0 +1,70 @@
+// WITH_RUNTIME
+// PARAM_TYPES: kotlin.Int
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in test
+// PARAM_DESCRIPTOR: var b: kotlin.Int defined in test
+
+// SIBLING:
+fun test(a: Int): Int {
+ var b: Int = 1
+ val t = i(a, b)
+ return t
+}
+
+private fun i(a: Int, b: Int): Int {
+ var b1 = b
+ return if (a > 0) {
+ b1++
+ b1 + a
+ } else {
+ b1--
+ b1 - a
+ }
+}
+
+fun foo1() {
+ val x = 1
+ var y: Int = x
+ println(
+ i(x, y)
+ )
+}
+
+fun foo2(x: Int) {
+ var p: Int = 1
+ var q: Int
+ if (x > 0) {
+ p++
+ q = p + x
+ }
+ else {
+ p--
+ q = p - x
+ }
+ println(q)
+}
+
+fun foo3(x: Int): Int {
+ var p: Int = 1
+ if (x > 0) {
+ p++
+ return p + x
+ }
+ else {
+ p--
+ return p - x
+ }
+}
+
+fun foo4() {
+ val t: (Int) -> (Int) = {
+ var n = it
+ i(it, n)
+ }
+ println(t(1))
+}
+
+fun foo5(x: Int): Int {
+ var p: Int = 1
+ i(x, p)
+}
diff --git a/idea/testData/refactoring/extractFunction/duplicates/branchingMatch2.kt b/idea/testData/refactoring/extractFunction/duplicates/branchingMatch2.kt
new file mode 100644
index 00000000000..69eda395179
--- /dev/null
+++ b/idea/testData/refactoring/extractFunction/duplicates/branchingMatch2.kt
@@ -0,0 +1,74 @@
+// WITH_RUNTIME
+// PARAM_TYPES: kotlin.Int
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in test
+// PARAM_DESCRIPTOR: var b: kotlin.Int defined in test
+
+// SIBLING:
+fun test(a: Int): Int {
+ var b: Int = 1
+ if (a > 0) {
+ b = b + a
+ }
+ else {
+ b = b - a
+ }
+ return b
+}
+
+fun foo1() {
+ val x = 1
+ var y: Int = x
+ println(
+ if (x > 0) {
+ y + x
+ }
+ else {
+ y - x
+ }
+ )
+}
+
+fun foo2(x: Int) {
+ var p: Int = 1
+ if (x > 0) {
+ p = p + x
+ }
+ else {
+ p = p - x
+ }
+ println(p)
+}
+
+fun foo3(x: Int): Int {
+ var p: Int = 1
+ if (x > 0) {
+ return p + x
+ }
+ else {
+ return p - x
+ }
+}
+
+fun foo4() {
+ val t: (Int) -> (Int) = {
+ var n = it
+ if (it > 0) {
+ n + it
+ }
+ else {
+ n - it
+ }
+ }
+ println(t(1))
+}
+
+fun foo5(x: Int): Int {
+ var p: Int = 1
+ if (x > 0) {
+ val t = p + x
+ }
+ else {
+ val u = p - x
+ }
+}
diff --git a/idea/testData/refactoring/extractFunction/duplicates/branchingMatch2.kt.after b/idea/testData/refactoring/extractFunction/duplicates/branchingMatch2.kt.after
new file mode 100644
index 00000000000..0f5ae56429d
--- /dev/null
+++ b/idea/testData/refactoring/extractFunction/duplicates/branchingMatch2.kt.after
@@ -0,0 +1,64 @@
+// WITH_RUNTIME
+// PARAM_TYPES: kotlin.Int
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in test
+// PARAM_DESCRIPTOR: var b: kotlin.Int defined in test
+
+// SIBLING:
+fun test(a: Int): Int {
+ var b: Int = 1
+ b = i(a, b)
+ return b
+}
+
+private fun i(a: Int, b: Int): Int {
+ var b1 = b
+ if (a > 0) {
+ b1 = b1 + a
+ } else {
+ b1 = b1 - a
+ }
+ return b1
+}
+
+fun foo1() {
+ val x = 1
+ var y: Int = x
+ println(
+ if (x > 0) {
+ y + x
+ }
+ else {
+ y - x
+ }
+ )
+}
+
+fun foo2(x: Int) {
+ var p: Int = 1
+ p = i(x, p)
+ println(p)
+}
+
+fun foo3(x: Int): Int {
+ var p: Int = 1
+ return i(x, p)
+}
+
+fun foo4() {
+ val t: (Int) -> (Int) = {
+ var n = it
+ if (it > 0) {
+ n + it
+ }
+ else {
+ n - it
+ }
+ }
+ println(t(1))
+}
+
+fun foo5(x: Int): Int {
+ var p: Int = 1
+ i(x, p)
+}
diff --git a/idea/testData/refactoring/extractFunction/duplicates/defaultCF.kt b/idea/testData/refactoring/extractFunction/duplicates/defaultCF.kt
new file mode 100644
index 00000000000..818d3297dcd
--- /dev/null
+++ b/idea/testData/refactoring/extractFunction/duplicates/defaultCF.kt
@@ -0,0 +1,20 @@
+// PARAM_TYPES: kotlin.Int
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo
+// PARAM_DESCRIPTOR: value-parameter val b: kotlin.Int defined in foo
+
+// SIBLING:
+fun foo(a: Int, b: Int) {
+ println("a = $a")
+ println("b = $b")
+ println(a + b*a)
+}
+
+fun bar() {
+ val x = 1
+ val y = 2
+
+ println("a = $x")
+ println("b = $y")
+ println(x + y*x)
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/extractFunction/duplicates/defaultCF.kt.after b/idea/testData/refactoring/extractFunction/duplicates/defaultCF.kt.after
new file mode 100644
index 00000000000..2a007b47f4b
--- /dev/null
+++ b/idea/testData/refactoring/extractFunction/duplicates/defaultCF.kt.after
@@ -0,0 +1,22 @@
+// PARAM_TYPES: kotlin.Int
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo
+// PARAM_DESCRIPTOR: value-parameter val b: kotlin.Int defined in foo
+
+// SIBLING:
+fun foo(a: Int, b: Int) {
+ unit(a, b)
+}
+
+private fun unit(a: Int, b: Int) {
+ println("a = $a")
+ println("b = $b")
+ println(a + b * a)
+}
+
+fun bar() {
+ val x = 1
+ val y = 2
+
+ unit(x, y)
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/extractFunction/duplicates/multipleOutputValuesMatching.kt b/idea/testData/refactoring/extractFunction/duplicates/multipleOutputValuesMatching.kt
new file mode 100644
index 00000000000..ad84426faa6
--- /dev/null
+++ b/idea/testData/refactoring/extractFunction/duplicates/multipleOutputValuesMatching.kt
@@ -0,0 +1,30 @@
+// WITH_RUNTIME
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in test
+
+// SIBLING:
+fun test(a: Int): Int {
+ val b = a + 1
+ val c = a - 1
+ return b*c
+}
+
+fun foo1(a: Int) {
+ var x = a + 1
+ var y = a - 1
+ println(x + y)
+}
+
+fun foo2(): Int {
+ var p: Int = 1
+ var q: Int
+ p = p + 1
+ q = p - 1
+ return p + q
+}
+
+fun foo4(a: Int) {
+ var b = a
+ b = b + 1
+ return b - 1
+}
diff --git a/idea/testData/refactoring/extractFunction/duplicates/multipleOutputValuesMatching.kt.after b/idea/testData/refactoring/extractFunction/duplicates/multipleOutputValuesMatching.kt.after
new file mode 100644
index 00000000000..5b34f7a97e1
--- /dev/null
+++ b/idea/testData/refactoring/extractFunction/duplicates/multipleOutputValuesMatching.kt.after
@@ -0,0 +1,35 @@
+// WITH_RUNTIME
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in test
+
+// SIBLING:
+fun test(a: Int): Int {
+ val (b, c) = pair(a)
+ return b*c
+}
+
+private fun pair(a: Int): Pair {
+ val b = a + 1
+ val c = a - 1
+ return Pair(b, c)
+}
+
+fun foo1(a: Int) {
+ var (x, y) = pair(a)
+ println(x + y)
+}
+
+fun foo2(): Int {
+ var p: Int = 1
+ var q: Int
+ val pair = pair(p)
+ p = pair.first
+ q = pair.second
+ return p + q
+}
+
+fun foo4(a: Int) {
+ var b = a
+ b = b + 1
+ return b - 1
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/extractFunction/duplicates/outputValueAndUnitMatching.kt b/idea/testData/refactoring/extractFunction/duplicates/outputValueAndUnitMatching.kt
new file mode 100644
index 00000000000..45ddeecf7c9
--- /dev/null
+++ b/idea/testData/refactoring/extractFunction/duplicates/outputValueAndUnitMatching.kt
@@ -0,0 +1,22 @@
+// WITH_RUNTIME
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: val z: kotlin.Int defined in test
+
+// SIBLING:
+fun test(): () -> Int {
+ val z = 1
+ return {
+ println(z)
+ z + 1
+ }
+}
+
+fun foo1(a: Int): Int {
+ val t = println(a)
+ return a + 1
+}
+
+fun foo2(a: Int) {
+ println(a)
+ a + 1
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/extractFunction/duplicates/outputValueAndUnitMatching.kt.after b/idea/testData/refactoring/extractFunction/duplicates/outputValueAndUnitMatching.kt.after
new file mode 100644
index 00000000000..8f62f66b5bd
--- /dev/null
+++ b/idea/testData/refactoring/extractFunction/duplicates/outputValueAndUnitMatching.kt.after
@@ -0,0 +1,25 @@
+// WITH_RUNTIME
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: val z: kotlin.Int defined in test
+
+// SIBLING:
+fun test(): () -> Int {
+ val z = 1
+ return {
+ i(z)
+ }
+}
+
+private fun i(z: Int): Int {
+ println(z)
+ return z + 1
+}
+
+fun foo1(a: Int): Int {
+ val t = println(a)
+ return a + 1
+}
+
+fun foo2(a: Int) {
+ i(a)
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/extractFunction/duplicates/outputValueMatching.kt b/idea/testData/refactoring/extractFunction/duplicates/outputValueMatching.kt
new file mode 100644
index 00000000000..7965dbc61d1
--- /dev/null
+++ b/idea/testData/refactoring/extractFunction/duplicates/outputValueMatching.kt
@@ -0,0 +1,41 @@
+// WITH_RUNTIME
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in test
+
+// SIBLING:
+fun test(a: Int): Int {
+ println(a)
+ return a + 1
+}
+
+fun foo1(): Int {
+ val x = 1
+ println(x)
+ val y = x + 1
+ return y
+}
+
+fun foo2(): () -> Int {
+ val z = 1
+ return {
+ println(z)
+ z + 1
+ }
+}
+
+fun foo3() {
+ var t = 1
+ println(t)
+ t = t + 1
+ println(t)
+}
+
+fun foo4(a: Int): Int {
+ val t = println(a)
+ return a + 1
+}
+
+fun foo5(a: Int) {
+ println(a)
+ a + 1
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/extractFunction/duplicates/outputValueMatching.kt.after b/idea/testData/refactoring/extractFunction/duplicates/outputValueMatching.kt.after
new file mode 100644
index 00000000000..c9e53e2cd13
--- /dev/null
+++ b/idea/testData/refactoring/extractFunction/duplicates/outputValueMatching.kt.after
@@ -0,0 +1,41 @@
+// WITH_RUNTIME
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in test
+
+// SIBLING:
+fun test(a: Int): Int {
+ return i(a)
+}
+
+private fun i(a: Int): Int {
+ println(a)
+ return a + 1
+}
+
+fun foo1(): Int {
+ val x = 1
+ val y = i(x)
+ return y
+}
+
+fun foo2(): () -> Int {
+ val z = 1
+ return {
+ i(z)
+ }
+}
+
+fun foo3() {
+ var t = 1
+ t = i(t)
+ println(t)
+}
+
+fun foo4(a: Int): Int {
+ val t = println(a)
+ return a + 1
+}
+
+fun foo5(a: Int) {
+ i(a)
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/extractFunction/duplicates/singleExpression.kt b/idea/testData/refactoring/extractFunction/duplicates/singleExpression.kt
new file mode 100644
index 00000000000..67c9d7058a4
--- /dev/null
+++ b/idea/testData/refactoring/extractFunction/duplicates/singleExpression.kt
@@ -0,0 +1,25 @@
+// PARAM_TYPES: kotlin.Int
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo
+// PARAM_DESCRIPTOR: value-parameter val b: kotlin.Int defined in foo
+
+// SIBLING:
+fun foo(a: Int, b: Int): Int {
+ return a + b*a + 1
+}
+
+fun bar() {
+ fun f() = 1
+
+ val a = 1
+ val b = 2
+ val c = 3
+
+ c + b*c
+ b + a*b
+ a plus a*a
+ a + a*b
+ a + c
+ f() + a*f()
+ f() + f().times(f())
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/extractFunction/duplicates/singleExpression.kt.after b/idea/testData/refactoring/extractFunction/duplicates/singleExpression.kt.after
new file mode 100644
index 00000000000..5fa9b70aa64
--- /dev/null
+++ b/idea/testData/refactoring/extractFunction/duplicates/singleExpression.kt.after
@@ -0,0 +1,29 @@
+// PARAM_TYPES: kotlin.Int
+// PARAM_TYPES: kotlin.Int
+// PARAM_DESCRIPTOR: value-parameter val a: kotlin.Int defined in foo
+// PARAM_DESCRIPTOR: value-parameter val b: kotlin.Int defined in foo
+
+// SIBLING:
+fun foo(a: Int, b: Int): Int {
+ return i(a, b) + 1
+}
+
+private fun i(a: Int, b: Int): Int {
+ return a + b * a
+}
+
+fun bar() {
+ fun f() = 1
+
+ val a = 1
+ val b = 2
+ val c = 3
+
+ i(c, b)
+ i(b, a)
+ i(a, a)
+ a + a*b
+ a + c
+ i(f(), a)
+ i(f(), f())
+}
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/JetExtractionTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/JetExtractionTestGenerated.java
index fb0e14e6237..cdac35c08ce 100644
--- a/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/JetExtractionTestGenerated.java
+++ b/idea/tests/org/jetbrains/jet/plugin/refactoring/introduce/introduceVariable/JetExtractionTestGenerated.java
@@ -175,7 +175,8 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
@TestMetadata("OccurrencesInStringTemplate.kt")
public void testOccurrencesInStringTemplate() throws Exception {
- doIntroduceVariableTest("idea/testData/refactoring/introduceVariable/OccurrencesInStringTemplate.kt");
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/OccurrencesInStringTemplate.kt");
+ doIntroduceVariableTest(fileName);
}
@TestMetadata("OneExplicitReceiver.kt")
@@ -216,7 +217,8 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
@TestMetadata("UnresolvedOccurrences.kt")
public void testUnresolvedOccurrences() throws Exception {
- doIntroduceVariableTest("idea/testData/refactoring/introduceVariable/UnresolvedOccurrences.kt");
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/UnresolvedOccurrences.kt");
+ doIntroduceVariableTest(fileName);
}
@TestMetadata("WhenAddBlock.kt")
@@ -259,7 +261,7 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
@TestMetadata("idea/testData/refactoring/extractFunction")
@TestDataPath("$PROJECT_ROOT")
- @InnerTestClasses({ExtractFunction.AsProperty.class, ExtractFunction.Basic.class, ExtractFunction.ControlFlow.class, ExtractFunction.DefaultContainer.class, ExtractFunction.Delegation.class, ExtractFunction.Initializers.class, ExtractFunction.Parameters.class, ExtractFunction.TypeParameters.class})
+ @InnerTestClasses({ExtractFunction.AsProperty.class, ExtractFunction.Basic.class, ExtractFunction.ControlFlow.class, ExtractFunction.DefaultContainer.class, ExtractFunction.Delegation.class, ExtractFunction.Duplicates.class, ExtractFunction.Initializers.class, ExtractFunction.Parameters.class, ExtractFunction.TypeParameters.class})
@RunWith(org.jetbrains.jet.JUnit3RunnerWithInners.class)
public static class ExtractFunction extends AbstractJetExtractionTest {
public void testAllFilesPresentInExtractFunction() throws Exception {
@@ -1051,6 +1053,58 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
}
+ @TestMetadata("idea/testData/refactoring/extractFunction/duplicates")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(org.jetbrains.jet.JUnit3RunnerWithInners.class)
+ public static class Duplicates extends AbstractJetExtractionTest {
+ public void testAllFilesPresentInDuplicates() throws Exception {
+ JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/extractFunction/duplicates"), Pattern.compile("^(.+)\\.kt$"), true);
+ }
+
+ @TestMetadata("branchingMatch1.kt")
+ public void testBranchingMatch1() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/duplicates/branchingMatch1.kt");
+ doExtractFunctionTest(fileName);
+ }
+
+ @TestMetadata("branchingMatch2.kt")
+ public void testBranchingMatch2() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/duplicates/branchingMatch2.kt");
+ doExtractFunctionTest(fileName);
+ }
+
+ @TestMetadata("defaultCF.kt")
+ public void testDefaultCF() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/duplicates/defaultCF.kt");
+ doExtractFunctionTest(fileName);
+ }
+
+ @TestMetadata("multipleOutputValuesMatching.kt")
+ public void testMultipleOutputValuesMatching() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/duplicates/multipleOutputValuesMatching.kt");
+ doExtractFunctionTest(fileName);
+ }
+
+ @TestMetadata("outputValueAndUnitMatching.kt")
+ public void testOutputValueAndUnitMatching() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/duplicates/outputValueAndUnitMatching.kt");
+ doExtractFunctionTest(fileName);
+ }
+
+ @TestMetadata("outputValueMatching.kt")
+ public void testOutputValueMatching() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/duplicates/outputValueMatching.kt");
+ doExtractFunctionTest(fileName);
+ }
+
+ @TestMetadata("singleExpression.kt")
+ public void testSingleExpression() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/duplicates/singleExpression.kt");
+ doExtractFunctionTest(fileName);
+ }
+
+ }
+
@TestMetadata("idea/testData/refactoring/extractFunction/initializers")
@TestDataPath("$PROJECT_ROOT")
@InnerTestClasses({Initializers.Accessors.class, Initializers.Classes.class, Initializers.Functions.class, Initializers.Properties.class})