From 99e4c8c06d2e10a038f2c40b255b127395d44a1a Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Wed, 27 Jan 2016 17:12:39 +0300 Subject: [PATCH] Create from Usage: Add commas after enum entries #KT-10800 Fixed --- .../jetbrains/kotlin/psi/psiUtil/psiUtils.kt | 2 + .../kotlin/idea/quickfix/generateUtil.kt | 6 +-- .../formatter/KotlinPreFormatProcessor.kt | 40 +++++++++++++++---- .../callableBuilder/CallableBuilder.kt | 3 +- .../referenceExpression/enumEntryAddComma.kt | 8 ++++ .../enumEntryAddComma.kt.after | 10 +++++ .../referenceExpression/enumEntryHasComma.kt | 8 ++++ .../enumEntryHasComma.kt.after | 10 +++++ .../idea/quickfix/QuickFixTestGenerated.java | 12 ++++++ 9 files changed, 88 insertions(+), 11 deletions(-) create mode 100644 idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumEntryAddComma.kt create mode 100644 idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumEntryAddComma.kt.after create mode 100644 idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumEntryHasComma.kt create mode 100644 idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumEntryHasComma.kt.after diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt index 191aa09fcd9..2fbeed42a4a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt @@ -142,6 +142,8 @@ fun PsiElement.getNextSiblingIgnoringWhitespaceAndComments(): PsiElement? { inline fun T.nextSiblingOfSameType() = PsiTreeUtil.getNextSiblingOfType(this, T::class.java) +inline fun T.prevSiblingOfSameType() = PsiTreeUtil.getPrevSiblingOfType(this, T::class.java) + fun PsiElement?.isAncestor(element: PsiElement, strict: Boolean = false): Boolean { return PsiTreeUtil.isAncestor(this, element, strict) } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/generateUtil.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/generateUtil.kt index f4930ec3cfb..259a92cb257 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/generateUtil.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/generateUtil.kt @@ -187,7 +187,7 @@ fun insertMembersAfter( else if (bound == null && body.declarations.isNotEmpty()) { afterAnchor = body.lBrace!! } - else if (bound != null && afterAnchor.startOffset >= bound.startOffset) { + else if (bound != null && afterAnchor.startOffset > bound.startOffset) { afterAnchor = bound.prevSibling!! } } @@ -207,6 +207,6 @@ fun insertMembersAfter( } } -fun insertMember(editor: Editor, classOrObject: KtClassOrObject, declaration: T): T { - return insertMembersAfter(editor, classOrObject, listOf(declaration)).single() +fun insertMember(editor: Editor?, classOrObject: KtClassOrObject, declaration: T, anchor: PsiElement? = null): T { + return insertMembersAfter(editor, classOrObject, listOf(declaration), anchor).single() } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinPreFormatProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinPreFormatProcessor.kt index 0672daa262b..b8954bdc1ed 100644 --- a/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinPreFormatProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinPreFormatProcessor.kt @@ -18,29 +18,55 @@ package org.jetbrains.kotlin.idea.formatter import com.intellij.lang.ASTNode import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement import com.intellij.psi.impl.source.codeStyle.PreFormatProcessor +import com.intellij.psi.tree.IElementType import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.allChildren +import org.jetbrains.kotlin.psi.psiUtil.nextSiblingOfSameType +import org.jetbrains.kotlin.psi.psiUtil.prevSiblingOfSameType import org.jetbrains.kotlin.utils.addToStdlib.lastIsInstanceOrNull class KotlinPreFormatProcessor : PreFormatProcessor { class Visitor(var range: TextRange) : KtTreeVisitorVoid() { + private fun PsiElement.containsToken(type: IElementType) = allChildren.any { it.node.elementType == type } + override fun visitNamedDeclaration(declaration: KtNamedDeclaration) { if (!range.contains(declaration.textRange)) return - if (declaration is KtEnumEntry) return val classBody = declaration.parent as? KtClassBody ?: return val klass = classBody.parent as? KtClass ?: return if (!klass.isEnum()) return - val lastEntry = klass.declarations.lastIsInstanceOrNull() - if (lastEntry != null && lastEntry.allChildren.any { it.node.elementType == KtTokens.SEMICOLON }) return - if (lastEntry == null && classBody.allChildren.any { it.node.elementType == KtTokens.SEMICOLON }) return + var delta = 0 - val semicolon = KtPsiFactory(klass).createSemicolon() - classBody.addAfter(semicolon, lastEntry) - range = TextRange(range.startOffset, range.endOffset + semicolon.textLength) + if (declaration is KtEnumEntry) { + val comma = KtPsiFactory(klass).createComma() + + declaration.nextSiblingOfSameType()?.let { nextEntry -> + if (declaration.containsToken(KtTokens.COMMA)) return@let + classBody.addAfter(comma, declaration) + delta += comma.textLength + } + + declaration.prevSiblingOfSameType()?.let { prevEntry -> + if (prevEntry.containsToken(KtTokens.COMMA)) return@let + classBody.addAfter(comma, prevEntry) + delta += comma.textLength + } + } + else { + val lastEntry = klass.declarations.lastIsInstanceOrNull() + if (lastEntry != null && lastEntry.containsToken(KtTokens.SEMICOLON)) return + if (lastEntry == null && classBody.containsToken(KtTokens.SEMICOLON)) return + + val semicolon = KtPsiFactory(klass).createSemicolon() + classBody.addAfter(semicolon, lastEntry) + delta += semicolon.textLength + } + + range = TextRange(range.startOffset, range.endOffset + delta) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt index f0a42057d7e..8f6f72e5814 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/callableBuilder/CallableBuilder.kt @@ -48,6 +48,7 @@ import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils import org.jetbrains.kotlin.idea.core.CollectingNameValidator import org.jetbrains.kotlin.idea.core.KotlinNameSuggester import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass.ClassKind +import org.jetbrains.kotlin.idea.quickfix.insertMember import org.jetbrains.kotlin.idea.refactoring.* import org.jetbrains.kotlin.idea.util.DialogWithEditor import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers @@ -576,7 +577,7 @@ class CallableBuilder(val config: CallableBuilderConfiguration) { } containingElement is KtClassOrObject -> { - addDeclarationToClassOrObject(containingElement, declaration) + insertMember(null, containingElement, declaration, containingElement.declarations.lastOrNull()) } else -> throw AssertionError("Invalid containing element: ${containingElement.text}") } diff --git a/idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumEntryAddComma.kt b/idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumEntryAddComma.kt new file mode 100644 index 00000000000..19ffa6567d9 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumEntryAddComma.kt @@ -0,0 +1,8 @@ +// "Create enum constant 'PUBLIC'" "true" +enum class TopicState { + PRIVATE +} + +fun foo() { + TopicState.PUBLIC +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumEntryAddComma.kt.after b/idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumEntryAddComma.kt.after new file mode 100644 index 00000000000..91f656b9fbe --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumEntryAddComma.kt.after @@ -0,0 +1,10 @@ +// "Create enum constant 'PUBLIC'" "true" +enum class TopicState { + PRIVATE, + + PUBLIC +} + +fun foo() { + TopicState.PUBLIC +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumEntryHasComma.kt b/idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumEntryHasComma.kt new file mode 100644 index 00000000000..5eac6fe927f --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumEntryHasComma.kt @@ -0,0 +1,8 @@ +// "Create enum constant 'PUBLIC'" "true" +enum class TopicState { + PRIVATE, +} + +fun foo() { + TopicState.PUBLIC +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumEntryHasComma.kt.after b/idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumEntryHasComma.kt.after new file mode 100644 index 00000000000..91f656b9fbe --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumEntryHasComma.kt.after @@ -0,0 +1,10 @@ +// "Create enum constant 'PUBLIC'" "true" +enum class TopicState { + PRIVATE, + + PUBLIC +} + +fun foo() { + TopicState.PUBLIC +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 6c8a6998fad..4c41ca84760 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -1448,6 +1448,18 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { doTest(fileName); } + @TestMetadata("enumEntryAddComma.kt") + public void testEnumEntryAddComma() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumEntryAddComma.kt"); + doTest(fileName); + } + + @TestMetadata("enumEntryHasComma.kt") + public void testEnumEntryHasComma() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumEntryHasComma.kt"); + doTest(fileName); + } + @TestMetadata("enumEntryNoReceiver.kt") public void testEnumEntryNoReceiver() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createClass/referenceExpression/enumEntryNoReceiver.kt");