mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-07 22:09:38 +07:00
[refactoring] IDEA-287118 Suggest change signature refactoring when call site is edited
Core + Java implementation GitOrigin-RevId: d32a646a3751be7e69c8051f2898a06d73bcdde9
This commit is contained in:
committed by
intellij-monorepo-bot
parent
e21dc50342
commit
c69d5d7264
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user