Skip to content

Commit

Permalink
Extract Function: Implement duplicate search
Browse files Browse the repository at this point in the history
  • Loading branch information
asedunov committed Sep 18, 2014
1 parent 03e2b4d commit 741e5f6
Show file tree
Hide file tree
Showing 30 changed files with 1,110 additions and 201 deletions.
10 changes: 10 additions & 0 deletions annotations/com/intellij/codeInsight/folding/annotations.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<root>
<item
name='com.intellij.codeInsight.folding.CodeFoldingManager com.intellij.codeInsight.folding.CodeFoldingManager getInstance(com.intellij.openapi.project.Project)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item
name='com.intellij.codeInsight.folding.CodeFoldingManager com.intellij.openapi.editor.FoldRegion[] getFoldRegionsAtOffset(com.intellij.openapi.editor.Editor, int)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
</root>
10 changes: 10 additions & 0 deletions annotations/com/intellij/codeInsight/folding/impl/annotations.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<root>
<item
name='com.intellij.codeInsight.folding.impl.CodeFoldingManagerImpl com.intellij.openapi.editor.FoldRegion[] getFoldRegionsAtOffset(com.intellij.openapi.editor.Editor, int)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item
name='com.intellij.codeInsight.folding.impl.FoldingUtil com.intellij.openapi.editor.FoldRegion[] getFoldRegionsAtOffset(com.intellij.openapi.editor.Editor, int)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
</root>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<root>
<item
name='com.intellij.codeInsight.highlighting.HighlightManager com.intellij.codeInsight.highlighting.HighlightManager getInstance(com.intellij.openapi.project.Project)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<PsiElement>): List<PsiElement> {
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,
Expand All @@ -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() })
Expand All @@ -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<ExtractionResult>(EXTRACT_FUNCTION) { adjustedDescriptor.generateDeclaration(adjustedGeneratorOptions) }
processDuplicates(result.duplicateReplacers, project, editor)
}

fun validateAndRefactor() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -123,29 +125,37 @@ class FqNameReplacement(val fqName: FqName): Replacement {
}

trait OutputValue {
val originalExpressions: List<JetExpression>
val valueType: JetType

class ExpressionValue(
val callSiteReturn: Boolean,
override val originalExpressions: List<JetExpression>,
override val valueType: JetType
): OutputValue

class Jump(
val elementsToReplace: List<JetElement>,
val elementsToReplace: List<JetExpression>,
val elementToInsertAfterCall: JetElement,
val conditional: Boolean
): OutputValue {
override val originalExpressions: List<JetExpression> 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<JetExpression>
): OutputValue {
override val valueType: JetType get() = parameter.parameterType
}

class Initializer(
val initializedDeclaration: JetProperty,
override val valueType: JetType
): OutputValue
): OutputValue {
override val originalExpressions: List<JetExpression> get() = Collections.singletonList(initializedDeclaration)
}
}

abstract class OutputValueBoxer(val outputValues: List<OutputValue>) {
Expand Down Expand Up @@ -315,6 +325,7 @@ data class ExtractionGeneratorOptions(

data class ExtractionResult(
val declaration: JetNamedDeclaration,
val duplicateReplacers: Map<JetPsiRange, () -> Unit>,
val nameByOffset: Map<Int, JetElement>
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -76,11 +77,12 @@ data class ResolvedReferenceInfo(

data class ExtractionData(
val originalFile: JetFile,
val originalElements: List<PsiElement>,
val originalRange: JetPsiRange,
val targetSibling: PsiElement,
val options: ExtractionOptions = ExtractionOptions.DEFAULT
) {
val project: Project = originalFile.getProject()
val originalElements: List<PsiElement> = originalRange.elements

val insertBefore: Boolean = targetSibling.getParentByType(javaClass<JetDeclaration>(), true)?.let {
it is JetDeclarationWithBody || it is JetClassInitializer
Expand Down
Original file line number Diff line number Diff line change
@@ -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<RangeHighlighter>()
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<JetPsiRange, () -> 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)
}
}
Loading

0 comments on commit 741e5f6

Please sign in to comment.