mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[extract method] kotlin implementation
GitOrigin-RevId: 670962d122063309443e919f879f5852915e3af9
This commit is contained in:
committed by
intellij-monorepo-bot
parent
be42a8d147
commit
58b9be5c13
@@ -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,
|
||||
|
||||
+22
-23
@@ -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))
|
||||
}
|
||||
}
|
||||
+67
-17
@@ -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> {
|
||||
|
||||
+183
-64
@@ -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
|
||||
}
|
||||
+82
-7
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+211
-98
@@ -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()
|
||||
}
|
||||
}
|
||||
+30
-8
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
+197
-63
@@ -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
|
||||
}
|
||||
}
|
||||
+62
-12
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
-73
@@ -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;
|
||||
}
|
||||
}
|
||||
+18
-4
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-7
@@ -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
|
||||
|
||||
+5
-1
@@ -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())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user