Convert to Kotlin: KotlinRefactoringUtil2

This commit is contained in:
Alexey Sedunov
2016-08-31 13:28:20 +03:00
parent 1f8dba9c89
commit fc9b28884d
8 changed files with 380 additions and 468 deletions
@@ -44,7 +44,7 @@ public abstract class KotlinUnwrapRemoveBase extends AbstractUnwrapper<KotlinUnw
@Override @Override
public String getDescription(PsiElement e) { public String getDescription(PsiElement e) {
assert e instanceof KtElement; assert e instanceof KtElement;
return KotlinBundle.message(key, KotlinRefactoringUtil2.getExpressionShortText((KtElement) e)); return KotlinBundle.message(key, KotlinRefactoringUtil2.INSTANCE.getExpressionShortText((KtElement) e));
} }
protected boolean canExtractExpression(@NotNull KtExpression expression, @NotNull KtElement parent) { protected boolean canExtractExpression(@NotNull KtExpression expression, @NotNull KtElement parent) {
@@ -154,7 +154,7 @@ public class KotlinFindClassUsagesDialog extends FindClassUsagesDialog {
: klass.getUserData(ORIGINAL_CLASS); : klass.getUserData(ORIGINAL_CLASS);
if (originalClass != null) { if (originalClass != null) {
coloredComponent.append(KotlinRefactoringUtil2.formatClass(originalClass)); coloredComponent.append(KotlinRefactoringUtil2.INSTANCE.formatClass(originalClass));
} }
} }
@@ -50,7 +50,7 @@ public class KotlinFindFunctionUsagesDialog extends FindMethodUsagesDialog {
@Override @Override
public void configureLabelComponent(@NotNull SimpleColoredComponent coloredComponent) { public void configureLabelComponent(@NotNull SimpleColoredComponent coloredComponent) {
coloredComponent.append(KotlinRefactoringUtil2.formatJavaOrLightMethod((PsiMethod) myPsiElement)); coloredComponent.append(KotlinRefactoringUtil2.INSTANCE.formatJavaOrLightMethod((PsiMethod) myPsiElement));
} }
@Override @Override
@@ -14,585 +14,497 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.idea.refactoring; package org.jetbrains.kotlin.idea.refactoring
import com.intellij.codeInsight.unwrap.ScopeHighlighter; import com.intellij.codeInsight.unwrap.ScopeHighlighter
import com.intellij.ide.IdeBundle; import com.intellij.ide.IdeBundle
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.popup.JBPopupAdapter
import com.intellij.openapi.ui.popup.JBPopupAdapter; import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.ui.popup.LightweightWindowEvent
import com.intellij.openapi.ui.popup.LightweightWindowEvent; import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*
import com.intellij.psi.*; import com.intellij.psi.search.searches.OverridingMethodsSearch
import com.intellij.psi.search.searches.OverridingMethodsSearch; import com.intellij.psi.util.PsiFormatUtil
import com.intellij.psi.util.PsiFormatUtil; import com.intellij.psi.util.PsiFormatUtilBase
import com.intellij.psi.util.PsiFormatUtilBase; import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiTreeUtil; import com.intellij.ui.components.JBList
import com.intellij.ui.components.JBList; import com.intellij.util.containers.ContainerUtil
import com.intellij.util.Function; import org.jetbrains.kotlin.asJava.elements.KtLightMethod
import com.intellij.util.containers.ContainerUtil; import org.jetbrains.kotlin.asJava.unwrapped
import kotlin.collections.CollectionsKt; import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import kotlin.jvm.functions.Function1; import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.asJava.LightClassUtilsKt; import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.asJava.elements.KtLightMethod; import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.descriptors.CallableDescriptor; import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.descriptors.ClassDescriptor; import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; import org.jetbrains.kotlin.idea.refactoring.introduce.findExpressionOrStringFragment
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor; import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.KotlinBundle; import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils; import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils; import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespaceAndComments
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde; import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
import org.jetbrains.kotlin.idea.refactoring.introduce.IntroduceUtilKt; import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers; import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt; import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt; import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.renderer.DescriptorRenderer; import java.awt.Component
import org.jetbrains.kotlin.resolve.BindingContext; import java.lang.RuntimeException
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils; import java.util.*
import org.jetbrains.kotlin.resolve.DescriptorUtils; import javax.swing.DefaultListCellRenderer
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode; import javax.swing.DefaultListModel
import org.jetbrains.kotlin.types.KotlinType; import javax.swing.JList
import javax.swing.event.ListSelectionListener
import javax.swing.*; object KotlinRefactoringUtil2 {
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.util.*;
import java.util.List;
public class KotlinRefactoringUtil2 { fun wrapOrSkip(s: String, inCode: Boolean): String {
private KotlinRefactoringUtil2() { return if (inCode) "<code>$s</code>" else s
} }
@NotNull fun formatClassDescriptor(classDescriptor: DeclarationDescriptor): String {
public static String wrapOrSkip(@NotNull String s, boolean inCode) { return IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.render(classDescriptor)
return inCode ? "<code>" + s + "</code>" : s;
} }
@NotNull fun formatPsiClass(
public static String formatClassDescriptor(@NotNull DeclarationDescriptor classDescriptor) { psiClass: PsiClass,
return IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.render(classDescriptor); markAsJava: Boolean,
} inCode: Boolean): String {
var description: String
@NotNull val kind = if (psiClass.isInterface) "interface " else "class "
public static String formatPsiClass(
@NotNull PsiClass psiClass,
boolean markAsJava,
boolean inCode
) {
String description;
String kind = psiClass.isInterface() ? "interface " : "class ";
description = kind + PsiFormatUtil.formatClass( description = kind + PsiFormatUtil.formatClass(
psiClass, psiClass,
PsiFormatUtilBase.SHOW_CONTAINING_CLASS PsiFormatUtilBase.SHOW_CONTAINING_CLASS or PsiFormatUtilBase.SHOW_NAME or PsiFormatUtilBase.SHOW_PARAMETERS or PsiFormatUtilBase.SHOW_TYPE)
| PsiFormatUtilBase.SHOW_NAME description = wrapOrSkip(description, inCode)
| PsiFormatUtilBase.SHOW_PARAMETERS
| PsiFormatUtilBase.SHOW_TYPE
);
description = wrapOrSkip(description, inCode);
return markAsJava ? "[Java] " + description : description; return if (markAsJava) "[Java] " + description else description
} }
@NotNull fun checkSuperMethods(
public static List<? extends PsiElement> checkSuperMethods( declaration: KtDeclaration,
@NotNull KtDeclaration declaration, ignore: Collection<PsiElement>?,
@Nullable Collection<PsiElement> ignore, actionStringKey: String): List<PsiElement> {
@NotNull String actionStringKey val bindingContext = declaration.analyze(BodyResolveMode.FULL)
) {
BindingContext bindingContext = ResolutionUtils.analyze(declaration, BodyResolveMode.FULL);
CallableDescriptor declarationDescriptor = val declarationDescriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration) as CallableDescriptor?
(CallableDescriptor)bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration);
if (declarationDescriptor == null || declarationDescriptor instanceof LocalVariableDescriptor) { if (declarationDescriptor == null || declarationDescriptor is LocalVariableDescriptor) {
return Collections.singletonList(declaration); return listOf(declaration)
} }
Project project = declaration.getProject(); val project = declaration.project
Map<PsiElement, CallableDescriptor> overriddenElementsToDescriptor = new HashMap<PsiElement, CallableDescriptor>(); val overriddenElementsToDescriptor = HashMap<PsiElement, CallableDescriptor>()
for (CallableDescriptor overriddenDescriptor : DescriptorUtils.getAllOverriddenDescriptors(declarationDescriptor)) { for (overriddenDescriptor in DescriptorUtils.getAllOverriddenDescriptors(declarationDescriptor)) {
PsiElement overriddenDeclaration = DescriptorToSourceUtilsIde.INSTANCE.getAnyDeclaration(project, overriddenDescriptor); val overriddenDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, overriddenDescriptor) ?: continue
if (PsiTreeUtil.instanceOf(overriddenDeclaration, KtNamedFunction.class, KtProperty.class, PsiMethod.class)) { if (PsiTreeUtil.instanceOf(overriddenDeclaration, KtNamedFunction::class.java, KtProperty::class.java, PsiMethod::class.java)) {
overriddenElementsToDescriptor.put(overriddenDeclaration, overriddenDescriptor); overriddenElementsToDescriptor.put(overriddenDeclaration, overriddenDescriptor)
} }
} }
if (ignore != null) { if (ignore != null) {
overriddenElementsToDescriptor.keySet().removeAll(ignore); overriddenElementsToDescriptor.keys.removeAll(ignore)
} }
if (overriddenElementsToDescriptor.isEmpty()) return Collections.singletonList(declaration); if (overriddenElementsToDescriptor.isEmpty()) return listOf(declaration)
List<String> superClasses = getClassDescriptions(overriddenElementsToDescriptor); val superClasses = getClassDescriptions(overriddenElementsToDescriptor)
return askUserForMethodsToSearch(declaration, declarationDescriptor, overriddenElementsToDescriptor, superClasses, actionStringKey); return askUserForMethodsToSearch(declaration, declarationDescriptor, overriddenElementsToDescriptor, superClasses, actionStringKey)
} }
@NotNull private fun askUserForMethodsToSearch(
private static List<? extends PsiElement> askUserForMethodsToSearch( declaration: KtDeclaration,
@NotNull KtDeclaration declaration, declarationDescriptor: CallableDescriptor,
@NotNull CallableDescriptor declarationDescriptor, overriddenElementsToDescriptor: Map<PsiElement, CallableDescriptor>,
@NotNull Map<PsiElement, CallableDescriptor> overriddenElementsToDescriptor, superClasses: List<String>,
@NotNull List<String> superClasses, actionStringKey: String): List<PsiElement> {
@NotNull String actionStringKey if (ApplicationManager.getApplication().isUnitTestMode) {
) { return ContainerUtil.newArrayList(overriddenElementsToDescriptor.keys)
if (ApplicationManager.getApplication().isUnitTestMode()) {
return ContainerUtil.newArrayList(overriddenElementsToDescriptor.keySet());
} }
String superClassesStr = "\n" + StringUtil.join(superClasses, ""); val superClassesStr = "\n" + StringUtil.join(superClasses, "")
String message = KotlinBundle.message( val message = KotlinBundle.message(
"x.overrides.y.in.class.list", "x.overrides.y.in.class.list",
DescriptorRenderer.COMPACT_WITH_SHORT_TYPES.render(declarationDescriptor), DescriptorRenderer.COMPACT_WITH_SHORT_TYPES.render(declarationDescriptor),
superClassesStr, superClassesStr,
KotlinBundle.message(actionStringKey) KotlinBundle.message(actionStringKey))
);
int exitCode = Messages.showYesNoCancelDialog(declaration.getProject(), message, IdeBundle.message("title.warning"), Messages.getQuestionIcon()); val exitCode = Messages.showYesNoCancelDialog(declaration.project, message, IdeBundle.message("title.warning"), Messages.getQuestionIcon())
switch (exitCode) { when (exitCode) {
case Messages.YES: Messages.YES -> return ContainerUtil.newArrayList(overriddenElementsToDescriptor.keys)
return ContainerUtil.newArrayList(overriddenElementsToDescriptor.keySet()); Messages.NO -> return listOf(declaration)
case Messages.NO: else -> return emptyList()
return Collections.singletonList(declaration);
default:
return Collections.emptyList();
} }
} }
@NotNull private fun getClassDescriptions(overriddenElementsToDescriptor: Map<PsiElement, CallableDescriptor>): List<String> {
private static List<String> getClassDescriptions(@NotNull Map<PsiElement, CallableDescriptor> overriddenElementsToDescriptor) { return overriddenElementsToDescriptor.entries.map { entry ->
return ContainerUtil.map( val description: String
overriddenElementsToDescriptor.entrySet(),
new Function<Map.Entry<PsiElement, CallableDescriptor>, String>() {
@Override
public String fun(Map.Entry<PsiElement, CallableDescriptor> entry) {
String description;
PsiElement element = entry.getKey(); val element = entry.key
CallableDescriptor descriptor = entry.getValue(); val descriptor = entry.value
if (element instanceof KtNamedFunction || element instanceof KtProperty) { if (element is KtNamedFunction || element is KtProperty) {
description = formatClassDescriptor(descriptor.getContainingDeclaration()); description = formatClassDescriptor(descriptor.getContainingDeclaration())
}
else {
assert element instanceof PsiMethod : "Invalid element: " + element.getText();
PsiClass psiClass = ((PsiMethod) element).getContainingClass();
assert psiClass != null : "Invalid element: " + element.getText();
description = formatPsiClass(psiClass, true, false);
}
return " " + description + "\n";
}
}
);
}
@NotNull
public static String formatClass(@NotNull DeclarationDescriptor classDescriptor, boolean inCode) {
PsiElement element = DescriptorToSourceUtils.descriptorToDeclaration(classDescriptor);
if (element instanceof PsiClass) {
return formatPsiClass((PsiClass) element, false, inCode);
}
return wrapOrSkip(formatClassDescriptor(classDescriptor), inCode);
}
@NotNull
public static String formatFunction(@NotNull DeclarationDescriptor functionDescriptor, boolean inCode) {
PsiElement element = DescriptorToSourceUtils.descriptorToDeclaration(functionDescriptor);
if (element instanceof PsiMethod) {
return formatPsiMethod((PsiMethod) element, false, inCode);
}
return wrapOrSkip(formatFunctionDescriptor(functionDescriptor), inCode);
}
@NotNull
private static String formatFunctionDescriptor(@NotNull DeclarationDescriptor functionDescriptor) {
return DescriptorRenderer.COMPACT.render(functionDescriptor);
}
@NotNull
public static String formatPsiMethod(
@NotNull PsiMethod psiMethod,
boolean showContainingClass,
boolean inCode) {
int options = PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_PARAMETERS | PsiFormatUtilBase.SHOW_TYPE;
if (showContainingClass) {
//noinspection ConstantConditions
options |= PsiFormatUtilBase.SHOW_CONTAINING_CLASS;
}
String description = PsiFormatUtil.formatMethod(psiMethod, PsiSubstitutor.EMPTY, options, PsiFormatUtilBase.SHOW_TYPE);
description = wrapOrSkip(description, inCode);
return "[Java] " + description;
}
@NotNull
public static String formatJavaOrLightMethod(@NotNull PsiMethod method) {
PsiElement originalDeclaration = LightClassUtilsKt.getUnwrapped(method);
if (originalDeclaration instanceof KtDeclaration) {
KtDeclaration ktDeclaration = (KtDeclaration) originalDeclaration;
BindingContext bindingContext = ResolutionUtils.analyze(ktDeclaration, BodyResolveMode.FULL);
DeclarationDescriptor descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, ktDeclaration);
if (descriptor != null) return formatFunctionDescriptor(descriptor);
}
return formatPsiMethod(method, false, false);
}
@NotNull
public static String formatClass(@NotNull KtClassOrObject classOrObject) {
BindingContext bindingContext = ResolutionUtils.analyze(classOrObject, BodyResolveMode.FULL);
DeclarationDescriptor descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, classOrObject);
if (descriptor instanceof ClassDescriptor) return formatClassDescriptor(descriptor);
return "class " + classOrObject.getName();
}
@Nullable
public static Collection<? extends PsiElement> checkParametersInMethodHierarchy(@NotNull PsiParameter parameter) {
PsiMethod method = (PsiMethod)parameter.getDeclarationScope();
Set<PsiElement> parametersToDelete = collectParametersHierarchy(method, parameter);
if (parametersToDelete.size() > 1) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
return parametersToDelete;
}
String message =
KotlinBundle.message("delete.param.in.method.hierarchy", formatJavaOrLightMethod(method));
int exitCode = Messages.showOkCancelDialog(
parameter.getProject(), message, IdeBundle.message("title.warning"), Messages.getQuestionIcon()
);
if (exitCode == Messages.OK) {
return parametersToDelete;
} }
else { else {
return null; assert(element is PsiMethod) { "Invalid element: " + element.getText() }
val psiClass = (element as PsiMethod).containingClass ?: error("Invalid element: " + element.getText())
description = formatPsiClass(psiClass, true, false)
}
" " + description + "\n"
}
}
fun formatClass(classDescriptor: DeclarationDescriptor, inCode: Boolean): String {
val element = DescriptorToSourceUtils.descriptorToDeclaration(classDescriptor)
if (element is PsiClass) {
return formatPsiClass((element as PsiClass?)!!, false, inCode)
}
return wrapOrSkip(formatClassDescriptor(classDescriptor), inCode)
}
fun formatFunction(functionDescriptor: DeclarationDescriptor, inCode: Boolean): String {
val element = DescriptorToSourceUtils.descriptorToDeclaration(functionDescriptor)
if (element is PsiMethod) {
return formatPsiMethod((element as PsiMethod?)!!, false, inCode)
}
return wrapOrSkip(formatFunctionDescriptor(functionDescriptor), inCode)
}
private fun formatFunctionDescriptor(functionDescriptor: DeclarationDescriptor): String {
return DescriptorRenderer.COMPACT.render(functionDescriptor)
}
fun formatPsiMethod(
psiMethod: PsiMethod,
showContainingClass: Boolean,
inCode: Boolean): String {
var options = PsiFormatUtilBase.SHOW_NAME or PsiFormatUtilBase.SHOW_PARAMETERS or PsiFormatUtilBase.SHOW_TYPE
if (showContainingClass) {
//noinspection ConstantConditions
options = options or PsiFormatUtilBase.SHOW_CONTAINING_CLASS
}
var description = PsiFormatUtil.formatMethod(psiMethod, PsiSubstitutor.EMPTY, options, PsiFormatUtilBase.SHOW_TYPE)
description = wrapOrSkip(description, inCode)
return "[Java] " + description
}
fun formatJavaOrLightMethod(method: PsiMethod): String {
val originalDeclaration = method.unwrapped
if (originalDeclaration is KtDeclaration) {
val bindingContext = originalDeclaration.analyze(BodyResolveMode.FULL)
val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, originalDeclaration)
if (descriptor != null) return formatFunctionDescriptor(descriptor)
}
return formatPsiMethod(method, false, false)
}
fun formatClass(classOrObject: KtClassOrObject): String {
val bindingContext = classOrObject.analyze(BodyResolveMode.FULL)
val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, classOrObject)
if (descriptor is ClassDescriptor) return formatClassDescriptor(descriptor)
return "class " + classOrObject.name!!
}
fun checkParametersInMethodHierarchy(parameter: PsiParameter): Collection<PsiElement>? {
val method = parameter.declarationScope as PsiMethod
val parametersToDelete = collectParametersHierarchy(method, parameter)
if (parametersToDelete.size > 1) {
if (ApplicationManager.getApplication().isUnitTestMode) {
return parametersToDelete
}
val message = KotlinBundle.message("delete.param.in.method.hierarchy", formatJavaOrLightMethod(method))
val exitCode = Messages.showOkCancelDialog(
parameter.project, message, IdeBundle.message("title.warning"), Messages.getQuestionIcon())
if (exitCode == Messages.OK) {
return parametersToDelete
}
else {
return null
} }
} }
return parametersToDelete; return parametersToDelete
} }
// TODO: generalize breadth-first search // TODO: generalize breadth-first search
@NotNull private fun collectParametersHierarchy(method: PsiMethod, parameter: PsiParameter): Set<PsiElement> {
private static Set<PsiElement> collectParametersHierarchy(@NotNull PsiMethod method, @NotNull PsiParameter parameter) { val queue = ArrayDeque<PsiMethod>()
Deque<PsiMethod> queue = new ArrayDeque<PsiMethod>(); val visited = HashSet<PsiMethod>()
Set<PsiMethod> visited = new HashSet<PsiMethod>(); val parametersToDelete = HashSet<PsiElement>()
Set<PsiElement> parametersToDelete = new HashSet<PsiElement>();
queue.add(method); queue.add(method)
while (!queue.isEmpty()) { while (!queue.isEmpty()) {
PsiMethod currentMethod = queue.poll(); val currentMethod = queue.poll()
visited.add(currentMethod); visited.add(currentMethod)
addParameter(currentMethod, parametersToDelete, parameter); addParameter(currentMethod, parametersToDelete, parameter)
for (PsiMethod superMethod : currentMethod.findSuperMethods(true)) { for (superMethod in currentMethod.findSuperMethods(true)) {
if (!visited.contains(superMethod)) { if (!visited.contains(superMethod)) {
queue.offer(superMethod); queue.offer(superMethod)
} }
} }
for (PsiMethod overrider : OverridingMethodsSearch.search(currentMethod)) { for (overrider in OverridingMethodsSearch.search(currentMethod)) {
if (!visited.contains(overrider)) { if (!visited.contains(overrider)) {
queue.offer(overrider); queue.offer(overrider)
} }
} }
} }
return parametersToDelete; return parametersToDelete
} }
private static void addParameter(@NotNull PsiMethod method, @NotNull Set<PsiElement> result, @NotNull PsiParameter parameter) { private fun addParameter(method: PsiMethod, result: MutableSet<PsiElement>, parameter: PsiParameter) {
int parameterIndex = KtPsiUtilKt.parameterIndex(LightClassUtilsKt.getUnwrapped(parameter)); val parameterIndex = parameter.unwrapped!!.parameterIndex()
if (method instanceof KtLightMethod) { if (method is KtLightMethod) {
KtDeclaration declaration = ((KtLightMethod) method).getKotlinOrigin(); val declaration = method.kotlinOrigin
if (declaration instanceof KtFunction) { if (declaration is KtFunction) {
result.add(((KtFunction) declaration).getValueParameters().get(parameterIndex)); result.add(declaration.valueParameters[parameterIndex])
} }
} }
else { else {
result.add(method.getParameterList().getParameters()[parameterIndex]); result.add(method.parameterList.parameters[parameterIndex])
} }
} }
public interface SelectElementCallback { @Throws(IntroduceRefactoringException::class)
void run(@Nullable PsiElement element); fun selectElement(
editor: Editor,
file: KtFile,
elementKinds: Collection<CodeInsightUtils.ElementKind>,
callback: (PsiElement?) -> Unit) {
selectElement(editor, file, true, elementKinds, callback)
} }
public static void selectElement( @Throws(IntroduceRefactoringException::class)
@NotNull Editor editor, fun selectElement(editor: Editor,
@NotNull KtFile file, file: KtFile,
@NotNull Collection<CodeInsightUtils.ElementKind> elementKinds, failOnEmptySuggestion: Boolean,
@NotNull SelectElementCallback callback elementKinds: Collection<CodeInsightUtils.ElementKind>,
) throws IntroduceRefactoringException { callback: (PsiElement?) -> Unit) {
selectElement(editor, file, true, elementKinds, callback); if (editor.selectionModel.hasSelection()) {
} var selectionStart = editor.selectionModel.selectionStart
var selectionEnd = editor.selectionModel.selectionEnd
public static void selectElement(@NotNull Editor editor, var firstElement: PsiElement = file.findElementAt(selectionStart)!!
@NotNull KtFile file, var lastElement: PsiElement = file.findElementAt(selectionEnd - 1)!!
boolean failOnEmptySuggestion,
@NotNull Collection<CodeInsightUtils.ElementKind> elementKinds,
@NotNull SelectElementCallback callback
) throws IntroduceRefactoringException {
if (editor.getSelectionModel().hasSelection()) {
int selectionStart = editor.getSelectionModel().getSelectionStart();
int selectionEnd = editor.getSelectionModel().getSelectionEnd();
PsiElement firstElement = file.findElementAt(selectionStart); if (PsiTreeUtil.getParentOfType(firstElement, KtLiteralStringTemplateEntry::class.java, KtEscapeStringTemplateEntry::class.java) == null && PsiTreeUtil.getParentOfType(lastElement, KtLiteralStringTemplateEntry::class.java, KtEscapeStringTemplateEntry::class.java) == null) {
PsiElement lastElement = file.findElementAt(selectionEnd - 1); firstElement = firstElement.getNextSiblingIgnoringWhitespaceAndComments(true)!!
lastElement = lastElement.getPrevSiblingIgnoringWhitespaceAndComments(true)!!
if (PsiTreeUtil.getParentOfType(firstElement, KtLiteralStringTemplateEntry.class, KtEscapeStringTemplateEntry.class) == null selectionStart = firstElement.textRange.startOffset
&& PsiTreeUtil.getParentOfType(lastElement, KtLiteralStringTemplateEntry.class, KtEscapeStringTemplateEntry.class) == null) { selectionEnd = lastElement.textRange.endOffset
firstElement = PsiUtilsKt.getNextSiblingIgnoringWhitespaceAndComments(firstElement, true);
lastElement = PsiUtilsKt.getPrevSiblingIgnoringWhitespaceAndComments(lastElement, true);
selectionStart = firstElement.getTextRange().getStartOffset();
selectionEnd = lastElement.getTextRange().getEndOffset();
} }
for (CodeInsightUtils.ElementKind elementKind : elementKinds) { for (elementKind in elementKinds) {
PsiElement element = findElement(file, selectionStart, selectionEnd, failOnEmptySuggestion, elementKind); val element = findElement(file, selectionStart, selectionEnd, failOnEmptySuggestion, elementKind)
if (element != null) { if (element != null) {
callback.run(element); callback(element)
return; return
} }
} }
callback.run(null); callback(null)
} }
else { else {
int offset = editor.getCaretModel().getOffset(); val offset = editor.caretModel.offset
smartSelectElement(editor, file, offset, failOnEmptySuggestion, elementKinds, callback); smartSelectElement(editor, file, offset, failOnEmptySuggestion, elementKinds, callback)
} }
} }
public static List<KtElement> getSmartSelectSuggestions( @Throws(IntroduceRefactoringException::class)
@NotNull PsiFile file, fun getSmartSelectSuggestions(
int offset, file: PsiFile,
@NotNull CodeInsightUtils.ElementKind elementKind offset: Int,
) throws IntroduceRefactoringException { elementKind: CodeInsightUtils.ElementKind): List<KtElement> {
if (offset < 0) { if (offset < 0) {
return new ArrayList<KtElement>(); return ArrayList()
} }
PsiElement element = file.findElementAt(offset); var element: PsiElement? = file.findElementAt(offset) ?: return ArrayList()
if (element == null) { if (element is PsiWhiteSpace) {
return new ArrayList<KtElement>(); return getSmartSelectSuggestions(file, offset - 1, elementKind)
}
if (element instanceof PsiWhiteSpace) {
return getSmartSelectSuggestions(file, offset - 1, elementKind);
} }
List<KtElement> elements = new ArrayList<KtElement>(); val elements = ArrayList<KtElement>()
while (element != null && !(element instanceof KtBlockExpression && !(element.getParent() instanceof KtFunctionLiteral)) && while (element != null && !(element is KtBlockExpression && element.parent !is KtFunctionLiteral) &&
!(element instanceof KtNamedFunction) element !is KtNamedFunction
&& !(element instanceof KtClassBody)) { && element !is KtClassBody) {
boolean addElement = false; var addElement = false
boolean keepPrevious = true; var keepPrevious = true
if (element instanceof KtTypeElement) { if (element is KtTypeElement) {
addElement = elementKind == CodeInsightUtils.ElementKind.TYPE_ELEMENT; addElement = elementKind == CodeInsightUtils.ElementKind.TYPE_ELEMENT
if (!addElement) { if (!addElement) {
keepPrevious = false; keepPrevious = false
} }
} }
else if (element instanceof KtExpression && !(element instanceof KtStatementExpression)) { else if (element is KtExpression && element !is KtStatementExpression) {
addElement = elementKind == CodeInsightUtils.ElementKind.EXPRESSION; addElement = elementKind == CodeInsightUtils.ElementKind.EXPRESSION
if (addElement) { if (addElement) {
if (element instanceof KtParenthesizedExpression) { if (element is KtParenthesizedExpression) {
addElement = false; addElement = false
} }
else if (KtPsiUtil.isLabelIdentifierExpression(element)) { else if (KtPsiUtil.isLabelIdentifierExpression(element)) {
addElement = false; addElement = false
} }
else if (element.getParent() instanceof KtQualifiedExpression) { else if (element.parent is KtQualifiedExpression) {
KtQualifiedExpression qualifiedExpression = (KtQualifiedExpression) element.getParent(); val qualifiedExpression = element.parent as KtQualifiedExpression
if (qualifiedExpression.getReceiverExpression() != element) { if (qualifiedExpression.receiverExpression !== element) {
addElement = false; addElement = false
} }
} }
else if (element.getParent() instanceof KtCallElement else if (element.parent is KtCallElement
|| element.getParent() instanceof KtThisExpression || element.parent is KtThisExpression
|| PsiTreeUtil.getParentOfType(element, KtSuperExpression.class) != null) { || PsiTreeUtil.getParentOfType(element, KtSuperExpression::class.java) != null) {
addElement = false; addElement = false
} }
else if (element.getParent() instanceof KtOperationExpression) { else if (element.parent is KtOperationExpression) {
KtOperationExpression operationExpression = (KtOperationExpression) element.getParent(); val operationExpression = element.parent as KtOperationExpression
if (operationExpression.getOperationReference() == element) { if (operationExpression.operationReference === element) {
addElement = false; addElement = false
} }
} }
if (addElement) { if (addElement) {
KtExpression expression = (KtExpression)element; val bindingContext = element.analyze(BodyResolveMode.FULL)
BindingContext bindingContext = ResolutionUtils.analyze(expression, BodyResolveMode.FULL); val expressionType = bindingContext.getType(element)
KotlinType expressionType = bindingContext.getType(expression);
if (expressionType == null || KotlinBuiltIns.isUnit(expressionType)) { if (expressionType == null || KotlinBuiltIns.isUnit(expressionType)) {
addElement = false; addElement = false
} }
} }
} }
} }
if (addElement) { if (addElement) {
elements.add((KtElement) element); elements.add(element as KtElement)
} }
if (!keepPrevious) { if (!keepPrevious) {
elements.clear(); elements.clear()
} }
element = element.getParent(); element = element.parent
} }
return elements; return elements
} }
private static void smartSelectElement( @Throws(IntroduceRefactoringException::class)
@NotNull Editor editor, private fun smartSelectElement(
@NotNull final PsiFile file, editor: Editor,
final int offset, file: PsiFile,
boolean failOnEmptySuggestion, offset: Int,
@NotNull Collection<CodeInsightUtils.ElementKind> elementKinds, failOnEmptySuggestion: Boolean,
@NotNull final SelectElementCallback callback elementKinds: Collection<CodeInsightUtils.ElementKind>,
) throws IntroduceRefactoringException { callback: (PsiElement?) -> Unit) {
List<KtElement> elements = CollectionsKt.flatMap( val elements = elementKinds.flatMap { kind -> getSmartSelectSuggestions(file, offset, kind) }
elementKinds, if (elements.size == 0) {
new Function1<CodeInsightUtils.ElementKind, Iterable<? extends KtElement>>() { if (failOnEmptySuggestion)
@Override throw IntroduceRefactoringException(
public Iterable<? extends KtElement> invoke(CodeInsightUtils.ElementKind kind) { KotlinRefactoringBundle.message("cannot.refactor.not.expression"))
return getSmartSelectSuggestions(file, offset, kind); callback(null)
} return
}
if (elements.size == 1 || ApplicationManager.getApplication().isUnitTestMode) {
callback(elements[0])
return
}
val model = DefaultListModel<PsiElement>()
for (element in elements) {
model.addElement(element)
}
val highlighter = ScopeHighlighter(editor)
val list = JBList(model)
list.setCellRenderer(object : DefaultListCellRenderer() {
override fun getListCellRendererComponent(list: JList<*>, value: Any?, index: Int, isSelected: Boolean, cellHasFocus: Boolean): Component {
val rendererComponent = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus)
val element = value as KtElement?
if (element!!.isValid) {
text = getExpressionShortText(element)
} }
); return rendererComponent
if (elements.size() == 0) {
if (failOnEmptySuggestion) throw new IntroduceRefactoringException(
KotlinRefactoringBundle.message("cannot.refactor.not.expression"));
callback.run(null);
return;
}
if (elements.size() == 1 || ApplicationManager.getApplication().isUnitTestMode()) {
callback.run(elements.get(0));
return;
}
final DefaultListModel model = new DefaultListModel();
for (PsiElement element : elements) {
model.addElement(element);
}
final ScopeHighlighter highlighter = new ScopeHighlighter(editor);
final JList list = new JBList(model);
list.setCellRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(@NotNull JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
Component rendererComponent = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
KtElement element = (KtElement) value;
if (element.isValid()) {
setText(getExpressionShortText(element));
}
return rendererComponent;
} }
}); })
list.addListSelectionListener(new ListSelectionListener() { list.addListSelectionListener(ListSelectionListener {
@Override highlighter.dropHighlight()
public void valueChanged(@NotNull ListSelectionEvent e) { val selectedIndex = list.getSelectedIndex()
highlighter.dropHighlight(); if (selectedIndex < 0) return@ListSelectionListener
int selectedIndex = list.getSelectedIndex(); val toExtract = ArrayList<PsiElement>()
if (selectedIndex < 0) return; toExtract.add(model.get(selectedIndex))
KtElement expression = (KtElement) model.get(selectedIndex); highlighter.highlight(model.get(selectedIndex), toExtract)
List<PsiElement> toExtract = new ArrayList<PsiElement>(); })
toExtract.add(expression);
highlighter.highlight(expression, toExtract);
}
});
String title = "Elements"; var title = "Elements"
if (elementKinds.size() == 1) { if (elementKinds.size == 1) {
switch (elementKinds.iterator().next()) { when (elementKinds.iterator().next()) {
case EXPRESSION: CodeInsightUtils.ElementKind.EXPRESSION -> title = "Expressions"
title = "Expressions"; CodeInsightUtils.ElementKind.TYPE_ELEMENT, CodeInsightUtils.ElementKind.TYPE_CONSTRUCTOR -> title = "Types"
break;
case TYPE_ELEMENT:
case TYPE_CONSTRUCTOR:
title = "Types";
break;
} }
} }
JBPopupFactory.getInstance().createListPopupBuilder(list). JBPopupFactory.getInstance().createListPopupBuilder(list).setTitle(title).setMovable(false).setResizable(false).setRequestFocus(true).setItemChoosenCallback { callback(list.getSelectedValue() as KtElement) }.addListener(object : JBPopupAdapter() {
setTitle(title).setMovable(false).setResizable(false). override fun onClosed(event: LightweightWindowEvent?) {
setRequestFocus(true).setItemChoosenCallback(new Runnable() { highlighter.dropHighlight()
@Override
public void run() {
callback.run((KtElement) list.getSelectedValue());
} }
}).addListener(new JBPopupAdapter() { }).createPopup().showInBestPositionFor(editor)
@Override
public void onClosed(LightweightWindowEvent event) {
highlighter.dropHighlight();
}
}).createPopup().showInBestPositionFor(editor);
} }
public static String getExpressionShortText(@NotNull KtElement element) { fun getExpressionShortText(element: KtElement): String {
String text = ElementRenderingUtilsKt.renderTrimmed(element); val text = element.renderTrimmed()
int firstNewLinePos = text.indexOf('\n'); val firstNewLinePos = text.indexOf('\n')
String trimmedText = text.substring(0, firstNewLinePos != -1 ? firstNewLinePos : Math.min(100, text.length())); var trimmedText = text.substring(0, if (firstNewLinePos != -1) firstNewLinePos else Math.min(100, text.length))
if (trimmedText.length() != text.length()) trimmedText += " ..."; if (trimmedText.length != text.length) trimmedText += " ..."
return trimmedText; return trimmedText
} }
@Nullable @Throws(IntroduceRefactoringException::class)
private static PsiElement findElement( private fun findElement(
@NotNull KtFile file, file: KtFile,
int startOffset, startOffset: Int,
int endOffset, endOffset: Int,
boolean failOnNoExpression, failOnNoExpression: Boolean,
@NotNull CodeInsightUtils.ElementKind elementKind elementKind: CodeInsightUtils.ElementKind): PsiElement? {
) throws IntroduceRefactoringException { var element = CodeInsightUtils.findElement(file, startOffset, endOffset, elementKind)
PsiElement element = CodeInsightUtils.findElement(file, startOffset, endOffset, elementKind);
if (element == null && elementKind == CodeInsightUtils.ElementKind.EXPRESSION) { if (element == null && elementKind == CodeInsightUtils.ElementKind.EXPRESSION) {
element = IntroduceUtilKt.findExpressionOrStringFragment(file, startOffset, endOffset); element = findExpressionOrStringFragment(file, startOffset, endOffset)
} }
if (element == null) { if (element == null) {
//todo: if it's infix expression => add (), then commit document then return new created expression //todo: if it's infix expression => add (), then commit document then return new created expression
if (failOnNoExpression) { if (failOnNoExpression) {
throw new IntroduceRefactoringException(KotlinRefactoringBundle.message("cannot.refactor.not.expression")); throw IntroduceRefactoringException(KotlinRefactoringBundle.message("cannot.refactor.not.expression"))
} }
return null; return null
} }
return element; return element
} }
public static class IntroduceRefactoringException extends RuntimeException { class IntroduceRefactoringException(private val myMessage: String) : RuntimeException() {
private final String myMessage; override val message: String?
get() = myMessage
public IntroduceRefactoringException(String message) {
myMessage = message;
}
@Override
public String getMessage() {
return myMessage;
}
} }
} }
@@ -110,7 +110,7 @@ class KotlinOverridingDialog extends DialogWrapper {
assert element instanceof PsiMethod assert element instanceof PsiMethod
: "Method accepts only kotlin functions/properties and java methods, but '" + element.getText() + "' was found"; : "Method accepts only kotlin functions/properties and java methods, but '" + element.getText() + "' was found";
return KotlinRefactoringUtil2.formatPsiMethod((PsiMethod) element, true, false); return KotlinRefactoringUtil2.INSTANCE.formatPsiMethod((PsiMethod) element, true, false);
} }
@Override @Override
@@ -18,8 +18,9 @@ package org.jetbrains.kotlin.idea;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.testFramework.LightCodeInsightTestCase; import com.intellij.testFramework.LightCodeInsightTestCase;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils; import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils;
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil2; import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil2;
import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.psi.KtFile;
@@ -34,15 +35,16 @@ public abstract class AbstractExpressionSelectionTest extends LightCodeInsightTe
final String expectedExpression = KotlinTestUtils.getLastCommentInFile((KtFile) getFile()); final String expectedExpression = KotlinTestUtils.getLastCommentInFile((KtFile) getFile());
try { try {
KotlinRefactoringUtil2.selectElement( KotlinRefactoringUtil2.INSTANCE.selectElement(
getEditor(), getEditor(),
(KtFile) getFile(), (KtFile) getFile(),
Collections.singletonList(CodeInsightUtils.ElementKind.EXPRESSION), Collections.singletonList(CodeInsightUtils.ElementKind.EXPRESSION),
new KotlinRefactoringUtil2.SelectElementCallback() { new Function1<PsiElement, Unit>() {
@Override @Override
public void run(@Nullable PsiElement element) { public Unit invoke(PsiElement element) {
assertNotNull("Selected expression mustn't be null", element); assertNotNull("Selected expression mustn't be null", element);
assertEquals(expectedExpression, element.getText()); assertEquals(expectedExpression, element.getText());
return Unit.INSTANCE;
} }
} }
); );
@@ -34,12 +34,12 @@ public abstract class AbstractSmartSelectionTest extends LightCodeInsightTestCas
configureByFile(path); configureByFile(path);
String expectedResultText = KotlinTestUtils.getLastCommentInFile((KtFile) getFile()); String expectedResultText = KotlinTestUtils.getLastCommentInFile((KtFile) getFile());
List<KtElement> elements = KotlinRefactoringUtil2.getSmartSelectSuggestions( List<KtElement> elements = KotlinRefactoringUtil2.INSTANCE.getSmartSelectSuggestions(
getFile(), getEditor().getCaretModel().getOffset(), CodeInsightUtils.ElementKind.EXPRESSION); getFile(), getEditor().getCaretModel().getOffset(), CodeInsightUtils.ElementKind.EXPRESSION);
List<String> textualExpressions = new ArrayList<String>(); List<String> textualExpressions = new ArrayList<String>();
for (KtElement element : elements) { for (KtElement element : elements) {
textualExpressions.add(KotlinRefactoringUtil2.getExpressionShortText(element)); textualExpressions.add(KotlinRefactoringUtil2.INSTANCE.getExpressionShortText(element));
} }
assertEquals(expectedResultText, StringUtil.join(textualExpressions, "\n")); assertEquals(expectedResultText, StringUtil.join(textualExpressions, "\n"));
} }
@@ -17,7 +17,6 @@
package org.jetbrains.kotlin.idea.refactoring.nameSuggester package org.jetbrains.kotlin.idea.refactoring.nameSuggester
import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
@@ -30,6 +29,7 @@ import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.test.InTextDirectivesUtils import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.test.KotlinTestUtils
import java.lang.AssertionError
class KotlinNameSuggesterTest : LightCodeInsightFixtureTestCase() { class KotlinNameSuggesterTest : LightCodeInsightFixtureTestCase() {
fun testArrayList() { doTest() } fun testArrayList() { doTest() }
@@ -90,19 +90,17 @@ class KotlinNameSuggesterTest : LightCodeInsightFixtureTestCase() {
if (withRuntime) { if (withRuntime) {
ConfigLibraryUtil.configureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk()) ConfigLibraryUtil.configureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk())
} }
KotlinRefactoringUtil2.selectElement(myFixture.editor, file, listOf(CodeInsightUtils.ElementKind.EXPRESSION), object : KotlinRefactoringUtil2.SelectElementCallback { KotlinRefactoringUtil2.selectElement(myFixture.editor, file, listOf(CodeInsightUtils.ElementKind.EXPRESSION)) {
override fun run(element: PsiElement?) { val names = KotlinNameSuggester
val names = KotlinNameSuggester .suggestNamesByExpressionAndType(it as KtExpression,
.suggestNamesByExpressionAndType(element as KtExpression, null,
null, it.analyze(BodyResolveMode.PARTIAL),
element.analyze(BodyResolveMode.PARTIAL), { true },
{ true }, "value")
"value") .sorted()
.sorted() val result = StringUtil.join(names, "\n").trim()
val result = StringUtil.join(names, "\n").trim() assertEquals(expectedResultText, result)
assertEquals(expectedResultText, result) }
}
})
} }
catch (e: KotlinRefactoringUtil2.IntroduceRefactoringException) { catch (e: KotlinRefactoringUtil2.IntroduceRefactoringException) {
throw AssertionError("Failed to find expression: " + e.message) throw AssertionError("Failed to find expression: " + e.message)