From b6a671c45ff196d1c52e26433af6ba9fc6c14e90 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Wed, 31 Aug 2016 14:46:17 +0300 Subject: [PATCH] Convert to Kotlin: KotlinRefactoringUtil2 (replace object with top-level declarations) --- .../unwrap/KotlinUnwrapRemoveBase.java | 4 +- .../KotlinFindUsagesHandlerFactory.kt | 5 +- .../dialogs/KotlinFindClassUsagesDialog.java | 4 +- .../KotlinFindFunctionUsagesDialog.java | 4 +- .../refactoring/KotlinRefactoringUtil2.kt | 773 +++++++++--------- .../refactoring/introduce/introduceUtil.kt | 4 +- .../KotlinIntroduceVariableHandler.kt | 4 +- .../safeDelete/KotlinOverridingDialog.java | 4 +- .../safeDelete/KotlinSafeDeleteProcessor.kt | 19 +- .../idea/AbstractExpressionSelectionTest.java | 7 +- .../idea/AbstractSmartSelectionTest.java | 6 +- .../introduce/AbstractExtractionTest.kt | 4 +- .../nameSuggester/KotlinNameSuggesterTest.kt | 7 +- 13 files changed, 424 insertions(+), 421 deletions(-) diff --git a/idea/src/org/jetbrains/kotlin/idea/codeInsight/unwrap/KotlinUnwrapRemoveBase.java b/idea/src/org/jetbrains/kotlin/idea/codeInsight/unwrap/KotlinUnwrapRemoveBase.java index 87393706ce6..f8420f28b1b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/codeInsight/unwrap/KotlinUnwrapRemoveBase.java +++ b/idea/src/org/jetbrains/kotlin/idea/codeInsight/unwrap/KotlinUnwrapRemoveBase.java @@ -22,7 +22,7 @@ import com.intellij.psi.PsiWhiteSpace; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.idea.KotlinBundle; -import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil2; +import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil2Kt; import org.jetbrains.kotlin.psi.KtBlockExpression; import org.jetbrains.kotlin.psi.KtElement; import org.jetbrains.kotlin.psi.KtExpression; @@ -44,7 +44,7 @@ public abstract class KotlinUnwrapRemoveBase extends AbstractUnwrapper$s" else s +fun wrapOrSkip(s: String, inCode: Boolean) = if (inCode) "$s" else s - fun formatClassDescriptor(classDescriptor: DeclarationDescriptor) = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.render(classDescriptor) +fun formatClassDescriptor(classDescriptor: DeclarationDescriptor) = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.render(classDescriptor) - fun formatPsiClass( - psiClass: PsiClass, - markAsJava: Boolean, - inCode: Boolean - ): String { - var description: String +fun formatPsiClass( + psiClass: PsiClass, + markAsJava: Boolean, + inCode: Boolean +): String { + var description: String - val kind = if (psiClass.isInterface) "interface " else "class " - description = kind + PsiFormatUtil.formatClass( - psiClass, - PsiFormatUtilBase.SHOW_CONTAINING_CLASS or PsiFormatUtilBase.SHOW_NAME or PsiFormatUtilBase.SHOW_PARAMETERS or PsiFormatUtilBase.SHOW_TYPE - ) - description = wrapOrSkip(description, inCode) + val kind = if (psiClass.isInterface) "interface " else "class " + description = kind + PsiFormatUtil.formatClass( + psiClass, + PsiFormatUtilBase.SHOW_CONTAINING_CLASS or PsiFormatUtilBase.SHOW_NAME or PsiFormatUtilBase.SHOW_PARAMETERS or PsiFormatUtilBase.SHOW_TYPE + ) + description = wrapOrSkip(description, inCode) - return if (markAsJava) "[Java] $description" else description + return if (markAsJava) "[Java] $description" else description +} + +fun checkSuperMethods( + declaration: KtDeclaration, + ignore: Collection?, + actionStringKey: String +): List { + val declarationDescriptor = declaration.resolveToDescriptor() as CallableDescriptor + + if (declarationDescriptor is LocalVariableDescriptor) return listOf(declaration) + + val project = declaration.project + val overriddenElementsToDescriptor = HashMap() + for (overriddenDescriptor in DescriptorUtils.getAllOverriddenDescriptors(declarationDescriptor)) { + val overriddenDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, overriddenDescriptor) ?: continue + if (overriddenDeclaration is KtNamedFunction || overriddenDeclaration is KtProperty || overriddenDeclaration is PsiMethod) { + overriddenElementsToDescriptor[overriddenDeclaration] = overriddenDescriptor + } + } + if (ignore != null) { + overriddenElementsToDescriptor.keys.removeAll(ignore) } - fun checkSuperMethods( - declaration: KtDeclaration, - ignore: Collection?, - actionStringKey: String - ): List { - val declarationDescriptor = declaration.resolveToDescriptor() as CallableDescriptor + if (overriddenElementsToDescriptor.isEmpty()) return listOf(declaration) - if (declarationDescriptor is LocalVariableDescriptor) return listOf(declaration) + return askUserForMethodsToSearch(declaration, declarationDescriptor, overriddenElementsToDescriptor, actionStringKey) +} - val project = declaration.project - val overriddenElementsToDescriptor = HashMap() - for (overriddenDescriptor in DescriptorUtils.getAllOverriddenDescriptors(declarationDescriptor)) { - val overriddenDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, overriddenDescriptor) ?: continue - if (overriddenDeclaration is KtNamedFunction || overriddenDeclaration is KtProperty || overriddenDeclaration is PsiMethod) { - overriddenElementsToDescriptor[overriddenDeclaration] = overriddenDescriptor +private fun askUserForMethodsToSearch( + declaration: KtDeclaration, + declarationDescriptor: CallableDescriptor, + overriddenElementsToDescriptor: Map, + actionStringKey: String +): List { + if (ApplicationManager.getApplication().isUnitTestMode) return overriddenElementsToDescriptor.keys.toList() + + val superClassDescriptions = getClassDescriptions(overriddenElementsToDescriptor) + + val message = KotlinBundle.message( + "x.overrides.y.in.class.list", + DescriptorRenderer.COMPACT_WITH_SHORT_TYPES.render(declarationDescriptor), + "\n${superClassDescriptions.joinToString(separator = "")}", + KotlinBundle.message(actionStringKey) + ) + + val exitCode = Messages.showYesNoCancelDialog(declaration.project, message, IdeBundle.message("title.warning"), Messages.getQuestionIcon()) + when (exitCode) { + Messages.YES -> return overriddenElementsToDescriptor.keys.toList() + Messages.NO -> return listOf(declaration) + else -> return emptyList() + } +} + +private fun getClassDescriptions(overriddenElementsToDescriptor: Map): List { + return overriddenElementsToDescriptor.entries.map { entry -> + val (element, descriptor) = entry + val description = when (element) { + is KtNamedFunction, is KtProperty -> formatClassDescriptor(descriptor.containingDeclaration) + is PsiMethod -> { + val psiClass = element.containingClass ?: error("Invalid element: ${element.getText()}") + formatPsiClass(psiClass, true, false) + } + else -> error("Unexpected element: ${element.getElementTextWithContext()}") + } + " $description\n" + } +} + +fun formatClass(classDescriptor: DeclarationDescriptor, inCode: Boolean): String { + val element = DescriptorToSourceUtils.descriptorToDeclaration(classDescriptor) + return if (element is PsiClass) { + formatPsiClass(element, false, inCode) + } + else { + wrapOrSkip(formatClassDescriptor(classDescriptor), inCode) + } +} + +fun formatFunction(functionDescriptor: DeclarationDescriptor, inCode: Boolean): String { + val element = DescriptorToSourceUtils.descriptorToDeclaration(functionDescriptor) + return if (element is PsiMethod) { + formatPsiMethod(element, false, inCode) + } + else { + wrapOrSkip(formatFunctionDescriptor(functionDescriptor), inCode) + } +} + +private fun formatFunctionDescriptor(functionDescriptor: DeclarationDescriptor) = 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 + return if (originalDeclaration is KtDeclaration) { + formatFunctionDescriptor(originalDeclaration.resolveToDescriptor()) + } + else { + formatPsiMethod(method, false, false) + } +} + +fun formatClass(classOrObject: KtClassOrObject) = formatClassDescriptor(classOrObject.resolveToDescriptor() as ClassDescriptor) + +fun checkParametersInMethodHierarchy(parameter: PsiParameter): Collection? { + val method = parameter.declarationScope as PsiMethod + + val parametersToDelete = collectParametersHierarchy(method, parameter) + if (parametersToDelete.size <= 1 || 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()) + return if (exitCode == Messages.OK) parametersToDelete else null +} + +// TODO: generalize breadth-first search +private fun collectParametersHierarchy(method: PsiMethod, parameter: PsiParameter): Set { + val queue = ArrayDeque() + val visited = HashSet() + val parametersToDelete = HashSet() + + queue.add(method) + while (!queue.isEmpty()) { + val currentMethod = queue.poll() + + visited += currentMethod + addParameter(currentMethod, parametersToDelete, parameter) + + currentMethod.findSuperMethods(true) + .filter { it !in visited } + .forEach { queue.offer(it) } + OverridingMethodsSearch.search(currentMethod) + .filter { it !in visited } + .forEach { queue.offer(it) } + } + return parametersToDelete +} + +private fun addParameter(method: PsiMethod, result: MutableSet, parameter: PsiParameter) { + val parameterIndex = parameter.unwrapped!!.parameterIndex() + + if (method is KtLightMethod) { + val declaration = method.kotlinOrigin + if (declaration is KtFunction) { + result.add(declaration.valueParameters[parameterIndex]) + } + } + else { + result.add(method.parameterList.parameters[parameterIndex]) + } +} + +@Throws(IntroduceRefactoringException::class) +fun selectElement( + editor: Editor, + file: KtFile, + elementKinds: Collection, + callback: (PsiElement?) -> Unit +) = selectElement(editor, file, true, elementKinds, callback) + +@Throws(IntroduceRefactoringException::class) +fun selectElement(editor: Editor, + file: KtFile, + failOnEmptySuggestion: Boolean, + elementKinds: Collection, + callback: (PsiElement?) -> Unit) { + if (editor.selectionModel.hasSelection()) { + var selectionStart = editor.selectionModel.selectionStart + var selectionEnd = editor.selectionModel.selectionEnd + + var firstElement: PsiElement = file.findElementAt(selectionStart)!! + var lastElement: PsiElement = file.findElementAt(selectionEnd - 1)!! + + if (PsiTreeUtil.getParentOfType(firstElement, KtLiteralStringTemplateEntry::class.java, KtEscapeStringTemplateEntry::class.java) == null + && PsiTreeUtil.getParentOfType(lastElement, KtLiteralStringTemplateEntry::class.java, KtEscapeStringTemplateEntry::class.java) == null) { + firstElement = firstElement.getNextSiblingIgnoringWhitespaceAndComments(true)!! + lastElement = lastElement.getPrevSiblingIgnoringWhitespaceAndComments(true)!! + selectionStart = firstElement.textRange.startOffset + selectionEnd = lastElement.textRange.endOffset + } + + val element = elementKinds.asSequence() + .mapNotNull { findElement(file, selectionStart, selectionEnd, failOnEmptySuggestion, it) } + .firstOrNull() + callback(element) + } + else { + val offset = editor.caretModel.offset + smartSelectElement(editor, file, offset, failOnEmptySuggestion, elementKinds, callback) + } +} + +@Throws(IntroduceRefactoringException::class) +fun getSmartSelectSuggestions( + file: PsiFile, + offset: Int, + elementKind: CodeInsightUtils.ElementKind +): List { + if (offset < 0) return emptyList() + + var element: PsiElement? = file.findElementAt(offset) ?: return emptyList() + + if (element is PsiWhiteSpace) return getSmartSelectSuggestions(file, offset - 1, elementKind) + + val elements = ArrayList() + while (element != null && !(element is KtBlockExpression && element.parent !is KtFunctionLiteral) && + element !is KtNamedFunction + && element !is KtClassBody) { + var addElement = false + var keepPrevious = true + + if (element is KtTypeElement) { + addElement = elementKind == CodeInsightUtils.ElementKind.TYPE_ELEMENT + if (!addElement) { + keepPrevious = false } } - if (ignore != null) { - overriddenElementsToDescriptor.keys.removeAll(ignore) - } - - if (overriddenElementsToDescriptor.isEmpty()) return listOf(declaration) - - return askUserForMethodsToSearch(declaration, declarationDescriptor, overriddenElementsToDescriptor, actionStringKey) - } - - private fun askUserForMethodsToSearch( - declaration: KtDeclaration, - declarationDescriptor: CallableDescriptor, - overriddenElementsToDescriptor: Map, - actionStringKey: String - ): List { - if (ApplicationManager.getApplication().isUnitTestMode) return overriddenElementsToDescriptor.keys.toList() - - val superClassDescriptions = getClassDescriptions(overriddenElementsToDescriptor) - - val message = KotlinBundle.message( - "x.overrides.y.in.class.list", - DescriptorRenderer.COMPACT_WITH_SHORT_TYPES.render(declarationDescriptor), - "\n${superClassDescriptions.joinToString(separator = "")}", - KotlinBundle.message(actionStringKey) - ) - - val exitCode = Messages.showYesNoCancelDialog(declaration.project, message, IdeBundle.message("title.warning"), Messages.getQuestionIcon()) - when (exitCode) { - Messages.YES -> return overriddenElementsToDescriptor.keys.toList() - Messages.NO -> return listOf(declaration) - else -> return emptyList() - } - } - - private fun getClassDescriptions(overriddenElementsToDescriptor: Map): List { - return overriddenElementsToDescriptor.entries.map { entry -> - val (element, descriptor) = entry - val description = when (element) { - is KtNamedFunction, is KtProperty -> formatClassDescriptor(descriptor.containingDeclaration) - is PsiMethod -> { - val psiClass = element.containingClass ?: error("Invalid element: ${element.getText()}") - formatPsiClass(psiClass, true, false) - } - else -> error("Unexpected element: ${element.getElementTextWithContext()}") - } - " $description\n" - } - } - - fun formatClass(classDescriptor: DeclarationDescriptor, inCode: Boolean): String { - val element = DescriptorToSourceUtils.descriptorToDeclaration(classDescriptor) - return if (element is PsiClass) { - formatPsiClass(element, false, inCode) - } - else { - wrapOrSkip(formatClassDescriptor(classDescriptor), inCode) - } - } - - fun formatFunction(functionDescriptor: DeclarationDescriptor, inCode: Boolean): String { - val element = DescriptorToSourceUtils.descriptorToDeclaration(functionDescriptor) - return if (element is PsiMethod) { - formatPsiMethod(element, false, inCode) - } - else { - wrapOrSkip(formatFunctionDescriptor(functionDescriptor), inCode) - } - } - - private fun formatFunctionDescriptor(functionDescriptor: DeclarationDescriptor) = 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 - return if (originalDeclaration is KtDeclaration) { - formatFunctionDescriptor(originalDeclaration.resolveToDescriptor()) - } - else { - formatPsiMethod(method, false, false) - } - } - - fun formatClass(classOrObject: KtClassOrObject) = formatClassDescriptor(classOrObject.resolveToDescriptor() as ClassDescriptor) - - fun checkParametersInMethodHierarchy(parameter: PsiParameter): Collection? { - val method = parameter.declarationScope as PsiMethod - - val parametersToDelete = collectParametersHierarchy(method, parameter) - if (parametersToDelete.size <= 1 || 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()) - return if (exitCode == Messages.OK) parametersToDelete else null - } - - // TODO: generalize breadth-first search - private fun collectParametersHierarchy(method: PsiMethod, parameter: PsiParameter): Set { - val queue = ArrayDeque() - val visited = HashSet() - val parametersToDelete = HashSet() - - queue.add(method) - while (!queue.isEmpty()) { - val currentMethod = queue.poll() - - visited += currentMethod - addParameter(currentMethod, parametersToDelete, parameter) - - currentMethod.findSuperMethods(true) - .filter { it !in visited } - .forEach { queue.offer(it) } - OverridingMethodsSearch.search(currentMethod) - .filter { it !in visited } - .forEach { queue.offer(it) } - } - return parametersToDelete - } - - private fun addParameter(method: PsiMethod, result: MutableSet, parameter: PsiParameter) { - val parameterIndex = parameter.unwrapped!!.parameterIndex() - - if (method is KtLightMethod) { - val declaration = method.kotlinOrigin - if (declaration is KtFunction) { - result.add(declaration.valueParameters[parameterIndex]) - } - } - else { - result.add(method.parameterList.parameters[parameterIndex]) - } - } - - @Throws(IntroduceRefactoringException::class) - fun selectElement( - editor: Editor, - file: KtFile, - elementKinds: Collection, - callback: (PsiElement?) -> Unit - ) = selectElement(editor, file, true, elementKinds, callback) - - @Throws(IntroduceRefactoringException::class) - fun selectElement(editor: Editor, - file: KtFile, - failOnEmptySuggestion: Boolean, - elementKinds: Collection, - callback: (PsiElement?) -> Unit) { - if (editor.selectionModel.hasSelection()) { - var selectionStart = editor.selectionModel.selectionStart - var selectionEnd = editor.selectionModel.selectionEnd - - var firstElement: PsiElement = file.findElementAt(selectionStart)!! - var lastElement: PsiElement = file.findElementAt(selectionEnd - 1)!! - - if (PsiTreeUtil.getParentOfType(firstElement, KtLiteralStringTemplateEntry::class.java, KtEscapeStringTemplateEntry::class.java) == null - && PsiTreeUtil.getParentOfType(lastElement, KtLiteralStringTemplateEntry::class.java, KtEscapeStringTemplateEntry::class.java) == null) { - firstElement = firstElement.getNextSiblingIgnoringWhitespaceAndComments(true)!! - lastElement = lastElement.getPrevSiblingIgnoringWhitespaceAndComments(true)!! - selectionStart = firstElement.textRange.startOffset - selectionEnd = lastElement.textRange.endOffset - } - - val element = elementKinds.asSequence() - .mapNotNull { findElement(file, selectionStart, selectionEnd, failOnEmptySuggestion, it) } - .firstOrNull() - callback(element) - } - else { - val offset = editor.caretModel.offset - smartSelectElement(editor, file, offset, failOnEmptySuggestion, elementKinds, callback) - } - } - - @Throws(IntroduceRefactoringException::class) - fun getSmartSelectSuggestions( - file: PsiFile, - offset: Int, - elementKind: CodeInsightUtils.ElementKind - ): List { - if (offset < 0) return emptyList() - - var element: PsiElement? = file.findElementAt(offset) ?: return emptyList() - - if (element is PsiWhiteSpace) return getSmartSelectSuggestions(file, offset - 1, elementKind) - - val elements = ArrayList() - while (element != null && !(element is KtBlockExpression && element.parent !is KtFunctionLiteral) && - element !is KtNamedFunction - && element !is KtClassBody) { - var addElement = false - var keepPrevious = true - - if (element is KtTypeElement) { - addElement = elementKind == CodeInsightUtils.ElementKind.TYPE_ELEMENT - if (!addElement) { - keepPrevious = false - } - } - else if (element is KtExpression && element !is KtStatementExpression) { - addElement = elementKind == CodeInsightUtils.ElementKind.EXPRESSION - - if (addElement) { - if (element is KtParenthesizedExpression) { - addElement = false - } - else if (KtPsiUtil.isLabelIdentifierExpression(element)) { - addElement = false - } - else if (element.parent is KtQualifiedExpression) { - val qualifiedExpression = element.parent as KtQualifiedExpression - if (qualifiedExpression.receiverExpression !== element) { - addElement = false - } - } - else if (element.parent is KtCallElement - || element.parent is KtThisExpression - || PsiTreeUtil.getParentOfType(element, KtSuperExpression::class.java) != null) { - addElement = false - } - else if (element.parent is KtOperationExpression) { - val operationExpression = element.parent as KtOperationExpression - if (operationExpression.operationReference === element) { - addElement = false - } - } - if (addElement) { - val bindingContext = element.analyze(BodyResolveMode.FULL) - val expressionType = bindingContext.getType(element) - if (expressionType == null || KotlinBuiltIns.isUnit(expressionType)) { - addElement = false - } - } - } - } + else if (element is KtExpression && element !is KtStatementExpression) { + addElement = elementKind == CodeInsightUtils.ElementKind.EXPRESSION if (addElement) { - elements.add(element as KtElement) - } - - if (!keepPrevious) { - elements.clear() - } - - element = element.parent - } - return elements - } - - @Throws(IntroduceRefactoringException::class) - private fun smartSelectElement( - editor: Editor, - file: PsiFile, - offset: Int, - failOnEmptySuggestion: Boolean, - elementKinds: Collection, - callback: (PsiElement?) -> Unit - ) { - val elements = elementKinds.flatMap { getSmartSelectSuggestions(file, offset, it) } - if (elements.isEmpty()) { - if (failOnEmptySuggestion) throw IntroduceRefactoringException(KotlinRefactoringBundle.message("cannot.refactor.not.expression")) - callback(null) - return - } - - if (elements.size == 1 || ApplicationManager.getApplication().isUnitTestMode) { - callback(elements.first()) - return - } - - val model = DefaultListModel() - elements.forEach { model.addElement(it) } - - val highlighter = ScopeHighlighter(editor) - - val list = JBList(model) - - list.cellRenderer = 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) + if (element is KtParenthesizedExpression) { + addElement = false + } + else if (KtPsiUtil.isLabelIdentifierExpression(element)) { + addElement = false + } + else if (element.parent is KtQualifiedExpression) { + val qualifiedExpression = element.parent as KtQualifiedExpression + if (qualifiedExpression.receiverExpression !== element) { + addElement = false + } + } + else if (element.parent is KtCallElement + || element.parent is KtThisExpression + || PsiTreeUtil.getParentOfType(element, KtSuperExpression::class.java) != null) { + addElement = false + } + else if (element.parent is KtOperationExpression) { + val operationExpression = element.parent as KtOperationExpression + if (operationExpression.operationReference === element) { + addElement = false + } + } + if (addElement) { + val bindingContext = element.analyze(BodyResolveMode.FULL) + val expressionType = bindingContext.getType(element) + if (expressionType == null || KotlinBuiltIns.isUnit(expressionType)) { + addElement = false + } } - return rendererComponent } } - list.addListSelectionListener { - highlighter.dropHighlight() - val selectedIndex = list.selectedIndex - if (selectedIndex < 0) return@addListSelectionListener - val toExtract = ArrayList() - toExtract.add(model.get(selectedIndex)) - highlighter.highlight(model.get(selectedIndex), toExtract) + if (addElement) { + elements.add(element as KtElement) } - var title = "Elements" - if (elementKinds.size == 1) { - when (elementKinds.iterator().next()) { - CodeInsightUtils.ElementKind.EXPRESSION -> title = "Expressions" - CodeInsightUtils.ElementKind.TYPE_ELEMENT, CodeInsightUtils.ElementKind.TYPE_CONSTRUCTOR -> title = "Types" - } + if (!keepPrevious) { + elements.clear() } - JBPopupFactory.getInstance() - .createListPopupBuilder(list) - .setTitle(title) - .setMovable(false) - .setResizable(false) - .setRequestFocus(true) - .setItemChoosenCallback { callback(list.selectedValue as KtElement) } - .addListener( - object : JBPopupAdapter() { - override fun onClosed(event: LightweightWindowEvent?) { - highlighter.dropHighlight() - } - } - ) - .createPopup() - .showInBestPositionFor(editor) - + element = element.parent } - - fun getExpressionShortText(element: KtElement): String { - val text = element.renderTrimmed() - val firstNewLinePos = text.indexOf('\n') - var trimmedText = text.substring(0, if (firstNewLinePos != -1) firstNewLinePos else Math.min(100, text.length)) - if (trimmedText.length != text.length) trimmedText += " ..." - return trimmedText - } - - @Throws(IntroduceRefactoringException::class) - private fun findElement( - file: KtFile, - startOffset: Int, - endOffset: Int, - failOnNoExpression: Boolean, - elementKind: CodeInsightUtils.ElementKind - ): PsiElement? { - var element = CodeInsightUtils.findElement(file, startOffset, endOffset, elementKind) - if (element == null && elementKind == CodeInsightUtils.ElementKind.EXPRESSION) { - element = findExpressionOrStringFragment(file, startOffset, endOffset) - } - if (element == null) { - //todo: if it's infix expression => add (), then commit document then return new created expression - - if (failOnNoExpression) { - throw IntroduceRefactoringException(KotlinRefactoringBundle.message("cannot.refactor.not.expression")) - } - return null - } - return element - } - - class IntroduceRefactoringException(message: String) : RuntimeException(message) + return elements } + +@Throws(IntroduceRefactoringException::class) +private fun smartSelectElement( + editor: Editor, + file: PsiFile, + offset: Int, + failOnEmptySuggestion: Boolean, + elementKinds: Collection, + callback: (PsiElement?) -> Unit +) { + val elements = elementKinds.flatMap { getSmartSelectSuggestions(file, offset, it) } + if (elements.isEmpty()) { + if (failOnEmptySuggestion) throw IntroduceRefactoringException(KotlinRefactoringBundle.message("cannot.refactor.not.expression")) + callback(null) + return + } + + if (elements.size == 1 || ApplicationManager.getApplication().isUnitTestMode) { + callback(elements.first()) + return + } + + val model = DefaultListModel() + elements.forEach { model.addElement(it) } + + val highlighter = ScopeHighlighter(editor) + + val list = JBList(model) + + list.cellRenderer = 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 + } + } + + list.addListSelectionListener { + highlighter.dropHighlight() + val selectedIndex = list.selectedIndex + if (selectedIndex < 0) return@addListSelectionListener + val toExtract = ArrayList() + toExtract.add(model.get(selectedIndex)) + highlighter.highlight(model.get(selectedIndex), toExtract) + } + + var title = "Elements" + if (elementKinds.size == 1) { + when (elementKinds.iterator().next()) { + CodeInsightUtils.ElementKind.EXPRESSION -> title = "Expressions" + CodeInsightUtils.ElementKind.TYPE_ELEMENT, CodeInsightUtils.ElementKind.TYPE_CONSTRUCTOR -> title = "Types" + } + } + + JBPopupFactory.getInstance() + .createListPopupBuilder(list) + .setTitle(title) + .setMovable(false) + .setResizable(false) + .setRequestFocus(true) + .setItemChoosenCallback { callback(list.selectedValue as KtElement) } + .addListener( + object : JBPopupAdapter() { + override fun onClosed(event: LightweightWindowEvent?) { + highlighter.dropHighlight() + } + } + ) + .createPopup() + .showInBestPositionFor(editor) +} + +fun getExpressionShortText(element: KtElement): String { + val text = element.renderTrimmed() + val firstNewLinePos = text.indexOf('\n') + var trimmedText = text.substring(0, if (firstNewLinePos != -1) firstNewLinePos else Math.min(100, text.length)) + if (trimmedText.length != text.length) trimmedText += " ..." + return trimmedText +} + +@Throws(IntroduceRefactoringException::class) +private fun findElement( + file: KtFile, + startOffset: Int, + endOffset: Int, + failOnNoExpression: Boolean, + elementKind: CodeInsightUtils.ElementKind +): PsiElement? { + var element = CodeInsightUtils.findElement(file, startOffset, endOffset, elementKind) + if (element == null && elementKind == CodeInsightUtils.ElementKind.EXPRESSION) { + element = findExpressionOrStringFragment(file, startOffset, endOffset) + } + if (element == null) { + //todo: if it's infix expression => add (), then commit document then return new created expression + + if (failOnNoExpression) { + throw IntroduceRefactoringException(KotlinRefactoringBundle.message("cannot.refactor.not.expression")) + } + return null + } + return element +} + +class IntroduceRefactoringException(message: String) : RuntimeException(message) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceUtil.kt index a9c6e45d9b7..15f14563647 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceUtil.kt @@ -27,7 +27,7 @@ import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils import org.jetbrains.kotlin.idea.refactoring.chooseContainerElementIfNecessary import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringBundle -import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil2 +import org.jetbrains.kotlin.idea.refactoring.selectElement import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiRange import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* @@ -122,7 +122,7 @@ fun selectElementsWithTargetParent( } fun selectSingleElement() { - KotlinRefactoringUtil2.selectElement(editor, file, false, elementKinds) { expr -> + selectElement(editor, file, false, elementKinds) { expr -> if (expr != null) { selectTargetContainer(listOf(expr)) } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinIntroduceVariableHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinIntroduceVariableHandler.kt index 50f6e90408d..e4810f7645d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinIntroduceVariableHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinIntroduceVariableHandler.kt @@ -719,11 +719,11 @@ object KotlinIntroduceVariableHandler : RefactoringActionHandler { if (file !is KtFile) return try { - KotlinRefactoringUtil2.selectElement(editor, file, listOf(CodeInsightUtils.ElementKind.EXPRESSION)) { + selectElement(editor, file, listOf(CodeInsightUtils.ElementKind.EXPRESSION)) { doRefactoring(project, editor, it as KtExpression?, null, null) } } - catch (e: KotlinRefactoringUtil2.IntroduceRefactoringException) { + catch (e: IntroduceRefactoringException) { showErrorHint(project, editor, e.message!!) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinOverridingDialog.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinOverridingDialog.java index 1bde7e5e02c..bca3ce44e24 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinOverridingDialog.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinOverridingDialog.java @@ -37,7 +37,7 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor; import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; import org.jetbrains.kotlin.idea.KotlinBundle; import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils; -import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil2; +import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil2Kt; import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers; import org.jetbrains.kotlin.psi.KtElement; import org.jetbrains.kotlin.psi.KtNamedFunction; @@ -110,7 +110,7 @@ class KotlinOverridingDialog extends DialogWrapper { assert element instanceof PsiMethod : "Method accepts only kotlin functions/properties and java methods, but '" + element.getText() + "' was found"; - return KotlinRefactoringUtil2.INSTANCE.formatPsiMethod((PsiMethod) element, true, false); + return KotlinRefactoringUtil2Kt.formatPsiMethod((PsiMethod) element, true, false); } @Override diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt index 8057d2a6fc6..63a697e6d11 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/KotlinSafeDeleteProcessor.kt @@ -39,7 +39,10 @@ import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.idea.KotlinBundle import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.core.deleteElementAndCleanParent -import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil2 +import org.jetbrains.kotlin.idea.refactoring.checkParametersInMethodHierarchy +import org.jetbrains.kotlin.idea.refactoring.checkSuperMethods +import org.jetbrains.kotlin.idea.refactoring.formatClass +import org.jetbrains.kotlin.idea.refactoring.formatFunction import org.jetbrains.kotlin.idea.references.KtReference import org.jetbrains.kotlin.idea.search.usagesSearch.processDelegationCallConstructorUsages import org.jetbrains.kotlin.lexer.KtTokens @@ -260,10 +263,10 @@ class KotlinSafeDeleteProcessor : JavaSafeDeleteProcessor() { .mapTo(ArrayList()) { overridenDescriptor -> KotlinBundle.message( "x.implements.y", - KotlinRefactoringUtil2.formatFunction(declarationDescriptor, true), - KotlinRefactoringUtil2.formatClass(declarationDescriptor.containingDeclaration, true), - KotlinRefactoringUtil2.formatFunction(overridenDescriptor, true), - KotlinRefactoringUtil2.formatClass(overridenDescriptor.containingDeclaration, true) + formatFunction(declarationDescriptor, true), + formatClass(declarationDescriptor.containingDeclaration, true), + formatFunction(overridenDescriptor, true), + formatClass(overridenDescriptor.containingDeclaration, true) ) } } @@ -332,18 +335,18 @@ class KotlinSafeDeleteProcessor : JavaSafeDeleteProcessor() { when (element) { is KtParameter -> return element.toPsiParameters().flatMap { psiParameter -> - KotlinRefactoringUtil2.checkParametersInMethodHierarchy(psiParameter) ?: emptyList() + checkParametersInMethodHierarchy(psiParameter) ?: emptyList() }.ifEmpty { listOf(element) } is PsiParameter -> - return KotlinRefactoringUtil2.checkParametersInMethodHierarchy(element) + return checkParametersInMethodHierarchy(element) } if (ApplicationManager.getApplication()!!.isUnitTestMode) return Collections.singletonList(element) return when (element) { is KtNamedFunction, is KtProperty -> - KotlinRefactoringUtil2.checkSuperMethods( + checkSuperMethods( element as KtDeclaration, allElementsToDelete, "super.methods.delete.with.usage.search" ) diff --git a/idea/tests/org/jetbrains/kotlin/idea/AbstractExpressionSelectionTest.java b/idea/tests/org/jetbrains/kotlin/idea/AbstractExpressionSelectionTest.java index 1920345a5f5..4e625be7805 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/AbstractExpressionSelectionTest.java +++ b/idea/tests/org/jetbrains/kotlin/idea/AbstractExpressionSelectionTest.java @@ -22,7 +22,8 @@ import kotlin.Unit; import kotlin.jvm.functions.Function1; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils; -import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil2; +import org.jetbrains.kotlin.idea.refactoring.IntroduceRefactoringException; +import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil2Kt; import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.test.KotlinTestUtils; @@ -35,7 +36,7 @@ public abstract class AbstractExpressionSelectionTest extends LightCodeInsightTe final String expectedExpression = KotlinTestUtils.getLastCommentInFile((KtFile) getFile()); try { - KotlinRefactoringUtil2.INSTANCE.selectElement( + KotlinRefactoringUtil2Kt.selectElement( getEditor(), (KtFile) getFile(), Collections.singletonList(CodeInsightUtils.ElementKind.EXPRESSION), @@ -49,7 +50,7 @@ public abstract class AbstractExpressionSelectionTest extends LightCodeInsightTe } ); } - catch (KotlinRefactoringUtil2.IntroduceRefactoringException e) { + catch (IntroduceRefactoringException e) { assertEquals(expectedExpression, ""); } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/AbstractSmartSelectionTest.java b/idea/tests/org/jetbrains/kotlin/idea/AbstractSmartSelectionTest.java index 8d000820333..9a07a92f9fa 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/AbstractSmartSelectionTest.java +++ b/idea/tests/org/jetbrains/kotlin/idea/AbstractSmartSelectionTest.java @@ -20,7 +20,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.testFramework.LightCodeInsightTestCase; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils; -import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil2; +import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil2Kt; import org.jetbrains.kotlin.psi.KtElement; import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.test.KotlinTestUtils; @@ -34,12 +34,12 @@ public abstract class AbstractSmartSelectionTest extends LightCodeInsightTestCas configureByFile(path); String expectedResultText = KotlinTestUtils.getLastCommentInFile((KtFile) getFile()); - List elements = KotlinRefactoringUtil2.INSTANCE.getSmartSelectSuggestions( + List elements = KotlinRefactoringUtil2Kt.getSmartSelectSuggestions( getFile(), getEditor().getCaretModel().getOffset(), CodeInsightUtils.ElementKind.EXPRESSION); List textualExpressions = new ArrayList(); for (KtElement element : elements) { - textualExpressions.add(KotlinRefactoringUtil2.INSTANCE.getExpressionShortText(element)); + textualExpressions.add(KotlinRefactoringUtil2Kt.getExpressionShortText(element)); } assertEquals(expectedResultText, StringUtil.join(textualExpressions, "\n")); } diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt index 6d504e9fc25..7cceecb7f7a 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt @@ -37,7 +37,6 @@ import com.intellij.refactoring.util.occurrences.ExpressionOccurrenceManager import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils -import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil2 import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.EXTRACT_FUNCTION import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandler import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.* @@ -47,6 +46,7 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.KotlinI import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.KotlinIntroduceTypeAliasHandler import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeParameter.KotlinIntroduceTypeParameterHandler import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler +import org.jetbrains.kotlin.idea.refactoring.selectElement import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.test.PluginTestCaseBase @@ -115,7 +115,7 @@ abstract class AbstractExtractionTest() : KotlinLightCodeInsightFixtureTestCase( with (handler) { val target = (file as KtFile).findElementByCommentPrefix("// TARGET:") as? KtNamedDeclaration if (target != null) { - KotlinRefactoringUtil2.selectElement(fixture.getEditor(), file, true, listOf(CodeInsightUtils.ElementKind.EXPRESSION)) { element -> + selectElement(fixture.getEditor(), file, true, listOf(CodeInsightUtils.ElementKind.EXPRESSION)) { element -> invoke(fixture.getProject(), fixture.getEditor(), element as KtExpression, target) } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/nameSuggester/KotlinNameSuggesterTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/nameSuggester/KotlinNameSuggesterTest.kt index 0aeedbd02fa..d8a4d79cbf1 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/nameSuggester/KotlinNameSuggesterTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/nameSuggester/KotlinNameSuggesterTest.kt @@ -21,7 +21,8 @@ import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils import org.jetbrains.kotlin.idea.core.KotlinNameSuggester -import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil2 +import org.jetbrains.kotlin.idea.refactoring.IntroduceRefactoringException +import org.jetbrains.kotlin.idea.refactoring.selectElement import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil import org.jetbrains.kotlin.idea.test.PluginTestCaseBase import org.jetbrains.kotlin.psi.KtExpression @@ -90,7 +91,7 @@ class KotlinNameSuggesterTest : LightCodeInsightFixtureTestCase() { if (withRuntime) { ConfigLibraryUtil.configureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk()) } - KotlinRefactoringUtil2.selectElement(myFixture.editor, file, listOf(CodeInsightUtils.ElementKind.EXPRESSION)) { + selectElement(myFixture.editor, file, listOf(CodeInsightUtils.ElementKind.EXPRESSION)) { val names = KotlinNameSuggester .suggestNamesByExpressionAndType(it as KtExpression, null, @@ -102,7 +103,7 @@ class KotlinNameSuggesterTest : LightCodeInsightFixtureTestCase() { assertEquals(expectedResultText, result) } } - catch (e: KotlinRefactoringUtil2.IntroduceRefactoringException) { + catch (e: IntroduceRefactoringException) { throw AssertionError("Failed to find expression: " + e.message) } finally {