J2K: JetChangeSignatureHandler

This commit is contained in:
Alexey Sedunov
2015-07-02 17:13:33 +03:00
parent 74570333c0
commit 0bf7bb3987
3 changed files with 144 additions and 226 deletions
@@ -47,16 +47,12 @@ import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.OverrideResolver import org.jetbrains.kotlin.resolve.OverrideResolver
public trait JetChangeSignatureConfiguration { public interface JetChangeSignatureConfiguration {
fun configure(originalDescriptor: JetMethodDescriptor, bindingContext: BindingContext): JetMethodDescriptor fun configure(originalDescriptor: JetMethodDescriptor, bindingContext: BindingContext): JetMethodDescriptor = originalDescriptor
fun performSilently(affectedFunctions: Collection<PsiElement>): Boolean = false
fun forcePerformForSelectedFunctionOnly(): Boolean = false
fun performSilently(affectedFunctions: Collection<PsiElement>): Boolean { object Empty: JetChangeSignatureConfiguration
return false
}
fun forcePerformForSelectedFunctionOnly(): Boolean {
return false
}
} }
fun JetMethodDescriptor.modify(action: (JetMutableMethodDescriptor) -> Unit): JetMethodDescriptor { fun JetMethodDescriptor.modify(action: (JetMutableMethodDescriptor) -> Unit): JetMethodDescriptor {
@@ -14,250 +14,171 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.idea.refactoring.changeSignature; package org.jetbrains.kotlin.idea.refactoring.changeSignature
import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.editor.ScrollType
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile
import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiMethod
import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiTreeUtil
import com.intellij.refactoring.HelpID; import com.intellij.refactoring.HelpID
import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.changeSignature.ChangeSignatureHandler; import com.intellij.refactoring.changeSignature.ChangeSignatureHandler
import com.intellij.refactoring.changeSignature.ChangeSignatureUtil; import com.intellij.refactoring.changeSignature.ChangeSignatureUtil
import com.intellij.refactoring.util.CommonRefactoringUtil; import com.intellij.refactoring.util.CommonRefactoringUtil
import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.annotations.TestOnly; import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.SYNTHESIZED
import org.jetbrains.kotlin.asJava.AsJavaPackage; import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage; import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils; import org.jetbrains.kotlin.idea.refactoring.JetRefactoringBundle
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde; import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor
import org.jetbrains.kotlin.idea.refactoring.JetRefactoringBundle; import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor; import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.calls.tasks.isDynamic
import org.jetbrains.kotlin.resolve.calls.tasks.TasksPackage; import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode;
import java.util.Collection; public class JetChangeSignatureHandler : ChangeSignatureHandler {
import static org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.SYNTHESIZED; override fun findTargetMember(file: PsiFile, editor: Editor) =
import static org.jetbrains.kotlin.idea.refactoring.changeSignature.ChangeSignaturePackage.runChangeSignature; file.findElementAt(editor.getCaretModel().getOffset())?.let { findTargetMember(it) }
public class JetChangeSignatureHandler implements ChangeSignatureHandler { override fun findTargetMember(element: PsiElement) =
@Nullable findTargetForRefactoring(element)
public static PsiElement findTargetForRefactoring(@NotNull PsiElement element) {
PsiElement elementParent = element.getParent();
if ((elementParent instanceof JetNamedFunction || elementParent instanceof JetClass || elementParent instanceof JetProperty)
&& ((JetNamedDeclaration) elementParent).getNameIdentifier() == element) return elementParent;
if (elementParent instanceof JetParameter) { override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext) {
JetParameter parameter = (JetParameter) elementParent; editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE)
JetPrimaryConstructor primaryConstructor = PsiTreeUtil.getParentOfType(parameter, JetPrimaryConstructor.class);
if (parameter.hasValOrVar() val element = findTargetMember(file, editor) ?: CommonDataKeys.PSI_ELEMENT.getData(dataContext) ?: return
&& (parameter.getNameIdentifier() == element || parameter.getValOrVarKeyword() == element) val elementAtCaret = file.findElementAt(editor.getCaretModel().getOffset()) ?: return
if (element !is JetElement) throw AssertionError("This handler must be invoked for Kotlin elements only: ${element.getText()}")
invokeChangeSignature(element, elementAtCaret, project, editor)
}
override fun invoke(project: Project, elements: Array<PsiElement>, dataContext: DataContext?) {
val element = elements.singleOrNull()?.unwrapped ?: return
if (element !is JetElement) throw AssertionError("This handler must be invoked for Kotlin elements only: ${element.getText()}")
val editor = dataContext?.let { CommonDataKeys.EDITOR.getData(it) }
invokeChangeSignature(element, element, project, editor)
}
override fun getTargetNotFoundMessage() =
JetRefactoringBundle.message("error.wrong.caret.position.function.or.constructor.name")
companion object {
public fun findTargetForRefactoring(element: PsiElement): PsiElement? {
val elementParent = element.getParent()
if ((elementParent is JetNamedFunction || elementParent is JetClass || elementParent is JetProperty)
&& (elementParent as JetNamedDeclaration).getNameIdentifier() === element) return elementParent
if (elementParent is JetParameter) {
val primaryConstructor = PsiTreeUtil.getParentOfType(elementParent, javaClass<JetPrimaryConstructor>())
if (elementParent.hasValOrVar()
&& (elementParent.getNameIdentifier() === element || elementParent.getValOrVarKeyword() === element)
&& primaryConstructor != null && primaryConstructor != null
&& primaryConstructor.getValueParameterList() == parameter.getParent()) return parameter; && primaryConstructor.getValueParameterList() === elementParent.getParent()) return elementParent
} }
if (elementParent instanceof JetSecondaryConstructor && if (elementParent is JetSecondaryConstructor && elementParent.getConstructorKeyword() === element) return elementParent
((JetSecondaryConstructor) elementParent).getConstructorKeyword() == element) return elementParent;
if (PsiTreeUtil.getParentOfType(element, JetParameterList.class) != null) { element.getStrictParentOfType<JetParameterList>()?.let { parameterList ->
return PsiTreeUtil.getParentOfType(element, JetFunction.class, JetProperty.class, JetClass.class); return PsiTreeUtil.getParentOfType(parameterList, javaClass<JetFunction>(), javaClass<JetProperty>(), javaClass<JetClass>())
} }
JetTypeParameterList typeParameterList = PsiTreeUtil.getParentOfType(element, JetTypeParameterList.class); element.getStrictParentOfType<JetTypeParameterList>()?.let { typeParameterList ->
if (typeParameterList != null) { return PsiTreeUtil.getParentOfType(typeParameterList, javaClass<JetFunction>(), javaClass<JetProperty>(), javaClass<JetClass>())
return PsiTreeUtil.getParentOfType(typeParameterList, JetFunction.class, JetProperty.class, JetClass.class);
} }
JetExpression calleeExpr; val call: JetCallElement? = PsiTreeUtil.getParentOfType(element,
JetCallElement call = PsiTreeUtil.getParentOfType(element, javaClass<JetCallExpression>(),
JetCallExpression.class, javaClass<JetDelegatorToSuperCall>(),
JetDelegatorToSuperCall.class, javaClass<JetConstructorDelegationCall>())
JetConstructorDelegationCall.class); val calleeExpr = call?.let {
if (call != null) { val callee = it.getCalleeExpression()
calleeExpr = call.getCalleeExpression(); (callee as? JetConstructorCalleeExpression)?.getConstructorReferenceExpression() ?: callee
} } ?: element.getStrictParentOfType<JetSimpleNameExpression>()
else {
calleeExpr = PsiTreeUtil.getParentOfType(element, JetSimpleNameExpression.class); if (calleeExpr is JetSimpleNameExpression || calleeExpr is JetConstructorDelegationReferenceExpression) {
val jetElement = element.getStrictParentOfType<JetElement>() ?: return null
val bindingContext = jetElement.analyze(BodyResolveMode.FULL)
val descriptor = bindingContext[BindingContext.REFERENCE_TARGET, calleeExpr as JetReferenceExpression]
if (descriptor is ClassDescriptor || descriptor is CallableDescriptor) return calleeExpr
} }
if (calleeExpr instanceof JetConstructorCalleeExpression) { return null
calleeExpr = ((JetConstructorCalleeExpression) calleeExpr).getConstructorReferenceExpression();
}
if (calleeExpr instanceof JetSimpleNameExpression || calleeExpr instanceof JetConstructorDelegationReferenceExpression) {
JetElement jetElement = PsiTreeUtil.getParentOfType(element, JetElement.class);
if (jetElement == null) return null;
BindingContext bindingContext = ResolvePackage.analyze(jetElement, BodyResolveMode.FULL);
DeclarationDescriptor descriptor = bindingContext.get(BindingContext.REFERENCE_TARGET, (JetReferenceExpression) calleeExpr);
if (descriptor instanceof ClassDescriptor || descriptor instanceof CallableDescriptor) return calleeExpr;
} }
return null; public fun invokeChangeSignature(element: JetElement, context: PsiElement, project: Project, editor: Editor?) {
val bindingContext = element.analyze(BodyResolveMode.FULL)
val callableDescriptor = findDescriptor(element, project, editor, bindingContext) ?: return
if (callableDescriptor is JavaCallableMemberDescriptor) {
val declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, callableDescriptor)
assert(declaration is PsiMethod) { "PsiMethod expected: $callableDescriptor" }
ChangeSignatureUtil.invokeChangeSignatureOn(declaration as PsiMethod, project)
return
} }
public static void invokeChangeSignature( if (callableDescriptor.isDynamic()) {
@NotNull JetElement element,
@NotNull PsiElement context,
@NotNull Project project,
@Nullable Editor editor
) {
BindingContext bindingContext = ResolvePackage.analyze(element, BodyResolveMode.FULL);
CallableDescriptor callableDescriptor = findDescriptor(element, project, editor, bindingContext);
if (callableDescriptor == null) {
return;
}
if (callableDescriptor instanceof JavaCallableMemberDescriptor) {
PsiElement declaration = DescriptorToSourceUtilsIde.INSTANCE$.getAnyDeclaration(project, callableDescriptor);
assert declaration instanceof PsiMethod : "PsiMethod expected: " + callableDescriptor;
ChangeSignatureUtil.invokeChangeSignatureOn((PsiMethod) declaration, project);
return;
}
if (TasksPackage.isDynamic(callableDescriptor)) {
if (editor != null) { if (editor != null) {
CodeInsightUtils.showErrorHint( CodeInsightUtils.showErrorHint(project, editor, "Change signature is not applicable to dynamically invoked functions", "Change Signature", null)
project, }
editor, return
"Change signature is not applicable to dynamically invoked functions", }
"Change Signature",
runChangeSignature(project, callableDescriptor, JetChangeSignatureConfiguration.Empty, bindingContext, context, null)
}
private fun getDescriptor(bindingContext: BindingContext, element: PsiElement): DeclarationDescriptor? {
val descriptor = when (element) {
is JetReferenceExpression -> bindingContext[BindingContext.REFERENCE_TARGET, element]
else -> bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, element]
}
return if (descriptor is ClassDescriptor) descriptor.getUnsubstitutedPrimaryConstructor() else descriptor
}
public fun findDescriptor(element: PsiElement, project: Project, editor: Editor?, bindingContext: BindingContext): CallableDescriptor? {
if (!CommonRefactoringUtil.checkReadOnlyStatus(project, element)) return null
var descriptor = getDescriptor(bindingContext, element)
return when (descriptor) {
is FunctionDescriptor -> {
if (descriptor.getValueParameters().any { it.getVarargElementType() != null }) {
val message = JetRefactoringBundle.message("error.cant.refactor.vararg.functions")
CommonRefactoringUtil.showErrorHint(project, editor, message, ChangeSignatureHandler.REFACTORING_NAME, HelpID.CHANGE_SIGNATURE)
return null
}
if (descriptor.getKind() === SYNTHESIZED) {
val message = JetRefactoringBundle.message("cannot.refactor.synthesized.function", descriptor.getName())
CommonRefactoringUtil.showErrorHint(project, editor, message, ChangeSignatureHandler.REFACTORING_NAME, HelpID.CHANGE_SIGNATURE)
return null
}
descriptor
}
is PropertyDescriptor, is ValueParameterDescriptor -> descriptor as CallableDescriptor
else -> {
val message = RefactoringBundle.getCannotRefactorMessage(JetRefactoringBundle.message("error.wrong.caret.position.function.or.constructor.name"))
CommonRefactoringUtil.showErrorHint(project, editor, message, ChangeSignatureHandler.REFACTORING_NAME, HelpID.CHANGE_SIGNATURE)
null null
);
}
return;
}
runChangeSignature(project, callableDescriptor, emptyConfiguration(), bindingContext, context, null);
}
@TestOnly
public static JetChangeSignatureConfiguration getConfiguration() {
return emptyConfiguration();
}
private static JetChangeSignatureConfiguration emptyConfiguration() {
return new JetChangeSignatureConfiguration() {
@NotNull
@Override
public JetMethodDescriptor configure(@NotNull JetMethodDescriptor originalDescriptor, @NotNull BindingContext bindingContext) {
//do nothing
return originalDescriptor;
}
@Override
public boolean performSilently(@NotNull Collection<? extends PsiElement> elements) {
return false;
}
@Override
public boolean forcePerformForSelectedFunctionOnly() {
return false;
}
};
}
@Nullable
@Override
public PsiElement findTargetMember(PsiFile file, Editor editor) {
return findTargetMember(file.findElementAt(editor.getCaretModel().getOffset()));
}
@Nullable
@Override
public PsiElement findTargetMember(PsiElement element) {
return findTargetForRefactoring(element);
}
@Override
public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file, DataContext dataContext) {
editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
PsiElement element = findTargetMember(file, editor);
if (element == null) {
element = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
}
PsiElement elementAtCaret = file.findElementAt(editor.getCaretModel().getOffset());
if (element != null && elementAtCaret != null) {
assert element instanceof JetElement : "This handler must be invoked for elements of JetLanguage : " + element.getText();
invokeChangeSignature((JetElement) element, elementAtCaret, project, editor);
} }
} }
@Override
public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, @Nullable DataContext dataContext) {
if (elements.length != 1) return;
Editor editor = dataContext != null ? CommonDataKeys.EDITOR.getData(dataContext) : null;
PsiElement element = AsJavaPackage.getUnwrapped(elements[0]);
assert element instanceof JetElement : "This handler must be invoked for elements of JetLanguage : " + element.getText();
invokeChangeSignature((JetElement) element, element, project, editor);
}
@Nullable
@Override
public String getTargetNotFoundMessage() {
return JetRefactoringBundle.message("error.wrong.caret.position.function.or.constructor.name");
}
@Nullable
public static CallableDescriptor findDescriptor(
@NotNull PsiElement element,
@NotNull Project project,
@Nullable Editor editor,
BindingContext bindingContext
) {
if (!CommonRefactoringUtil.checkReadOnlyStatus(project, element)) return null;
DeclarationDescriptor descriptor;
if (element instanceof JetReferenceExpression) {
descriptor = bindingContext.get(BindingContext.REFERENCE_TARGET, (JetReferenceExpression) element);
} else {
descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element);
}
if (descriptor instanceof ClassDescriptor) {
descriptor = ((ClassDescriptor) descriptor).getUnsubstitutedPrimaryConstructor();
}
if (descriptor instanceof FunctionDescriptor) {
for (ValueParameterDescriptor parameter : ((FunctionDescriptor) descriptor).getValueParameters()) {
if (parameter.getVarargElementType() != null) {
String message = JetRefactoringBundle.message("error.cant.refactor.vararg.functions");
CommonRefactoringUtil.showErrorHint(project, editor, message,
REFACTORING_NAME,
HelpID.CHANGE_SIGNATURE);
return null;
}
}
if (((FunctionDescriptor) descriptor).getKind() == SYNTHESIZED) {
String message = JetRefactoringBundle.message("cannot.refactor.synthesized.function", descriptor.getName());
CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.CHANGE_SIGNATURE);
return null;
}
return (FunctionDescriptor) descriptor;
}
else if (descriptor instanceof PropertyDescriptor || descriptor instanceof ValueParameterDescriptor) {
return (CallableDescriptor) descriptor;
}
else {
String message = RefactoringBundle.getCannotRefactorMessage(JetRefactoringBundle.message(
"error.wrong.caret.position.function.or.constructor.name"));
CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.CHANGE_SIGNATURE);
return null;
} }
} }
} }
@@ -1128,11 +1128,12 @@ public class JetChangeSignatureTest extends KotlinCodeInsightTestCase {
PsiElement context = file.findElementAt(editor.getCaretModel().getOffset()); PsiElement context = file.findElementAt(editor.getCaretModel().getOffset());
assertNotNull(context); assertNotNull(context);
CallableDescriptor callableDescriptor = JetChangeSignatureHandler.findDescriptor(element, project, editor, bindingContext); CallableDescriptor callableDescriptor = JetChangeSignatureHandler.Companion.findDescriptor(element, project, editor, bindingContext);
assertNotNull(callableDescriptor); assertNotNull(callableDescriptor);
return ChangeSignaturePackage.createChangeInfo( return ChangeSignaturePackage.createChangeInfo(
project, callableDescriptor, JetChangeSignatureHandler.getConfiguration(), bindingContext, context); project, callableDescriptor, JetChangeSignatureConfiguration.Empty.INSTANCE$, bindingContext, context
);
} }
private class JavaRefactoringProvider { private class JavaRefactoringProvider {