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()
&& (parameter.getNameIdentifier() == element || parameter.getValOrVarKeyword() == element)
&& primaryConstructor != null
&& primaryConstructor.getValueParameterList() == parameter.getParent()) return parameter;
}
if (elementParent instanceof JetSecondaryConstructor && val element = findTargetMember(file, editor) ?: CommonDataKeys.PSI_ELEMENT.getData(dataContext) ?: return
((JetSecondaryConstructor) elementParent).getConstructorKeyword() == element) return elementParent; 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()}")
if (PsiTreeUtil.getParentOfType(element, JetParameterList.class) != null) { invokeChangeSignature(element, elementAtCaret, project, editor)
return PsiTreeUtil.getParentOfType(element, JetFunction.class, JetProperty.class, JetClass.class);
}
JetTypeParameterList typeParameterList = PsiTreeUtil.getParentOfType(element, JetTypeParameterList.class);
if (typeParameterList != null) {
return PsiTreeUtil.getParentOfType(typeParameterList, JetFunction.class, JetProperty.class, JetClass.class);
}
JetExpression calleeExpr;
JetCallElement call = PsiTreeUtil.getParentOfType(element,
JetCallExpression.class,
JetDelegatorToSuperCall.class,
JetConstructorDelegationCall.class);
if (call != null) {
calleeExpr = call.getCalleeExpression();
}
else {
calleeExpr = PsiTreeUtil.getParentOfType(element, JetSimpleNameExpression.class);
}
if (calleeExpr instanceof JetConstructorCalleeExpression) {
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 static void invokeChangeSignature( override fun invoke(project: Project, elements: Array<PsiElement>, dataContext: DataContext?) {
@NotNull JetElement element, val element = elements.singleOrNull()?.unwrapped ?: return
@NotNull PsiElement context, if (element !is JetElement) throw AssertionError("This handler must be invoked for Kotlin elements only: ${element.getText()}")
@NotNull Project project,
@Nullable Editor editor
) {
BindingContext bindingContext = ResolvePackage.analyze(element, BodyResolveMode.FULL);
CallableDescriptor callableDescriptor = findDescriptor(element, project, editor, bindingContext); val editor = dataContext?.let { CommonDataKeys.EDITOR.getData(it) }
if (callableDescriptor == null) { invokeChangeSignature(element, element, project, editor)
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) {
CodeInsightUtils.showErrorHint(
project,
editor,
"Change signature is not applicable to dynamically invoked functions",
"Change Signature",
null
);
}
return;
}
runChangeSignature(project, callableDescriptor, emptyConfiguration(), bindingContext, context, null);
} }
@TestOnly override fun getTargetNotFoundMessage() =
public static JetChangeSignatureConfiguration getConfiguration() { JetRefactoringBundle.message("error.wrong.caret.position.function.or.constructor.name")
return emptyConfiguration();
}
private static JetChangeSignatureConfiguration emptyConfiguration() { companion object {
return new JetChangeSignatureConfiguration() { public fun findTargetForRefactoring(element: PsiElement): PsiElement? {
@NotNull val elementParent = element.getParent()
@Override
public JetMethodDescriptor configure(@NotNull JetMethodDescriptor originalDescriptor, @NotNull BindingContext bindingContext) { if ((elementParent is JetNamedFunction || elementParent is JetClass || elementParent is JetProperty)
//do nothing && (elementParent as JetNamedDeclaration).getNameIdentifier() === element) return elementParent
return originalDescriptor;
if (elementParent is JetParameter) {
val primaryConstructor = PsiTreeUtil.getParentOfType(elementParent, javaClass<JetPrimaryConstructor>())
if (elementParent.hasValOrVar()
&& (elementParent.getNameIdentifier() === element || elementParent.getValOrVarKeyword() === element)
&& primaryConstructor != null
&& primaryConstructor.getValueParameterList() === elementParent.getParent()) return elementParent
} }
@Override if (elementParent is JetSecondaryConstructor && elementParent.getConstructorKeyword() === element) return elementParent
public boolean performSilently(@NotNull Collection<? extends PsiElement> elements) {
return false; element.getStrictParentOfType<JetParameterList>()?.let { parameterList ->
return PsiTreeUtil.getParentOfType(parameterList, javaClass<JetFunction>(), javaClass<JetProperty>(), javaClass<JetClass>())
} }
@Override element.getStrictParentOfType<JetTypeParameterList>()?.let { typeParameterList ->
public boolean forcePerformForSelectedFunctionOnly() { return PsiTreeUtil.getParentOfType(typeParameterList, javaClass<JetFunction>(), javaClass<JetProperty>(), javaClass<JetClass>())
return false;
} }
};
}
@Nullable val call: JetCallElement? = PsiTreeUtil.getParentOfType(element,
@Override javaClass<JetCallExpression>(),
public PsiElement findTargetMember(PsiFile file, Editor editor) { javaClass<JetDelegatorToSuperCall>(),
return findTargetMember(file.findElementAt(editor.getCaretModel().getOffset())); javaClass<JetConstructorDelegationCall>())
} val calleeExpr = call?.let {
val callee = it.getCalleeExpression()
(callee as? JetConstructorCalleeExpression)?.getConstructorReferenceExpression() ?: callee
} ?: element.getStrictParentOfType<JetSimpleNameExpression>()
@Nullable if (calleeExpr is JetSimpleNameExpression || calleeExpr is JetConstructorDelegationReferenceExpression) {
@Override val jetElement = element.getStrictParentOfType<JetElement>() ?: return null
public PsiElement findTargetMember(PsiElement element) {
return findTargetForRefactoring(element);
}
@Override val bindingContext = jetElement.analyze(BodyResolveMode.FULL)
public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file, DataContext dataContext) { val descriptor = bindingContext[BindingContext.REFERENCE_TARGET, calleeExpr as JetReferenceExpression]
editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
PsiElement element = findTargetMember(file, editor); if (descriptor is ClassDescriptor || descriptor is CallableDescriptor) return calleeExpr
if (element == null) { }
element = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
return null
} }
PsiElement elementAtCaret = file.findElementAt(editor.getCaretModel().getOffset()); public fun invokeChangeSignature(element: JetElement, context: PsiElement, project: Project, editor: Editor?) {
if (element != null && elementAtCaret != null) { val bindingContext = element.analyze(BodyResolveMode.FULL)
assert element instanceof JetElement : "This handler must be invoked for elements of JetLanguage : " + element.getText();
invokeChangeSignature((JetElement) element, elementAtCaret, project, editor); 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
}
if (callableDescriptor.isDynamic()) {
if (editor != null) {
CodeInsightUtils.showErrorHint(project, editor, "Change signature is not applicable to dynamically invoked functions", "Change Signature", null)
}
return
}
runChangeSignature(project, callableDescriptor, JetChangeSignatureConfiguration.Empty, bindingContext, context, null)
} }
}
@Override private fun getDescriptor(bindingContext: BindingContext, element: PsiElement): DeclarationDescriptor? {
public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, @Nullable DataContext dataContext) { val descriptor = when (element) {
if (elements.length != 1) return; is JetReferenceExpression -> bindingContext[BindingContext.REFERENCE_TARGET, element]
Editor editor = dataContext != null ? CommonDataKeys.EDITOR.getData(dataContext) : null; else -> bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, element]
}
PsiElement element = AsJavaPackage.getUnwrapped(elements[0]); return if (descriptor is ClassDescriptor) descriptor.getUnsubstitutedPrimaryConstructor() else descriptor
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(); public fun findDescriptor(element: PsiElement, project: Project, editor: Editor?, bindingContext: BindingContext): CallableDescriptor? {
} if (!CommonRefactoringUtil.checkReadOnlyStatus(project, element)) return null
if (descriptor instanceof FunctionDescriptor) {
for (ValueParameterDescriptor parameter : ((FunctionDescriptor) descriptor).getValueParameters()) { var descriptor = getDescriptor(bindingContext, element)
if (parameter.getVarargElementType() != null) {
String message = JetRefactoringBundle.message("error.cant.refactor.vararg.functions"); return when (descriptor) {
CommonRefactoringUtil.showErrorHint(project, editor, message, is FunctionDescriptor -> {
REFACTORING_NAME, if (descriptor.getValueParameters().any { it.getVarargElementType() != null }) {
HelpID.CHANGE_SIGNATURE); val message = JetRefactoringBundle.message("error.cant.refactor.vararg.functions")
return null; 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
} }
} }
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 {