[extract method] kotlin implementation

GitOrigin-RevId: 670962d122063309443e919f879f5852915e3af9
This commit is contained in:
Alexandr Suhinin
2020-03-30 14:31:51 +00:00
committed by intellij-monorepo-bot
parent be42a8d147
commit 58b9be5c13
16 changed files with 1157 additions and 465 deletions
@@ -85,7 +85,7 @@ public class ExtractMethodDialog extends RefactoringDialog implements AbstractEx
private TypeSelector mySelector;
private final Supplier<Integer> myDuplicatesCountSupplier;
protected ExtractMethodDialog(Project project, PsiClass targetClass, InputVariables inputVariables,
public ExtractMethodDialog(Project project, PsiClass targetClass, InputVariables inputVariables,
PsiType returnType, PsiTypeParameterList typeParameterList, PsiType[] exceptions,
boolean isStatic, boolean canBeStatic, boolean canBeChainedConstructor,
String title, String helpId, @Nullable Nullability nullability, PsiElement[] elementsToExtract,
@@ -47,11 +47,7 @@ import com.intellij.refactoring.HelpID;
import com.intellij.refactoring.IntroduceTargetChooser;
import com.intellij.refactoring.RefactoringActionHandler;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodAnalyzerKt;
import com.intellij.refactoring.extractMethod.newImpl.MethodExtractorKt;
import com.intellij.refactoring.extractMethod.newImpl.ExtractSelector;
import com.intellij.refactoring.extractMethod.newImpl.ExtractOptionsPipelineKt;
import com.intellij.refactoring.extractMethod.newImpl.structures.ExtractOptions;
import com.intellij.refactoring.extractMethod.newImpl.*;
import com.intellij.refactoring.extractMethod.preview.ExtractMethodPreviewManager;
import com.intellij.refactoring.introduceVariable.IntroduceVariableBase;
import com.intellij.refactoring.listeners.RefactoringEventData;
@@ -156,7 +152,28 @@ public class ExtractMethodHandler implements RefactoringActionHandler, ContextAw
return expressions.toArray(PsiElement.EMPTY_ARRAY);
}
public static boolean shouldUseNewImpl(@NotNull Project project, PsiFile file, PsiElement @NotNull [] elements){
if (! Registry.is("java.refactoring.extractMethod.newImplementation")) return false;
final ExtractMethodProcessor processor = getProcessor(project, elements, file, false);
if (processor == null) return true;
try {
processor.prepare(null);
processor.testPrepare(); //TODO remove
processor.myMethodName = "extracted";
}
catch (PrepareFailedException e) {
return true;
}
return processor.estimateDuplicatesCount() == 0 && !processor.myInputVariables.isFoldable();
}
public static void invokeOnElements(@NotNull Project project, final Editor editor, PsiFile file, PsiElement @NotNull [] elements) {
if (shouldUseNewImpl(project, file, elements)) {
new MethodExtractor().doExtract(editor, getRefactoringName(), HelpID.EXTRACT_METHOD);
return;
}
getProcessor(elements, project, file, editor, true, new Pass<ExtractMethodProcessor>(){
@Override
public void pass(ExtractMethodProcessor processor) {
@@ -192,15 +209,6 @@ public class ExtractMethodHandler implements RefactoringActionHandler, ContextAw
() -> doRefactoring(project, processor)), getRefactoringName(), null);
}
private static void extractWithNewImpl(Editor editor, ExtractMethodProcessor processor){
final List<PsiElement> statements = new ExtractSelector().suggestElementsToExtract(editor);
final ExtractOptions extractOptions = ExtractMethodAnalyzerKt.findExtractOptions(statements);
if (extractOptions == null) return;
final ExtractOptions mappedOptions = ExtractOptionsPipelineKt.remap(extractOptions, processor.myVariableDatum, processor.myMethodName,
processor.myStatic, processor.myMethodVisibility, processor.myIsChainedConstructor);
MethodExtractorKt.extractMethod(mappedOptions);
}
private static void doRefactoring(@NotNull Project project, @NotNull ExtractMethodProcessor processor) {
try {
final RefactoringEventData beforeData = new RefactoringEventData();
@@ -208,15 +216,6 @@ public class ExtractMethodHandler implements RefactoringActionHandler, ContextAw
project.getMessageBus().syncPublisher(
RefactoringEventListener.REFACTORING_EVENT_TOPIC).refactoringStarted("refactoring.extract.method", beforeData);
if (Registry.is("java.refactoring.extractMethod.newImplementation")
&& processor.estimateDuplicatesCount() == 0
&& ! processor.myInputVariables.isFoldable()
) {
final Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
extractWithNewImpl(editor, processor);
return;
}
processor.doRefactoring();
final RefactoringEventData data = new RefactoringEventData();
@@ -1,16 +1,18 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.refactoring.extractMethod.newImpl
import com.intellij.codeInsight.CodeInsightUtil
import com.intellij.psi.*
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiUtil
import com.intellij.refactoring.extractMethod.newImpl.structures.CodeFragment
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodHelper.createDeclaration
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodHelper.findInCopy
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodHelper.findTopmostParenthesis
import com.intellij.refactoring.extractMethod.newImpl.structures.DataOutput
import com.intellij.refactoring.extractMethod.newImpl.structures.DataOutput.*
import com.intellij.refactoring.extractMethod.newImpl.structures.FlowOutput
import com.intellij.refactoring.extractMethod.newImpl.structures.FlowOutput.*
import com.intellij.refactoring.extractMethod.newImpl.structures.InputParameter
import java.lang.RuntimeException
data class PsiReplace(val source: PsiElement, val target: PsiElement)
@@ -20,113 +22,137 @@ class BodyBuilder(private val factory: PsiElementFactory) {
private fun statementOf(statement: String) = factory.createStatementFromText(statement, null)
private fun findExitSubstitutions(flowOutput: FlowOutput, dataOutput: DataOutput): List<PsiReplace> {
val replacement = when (flowOutput) {
private fun findDefaultFlowSubstitution(flowOutput: FlowOutput, dataOutput: DataOutput): String? {
return when (flowOutput) {
is ConditionalFlow -> when (dataOutput) {
is VariableOutput -> "return null;"
ArtificialBooleanOutput -> "return true;"
is ExpressionOutput -> "return null;"
is EmptyOutput -> throw IllegalArgumentException()
is EmptyOutput -> null
}
is UnconditionalFlow -> when (dataOutput) {
is VariableOutput, EmptyOutput -> "return;"
is ExpressionOutput -> null
ArtificialBooleanOutput -> throw IllegalArgumentException()
is VariableOutput, is EmptyOutput -> "return;"
is ExpressionOutput, ArtificialBooleanOutput -> null
}
is EmptyFlow -> null
}
return when {
replacement != null -> flowOutput.statements.map { statement -> PsiReplace(statement, statementOf(replacement)) }
else -> emptyList()
}
}
private fun createMissedDeclarations(missedDeclarations: List<PsiVariable>): List<PsiDeclarationStatement> {
return missedDeclarations.map { variable ->
factory.createVariableDeclarationStatement(requireNotNull(variable.name), variable.type, null)
}
private fun findExitReplacements(flowOutput: FlowOutput, dataOutput: DataOutput): List<PsiReplace> {
val replacement = findDefaultFlowSubstitution(flowOutput, dataOutput) ?: return emptyList()
return flowOutput.statements.map { statement -> PsiReplace(statement, statementOf(replacement)) }
}
private fun createInputReplacements(inputGroup: InputParameter): List<PsiReplace> {
return inputGroup.references.map { reference -> PsiReplace(reference, expressionOf(inputGroup.name)) }
return inputGroup.references
.map { referenceExpression -> findTopmostParenthesis(referenceExpression) }
.map { normalizedExpression -> PsiReplace(normalizedExpression, expressionOf(inputGroup.name)) }
}
private fun getDefaultReturn(dataOutput: DataOutput, flowOutput: FlowOutput): PsiStatement? {
return when(dataOutput) {
is VariableOutput -> statementOf("return ${dataOutput.name};")
is ExpressionOutput -> if (flowOutput is ConditionalFlow) statementOf("return null;") else null
ArtificialBooleanOutput -> statementOf("return false;")
EmptyOutput -> null
private fun findDefaultReturn(dataOutput: DataOutput, flowOutput: FlowOutput): String? {
return when (dataOutput) {
is VariableOutput -> "return ${dataOutput.name};"
is ExpressionOutput -> if (flowOutput is ConditionalFlow) "return null;" else null
ArtificialBooleanOutput -> "return false;"
is EmptyOutput -> null
}
}
private fun buildBodyForExpression(inputParameters: List<InputParameter>, dataOutput: ExpressionOutput): PsiCodeBlock {
val expression = dataOutput.returnExpressions.single()
val normalizedExpression = PsiUtil.skipParenthesizedExprDown(expression)!!
val endStatement = when (dataOutput.type) {
PsiType.VOID -> factory.createStatementFromText("${normalizedExpression.text};", null)
else -> factory.createStatementFromText("return ${normalizedExpression.text};", null)
}
val copyExpression = when (endStatement) {
is PsiReturnStatement -> endStatement.returnValue!!
is PsiExpressionStatement -> endStatement.expression
else -> throw RuntimeException()
}
fun <T: PsiElement> findInCopy(element: T): T {
val sourceStartOffset: Int = normalizedExpression.textRange.startOffset
val copyStartOffset: Int = copyExpression.textRange.startOffset
val range = element.textRange.shiftRight(copyStartOffset - sourceStartOffset)
return CodeInsightUtil.findElementInRange(copyExpression.containingFile, range.startOffset, range.endOffset, element.javaClass)
}
val inCopyParameters = inputParameters.map { parameter ->
InputParameter(
references = parameter.references.map (::findInCopy),
name = parameter.name,
type = parameter.type
)
}
val replacements = inCopyParameters.map { createInputReplacements(it) }.flatten()
replacements.forEach { replacement -> replacement.source.replace(replacement.target) }
private fun createDeclarationForDisabledParameter(parameter: InputParameter): PsiDeclarationStatement {
val styleManager = CodeStyleManager.getInstance(parameter.references.first().project)
val declaration = factory.createStatementFromText("int ${parameter.name} = ;", null) as PsiDeclarationStatement
val variable = declaration.declaredElements.first() as? PsiVariable
val typeElement = factory.createTypeElement(parameter.type)
variable?.typeElement?.replace(typeElement)
return styleManager.reformat(declaration) as PsiDeclarationStatement
}
val codeBlock = factory.createCodeBlock()
codeBlock.add(endStatement)
return codeBlock
private fun wrapExpression(expression: PsiExpression, shouldBeReturned: Boolean): Pair<PsiStatement, PsiExpression> {
val statement = when (shouldBeReturned) {
false -> factory.createStatementFromText("${expression.text};", null)
true -> factory.createStatementFromText("return ${expression.text};", null)
}
val block = factory.createCodeBlockFromText("{\n}", expression.context)
val addedStatement = block.add(statement) as PsiStatement
val inCopyExpression = when (addedStatement) {
is PsiReturnStatement -> addedStatement.returnValue!!
is PsiExpressionStatement -> addedStatement.expression
else -> throw IllegalStateException()
}
return Pair(addedStatement, inCopyExpression)
}
private fun findInputParameterInCopy(source: PsiElement, copy: PsiElement, parameter: InputParameter): InputParameter {
return InputParameter(
references = parameter.references.map { reference -> findInCopy(source, copy, reference) },
name = parameter.name,
type = parameter.type
)
}
fun copyOf(elements: List<PsiElement>): List<PsiElement> {
val block = factory.createCodeBlockFromText("{}", elements.first().context)
block.add(PsiParserFacade.SERVICE.getInstance(elements.first().project).createWhiteSpaceFromText("\n "))
val first = block.addRange(elements.first(), elements.last())
val last = block.lastBodyElement!!
return PsiTreeUtil.getElementsOfRange(first, last)
}
fun build(elements: List<PsiElement>,
flowOutput: FlowOutput,
dataOutput: DataOutput,
inputParameters: List<InputParameter>,
disabledParameters: List<InputParameter>,
missedDeclarations: List<PsiVariable>): PsiCodeBlock {
val expression = elements.singleOrNull() as? PsiExpression
if (expression != null && dataOutput is ExpressionOutput) return buildBodyForExpression(inputParameters, dataOutput)
val project = elements.first().project
val fragment = CodeFragment.of(elements)
val copy = CodeFragment.copyOf(fragment)
val exitCopies = flowOutput.statements.map { statement -> CodeFragment.findSameElementInCopy(fragment, copy, statement) }
val expression = elements.singleOrNull() as? PsiExpression
val normalizedExpression = PsiUtil.skipParenthesizedExprDown(expression)
if (normalizedExpression != null) {
require(dataOutput is ExpressionOutput)
val (wrappedStatement, wrappedExpression) = wrapExpression(normalizedExpression, dataOutput.type != PsiType.VOID)
val wrappedInputParameters = inputParameters.map { parameter -> findInputParameterInCopy(normalizedExpression, wrappedExpression, parameter) }
val wrappedFlowOutput = UnconditionalFlow(listOf(wrappedStatement), true)
val wrappedDataOutput = dataOutput.copy(returnExpressions = listOf(wrappedExpression))
return build(listOf(wrappedStatement), wrappedFlowOutput, wrappedDataOutput, wrappedInputParameters, disabledParameters, missedDeclarations)
}
val blockStatement = elements.singleOrNull() as? PsiBlockStatement
val firstElement = blockStatement?.codeBlock?.firstBodyElement ?: elements.first()
val lastElement = blockStatement?.codeBlock?.lastBodyElement ?: elements.last()
val normalizedElements = PsiTreeUtil.getElementsOfRange(firstElement, lastElement)
.dropWhile { it is PsiWhiteSpace }
.dropLastWhile { it is PsiWhiteSpace }
val copy = copyOf(normalizedElements)
val exitCopies = flowOutput.statements.map { statement -> findInCopy(normalizedElements.first(), copy.first(), statement) }
val inCopyFlowOutput = when (flowOutput) {
is ConditionalFlow -> ConditionalFlow(exitCopies)
is UnconditionalFlow -> UnconditionalFlow(exitCopies, flowOutput.isDefaultExit)
EmptyFlow -> EmptyFlow
}
val inCopyInputGroups = inputParameters.map { group ->
InputParameter(
references = group.references.map { statement -> CodeFragment.findSameElementInCopy(fragment, copy, statement) },
name = group.name,
type = group.type
)
}
val exitSubstitution = findExitSubstitutions(inCopyFlowOutput, dataOutput)
val defaultReturn = getDefaultReturn(dataOutput, flowOutput)
val inCopyInputGroups = inputParameters.map { parameter -> findInputParameterInCopy(normalizedElements.first(), copy.first(), parameter) }
val exitSubstitution = findExitReplacements(inCopyFlowOutput, dataOutput)
val inputReplacements = inCopyInputGroups.map { createInputReplacements(it) }.flatten()
val requiredDeclarations = createMissedDeclarations(missedDeclarations)
val requiredDeclarations = missedDeclarations.map { createDeclaration(it) }
(inputReplacements + exitSubstitution).forEach { (source, target) -> source.replace(target) }
val block = copy.first().parent as PsiCodeBlock
val defaultReturn = findDefaultReturn(dataOutput, flowOutput)
if (defaultReturn != null) {
copy.commonParent.addAfter(defaultReturn, copy.lastElement)
block.addAfter(statementOf(defaultReturn), copy.last())
}
requiredDeclarations.forEach { declaration -> copy.commonParent.addBefore(declaration, copy.firstElement) }
return copy.commonParent as PsiCodeBlock
val disabledDeclarations = disabledParameters.map { createDeclarationForDisabledParameter(it) }
disabledDeclarations.reversed().forEach { declaration ->
block.addBefore(declaration, copy.first())
val newLine = PsiParserFacade.SERVICE.getInstance(project).createWhiteSpaceFromText("\n")
block.addBefore(newLine, copy.first())
}
requiredDeclarations.forEach { declaration -> block.addBefore(declaration, copy.first()) }
return block
}
}
@@ -1,32 +1,41 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.refactoring.extractMethod.newImpl
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.util.PsiUtil
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodHelper.createDeclaration
import com.intellij.refactoring.extractMethod.newImpl.structures.DataOutput
import com.intellij.refactoring.extractMethod.newImpl.structures.DataOutput.*
import com.intellij.refactoring.extractMethod.newImpl.structures.FlowOutput
import com.intellij.refactoring.extractMethod.newImpl.structures.FlowOutput.*
import java.lang.IllegalArgumentException
import java.lang.IllegalStateException
class CallFactory(private val factory: PsiElementFactory) {
class CallBuilder(project: Project, private val context: PsiElement?) {
private fun expressionOf(expression: String) = factory.createExpressionFromText(expression, null)
private val factory: PsiElementFactory = PsiElementFactory.getInstance(project)
private fun statementsOf(vararg statements: String) = statements.map { statement -> factory.createStatementFromText(statement, null) }
private fun expressionOf(expression: String) = factory.createExpressionFromText(expression, context)
private fun statementsOf(vararg statements: String) = statements.map { statement -> factory.createStatementFromText(statement, context) }
private fun createDeclaration(type: PsiType?, name: String, initializer: String): PsiStatement {
return when {
type != null -> factory.createVariableDeclarationStatement(name, type, expressionOf(initializer))
else -> factory.createStatementFromText("$name = $initializer;", null)
else -> factory.createStatementFromText("$name = $initializer;", context)
}
}
private fun variableDeclaration(methodCall: String, dataOutput: DataOutput): List<PsiStatement> {
val declaration = when (dataOutput) {
is VariableOutput -> createDeclaration(dataOutput.type.takeIf { dataOutput.declareType }, dataOutput.name, methodCall)
is ExpressionOutput -> createDeclaration(dataOutput.type, dataOutput.name, methodCall)
ArtificialBooleanOutput, EmptyOutput -> null
is ExpressionOutput -> createDeclaration(dataOutput.type, dataOutput.name!!, methodCall)
ArtificialBooleanOutput, is EmptyOutput -> null
}
val declarationStatement = declaration as? PsiDeclarationStatement
val declaredVariable = declarationStatement?.declaredElements?.firstOrNull() as? PsiVariable
if (dataOutput is VariableOutput && declaredVariable != null) {
val needsFinal = dataOutput.variable.hasModifierProperty(PsiModifier.FINAL)
PsiUtil.setModifierProperty(declaredVariable, PsiModifier.FINAL, needsFinal)
}
return listOfNotNull(declaration)
}
@@ -36,7 +45,7 @@ class CallFactory(private val factory: PsiElementFactory) {
is VariableOutput -> "if (${dataOutput.name} == null) ${flow.statements.first().text}"
is ExpressionOutput -> "if (${dataOutput.name} != null) return ${dataOutput.name};"
ArtificialBooleanOutput -> "if ($methodCall) ${flow.statements.first().text}"
EmptyOutput -> throw IllegalArgumentException()
is EmptyOutput -> throw IllegalArgumentException()
}
return statementsOf(exit)
}
@@ -50,7 +59,7 @@ class CallFactory(private val factory: PsiElementFactory) {
"return $methodCall;"
)
ArtificialBooleanOutput -> throw IllegalStateException()
EmptyOutput -> when {
is EmptyOutput -> when {
flow.isDefaultExit -> statementsOf("$methodCall;")
else -> statementsOf(
"$methodCall;",
@@ -68,12 +77,17 @@ class CallFactory(private val factory: PsiElementFactory) {
}
}
private fun declarationsOf(variables: List<PsiVariable>): List<PsiDeclarationStatement> {
return variables.map { variable -> factory.createVariableDeclarationStatement(requireNotNull(variable.name), variable.type, null) }
}
fun buildCall(methodCall: String, flowOutput: FlowOutput, dataOutput: DataOutput, exposedDeclarations: List<PsiVariable>): List<PsiStatement> {
val variableDeclaration = if (flowOutput !is ConditionalFlow && dataOutput is ExpressionOutput) emptyList() else variableDeclaration(methodCall, dataOutput)
return variableDeclaration + createFlowStatements(methodCall, flowOutput, dataOutput) + declarationsOf(exposedDeclarations)
return variableDeclaration + createFlowStatements(methodCall, flowOutput, dataOutput) + exposedDeclarations.map { createDeclaration(it) }
}
fun buildExpressionCall(methodCall: String, dataOutput: DataOutput): List<PsiElement> {
require(dataOutput is ExpressionOutput)
val expression = when {
dataOutput.name != null -> "${dataOutput.name} = $methodCall"
else -> methodCall
}
return listOf(factory.createExpressionFromText(expression, context))
}
}
@@ -1,20 +1,32 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.refactoring.extractMethod.newImpl
import com.intellij.codeInsight.CodeInsightUtil
import com.intellij.codeInsight.ExceptionUtil
import com.intellij.codeInsight.Nullability
import com.intellij.codeInsight.generation.GenerateMembersUtil
import com.intellij.codeInspection.dataFlow.*
import com.intellij.codeInspection.dataFlow.value.DfaValue
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.intellij.psi.codeStyle.JavaCodeStyleManager
import com.intellij.psi.codeStyle.VariableKind
import com.intellij.psi.controlFlow.*
import com.intellij.psi.controlFlow.ControlFlow
import com.intellij.psi.controlFlow.ControlFlowUtil.DEFAULT_EXIT_STATEMENTS_CLASSES
import com.intellij.psi.impl.source.codeStyle.JavaCodeStyleManagerImpl
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiUtil
import com.intellij.refactoring.extractMethod.newImpl.structures.DataOutput
import com.intellij.refactoring.extractMethod.newImpl.structures.ExtractOptions
import com.intellij.refactoring.util.classMembers.ClassMemberReferencesVisitor
import com.intellij.util.containers.IntArrayList
import com.siyeh.ig.psiutils.VariableAccessUtils
import java.util.LinkedHashSet
import kotlin.Comparator
import kotlin.collections.ArrayList
import kotlin.collections.HashSet
data class ExitDescription(val statements: List<PsiStatement>, val numberOfExits: Int, val hasSpecialExits: Boolean)
data class ExternalReference(val variable: PsiVariable, val references: List<PsiReferenceExpression>)
@@ -25,13 +37,12 @@ class CodeFragmentAnalyzer(val elements: List<PsiElement>) {
init {
require(elements.isNotEmpty())
}
private val codeFragment = ControlFlowUtil.findCodeFragment(elements.first())
private val flow: ControlFlow = createControlFlow(elements)
private val flowRange = findFlowRange(flow, elements)
private fun createControlFlow(elements: List<PsiElement>): ControlFlow {
val fragmentToAnalyze: PsiElement = ControlFlowUtil.findCodeFragment(elements.first())
val fragmentToAnalyze: PsiElement = codeFragment
val flowPolicy = LocalsControlFlowPolicy(fragmentToAnalyze)
val factory: ControlFlowFactory = ControlFlowFactory.getInstance(elements.first().project)
return factory.getControlFlow(fragmentToAnalyze, flowPolicy, false, false)
@@ -54,9 +65,26 @@ class CodeFragmentAnalyzer(val elements: List<PsiElement>) {
fun findExternalReferences(): List<ExternalReference> {
return ControlFlowUtil.getInputVariables(flow, flowRange.first, flowRange.last)
.filterNot { variable -> variable in this }
.sortedWith( Comparator { v1: PsiVariable, v2: PsiVariable -> when {
v1.type is PsiEllipsisType -> 1
v2.type is PsiEllipsisType -> -1
else -> v1.textOffset - v2.textOffset
}})
.map { variable -> ExternalReference(variable, findVariableReferences(variable)) }
}
fun findUsedVariablesAfter(): List<PsiVariable> {
return ControlFlowUtil.getUsedVariables(flow, flowRange.last, flow.size)
}
fun findOuterLocals(sourceClassMember: PsiElement, targetClassMember: PsiElement): List<ExternalReference>? {
val outerVariables = mutableListOf<PsiVariable>()
val canBeExtracted = elements
.all { element -> ControlFlowUtil.collectOuterLocals(outerVariables, element, sourceClassMember, targetClassMember) }
if (!canBeExtracted) return null
return outerVariables.map { variable -> ExternalReference(variable, findVariableReferences(variable)) }
}
fun findOutputVariables(): List<PsiVariable> {
val exitPoints = IntArrayList()
ControlFlowUtil.findExitPointsAndStatements(flow, flowRange.first, flowRange.last, exitPoints, *DEFAULT_EXIT_STATEMENTS_CLASSES)
@@ -71,6 +99,10 @@ class CodeFragmentAnalyzer(val elements: List<PsiElement>) {
}
}
fun hasObservableThrowExit(): Boolean {
return ControlFlowUtil.hasObservableThrowExitPoints(flow, flowRange.first, flowRange.last, elements.toTypedArray(), codeFragment)
}
fun findExitDescription(): ExitDescription {
val statements = ControlFlowUtil
.findExitPointsAndStatements(flow, flowRange.first, flowRange.last, IntArrayList(), *DEFAULT_EXIT_STATEMENTS_CLASSES)
@@ -141,7 +173,8 @@ class CodeFragmentAnalyzer(val elements: List<PsiElement>) {
else -> listOf(flowRange.last)
}
return defaultExits.filterNot { it in flowRange.first until flowRange.last }
} else {
}
else {
return emptyList()
}
}
@@ -169,12 +202,14 @@ class CodeFragmentAnalyzer(val elements: List<PsiElement>) {
}
fun findExposedLocalVariables(expressions: List<PsiExpression>): List<PsiVariable> {
val writtenVariables = ControlFlowUtil.getWrittenVariables(flow, flowRange.first, flowRange.last, false)
val exposedLocalVariables = HashSet<PsiVariable>()
val visitor = object : JavaRecursiveElementWalkingVisitor() {
override fun visitReferenceExpression(reference: PsiReferenceExpression) {
exposedLocalVariables.addAll(writtenVariables.filter { variable: PsiVariable -> reference.isReferenceTo(variable) })
val variable = reference.resolve() as? PsiVariable ?: return
if (variable.textRange in TextRange(elements.first().textRange.startOffset, elements.last().textRange.endOffset)) {
exposedLocalVariables += variable
}
}
}
expressions.forEach { it.accept(visitor) }
@@ -203,6 +238,8 @@ class CodeFragmentAnalyzer(val elements: List<PsiElement>) {
companion object {
fun inferNullability(expressionGroup: List<PsiExpression>): Nullability {
if (expressionGroup.any { it.text == "null" }) return Nullability.NULLABLE
if (expressionGroup.isEmpty()) return Nullability.UNKNOWN
val fragmentToAnalyze = ControlFlowUtil.findCodeFragment(expressionGroup.first())
val dfaRunner = DataFlowRunner(fragmentToAnalyze.project)
@@ -230,17 +267,30 @@ class CodeFragmentAnalyzer(val elements: List<PsiElement>) {
}
}
fun inferNullability(scopeElements: List<PsiElement>, expression: String?): Nullability {
if (expression == null) return Nullability.UNKNOWN
val factory = PsiElementFactory.getInstance(scopeElements.first().project)
val analyzer = CodeFragmentAnalyzer(scopeElements)
val codeBlock = factory.createCodeBlock()
val inputVariables = analyzer.findUndeclaredVariables()
val virtualReturn = factory.createStatementFromText("return $expression;", null)
inputVariables.forEach { codeBlock.add(it) }
codeBlock.addRange(scopeElements.first(), scopeElements.last())
val probeExpression = (codeBlock.add(virtualReturn) as PsiReturnStatement).returnValue ?: return Nullability.UNKNOWN
return inferNullability(listOf(probeExpression))
fun inferNullability(place: PsiStatement, probeExpression: String?): Nullability {
if (probeExpression == null) return Nullability.UNKNOWN
val factory = PsiElementFactory.getInstance(place.project)
val sourceClass = findClassMember(place)?.containingClass ?: return Nullability.UNKNOWN
val copyClass = sourceClass.copy()
val copyPlace = findSameElementInCopy(sourceClass, copyClass, place)
val probeStatement = factory.createStatementFromText("return $probeExpression;", null)
val parent = copyPlace.parent
val codeBlock = when(parent) {
is PsiCodeBlock -> copyPlace.parent as PsiCodeBlock
else -> (copyPlace.parent.replace(factory.createCodeBlock()) as PsiCodeBlock)
.also { codeBlock -> codeBlock.add(copyPlace) }
}
val artificialReturn = codeBlock.add(probeStatement) as PsiReturnStatement
val artificialExpression = requireNotNull(artificialReturn.returnValue)
return inferNullability(listOf(artificialExpression))
}
private fun <T: PsiElement> findSameElementInCopy(source: PsiElement, copy: PsiElement, element: T): T {
val sourceStartOffset = source.textRange.startOffset
val copyStartOffset = copy.textRange.startOffset
val range = element.textRange.shiftRight(copyStartOffset - sourceStartOffset)
return CodeInsightUtil.findElementInRange(copy.containingFile, range.startOffset, range.endOffset, element.javaClass)
}
fun findReturnExpressionsIn(scope: PsiElement): List<PsiExpression> {
@@ -1,10 +1,23 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.refactoring.extractMethod.newImpl
import com.intellij.codeInsight.AnnotationUtil
import com.intellij.codeInsight.Nullability
import com.intellij.codeInsight.NullableNotNullManager
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiTypesUtil
import com.intellij.psi.util.PsiUtil
import com.intellij.refactoring.extractMethod.PrepareFailedException
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodHelper.findUsedTypeParameters
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodHelper.getExpressionType
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodHelper.guessName
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodHelper.hasExplicitModifier
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodHelper.inputParameterOf
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodHelper.normalizedAnchor
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodHelper.uniqueNameOf
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodHelper.withBoxedType
import com.intellij.refactoring.extractMethod.newImpl.structures.DataOutput
import com.intellij.refactoring.extractMethod.newImpl.structures.DataOutput.*
@@ -12,54 +25,47 @@ import com.intellij.refactoring.extractMethod.newImpl.structures.ExtractOptions
import com.intellij.refactoring.extractMethod.newImpl.structures.FlowOutput
import com.intellij.refactoring.extractMethod.newImpl.structures.FlowOutput.*
import com.intellij.refactoring.extractMethod.newImpl.structures.InputParameter
import com.intellij.refactoring.util.RefactoringUtil
import com.intellij.util.Processor
import java.util.*
fun findExtractOptions(elements: List<PsiElement>): ExtractOptions? {
fun findExtractOptions(elements: List<PsiElement>): ExtractOptions {
require(elements.isNotEmpty())
val analyzer = CodeFragmentAnalyzer(elements)
val flowOutput = findFlowOutput(analyzer) ?: return null
val flowOutput = findFlowOutput(analyzer) ?: throw PrepareFailedException("too many exits", elements.first())
val variableData = findVariableData(analyzer, analyzer.findOutputVariables()) ?: return null
val variableData = findVariableData(analyzer, analyzer.findOutputVariables())
val expression = elements.singleOrNull() as? PsiExpression
//TODO use correct error messages
val dataOutput = when {
expression != null -> ExpressionOutput(ExtractMethodHelper.getExpressionType(expression), "x", listOf(expression))
variableData != EmptyOutput -> when {
ExtractMethodHelper.areSame(flowOutput.statements) -> variableData
else -> return null
expression != null -> ExpressionOutput(getExpressionType(expression), null, listOf(expression), CodeFragmentAnalyzer.inferNullability(listOf(expression)))
variableData is VariableOutput -> when {
! ExtractMethodHelper.areSame(flowOutput.statements) && flowOutput is ConditionalFlow -> throw PrepareFailedException("Out var and different flow statements", flowOutput.statements.first())
variableData.nullability != Nullability.NOT_NULL && flowOutput is ConditionalFlow -> throw PrepareFailedException("Nullable out var and branching", variableData.variable)
flowOutput is ConditionalFlow -> variableData.copy(nullability = Nullability.NULLABLE)
else -> variableData
}
else -> findFlowData(analyzer, flowOutput) ?: return null
else -> findFlowData(analyzer, flowOutput)
}
val boxedDataOutput = when (flowOutput) {
is ConditionalFlow -> dataOutput.withBoxedType()
else -> dataOutput
}
val anchor = findClassMember(elements.first()) ?: throw PrepareFailedException("No upper class", elements.first())
if (flowOutput is ConditionalFlow && !isNotNullData(analyzer, dataOutput)) return null
val typeParameters = findUsedTypeParameters((anchor as? PsiTypeParameterListOwner)?.typeParameterList, elements)
val anchor = findDefaultAnchor(elements.first()) ?: return null
fun findUsedTypeParameters(source: PsiTypeParameterList?, searchScope: List<PsiElement>): List<PsiTypeParameter> {
return RefactoringUtil
.createTypeParameterListWithUsedTypeParameters(source, *searchScope.toTypedArray())?.typeParameters.orEmpty().toList()
}
val typeParameters = PsiElementFactory.getInstance(elements.first().project).createTypeParameterList()
if (anchor is PsiMethod){
val classTypeParameters = findUsedTypeParameters(anchor.containingClass?.typeParameterList, elements)
val methodTypeParameters = findUsedTypeParameters(anchor.typeParameterList, elements)
(classTypeParameters + methodTypeParameters).forEach { typeParameters.add(it) }
}
val inputParameters = analyzer.findExternalReferences().map { externalReference -> inputParameterOf(externalReference) }
val inputParameters = analyzer.findExternalReferences()
.map { externalReference -> inputParameterOf(externalReference) }
.map { it.copy(type = normalizeType(it.type)) }
val parameterNames = inputParameters.map { it.name }.toSet()
val extractOptions = ExtractOptions(
val exposedVariables = analyzer.findExposedLocalDeclarations()
var extractOptions = ExtractOptions(
anchor = anchor,
elements = elements,
flowOutput = flowOutput,
dataOutput = boxedDataOutput,
dataOutput = normalizeDataOutput(dataOutput, flowOutput, elements, exposedVariables.mapNotNull { it.name }),
thrownExceptions = analyzer.findThrownExceptions(),
requiredVariablesInside = analyzer.findUndeclaredVariables().filterNot { it.name in parameterNames },
typeParameters = typeParameters,
@@ -67,74 +73,187 @@ fun findExtractOptions(elements: List<PsiElement>): ExtractOptions? {
isConstructor = false,
isStatic = false,
visibility = "private",
inputParameters = analyzer.findExternalReferences().map { externalReference -> inputParameterOf(externalReference) },
exposedLocalVariables = analyzer.findExposedLocalDeclarations()
inputParameters = inputParameters,
disabledParameters = emptyList(),
exposedLocalVariables = exposedVariables
)
extractOptions = ExtractMethodPipeline.withCastedParameters(extractOptions)
val targetClass = PsiTreeUtil.getParentOfType(ExtractMethodHelper.getValidParentOf(elements.first()), PsiClass::class.java)!!
val fieldUsages = analyzer.findFieldUsages(targetClass, elements)
if (!extractOptions.isConstructor && fieldUsages.any { it.isWrite && it.field.hasExplicitModifier("final") }) {
return null
val finalFields = fieldUsages.filter { it.isWrite && it.field.hasExplicitModifier("final") }.map { it.field }.distinct()
extractOptions = when (finalFields.size) {
0 -> extractOptions
1 -> when (extractOptions.dataOutput is EmptyOutput) {
true -> extractOptions.copy(
dataOutput = VariableOutput(finalFields.first().type, finalFields.first(), false),
requiredVariablesInside = listOf(finalFields.first())
)
false -> throw PrepareFailedException("Too many final fields", finalFields.first())
}
else -> throw PrepareFailedException("Too many final fields", finalFields.first())
}
return extractOptions.copy(inputParameters = extractOptions.inputParameters).withDefaultStatic()
checkLocalClass(extractOptions)
return ExtractMethodPipeline.withDefaultStatic(extractOptions)
}
private fun findDefaultAnchor(element: PsiElement): PsiMember? {
private fun normalizeDataOutput(dataOutput: DataOutput, flowOutput: FlowOutput, elements: List<PsiElement>, reservedNames: List<String>): DataOutput {
val boxedDataOutput = when (flowOutput) {
is ConditionalFlow -> dataOutput.withBoxedType()
else -> dataOutput
}
val uniqueName = when (boxedDataOutput) {
is ExpressionOutput -> boxedDataOutput.copy(name = uniqueNameOf(boxedDataOutput.name, elements, reservedNames))
else -> boxedDataOutput
}
return uniqueName
}
private fun normalizeType(type: PsiType): PsiType {
return when (type) {
is PsiDisjunctionType -> PsiTypesUtil.getLowestUpperBoundClassType(type)!!
else -> GenericsUtil.getVariableTypeByExpressionType(type)
}
}
fun findClassMember(element: PsiElement): PsiMember? {
val holderTypes = arrayOf(PsiMethod::class.java, PsiField::class.java, PsiClassInitializer::class.java)
return PsiTreeUtil.getNonStrictParentOfType(ExtractMethodHelper.getValidParentOf(element), *holderTypes)
val anchor = PsiTreeUtil.getNonStrictParentOfType(ExtractMethodHelper.getValidParentOf(element), *holderTypes) ?: return null
return normalizedAnchor(anchor)
}
private fun findFlowOutput(analyzer: CodeFragmentAnalyzer): FlowOutput? {
if (analyzer.hasObservableThrowExit()) return null
val (exitStatements, numberOfExits, hasSpecialExits) = analyzer.findExitDescription()
return when (numberOfExits) {
1 -> if (exitStatements.isNotEmpty()) UnconditionalFlow(exitStatements, !hasSpecialExits) else EmptyFlow
2 -> ConditionalFlow(exitStatements)
2 -> if (exitStatements.isNotEmpty()) ConditionalFlow(exitStatements) else null
else -> return null
}
}
private fun inputParameterOf(externalReference: ExternalReference) = with(externalReference) {
InputParameter(references, requireNotNull(variable.name), variable.type)
}
private fun findFlowData(analyzer: CodeFragmentAnalyzer, flowOutput: FlowOutput): DataOutput? {
private fun findOutputFromReturn(flowOutput: FlowOutput): ExpressionOutput? {
val returnExpressions = flowOutput.statements.mapNotNull { statement -> (statement as? PsiReturnStatement)?.returnValue }
val returnType = returnExpressions.asSequence().mapNotNull { expression -> expression.type }.firstOrNull()
val variableName = returnExpressions.asSequence().map { expression -> ExtractMethodHelper.guessName(expression) }.firstOrNull() ?: "out"
val returnOutput = if (returnType != null) ExpressionOutput(returnType, variableName, returnExpressions) else null
val variableName = returnExpressions.asSequence().map { expression -> guessName(expression) }.firstOrNull() ?: "x"
val nullability = CodeFragmentAnalyzer.inferNullability(returnExpressions)
return if (returnType != null) ExpressionOutput(returnType, variableName, returnExpressions, nullability) else null
}
private fun findFlowData(analyzer: CodeFragmentAnalyzer, flowOutput: FlowOutput): DataOutput {
val returnOutput = findOutputFromReturn(flowOutput)
return when (flowOutput) {
is ConditionalFlow -> when {
ExtractMethodHelper.areSame(
flowOutput.statements) && analyzer.findExposedLocalVariables(returnExpressions).isEmpty() -> ArtificialBooleanOutput
CodeFragmentAnalyzer.inferNullability(returnExpressions) == Nullability.NOT_NULL -> returnOutput ?: return null
else -> null
returnOutput?.nullability == Nullability.NOT_NULL && returnOutput.type != PsiType.BOOLEAN -> returnOutput.copy(nullability = Nullability.NULLABLE)
ExtractMethodHelper.areSame(flowOutput.statements) && analyzer.findExposedLocalVariables(returnOutput?.returnExpressions.orEmpty()).isEmpty() ->
ArtificialBooleanOutput
else -> throw PrepareFailedException("Nullable output and branching", analyzer.elements.first())
}
is UnconditionalFlow -> returnOutput ?: EmptyOutput
EmptyFlow -> EmptyOutput
is UnconditionalFlow -> returnOutput ?: EmptyOutput()
EmptyFlow -> EmptyOutput()
}
}
private fun findVariableData(analyzer: CodeFragmentAnalyzer, variables: List<PsiVariable>): DataOutput? {
return when {
variables.isEmpty() -> EmptyOutput
variables.size == 1 -> VariableOutput(variables.single().type, variables.single(), variables.single() in analyzer)
else -> null
//TODO correct messages in PrepareFailedException
private fun findVariableData(analyzer: CodeFragmentAnalyzer, variables: List<PsiVariable>): DataOutput {
val variable = when {
analyzer.elements.singleOrNull() is PsiExpression && variables.isNotEmpty() -> throw PrepareFailedException("Var in expression", variables.first())
variables.isEmpty() -> return EmptyOutput()
variables.size > 1 -> throw PrepareFailedException("Many vars", variables[1])
else -> variables.single()
}
val nullability = CodeFragmentAnalyzer.inferNullability(analyzer.elements.last() as PsiStatement, variable.name)
return VariableOutput(variables.single().type, variables.single(), variables.single() in analyzer, nullability)
}
private fun isNotNullData(analyzer: CodeFragmentAnalyzer, dataOutput: DataOutput): Boolean {
return when (dataOutput) {
is VariableOutput -> CodeFragmentAnalyzer.inferNullability(analyzer.elements, dataOutput.variable.name) == Nullability.NOT_NULL
is ExpressionOutput -> CodeFragmentAnalyzer.inferNullability(dataOutput.returnExpressions) == Nullability.NOT_NULL
is ArtificialBooleanOutput, is EmptyOutput -> true
}
private fun PsiModifierListOwner?.hasNullabilityAnnotation(): Boolean {
if (this == null) return false
val nullabilityManager = NullableNotNullManager.getInstance(project)
val nullabilityAnnotations = nullabilityManager.notNulls + nullabilityManager.nullables
return AnnotationUtil.isAnnotated(this, nullabilityAnnotations, AnnotationUtil.CHECK_TYPE)
}
internal fun updateMethodAnnotations(method: PsiMethod) {
internal fun updateMethodAnnotations(method: PsiMethod,inputParameters: List<InputParameter>) {
if (method.returnType !is PsiPrimitiveType) {
//TODO use dataoutput.nullability instead
val resultNullability = CodeFragmentAnalyzer.inferNullability(CodeFragmentAnalyzer.findReturnExpressionsIn(method))
ExtractMethodHelper.addNullabilityAnnotation(method, resultNullability)
}
val parameters = method.parameterList.parameters
inputParameters
.filter { ((it.references.first() as? PsiReferenceExpression)?.resolve() as? PsiModifierListOwner).hasNullabilityAnnotation() }
.forEach { inputParameter ->
val parameterNullability = CodeFragmentAnalyzer.inferNullability(inputParameter.references)
val parameter = parameters.find { it.name == inputParameter.name }
if (parameter != null) ExtractMethodHelper.addNullabilityAnnotation(parameter, parameterNullability)
}
}
private fun checkLocalClass(options: ExtractOptions): Boolean {
var container: PsiElement? = PsiTreeUtil.getParentOfType(options.elements.first(), PsiClass::class.java, PsiMethod::class.java)
while (container is PsiMethod && container.containingClass !== options.anchor.parent) {
container = PsiTreeUtil.getParentOfType(container, PsiMethod::class.java, true)
}
container ?: return true
val analyzer = CodeFragmentAnalyzer(options.elements)
val localClasses = mutableListOf<PsiClass>()
container.accept(object : JavaRecursiveElementWalkingVisitor() {
override fun visitClass(aClass: PsiClass) {
localClasses.add(aClass)
}
override fun visitAnonymousClass(aClass: PsiAnonymousClass) {
visitElement(aClass)
}
override fun visitTypeParameter(classParameter: PsiTypeParameter) {
visitElement(classParameter)
}
})
fun isExtracted(element: PsiElement): Boolean {
return element.textRange in TextRange(options.elements.first().textRange.startOffset, options.elements.last().textRange.endOffset)
}
for (localClass in localClasses) {
val classExtracted: Boolean = isExtracted(localClass)
val extractedReferences = Collections.synchronizedList(ArrayList<PsiElement>())
val remainingReferences = Collections.synchronizedList(ArrayList<PsiElement>())
ReferencesSearch.search(localClass).forEach(Processor { psiReference: PsiReference ->
val element = psiReference.element
val elementExtracted: Boolean = isExtracted(element)
if (elementExtracted && !classExtracted) {
extractedReferences.add(element)
return@Processor false
}
if (!elementExtracted && classExtracted) {
remainingReferences.add(element)
return@Processor false
}
true
})
if (extractedReferences.isNotEmpty()) {
throw PrepareFailedException(
"Cannot extract method because the selected code fragment uses local classes defined outside of the fragment",
extractedReferences[0])
}
if (remainingReferences.isNotEmpty()) {
throw PrepareFailedException(
"Cannot extract method because the selected code fragment defines local classes used outside of the fragment",
remainingReferences[0])
}
if (classExtracted) {
analyzer.findUsedVariablesAfter()
.filter { isExtracted(it) }
.filter { PsiUtil.resolveClassInType(it.type) === localClass }
.forEach {
throw PrepareFailedException(
"Cannot extract method because the selected code fragment defines variable of local class type used outside of the fragment", it
)
}
}
}
return true
}
@@ -1,29 +1,42 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.refactoring.extractMethod.newImpl
import com.intellij.codeInsight.CodeInsightUtil
import com.intellij.codeInsight.Nullability
import com.intellij.codeInsight.NullableNotNullManager
import com.intellij.codeInsight.PsiEquivalenceUtil
import com.intellij.codeInsight.intention.AddAnnotationPsiFix
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.intellij.psi.codeStyle.JavaCodeStyleManager
import com.intellij.psi.impl.source.DummyHolder
import com.intellij.psi.impl.source.codeStyle.JavaCodeStyleManagerImpl
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiUtil
import com.intellij.refactoring.extractMethod.newImpl.structures.DataOutput
import com.intellij.refactoring.extractMethod.newImpl.structures.DataOutput.*
import com.intellij.refactoring.extractMethod.newImpl.structures.InputParameter
import com.intellij.refactoring.util.RefactoringUtil
object ExtractMethodHelper {
fun wrapWithCodeBlock(elements: List<PsiStatement>): List<PsiCodeBlock> {
fun wrapWithCodeBlock(elements: List<PsiElement>): List<PsiCodeBlock> {
require(elements.isNotEmpty())
val codeBlock = PsiElementFactory.getInstance(elements.first().project).createCodeBlock()
elements.forEach { codeBlock.add(it) }
return listOf(codeBlock)
}
fun findUsedTypeParameters(source: PsiTypeParameterList?, searchScope: List<PsiElement>): List<PsiTypeParameter> {
val typeParameterList = RefactoringUtil.createTypeParameterListWithUsedTypeParameters(source, *searchScope.toTypedArray())
return typeParameterList?.typeParameters.orEmpty().toList()
}
fun inputParameterOf(externalReference: ExternalReference) = with(externalReference) {
InputParameter(references, requireNotNull(variable.name), variable.type)
}
fun PsiElement.addSiblingAfter(element: PsiElement): PsiElement {
return this.parent.addAfter(element, this)
}
@@ -38,6 +51,21 @@ object ExtractMethodHelper {
return physicalParent ?: throw IllegalArgumentException()
}
fun normalizedAnchor(anchor: PsiMember): PsiMember {
return when (anchor) {
is PsiField -> findLastFieldInDeclaration(anchor)
else -> anchor
}
}
private fun findLastFieldInDeclaration(field: PsiField): PsiField {
val nextSibling = PsiTreeUtil.skipWhitespacesForward(field)
return when (PsiUtil.getElementType(nextSibling)) {
JavaTokenType.COMMA -> PsiTreeUtil.skipWhitespacesForward(nextSibling) as PsiField
else -> field
}
}
fun addNullabilityAnnotation(owner: PsiModifierListOwner, nullability: Nullability) {
val nullabilityManager = NullableNotNullManager.getInstance(owner.project)
val annotation = when (nullability) {
@@ -50,14 +78,61 @@ object ExtractMethodHelper {
JavaCodeStyleManager.getInstance(owner.project).shortenClassReferences(annotationElement)
}
private fun findVariableReferences(element: PsiElement): Sequence<PsiVariable> {
val references = PsiTreeUtil.findChildrenOfAnyType(element, PsiReferenceExpression::class.java)
return references.asSequence().mapNotNull { reference -> (reference.resolve() as? PsiVariable) }
}
fun <T: PsiElement> findInCopy(firstInSource: PsiElement, firstInCopy: PsiElement, element: T): T {
val sourceStartOffset: Int = firstInSource.textRange.startOffset
val copyStartOffset: Int = firstInCopy.textRange.startOffset
val range = element.textRange.shiftRight(copyStartOffset - sourceStartOffset)
return CodeInsightUtil.findElementInRange(firstInCopy.containingFile, range.startOffset, range.endOffset, element.javaClass)
}
fun hasConflictResolve(name: String?, scopeToIgnore: List<PsiElement>): Boolean {
require(scopeToIgnore.isNotEmpty())
if (name == null) return false
val lastElement = scopeToIgnore.last()
val helper = JavaPsiFacade.getInstance(lastElement.project).resolveHelper
val resolvedRange = helper.resolveAccessibleReferencedVariable(name, lastElement.context)?.textRange ?: return false
return resolvedRange !in TextRange(scopeToIgnore.first().textRange.startOffset, scopeToIgnore.last().textRange.endOffset)
}
fun uniqueNameOf(name: String?, scopeToIgnore: List<PsiElement>, reservedNames: List<String>): String? {
require(scopeToIgnore.isNotEmpty())
if (name == null) return null
val lastElement = scopeToIgnore.last()
if (hasConflictResolve(name, scopeToIgnore) || name in reservedNames){
val styleManager = JavaCodeStyleManager.getInstance(lastElement.project) as JavaCodeStyleManagerImpl
return styleManager.suggestUniqueVariableName(name, lastElement, true)
} else {
return name
}
}
fun guessName(expression: PsiExpression): String? {
val codeStyleManager = JavaCodeStyleManager.getInstance(expression.project) as JavaCodeStyleManagerImpl
val name = codeStyleManager
.suggestSemanticNames(expression).firstOrNull()
?: PsiTreeUtil.findChildOfType(expression, PsiReferenceExpression::class.java)?.referenceName
?: "x"
return codeStyleManager.suggestUniqueVariableName(name, expression, true)
return findVariableReferences(expression).mapNotNull { variable -> variable.name }.firstOrNull()
?: codeStyleManager.suggestSemanticNames(expression).firstOrNull()
?: "x"
}
fun createDeclaration(variable: PsiVariable): PsiDeclarationStatement {
val factory = PsiElementFactory.getInstance(variable.project)
val declaration = factory.createVariableDeclarationStatement(requireNotNull(variable.name), variable.type, null)
val declaredVariable = declaration.declaredElements.first() as PsiVariable
PsiUtil.setModifierProperty(declaredVariable, PsiModifier.FINAL, variable.hasModifierProperty(PsiModifier.FINAL))
return declaration
}
tailrec fun findTopmostParenthesis(expression: PsiExpression): PsiExpression {
val parent = expression.parent as? PsiParenthesizedExpression
return when (parent?.expression) {
expression -> findTopmostParenthesis(parent)
else -> expression
}
}
fun getExpressionType(expression: PsiExpression): PsiType {
@@ -94,7 +169,7 @@ object ExtractMethodHelper {
return when (this) {
is VariableOutput -> copy(type = boxedTypeOf(type, variable))
is ExpressionOutput -> copy(type = boxedTypeOf(type, returnExpressions.first()))
ArtificialBooleanOutput, EmptyOutput -> this
ArtificialBooleanOutput, is EmptyOutput -> this
}
}
}
@@ -1,126 +1,239 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.refactoring.extractMethod.newImpl
import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil
import com.intellij.codeInsight.daemon.impl.quickfix.AnonymousTargetClassPreselectionUtil
import com.intellij.codeInsight.navigation.NavigationUtil
import com.intellij.ide.util.PsiClassListCellRenderer
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.util.TextRange
import com.intellij.pom.java.LanguageLevel
import com.intellij.psi.*
import com.intellij.psi.search.PsiElementProcessor
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiTypesUtil
import com.intellij.psi.util.PsiUtil
import com.intellij.refactoring.extractMethod.PrepareFailedException
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodHelper.findUsedTypeParameters
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodHelper.hasExplicitModifier
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodHelper.inputParameterOf
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodHelper.normalizedAnchor
import com.intellij.refactoring.extractMethod.newImpl.structures.ExtractOptions
import com.intellij.refactoring.extractMethod.newImpl.structures.InputParameter
import com.intellij.refactoring.util.VariableData
private fun findFoldableArrayExpression(reference: PsiElement): PsiArrayAccessExpression? {
val arrayAccess = reference.parent as? PsiArrayAccessExpression
return when (arrayAccess?.arrayExpression) {
reference -> arrayAccess
else -> null
}
}
object ExtractMethodPipeline {
fun remap(extractOptions: ExtractOptions,
variableData: Array<VariableData>,
methodName: String,
isStatic: Boolean,
visibility: String,
isConstructor: Boolean): ExtractOptions {
val analyzer = CodeFragmentAnalyzer(extractOptions.elements)
val remappedName = extractOptions.withMappedName(methodName)
val remappedInput = remappedName.withMappedParametersInput(variableData.toList())
val remappedStatic = remappedInput.takeIf { isStatic }
?.withForcedStatic(analyzer = analyzer)
?: remappedInput
val mappedToConstructor = remappedStatic.takeIf { isConstructor }?.asConstructor(analyzer) ?: remappedStatic
fun remap(extractOptions: ExtractOptions,
variableData: Array<VariableData>,
methodName: String,
isStatic: Boolean,
visibility: String,
isConstructor: Boolean,
returnType: PsiType
): ExtractOptions {
val analyzer = CodeFragmentAnalyzer(extractOptions.elements)
var options = withMappedName(extractOptions, methodName)
options = withMappedParametersInput(options, variableData.toList())
val targetClass = extractOptions.anchor.containingClass!!
options = when {
targetClass.isInterface -> adjustModifiersForInterface(options.copy(visibility = PsiModifier.PRIVATE))
else -> options.copy(visibility = visibility)
}
return mappedToConstructor.copy(visibility = visibility)
}
if (isStatic && ! options.isStatic) {
options = withForcedStatic(analyzer, options) ?: throw PrepareFailedException("Fail", options.elements.first())
}
fun ExtractOptions.withMappedParametersInput(variablesData: List<VariableData>): ExtractOptions {
fun findMappedParameter(variableData: VariableData): InputParameter? {
return inputParameters
.find { (it.references.first() as? PsiReferenceExpression)?.text == variableData.variable.name }
?.copy(name = variableData.name ?: "x", type = variableData.type)
if (isConstructor) {
options = asConstructor(analyzer, options)
}
return options.copy(dataOutput = extractOptions.dataOutput.withType(returnType))
}
val mappedParameters = variablesData.mapNotNull(::findMappedParameter)
fun withTargetClass(analyzer: CodeFragmentAnalyzer, extractOptions: ExtractOptions, targetClass: PsiClass): ExtractOptions? {
val anchor = extractOptions.anchor
if (anchor.parent == targetClass) return extractOptions
if (mappedParameters.size != inputParameters.size) return this
val newAnchor = targetClass.children.find { child -> anchor.textRange in child.textRange } as? PsiMember
if (newAnchor == null) return null
return copy(
inputParameters = mappedParameters
)
}
val typeParameters = findAllTypeLists(anchor, targetClass).flatMap { findUsedTypeParameters(it, extractOptions.elements) }
fun ExtractOptions.withMappedName(methodName: String) = if (this.isConstructor) this else this.copy(methodName = methodName)
fun ExtractOptions.withDefaultStatic(): ExtractOptions {
val parent = ExtractMethodHelper.getValidParentOf(elements.first())
val parentHolder = PsiTreeUtil.getNonStrictParentOfType(parent, PsiMethod::class.java, PsiClassInitializer::class.java,
PsiField::class.java)
val shouldBeStatic = when {
parent is PsiField -> true
parentHolder.hasExplicitModifier(PsiModifier.STATIC) -> true
else -> false
}
return copy(isStatic = shouldBeStatic)
}
fun ExtractOptions.withFoldedArrayParameters(analyzer: CodeFragmentAnalyzer): ExtractOptions {
val writtenVariables = analyzer.findWrittenVariables().mapNotNull { it.name }
fun findFoldedCandidate(inputParameter: InputParameter): InputParameter? {
val arrayAccesses = inputParameter.references.map { findFoldableArrayExpression(it) ?: return null }
if (arrayAccesses.any { (it.parent as? PsiAssignmentExpression)?.lExpression == it }) return null
if (!ExtractMethodHelper.areSame(arrayAccesses.map { it.indexExpression })) return null
if (arrayAccesses.any { it.indexExpression?.text in writtenVariables }) return null
val parameterName = arrayAccesses.first().arrayExpression.text + "Element"
return InputParameter(arrayAccesses, parameterName, arrayAccesses.first().type ?: return null)
val additionalReferences = analyzer.findOuterLocals(anchor, newAnchor) ?: return null
val additionalParameters = additionalReferences.map { inputParameterOf(it) }
val options = extractOptions.copy(
anchor = normalizedAnchor(newAnchor),
inputParameters = extractOptions.inputParameters + additionalParameters,
typeParameters = typeParameters
)
return withDefaultStatic(options)
}
fun findHiddenExpression(arrayAccess: PsiArrayAccessExpression?): List<InputParameter> {
return inputParameters.filter {
ExtractMethodHelper.areSame(it.references.first(), arrayAccess?.arrayExpression)
|| ExtractMethodHelper.areSame(it.references.first(), arrayAccess?.indexExpression)
private fun findAllTypeLists(element: PsiElement, stopper: PsiElement): List<PsiTypeParameterList> {
return generateSequence (element) { it.parent }
.takeWhile { it != stopper && it !is PsiFile }
.filterIsInstance<PsiTypeParameterListOwner>()
.mapNotNull { it.typeParameterList }
.toList()
}
private fun findCommonCastParameter(inputParameter: InputParameter): InputParameter? {
val castExpressions = inputParameter.references.map { reference -> (reference.parent as? PsiTypeCastExpression) ?: return null }
val type = castExpressions.first().castType?.type ?: return null
if ( castExpressions.any { castExpression -> castExpression.castType?.type != type } ) return null
return InputParameter(name = inputParameter.name, type = type, references = castExpressions)
}
fun withCastedParameters(extractOptions: ExtractOptions): ExtractOptions {
val parameters = extractOptions.inputParameters.map { inputParameter -> findCommonCastParameter(inputParameter) ?: inputParameter }
return extractOptions.copy(inputParameters = parameters)
}
fun withMappedParametersInput(extractOptions: ExtractOptions, variablesData: List<VariableData>): ExtractOptions {
fun findMappedParameter(variableData: VariableData): InputParameter? {
return extractOptions.inputParameters
.find { parameter -> parameter.name == variableData.variable.name }
?.copy(name = variableData.name ?: "x", type = variableData.type)
}
val mappedParameters = variablesData.filter { it.passAsParameter }.mapNotNull(::findMappedParameter)
val disabledParameters = variablesData.filterNot { it.passAsParameter }.mapNotNull(::findMappedParameter)
return extractOptions.copy(
inputParameters = mappedParameters,
disabledParameters = disabledParameters
)
}
fun adjustModifiersForInterface(options: ExtractOptions): ExtractOptions {
val targetClass = options.anchor.containingClass!!
if (! targetClass.isInterface) return options
val languageLevel: LanguageLevel = PsiUtil.getLanguageLevel(targetClass)
val isAtLeastJava9 = languageLevel.isAtLeast(LanguageLevel.JDK_1_9)
val isJava8 = languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && !isAtLeastJava9
val visibility = if (options.visibility == PsiModifier.PRIVATE && isJava8) null else options.visibility
val holder = findClassMember(options.elements.first())
val isStatic = holder is PsiField || options.isStatic
return options.copy(visibility = visibility, isStatic = isStatic)
}
fun withMappedName(extractOptions: ExtractOptions, methodName: String) = if (extractOptions.isConstructor) extractOptions else extractOptions.copy(methodName = methodName)
fun withDefaultStatic(extractOptions: ExtractOptions): ExtractOptions {
val expression = extractOptions.elements.singleOrNull() as? PsiExpression
val statement = PsiTreeUtil.getParentOfType(expression, PsiExpressionStatement::class.java)
if (statement != null && JavaHighlightUtil.isSuperOrThisCall(statement, true, true)) {
return extractOptions.copy(isStatic = true)
}
val shouldBeStatic = when {
extractOptions.anchor.hasExplicitModifier(PsiModifier.STATIC) -> true
else -> false
}
return extractOptions.copy(isStatic = shouldBeStatic)
}
fun findTargetCandidates(analyzer: CodeFragmentAnalyzer, options: ExtractOptions): List<PsiClass> {
return generateSequence (options.anchor as PsiElement) { it.parent }
.takeWhile { it !is PsiFile }
.filterIsInstance<PsiClass>()
.filter { targetClass -> withTargetClass(analyzer, options, targetClass) != null }
.toList()
}
fun findDefaultTargetCandidate(candidates: List<PsiClass>): PsiClass {
return AnonymousTargetClassPreselectionUtil.getPreselection(candidates, candidates.first()) ?: candidates.first()
}
fun <T> selectTargetClass(options: ExtractOptions, onSelected: (ExtractOptions) -> T): ExtractOptions {
val analyzer = CodeFragmentAnalyzer(options.elements)
val targetCandidates = findTargetCandidates(analyzer, options)
val preselection = findDefaultTargetCandidate(targetCandidates)
val editor = FileEditorManager.getInstance(options.project).selectedTextEditor ?: return options
val processor = PsiElementProcessor<PsiClass> { selected ->
val mappedOptions = withTargetClass(analyzer, options, selected)!!
onSelected(mappedOptions)
true
}
if (targetCandidates.size > 1) {
NavigationUtil.getPsiElementPopup(targetCandidates.toTypedArray(), PsiClassListCellRenderer(), "Choose Destination Class", processor, preselection)
.showInBestPositionFor(editor)
} else {
processor.execute(preselection)
}
return options
}
private fun findFoldableArrayExpression(reference: PsiElement): PsiArrayAccessExpression? {
val arrayAccess = reference.parent as? PsiArrayAccessExpression
return when (arrayAccess?.arrayExpression) {
reference -> arrayAccess
else -> null
}
}
val foldedCandidates = inputParameters.mapNotNull { findFoldedCandidate(it) }
fun withFoldedArrayParameters(analyzer: CodeFragmentAnalyzer, extractOptions: ExtractOptions): ExtractOptions {
val writtenVariables = analyzer.findWrittenVariables().mapNotNull { it.name }
val (folded, hidden) = foldedCandidates
.map { it to findHiddenExpression(it.references.first() as? PsiArrayAccessExpression) }
.filter { it.second.size > 1 }.unzip()
return this.copy(inputParameters = this.inputParameters - hidden.flatten() + folded)
}
fun ExtractOptions.asConstructor(analyzer: CodeFragmentAnalyzer): ExtractOptions {
return if (canBeConstructor(analyzer)) copy(isConstructor = true, methodName = "this") else this
}
fun ExtractOptions.withForcedStatic(analyzer: CodeFragmentAnalyzer): ExtractOptions? {
val targetClass = PsiTreeUtil.getParentOfType(ExtractMethodHelper.getValidParentOf(elements.first()), PsiClass::class.java)!!
val fieldUsages = analyzer.findFieldUsages(targetClass, elements)
if (fieldUsages.any { it.isWrite }) return null
val fieldInputParameters =
fieldUsages.groupBy { it.field }.entries.map { (field, fieldUsages) ->
InputParameter(
references = fieldUsages.map { it.classMemberReference },
name = field.name,
type = field.type
)
fun findFoldedCandidate(inputParameter: InputParameter): InputParameter? {
val arrayAccesses = inputParameter.references.map { findFoldableArrayExpression(it) ?: return null }
if (arrayAccesses.any { (it.parent as? PsiAssignmentExpression)?.lExpression == it }) return null
if (!ExtractMethodHelper.areSame(arrayAccesses.map { it.indexExpression })) return null
if (arrayAccesses.any { it.indexExpression?.text in writtenVariables }) return null
val parameterName = arrayAccesses.first().arrayExpression.text + "Element"
return InputParameter(arrayAccesses, parameterName, arrayAccesses.first().type ?: return null)
}
return copy(inputParameters = inputParameters + fieldInputParameters, isStatic = true)
}
private fun canBeConstructor(analyzer: CodeFragmentAnalyzer): Boolean {
val elements = analyzer.elements
val parent = ExtractMethodHelper.getValidParentOf(elements.first())
val holderClass = PsiTreeUtil.getNonStrictParentOfType(parent, PsiClass::class.java) ?: return false
val method = PsiTreeUtil.getNonStrictParentOfType(parent, PsiMethod::class.java) ?: return false
val firstStatement = method.body?.statements?.firstOrNull() ?: return false
val startsOnBegin = firstStatement.textRange in TextRange(elements.first().textRange.startOffset, elements.last().textRange.endOffset)
val outStatements = method.body?.statements.orEmpty().dropWhile { it.textRange.endOffset <= elements.last().textRange.endOffset }
val hasOuterFinalFieldAssignments = analyzer
.findFieldUsages(holderClass, outStatements)
.any { it.isWrite && it.field.hasExplicitModifier("final") }
return method.isConstructor && startsOnBegin && !hasOuterFinalFieldAssignments && analyzer.findOutputVariables().isEmpty()
fun findHiddenExpression(arrayAccess: PsiArrayAccessExpression?): List<InputParameter> {
return extractOptions.inputParameters.filter {
ExtractMethodHelper.areSame(it.references.first(), arrayAccess?.arrayExpression)
|| ExtractMethodHelper.areSame(it.references.first(), arrayAccess?.indexExpression)
}
}
val foldedCandidates = extractOptions.inputParameters.mapNotNull { findFoldedCandidate(it) }
val (folded, hidden) = foldedCandidates
.map { it to findHiddenExpression(it.references.first() as? PsiArrayAccessExpression) }
.filter { it.second.size > 1 }.unzip()
return extractOptions.copy(inputParameters = extractOptions.inputParameters - hidden.flatten() + folded)
}
fun asConstructor(analyzer: CodeFragmentAnalyzer, extractOptions: ExtractOptions): ExtractOptions {
return if (canBeConstructor(analyzer)) extractOptions.copy(isConstructor = true, methodName = "this") else extractOptions
}
fun withForcedStatic(analyzer: CodeFragmentAnalyzer, extractOptions: ExtractOptions): ExtractOptions? {
val targetClass = PsiTreeUtil.getParentOfType(ExtractMethodHelper.getValidParentOf(extractOptions.elements.first()), PsiClass::class.java)!!
val fieldUsages = analyzer.findFieldUsages(targetClass, extractOptions.elements)
if (fieldUsages.any { it.isWrite }) return null
val fieldInputParameters =
fieldUsages.groupBy { it.field }.entries.map { (field, fieldUsages) ->
InputParameter(
references = fieldUsages.map { it.classMemberReference },
name = field.name,
type = field.type
)
}
return extractOptions.copy(inputParameters = extractOptions.inputParameters + fieldInputParameters, isStatic = true)
}
fun canBeConstructor(analyzer: CodeFragmentAnalyzer): Boolean {
val elements = analyzer.elements
val parent = ExtractMethodHelper.getValidParentOf(elements.first())
val holderClass = PsiTreeUtil.getNonStrictParentOfType(parent, PsiClass::class.java) ?: return false
val method = PsiTreeUtil.getNonStrictParentOfType(parent, PsiMethod::class.java) ?: return false
val firstStatement = method.body?.statements?.firstOrNull() ?: return false
val startsOnBegin = firstStatement.textRange in TextRange(elements.first().textRange.startOffset, elements.last().textRange.endOffset)
val outStatements = method.body?.statements.orEmpty().dropWhile { it.textRange.endOffset <= elements.last().textRange.endOffset }
val hasOuterFinalFieldAssignments = analyzer
.findFieldUsages(holderClass, outStatements)
.any { it.isWrite && it.field.hasExplicitModifier("final") }
return method.isConstructor && startsOnBegin && !hasOuterFinalFieldAssignments && analyzer.findOutputVariables().isEmpty()
}
}
@@ -5,8 +5,11 @@ import com.intellij.codeInsight.CodeInsightUtil
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.SelectionModel
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.refactoring.extractMethod.PrepareFailedException
import com.intellij.refactoring.introduceVariable.IntroduceVariableBase
import com.intellij.refactoring.util.RefactoringUtil
import kotlin.math.exp
class ExtractSelector {
@@ -41,15 +44,20 @@ class ExtractSelector {
fun suggestElementsToExtract(editor: Editor): List<PsiElement> {
val selectedElements = findSelectedElements(editor)
return alignElements(selectedElements)
val alignedElements = alignElements(selectedElements)
return when {
alignedElements.isEmpty() -> throw PrepareFailedException("Fail", selectedElements.first())
else -> alignedElements
}
}
private fun alignElements(elements: List<PsiElement>): List<PsiElement> {
val singleElement = elements.singleOrNull()
val alignedElements = when {
elements.size > 1 -> alignStatements(elements)
singleElement is PsiBlockStatement -> if (singleElement.codeBlock.firstBodyElement != null) listOf(singleElement) else emptyList()
singleElement is PsiCodeBlock -> alignCodeBlock(singleElement)
singleElement is PsiStatement -> listOfNotNull(alignStatement(singleElement))
//singleElement is PsiStatement -> listOfNotNull(alignStatement(singleElement))
singleElement is PsiExpression -> listOfNotNull(alignExpression(singleElement))
else -> elements
}
@@ -60,25 +68,39 @@ class ExtractSelector {
}
}
private fun alignExpression(expression: PsiExpression?): PsiExpression? {
return when (expression) {
null -> return null
is PsiReturnStatement -> alignExpression(expression.returnValue)
is PsiAssignmentExpression -> alignExpression(expression.rExpression)
private tailrec fun alignExpression(expression: PsiExpression?): PsiExpression? {
return when {
expression == null -> null
expression.type is PsiLambdaParameterType -> null
hasAssignmentInside(expression) -> null
isInsideAnnotation(expression) -> null
expression is PsiReturnStatement -> alignExpression(expression.returnValue)
expression is PsiParenthesizedExpression -> expression.takeIf { expression.expression != null }
//is PsiAssignmentExpression -> alignExpression(expression.rExpression)
//expression.parent is PsiParenthesizedExpression -> alignExpression(expression.parent as PsiExpression)
// PsiUtil.skipParenthesizedExprDown((PsiExpression)elements[0]);
else -> expression
}
}
private fun hasAssignmentInside(expression: PsiExpression): Boolean {
return PsiTreeUtil.findChildOfType(expression, PsiAssignmentExpression::class.java, false) != null
}
private fun alignStatements(statements: List<PsiElement>): List<PsiElement> {
val filteredStatements = statements.dropWhile { it is PsiSwitchLabelStatement }.dropLastWhile { it is PsiSwitchLabelStatement }
val filteredStatements = statements
.dropWhile { it is PsiSwitchLabelStatement || it is PsiWhiteSpace }
.dropLastWhile { it is PsiSwitchLabelStatement || it is PsiWhiteSpace }
return when {
filteredStatements.any { it is PsiSwitchLabelStatement } -> emptyList()
else -> filteredStatements
}
}
private fun isInsideAnnotation(expression: PsiExpression): Boolean {
return PsiTreeUtil.getParentOfType(expression, PsiAnnotation::class.java) != null
}
private fun alignCodeBlock(codeBlock: PsiCodeBlock): List<PsiElement> {
return when (codeBlock.parent) {
is PsiSwitchStatement -> listOf(codeBlock.parent)
@@ -0,0 +1,147 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.refactoring.extractMethod.newImpl
import com.intellij.codeInsight.CodeInsightUtil
import com.intellij.codeInsight.Nullability
import com.intellij.codeInsight.generation.GenerateMembersUtil
import com.intellij.psi.*
import com.intellij.psi.codeStyle.JavaCodeStyleManager
import com.intellij.psi.codeStyle.VariableKind
import com.intellij.psi.impl.source.codeStyle.JavaCodeStyleManagerImpl
import com.intellij.psi.search.LocalSearchScope
import com.intellij.refactoring.HelpID
import com.intellij.refactoring.extractMethod.ExtractMethodDialog
import com.intellij.refactoring.extractMethod.InputVariables
import com.intellij.refactoring.extractMethod.newImpl.structures.DataOutput
import com.intellij.refactoring.extractMethod.newImpl.structures.ExtractOptions
import com.intellij.util.containers.MultiMap
import java.util.*
object MapFromDialog {
fun mapFromDialog(extractOptions: ExtractOptions, title: String, helpId: String): ExtractOptions? {
val dialog = createDialog(extractOptions, title, helpId)
val isOk = dialog.showAndGet()
if (isOk){
return ExtractMethodPipeline.remap(extractOptions, dialog.chosenParameters, dialog.chosenMethodName,
dialog.isMakeStatic, dialog.visibility, dialog.isChainedConstructor, dialog.returnType)
} else {
return null
}
}
private fun createDialog(extractOptions: ExtractOptions, refactoringName: String, helpId: String): ExtractMethodDialog {
val project = extractOptions.project
val returnType = extractOptions.dataOutput.type
val thrownExceptions = extractOptions.thrownExceptions.toTypedArray()
val isStatic = extractOptions.isStatic
val typeParameters = extractOptions.typeParameters
val targetClass = extractOptions.anchor.containingClass
val elements = extractOptions.elements.toTypedArray()
val nullability = extractOptions.dataOutput.nullability
val analyzer = CodeFragmentAnalyzer(extractOptions.elements)
val canBeStatic = ExtractMethodPipeline.withForcedStatic(analyzer, extractOptions) != null
val canBeChainedConstructor = ExtractMethodPipeline.canBeConstructor(analyzer)
val variables = extractOptions.inputParameters
.map { it.references.first() as? PsiReferenceExpression }
.map { it?.resolve() as? PsiVariable }
val inputVariables = InputVariables(
variables,
extractOptions.project,
LocalSearchScope(extractOptions.elements.toTypedArray()),
false,
emptySet()
)
val typeParameterList = PsiElementFactory.getInstance(extractOptions.project).createTypeParameterList()
typeParameters.forEach { typeParameterList.add(it) }
val methodNames = suggestInitialMethodName(extractOptions)
return object: ExtractMethodDialog(project, targetClass, inputVariables, returnType, typeParameterList,
thrownExceptions, isStatic, canBeStatic, canBeChainedConstructor,
refactoringName, helpId, nullability, elements, {0}) {
override fun areTypesDirected() = true
override fun suggestMethodNames(): Array<String> {
return methodNames
}
override fun isVoidReturn(): Boolean = false
override fun findOccurrences(): Array<PsiExpression> {
return when (val dataOutput = extractOptions.dataOutput) {
is DataOutput.VariableOutput -> CodeInsightUtil.findReferenceExpressions(extractOptions.anchor, dataOutput.variable)
is DataOutput.ExpressionOutput -> dataOutput.returnExpressions.toTypedArray()
else -> emptyArray()
}
}
override fun isOutputVariable(variable: PsiVariable): Boolean {
return (extractOptions.dataOutput as? DataOutput.VariableOutput)?.variable == variable
}
override fun checkMethodConflicts(conflicts: MultiMap<PsiElement, String>) {
super.checkMethodConflicts(conflicts)
val parameters = chosenParameters
val vars: MutableMap<String, PsiLocalVariable> = HashMap()
for (element in elements) {
element.accept(object : JavaRecursiveElementWalkingVisitor() {
override fun visitLocalVariable(variable: PsiLocalVariable) {
super.visitLocalVariable(variable)
vars[variable.name] = variable
}
override fun visitClass(aClass: PsiClass) {}
})
}
for (parameter in parameters) {
val paramName = parameter.name
val variable = vars[paramName]
if (variable != null) {
//TODO bundle
conflicts.putValue(variable, "Variable with name $paramName is already defined in the selected scope")
}
}
}
override fun hasPreviewButton() = false
}
}
private fun suggestInitialMethodName(options: ExtractOptions): Array<String> {
val project = options.project
val initialMethodNames: MutableSet<String> = LinkedHashSet()
val codeStyleManager = JavaCodeStyleManager.getInstance(project) as JavaCodeStyleManagerImpl
val returnType = options.dataOutput.type
val expression = options.elements.singleOrNull() as? PsiExpression
if (expression != null || returnType !is PsiPrimitiveType) {
codeStyleManager.suggestVariableName(VariableKind.FIELD, null, expression, returnType).names
.map { name -> codeStyleManager.variableNameToPropertyName(name, VariableKind.FIELD) }
.forEach { name ->
initialMethodNames += name
}
}
val outVariable = (options.dataOutput as? DataOutput.VariableOutput)?.variable
if (outVariable != null) {
val outKind = codeStyleManager.getVariableKind(outVariable)
val propertyName = codeStyleManager.variableNameToPropertyName(outVariable.name!!, outKind)
val names = codeStyleManager.suggestVariableName(VariableKind.FIELD, propertyName, null, outVariable.type).names
names.forEach { name ->
initialMethodNames += codeStyleManager.variableNameToPropertyName(name, VariableKind.FIELD)
}
}
val normalizedType = (returnType as? PsiEllipsisType)?.toArrayType() ?: returnType
val field = JavaPsiFacade.getElementFactory(project).createField("fieldNameToReplace", normalizedType)
fun suggestGetterName(name: String): String? {
field.name = name
return GenerateMembersUtil.suggestGetterName(field)
}
val getters: List<String> = initialMethodNames.filter { PsiNameHelper.getInstance(project).isIdentifier(it) }
.mapNotNull { propertyName -> suggestGetterName(propertyName) }
return getters.toTypedArray()
}
}
@@ -3,84 +3,218 @@ package com.intellij.refactoring.extractMethod.newImpl
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.impl.source.PostprocessReformattingAspect
import com.intellij.refactoring.HelpID
import com.intellij.refactoring.extractMethod.ExtractMethodDialog
import com.intellij.refactoring.extractMethod.ExtractMethodHandler
import com.intellij.refactoring.extractMethod.PrepareFailedException
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodHelper.addSiblingAfter
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodHelper.wrapWithCodeBlock
import com.intellij.refactoring.extractMethod.newImpl.structures.DataOutput.*
import com.intellij.refactoring.extractMethod.newImpl.ExtractMethodPipeline.selectTargetClass
import com.intellij.refactoring.extractMethod.newImpl.MapFromDialog.mapFromDialog
import com.intellij.refactoring.extractMethod.newImpl.structures.DataOutput.ExpressionOutput
import com.intellij.refactoring.extractMethod.newImpl.structures.ExtractOptions
import com.intellij.refactoring.extractMethod.newImpl.structures.FlowOutput.*
import com.intellij.refactoring.extractMethod.newImpl.structures.FlowOutput.ConditionalFlow
import com.intellij.refactoring.introduceVariable.IntroduceVariableBase
import com.intellij.refactoring.listeners.RefactoringEventData
import com.intellij.refactoring.listeners.RefactoringEventListener
import com.intellij.refactoring.util.CommonRefactoringUtil
import com.intellij.util.IncorrectOperationException
fun extractMethod(dependencies: ExtractOptions) {
val factory = PsiElementFactory.getInstance(dependencies.project)
val styleManager = CodeStyleManager.getInstance(dependencies.project)
val flowOutput = dependencies.flowOutput
val newFlowOutput = if (dependencies.dataOutput is ExpressionOutput && flowOutput is ConditionalFlow) {
flowOutput.copy(statements = flowOutput.statements.filterNot { it is PsiReturnStatement })
}
else {
flowOutput
}
val codeBlock = with(dependencies) {
BodyBuilder(factory).build(
elements = elements,
flowOutput = newFlowOutput,
dataOutput = dataOutput,
inputParameters = inputParameters,
missedDeclarations = requiredVariablesInside
)
}
val signature = SignatureBuilder(dependencies.project)
.build(
isStatic = dependencies.isStatic,
visibility = dependencies.visibility,
typeParameters = dependencies.typeParameters,
returnType = dependencies.dataOutput.type.takeIf { !dependencies.isConstructor },
methodName = dependencies.methodName,
inputParameters = dependencies.inputParameters,
thrownExceptions = dependencies.thrownExceptions
)
val method = styleManager.reformat(signature) as PsiMethod
method.body?.replace(codeBlock)
class MethodExtractor {
if (needsNullabilityAnnotations(dependencies.project)) {
updateMethodAnnotations(method)
}
private val LOG = Logger.getInstance(MethodExtractor::class.java)
ApplicationManager.getApplication().runWriteAction {
dependencies.anchor.addSiblingAfter(method)
val methodCall = dependencies.methodName + "(" + dependencies.inputParameters.joinToString { it.references.first().text } + ")"
val expressionElement = (dependencies.elements.singleOrNull() as? PsiExpression)
if (expressionElement != null) {
val callExpression = PsiElementFactory.getInstance(expressionElement.project).createExpressionFromText(methodCall, null)
IntroduceVariableBase.replace(expressionElement, callExpression, expressionElement.project)
fun doExtract(editor: Editor, refactoringName: String, helpId: String): Boolean {
val statements = ExtractSelector().suggestElementsToExtract(editor)
try {
val extractOptions = findExtractOptions(statements) ?: return false
selectTargetClass(extractOptions) { targetOptions ->
val withDialog = mapFromDialog(targetOptions, refactoringName, helpId) ?: return@selectTargetClass false
val project = withDialog.project
CommandProcessor.getInstance().executeCommand(
project,
{ PostprocessReformattingAspect.getInstance(project).postponeFormattingInside { doRefactoring(withDialog) } },
ExtractMethodHandler.getRefactoringName(), null)
return@selectTargetClass false
}
} catch (e: PrepareFailedException){
CommonRefactoringUtil.showErrorHint(statements.first().project, editor, e.message!!, refactoringName, HelpID.EXTRACT_METHOD)
}
else {
val callElements = CallFactory(PsiElementFactory.getInstance(dependencies.project)).buildCall(
methodCall = methodCall,
flowOutput = dependencies.flowOutput,
dataOutput = dependencies.dataOutput,
exposedDeclarations = dependencies.exposedLocalVariables
return true
}
private fun doRefactoring(options: ExtractOptions){
try {
val beforeData = RefactoringEventData()
beforeData.addElements(options.elements.toTypedArray())
options.project.messageBus.syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC)
.refactoringStarted("refactoring.extract.method", beforeData)
val method = extractMethod(options)
val data = RefactoringEventData()
data.addElement(method)
options.project.messageBus.syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC)
.refactoringDone("refactoring.extract.method", data)
}
catch (e: IncorrectOperationException) {
LOG.error(e)
}
}
fun doTestExtract(
doRefactor: Boolean,
editor: Editor,
isConstructor: Boolean?,
isStatic: Boolean?,
returnType: PsiType?,
newNameOfFirstParam: String?,
targetClass: PsiClass?,
@PsiModifier.ModifierConstant visibility: String?,
vararg disabledParameters: Int
): Boolean {
val elements = ExtractSelector().suggestElementsToExtract(editor)
var options = findExtractOptions(elements) ?: throw PrepareFailedException("Fail", elements.first())
val analyzer = CodeFragmentAnalyzer(elements)
val candidates = ExtractMethodPipeline.findTargetCandidates(analyzer, options)
options = ExtractMethodPipeline.withTargetClass(analyzer, options, candidates.firstOrNull { it !is PsiAnonymousClass } ?: candidates.first() )
?: throw PrepareFailedException("Fail", elements.first())
options = options.copy(methodName = "newMethod")
if (isConstructor != options.isConstructor){
options = ExtractMethodPipeline.asConstructor(analyzer, options)
}
if (! options.isStatic && isStatic == true) {
options = ExtractMethodPipeline.withForcedStatic(analyzer, options) ?: throw PrepareFailedException("Fail", elements.first())
}
if (newNameOfFirstParam != null) {
options = options.copy(
inputParameters = listOf(options.inputParameters.first().copy(name = newNameOfFirstParam)) + options.inputParameters.drop(1)
)
val elementsToAdd = when {
callElements.size > 1 && dependencies.elements.first().parent !is PsiCodeBlock -> wrapWithCodeBlock(callElements)
else -> callElements
}
elementsToAdd.reversed().forEach { statement ->
val addedStatement = dependencies.elements.last().addSiblingAfter(statement)
styleManager.reformat(addedStatement)
}
dependencies.elements.first().parent.deleteChildRange(dependencies.elements.first(), dependencies.elements.last())
}
if (returnType != null) {
options = options.copy(dataOutput = options.dataOutput.withType(returnType))
}
if (targetClass != null) {
options = ExtractMethodPipeline.withTargetClass(analyzer, options, targetClass) ?: throw PrepareFailedException("Fail", elements.first())
}
if (disabledParameters.isNotEmpty()) {
options = options.copy(
disabledParameters = options.inputParameters.filterIndexed { index, _ -> index in disabledParameters },
inputParameters = options.inputParameters.filterIndexed { index, _ -> index !in disabledParameters }
)
}
if (visibility != null) {
options = options.copy(visibility = visibility)
}
if (options.anchor.containingClass?.isInterface == true) {
options = ExtractMethodPipeline.adjustModifiersForInterface(options.copy(visibility = PsiModifier.PRIVATE))
}
if (doRefactor) {
extractMethod(options)
}
return true
}
fun extractMethod(dependencies: ExtractOptions): PsiMethod {
val factory = PsiElementFactory.getInstance(dependencies.project)
val styleManager = CodeStyleManager.getInstance(dependencies.project)
val flowOutput = dependencies.flowOutput
val newFlowOutput = when {
dependencies.dataOutput is ExpressionOutput && flowOutput is ConditionalFlow ->
flowOutput.copy(statements = flowOutput.statements.filterNot { it is PsiReturnStatement })
else -> flowOutput
}
val codeBlock = with(dependencies) {
BodyBuilder(factory).build(
elements = elements,
flowOutput = newFlowOutput,
dataOutput = dataOutput,
inputParameters = inputParameters,
missedDeclarations = requiredVariablesInside,
disabledParameters = disabledParameters
)
}
val method = SignatureBuilder(dependencies.project)
.build(
context = dependencies.anchor.context,
scope = dependencies.elements,
isStatic = dependencies.isStatic,
visibility = dependencies.visibility,
typeParameters = dependencies.typeParameters,
returnType = dependencies.dataOutput.type.takeIf { !dependencies.isConstructor },
methodName = dependencies.methodName,
inputParameters = dependencies.inputParameters,
thrownExceptions = dependencies.thrownExceptions,
anchor = dependencies.anchor
)
method.body?.replace(codeBlock)
val methodCall = findExtractQualifier(dependencies) + "(" + dependencies.inputParameters.map { it.references.first() }.joinToString { it.text } + ")"
val callBuilder = CallBuilder(dependencies.project, dependencies.elements.first().context)
val expressionElement = (dependencies.elements.singleOrNull() as? PsiExpression)
val callElements = when (expressionElement) {
null -> callBuilder.buildCall(methodCall, dependencies.flowOutput, dependencies.dataOutput, dependencies.exposedLocalVariables)
else -> callBuilder.buildExpressionCall(methodCall, dependencies.dataOutput)
}
val formattedCallElements = callElements.map { styleManager.reformat(it) }
if (needsNullabilityAnnotations(dependencies.project)) {
updateMethodAnnotations(method, dependencies.inputParameters)
}
var addedMethod: PsiMethod? = null
ApplicationManager.getApplication().runWriteAction {
addedMethod = dependencies.anchor.addSiblingAfter(method) as PsiMethod
replace(dependencies.elements, formattedCallElements)
}
return addedMethod!!
}
private fun replace(source: List<PsiElement>, target: List<PsiElement>) {
val sourceAsExpression = source.singleOrNull() as? PsiExpression
val targetAsExpression = target.singleOrNull() as? PsiExpression
if (sourceAsExpression != null && targetAsExpression != null) {
IntroduceVariableBase.replace(sourceAsExpression, targetAsExpression, sourceAsExpression.project)
return
}
val normalizedTarget = when {
target.size > 1 && source.first().parent !is PsiCodeBlock -> wrapWithCodeBlock(target)
else -> target
}
normalizedTarget.reversed().forEach { statement ->
source.last().addSiblingAfter(statement)
}
source.first().parent.deleteChildRange(source.first(), source.last())
}
private fun needsNullabilityAnnotations(project: Project): Boolean {
return PropertiesComponent.getInstance(project).getBoolean(ExtractMethodDialog.EXTRACT_METHOD_GENERATE_ANNOTATIONS, true)
}
}
private fun needsNullabilityAnnotations(project: Project): Boolean {
return PropertiesComponent.getInstance(project).getBoolean(ExtractMethodDialog.EXTRACT_METHOD_GENERATE_ANNOTATIONS, true)
private fun findExtractQualifier(options: ExtractOptions): String {
val callText = options.methodName + "(" + options.inputParameters.map { it.references.first() }.joinToString { it.text } + ")"
val factory = PsiElementFactory.getInstance(options.project)
val callElement = factory.createExpressionFromText(callText, options.elements.first().context) as PsiMethodCallExpression
val targetClassName = options.anchor.containingClass?.name
val member = findClassMember(options.elements.first())
if (member == options.anchor) return options.methodName
if (callElement.resolveMethod() != null && !options.isConstructor) {
return when {
options.isStatic -> "$targetClassName.${options.methodName}"
else -> "$targetClassName.this.${options.methodName}"
}
} else {
return options.methodName
}
}
@@ -2,35 +2,85 @@
package com.intellij.refactoring.extractMethod.newImpl
import com.intellij.openapi.project.Project
import com.intellij.pom.java.LanguageLevel
import com.intellij.psi.*
import com.intellij.psi.codeStyle.JavaCodeStyleManager
import com.intellij.psi.codeStyle.JavaCodeStyleSettings
import com.intellij.psi.util.PsiUtil
import com.intellij.refactoring.extractMethod.newImpl.structures.InputParameter
import com.intellij.refactoring.util.RefactoringUtil
class SignatureBuilder(project: Project) {
class SignatureBuilder(private val project: Project) {
private val factory: PsiElementFactory = PsiElementFactory.getInstance(project)
fun build(
context: PsiElement?,
scope: List<PsiElement>,
isStatic: Boolean = false,
visibility: String,
typeParameters: PsiTypeParameterList,
visibility: String?,
typeParameters: List<PsiTypeParameter>,
returnType: PsiType? = null,
methodName: String = "extracted",
inputParameters: List<InputParameter> = emptyList(),
thrownExceptions: List<PsiClassType> = emptyList()
thrownExceptions: List<PsiClassType> = emptyList(),
anchor: PsiMember
): PsiMethod {
val parameterList = factory.createParameterList(
inputParameters.map { it.name }.toTypedArray(),
inputParameters.map { it.type }.toTypedArray()
)
val parameterList = createParameterList(inputParameters, scope)
val method = when (returnType) {
null -> factory.createConstructor()
else -> factory.createMethod(methodName, returnType)
null -> factory.createConstructor("methodName", context)
else -> factory.createMethod(methodName, returnType, context)
}
method.typeParameterList?.replace(typeParameters)
JavaCodeStyleManager.getInstance(method.project).shortenClassReferences(method)
val isInInterface = anchor.containingClass?.isInterface == true
val shouldHaveDefaultModifier = isJava8() && ! isStatic && isInInterface
val typeParameterList = factory.createTypeParameterList()
typeParameters.forEach { typeParameterList.add(it) }
method.typeParameterList?.replace(typeParameterList)
method.parameterList.replace(parameterList)
method.modifierList.setModifierProperty(PsiModifier.STATIC, isStatic)
method.modifierList.setModifierProperty(visibility, true)
method.modifierList.setModifierProperty(PsiModifier.DEFAULT, shouldHaveDefaultModifier)
if (visibility != null) method.modifierList.setModifierProperty(visibility, true)
thrownExceptions.map { exception -> factory.createReferenceElementByType(exception) }.forEach { method.throwsList.add(it) }
return method
}
private fun createParameterList(inputParameters: List<InputParameter>, scope: List<PsiElement>): PsiParameterList {
val parameterList = factory.createParameterList(
inputParameters.map { it.name }.toTypedArray(),
inputParameters.map { it.type }.toTypedArray()
)
if (inputParameters.isEmpty()) return parameterList
val element = inputParameters.first().references.first()
val useDefaultFinal = JavaCodeStyleSettings.getInstance(scope.first().project).GENERATE_FINAL_PARAMETERS
inputParameters.forEach { parameter ->
val shouldBeFinal = when {
useDefaultFinal -> parameter.references.none { reference -> PsiUtil.isAccessedForWriting(reference) }
! PsiUtil.isLanguageLevel8OrHigher(element) -> parameter.references.any { reference -> isInsideAnonymousOrLocal(reference, scope) }
else -> false
}
val methodParameter = parameterList.parameters.find { it.name == parameter.name }
PsiUtil.setModifierProperty(methodParameter!!, PsiModifier.FINAL, shouldBeFinal)
}
return parameterList
}
private fun isInsideAnonymousOrLocal(element: PsiElement, scope: List<PsiElement>): Boolean {
return scope.any { upperBound -> RefactoringUtil.isInsideAnonymousOrLocal(element, upperBound) }
}
private fun isJava8(): Boolean {
val languageLevel: LanguageLevel = PsiUtil.getLanguageLevel(project)
val isAtLeastJava9 = languageLevel.isAtLeast(LanguageLevel.JDK_1_9)
return languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && !isAtLeastJava9
}
}
@@ -1,73 +0,0 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.refactoring.extractMethod.newImpl.structures;
import com.intellij.codeInsight.CodeInsightUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class CodeFragment {
public final List<PsiElement> elements;
private CodeFragment(List<PsiElement> elements) {
this.elements = elements;
}
public static CodeFragment of(List<PsiElement> elements) {
checkRange(elements);
return new CodeFragment(elements);
}
public PsiElement getFirstElement() {
return elements.get(0);
}
public PsiElement getLastElement() {
return elements.get(elements.size() - 1);
}
public PsiElement getCommonParent() {
return getFirstElement().getParent();
}
public Project getProject() {
return getFirstElement().getProject();
}
public PsiFile getContainingFile() {
return getFirstElement().getContainingFile();
}
public TextRange getTextRange() {
return new TextRange(getFirstElement().getTextRange().getStartOffset(), getLastElement().getTextRange().getEndOffset());
}
private static void checkRange(List<PsiElement> elements) {
if (elements.isEmpty()) throw new IllegalArgumentException();
final PsiElement parent = elements.get(0).getParent();
final boolean areNotSiblings = elements.stream().anyMatch(element -> element.getParent() != parent);
if (parent == null || areNotSiblings) throw new IllegalArgumentException();
}
public static CodeFragment copyOf(CodeFragment codeFragment) {
final PsiCodeBlock block = PsiElementFactory.getInstance(codeFragment.getProject()).createCodeBlockFromText("{}", codeFragment.getFirstElement().getContext());
block.addRange(codeFragment.getFirstElement(), codeFragment.getLastElement());
final List<PsiElement> elements = PsiTreeUtil.getElementsOfRange(block.getFirstBodyElement(), block.getLastBodyElement());
return of(elements);
}
@SuppressWarnings("unchecked")
@NotNull
public static <T extends PsiElement> T findSameElementInCopy(CodeFragment source, CodeFragment copy, T element) {
final int sourceStartOffset = source.getTextRange().getStartOffset();
final int copyStartOffset = copy.getTextRange().getStartOffset();
final TextRange range = element.getTextRange().shiftRight(copyStartOffset - sourceStartOffset);
final PsiElement elementCopy =
CodeInsightUtil.findElementInRange(copy.getContainingFile(), range.getStartOffset(), range.getEndOffset(), element.getClass());
return (T) elementCopy;
}
}
@@ -1,24 +1,38 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.refactoring.extractMethod.newImpl.structures
import com.intellij.codeInsight.Nullability
import com.intellij.psi.PsiExpression
import com.intellij.psi.PsiType
import com.intellij.psi.PsiVariable
sealed class DataOutput {
abstract val type: PsiType
abstract val nullability: Nullability
data class VariableOutput(override val type: PsiType, val variable: PsiVariable, val declareType: Boolean) : DataOutput() {
data class VariableOutput(override val type: PsiType, val variable: PsiVariable, val declareType: Boolean,
override val nullability: Nullability = Nullability.UNKNOWN) : DataOutput() {
val name: String = requireNotNull(variable.name)
}
data class ExpressionOutput(override val type: PsiType, val name: String, val returnExpressions: List<PsiExpression>) : DataOutput()
data class ExpressionOutput(override val type: PsiType, val name: String?, val returnExpressions: List<PsiExpression>,
override val nullability: Nullability = Nullability.UNKNOWN) : DataOutput()
object ArtificialBooleanOutput : DataOutput() {
override val type: PsiType = PsiType.BOOLEAN
override val nullability: Nullability = Nullability.UNKNOWN
}
object EmptyOutput : DataOutput() {
override val type: PsiType = PsiType.VOID
data class EmptyOutput(override val type: PsiType = PsiType.VOID) : DataOutput(){
override val nullability: Nullability = Nullability.UNKNOWN
}
fun withType(type: PsiType): DataOutput {
return when(this) {
is VariableOutput -> this.copy(type = type)
is ExpressionOutput -> this.copy(type = type)
is EmptyOutput -> this.copy(type = type)
ArtificialBooleanOutput -> this
}
}
}
@@ -2,24 +2,22 @@
package com.intellij.refactoring.extractMethod.newImpl.structures
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiTypeParameterList
import com.intellij.psi.PsiVariable
import com.intellij.psi.*
data class ExtractOptions(
val anchor: PsiElement,
val anchor: PsiMember,
val elements: List<PsiElement>,
val flowOutput: FlowOutput,
val dataOutput: DataOutput,
val thrownExceptions: List<PsiClassType>,
val requiredVariablesInside: List<PsiVariable>,
val inputParameters: List<InputParameter>,
val typeParameters: PsiTypeParameterList,
val typeParameters: List<PsiTypeParameter>,
val methodName: String,
val isStatic: Boolean,
val visibility: String,
val visibility: String?,
val exposedLocalVariables: List<PsiVariable>,
val disabledParameters: List<InputParameter>,
val isConstructor: Boolean
) {
val project: Project
@@ -4,4 +4,8 @@ package com.intellij.refactoring.extractMethod.newImpl.structures
import com.intellij.psi.PsiExpression
import com.intellij.psi.PsiType
data class InputParameter(val references: List<PsiExpression>, val name: String, val type: PsiType)
data class InputParameter(val references: List<PsiExpression>, val name: String, val type: PsiType) {
init {
require(references.isNotEmpty())
}
}