[refactoring] IDEA-287118 Suggest change signature refactoring when call site is edited

Core + Java implementation

GitOrigin-RevId: d32a646a3751be7e69c8051f2898a06d73bcdde9
This commit is contained in:
Tagir Valeev
2022-01-24 15:43:05 +07:00
committed by intellij-monorepo-bot
parent e21dc50342
commit c69d5d7264
26 changed files with 670 additions and 252 deletions

View File

@@ -49,6 +49,7 @@ class JavaSuggestedRefactoringAvailability(refactoringSupport: SuggestedRefactor
// we use resolve to filter out annotations that we don't want to spread over hierarchy
override fun refineSignaturesWithResolve(state: SuggestedRefactoringState): SuggestedRefactoringState {
val declaration = state.declaration as? PsiMethod ?: return state
if (state.anchor is PsiCallExpression) return state // TODO
val restoredDeclarationCopy = state.restoredDeclarationCopy() as PsiMethod
val psiFile = declaration.containingFile
return state
@@ -66,10 +67,20 @@ class JavaSuggestedRefactoringAvailability(refactoringSupport: SuggestedRefactor
return SuggestedRenameData(declaration as PsiNamedElement, oldSignature.name)
}
val anchor = state.anchor
val whatToUpdate: String
if (anchor is PsiCallExpression) {
// Do not suggest refactoring from usage if usage is correctly resolved
if (anchor.resolveMethodGenerics().isValidResult) return null
whatToUpdate = RefactoringBundle.message("suggested.refactoring.declaration")
} else {
whatToUpdate = RefactoringBundle.message("suggested.refactoring.usages")
}
val canHaveOverrides = declaration.canHaveOverrides(oldSignature) && state.additionalData[HAS_OVERRIDES] != false
if (state.additionalData[HAS_USAGES] == false && !canHaveOverrides) return null
val updateUsagesData = SuggestedChangeSignatureData.create(state, RefactoringBundle.message("suggested.refactoring.usages"))
val updateUsagesData = SuggestedChangeSignatureData.create(state, whatToUpdate)
if (hasParameterAddedRemovedOrReordered(oldSignature, newSignature)) return updateUsagesData
@@ -120,7 +131,7 @@ class JavaSuggestedRefactoringAvailability(refactoringSupport: SuggestedRefactor
val annotations = extractAnnotations(psiParameter.type, psiParameter, psiFile)
parameter.copy(additionalData = JavaParameterAdditionalData(annotations))
},
JavaSignatureAdditionalData(
JavaDeclarationAdditionalData(
signature.visibility,
extractAnnotations(declaration.returnType, declaration, psiFile),
signature.exceptionTypes

View File

@@ -16,9 +16,13 @@ import com.intellij.refactoring.util.CanonicalTypes
class JavaSuggestedRefactoringExecution(refactoringSupport: SuggestedRefactoringSupport) :
SuggestedRefactoringExecution(refactoringSupport) {
override fun prepareChangeSignature(data: SuggestedChangeSignatureData): Any? {
override fun prepareChangeSignature(data: SuggestedChangeSignatureData): Any {
val method = data.declaration as PsiMethod
return extractTypes(method, method.containingFile)
val extractedTypes = extractTypes(method, method.containingFile)
val parameterTypes = extractedTypes.parameterTypes
val correctedTypes = data.correctParameterTypes(parameterTypes)
return if (correctedTypes === parameterTypes) extractedTypes else
ExtractedTypes(correctedTypes, extractedTypes.returnType, extractedTypes.exceptionTypes)
}
private data class ExtractedTypes(

View File

@@ -1,18 +1,22 @@
// 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.java.refactoring.suggested
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.intellij.psi.codeStyle.VariableKind
import com.intellij.refactoring.suggested.SuggestedRefactoringState
import com.intellij.refactoring.suggested.SuggestedRefactoringStateChanges
import com.intellij.refactoring.suggested.SuggestedRefactoringSupport
import com.intellij.refactoring.suggested.SuggestedRefactoringSupport.Parameter
import com.intellij.refactoring.suggested.SuggestedRefactoringSupport.Signature
import com.siyeh.ig.psiutils.VariableNameGenerator
class JavaSuggestedRefactoringStateChanges(refactoringSupport: SuggestedRefactoringSupport) :
SuggestedRefactoringStateChanges(refactoringSupport) {
override fun createInitialState(declaration: PsiElement): SuggestedRefactoringState? {
val state = super.createInitialState(declaration) ?: return null
override fun createInitialState(anchor: PsiElement): SuggestedRefactoringState? {
val state = super.createInitialState(anchor) ?: return null
val declaration = state.declaration
if (declaration is PsiMember && isDuplicate(declaration, state.oldSignature)) return null
return state
}
@@ -48,8 +52,18 @@ class JavaSuggestedRefactoringStateChanges(refactoringSupport: SuggestedRefactor
}
}
override fun signature(declaration: PsiElement, prevState: SuggestedRefactoringState?): Signature? {
declaration as PsiNameIdentifierOwner
override fun findDeclaration(state: SuggestedRefactoringState?, anchor: PsiElement): PsiElement? {
return when (anchor) {
is PsiCallExpression -> state?.declaration ?: if (DumbService.isDumb(anchor.project)) null else anchor.resolveMethod()
else -> anchor
}
}
override fun signature(anchor: PsiElement, prevState: SuggestedRefactoringState?): Signature? {
if (anchor is PsiCallExpression) {
return signatureFromCall(anchor, prevState)
}
val declaration = anchor as PsiNameIdentifierOwner
val name = declaration.name ?: return null
if (declaration !is PsiMethod) {
return Signature.create(name, null, emptyList(), null)
@@ -58,20 +72,56 @@ class JavaSuggestedRefactoringStateChanges(refactoringSupport: SuggestedRefactor
val visibility = declaration.visibility()
val parameters = declaration.parameterList.parameters.map { it.extractParameterData() ?: return null }
val annotations = declaration.extractAnnotations()
val exceptions = declaration.throwsList.referenceElements.map { it.text }
val exceptions = declaration.extractExceptions()
val signature = Signature.create(
name,
declaration.returnTypeElement?.text,
parameters,
JavaSignatureAdditionalData(visibility, annotations, exceptions)
JavaDeclarationAdditionalData(visibility, annotations, exceptions)
) ?: return null
return if (prevState == null) signature else matchParametersWithPrevState(signature, declaration, prevState)
}
override fun parameterMarkerRanges(declaration: PsiElement): List<TextRange?> {
if (declaration !is PsiMethod) return emptyList()
return declaration.parameterList.parameters.map { it.typeElement?.textRange }
private fun signatureFromCall(anchor: PsiCallExpression, prevState: SuggestedRefactoringState?): Signature? {
val expressions = anchor.argumentList!!.expressions
val args = expressions.map { ex -> ex.text }
if (prevState == null) {
val resolveResult = anchor.resolveMethodGenerics()
if (!resolveResult.isValidResult) return null
val method = resolveResult.element as? PsiMethod ?: return null
if (method is PsiCompiledElement) return null
// TODO: support vararg methods
if (method.isVarArgs) return null
val parameters = method.parameterList.parameters.map { it.extractParameterData() ?: return null }
if (parameters.size != args.size) return null
return Signature.create(method.name, method.returnTypeElement?.text, parameters,
JavaCallAdditionalData(args, method))
}
val oldSignature = prevState.oldSignature
val method = prevState.declaration as? PsiMethod ?: return null
val origArgs = ArrayList(oldSignature.origArguments ?: return null)
val newParams = args.mapIndexed { idx, argText ->
val origIdx = origArgs.indexOf(argText)
if (origIdx >= 0) {
origArgs[origIdx] = null
oldSignature.parameters[origIdx]
} else {
val newArg = expressions[idx]
val type = newArg.type
val name = VariableNameGenerator(method, VariableKind.PARAMETER).byExpression(newArg).byType(type).generate(true)
Parameter(Any(), name, type?.presentableText ?: "Object", JavaParameterAdditionalData("", newArg.text))
}
}
return Signature.create(oldSignature.name, oldSignature.type, newParams, oldSignature.additionalData)
}
override fun parameterMarkerRanges(anchor: PsiElement): List<TextRange?> {
if (anchor is PsiCallExpression) {
return anchor.argumentList!!.expressions.map { null }
}
if (anchor !is PsiMethod) return emptyList()
return anchor.parameterList.parameters.map { it.typeElement?.textRange }
}
private fun PsiParameter.extractParameterData(): Parameter? {
@@ -82,14 +132,4 @@ class JavaSuggestedRefactoringStateChanges(refactoringSupport: SuggestedRefactor
JavaParameterAdditionalData(extractAnnotations())
)
}
private fun PsiJvmModifiersOwner.extractAnnotations(): String {
return annotations.joinToString(separator = " ") { it.text } //TODO: skip comments and spaces
}
private val visibilityModifiers = listOf(PsiModifier.PUBLIC, PsiModifier.PROTECTED, PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE)
private fun PsiMethod.visibility(): String? {
return visibilityModifiers.firstOrNull { hasModifierProperty(it) }
}
}

View File

@@ -6,24 +6,32 @@ import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.intellij.psi.impl.source.tree.ChildRole
import com.intellij.psi.impl.source.tree.java.MethodElement
import com.intellij.refactoring.suggested.SuggestedChangeSignatureData
import com.intellij.refactoring.suggested.SuggestedRefactoringSupport
import com.intellij.refactoring.suggested.endOffset
import com.intellij.refactoring.suggested.startOffset
import com.siyeh.ig.psiutils.TypeUtils
class JavaSuggestedRefactoringSupport : SuggestedRefactoringSupport {
override fun isDeclaration(psiElement: PsiElement): Boolean {
override fun isAnchor(psiElement: PsiElement): Boolean {
if (psiElement is PsiCallExpression) {
return psiElement.argumentList != null
}
if (psiElement !is PsiNameIdentifierOwner) return false
if (psiElement is PsiParameter && psiElement.parent is PsiParameterList && psiElement.parent.parent is PsiMethod) return false
return true
}
override fun signatureRange(declaration: PsiElement): TextRange? {
val nameIdentifier = (declaration as PsiNameIdentifierOwner).nameIdentifier ?: return null
return when (declaration) {
override fun signatureRange(anchor: PsiElement): TextRange? {
if (anchor is PsiCallExpression) {
return anchor.argumentList!!.textRange
}
val nameIdentifier = (anchor as PsiNameIdentifierOwner).nameIdentifier ?: return null
return when (anchor) {
is PsiMethod -> {
val startOffset = declaration.modifierList.startOffset
val semicolon = (declaration.node as MethodElement).findChildByRole(ChildRole.CLOSING_SEMICOLON)
val endOffset = semicolon?.startOffset ?: declaration.body?.startOffset ?: declaration.endOffset
val startOffset = anchor.modifierList.startOffset
val semicolon = (anchor.node as MethodElement).findChildByRole(ChildRole.CLOSING_SEMICOLON)
val endOffset = semicolon?.startOffset ?: anchor.body?.startOffset ?: anchor.endOffset
TextRange(startOffset, endOffset)
}
@@ -35,8 +43,13 @@ class JavaSuggestedRefactoringSupport : SuggestedRefactoringSupport {
return (psiFile as PsiJavaFile).importList!!.textRange
}
override fun nameRange(declaration: PsiElement): TextRange? {
return (declaration as PsiNameIdentifierOwner).nameIdentifier?.textRange
override fun nameRange(anchor: PsiElement): TextRange? {
return (when (anchor) {
is PsiMethodCallExpression -> anchor.methodExpression.referenceNameElement
is PsiNewExpression -> anchor.classOrAnonymousClassReference
is PsiNameIdentifierOwner -> anchor.nameIdentifier
else -> null
})?.textRange
}
override fun isIdentifierStart(c: Char) = c.isJavaIdentifierStart()
@@ -72,14 +85,33 @@ class JavaSuggestedRefactoringSupport : SuggestedRefactoringSupport {
}
data class JavaParameterAdditionalData(
val annotations: String
val annotations: String,
val defaultValue: String = ""
) : SuggestedRefactoringSupport.ParameterAdditionalData
data class JavaSignatureAdditionalData(
val visibility: String?,
val annotations: String,
interface JavaSignatureAdditionalData : SuggestedRefactoringSupport.SignatureAdditionalData {
val visibility: String?
val annotations: String
val exceptionTypes: List<String>
) : SuggestedRefactoringSupport.SignatureAdditionalData
}
data class JavaDeclarationAdditionalData(
override val visibility: String?,
override val annotations: String,
override val exceptionTypes: List<String>
) : JavaSignatureAdditionalData
data class JavaCallAdditionalData(
val origArguments: List<String>,
val origMethod: PsiMethod
) : JavaSignatureAdditionalData {
override val visibility: String?
get() = origMethod.visibility()
override val annotations: String
get() = origMethod.extractAnnotations()
override val exceptionTypes: List<String>
get() = origMethod.extractExceptions()
}
internal val SuggestedRefactoringSupport.Parameter.annotations: String
get() = (additionalData as JavaParameterAdditionalData?)?.annotations ?: ""
@@ -92,3 +124,38 @@ internal val SuggestedRefactoringSupport.Signature.annotations: String
internal val SuggestedRefactoringSupport.Signature.exceptionTypes: List<String>
get() = (additionalData as JavaSignatureAdditionalData?)?.exceptionTypes ?: emptyList()
internal val SuggestedRefactoringSupport.Signature.origArguments: List<String>?
get() = (additionalData as? JavaCallAdditionalData)?.origArguments
private val visibilityModifiers = listOf(PsiModifier.PUBLIC, PsiModifier.PROTECTED, PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE)
internal fun PsiMethod.visibility(): String? {
return visibilityModifiers.firstOrNull { hasModifierProperty(it) }
}
internal fun PsiJvmModifiersOwner.extractAnnotations(): String {
return annotations.joinToString(separator = " ") { it.text } //TODO: skip comments and spaces
}
internal fun PsiMethod.extractExceptions(): List<String> {
return throwsList.referenceElements.map { it.text }
}
internal fun SuggestedChangeSignatureData.correctParameterTypes(origTypes: List<PsiType>): List<PsiType> {
val anchor = this.anchor
return if (anchor is PsiCallExpression) {
// From call site
val expressions = anchor.argumentList!!.expressions
this.newSignature.parameters.mapIndexed { idx, param ->
val oldParam = this.oldSignature.parameterById(param.id)
if (oldParam != null) {
origTypes[this.oldSignature.parameterIndex(oldParam)]
}
else {
expressions[idx].type ?: TypeUtils.getObjectType(this.declaration)
}
}
}
else origTypes
}

View File

@@ -19,18 +19,20 @@ object JavaSuggestedRefactoringUI : SuggestedRefactoringUI() {
override fun extractNewParameterData(data: SuggestedChangeSignatureData): List<NewParameterData> {
val psiMethod = data.declaration as PsiMethod
val psiParameters = psiMethod.parameterList.parameters
val parameterTypes = data.correctParameterTypes(psiMethod.parameterList.parameters.map { p -> p.type })
val project = psiMethod.project
val factory = JavaCodeFragmentFactory.getInstance(project)
val fromCallSite = data.anchor is PsiCallExpression
fun createCodeFragment(parameterType: PsiType) =
factory.createExpressionCodeFragment("", psiMethod, parameterType, true)
fun createCodeFragment(parameterType: PsiType, value: String) =
factory.createExpressionCodeFragment(value, psiMethod, parameterType, true)
return data.newSignature.parameters.zip(psiParameters)
return data.newSignature.parameters.zip(parameterTypes)
.filter { (parameter, _) -> data.oldSignature.parameterById(parameter.id) == null }
.map { (parameter, psiParameter) ->
val type = psiParameter.type
NewParameterData(parameter.name, createCodeFragment(type), offerToUseAnyVariable(type))
.map { (parameter, type) ->
NewParameterData(parameter.name, createCodeFragment(type, (parameter.additionalData as JavaParameterAdditionalData).defaultValue),
offerToUseAnyVariable(type),
suggestRename = fromCallSite)
}
}