From c50f38aa5284c2e8ca5515a085d9bc3978a79aa4 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Mon, 20 Jun 2016 20:09:18 +0300 Subject: [PATCH] Intentions: Implement intention to replace camel-case test function name with a space-separated one #KT-12489 Fixed (cherry picked from commit 21e24a1) --- ChangeLog.md | 1 + .../after.kt.template | 5 + .../before.kt.template | 5 + .../description.html | 5 + idea/src/META-INF/plugin.xml | 5 + ...tCamelCaseTestFunctionToSpacedIntention.kt | 136 ++++++++++++++++++ .../.intention | 1 + .../letters.kt_ignored | 12 ++ .../nonFunction.kt | 4 + .../nonLetters.kt_ignored | 12 ++ .../notAtIdentifier.kt | 4 + .../notTestFunction.kt | 4 + .../unchanged.kt_ignored | 10 ++ .../intentions/AbstractIntentionTest.java | 4 + .../intentions/IntentionTestGenerated.java | 27 ++++ 15 files changed, 235 insertions(+) create mode 100644 idea/resources/intentionDescriptions/ConvertCamelCaseTestFunctionToSpacedIntention/after.kt.template create mode 100644 idea/resources/intentionDescriptions/ConvertCamelCaseTestFunctionToSpacedIntention/before.kt.template create mode 100644 idea/resources/intentionDescriptions/ConvertCamelCaseTestFunctionToSpacedIntention/description.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/ConvertCamelCaseTestFunctionToSpacedIntention.kt create mode 100644 idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/.intention create mode 100644 idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/letters.kt_ignored create mode 100644 idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/nonFunction.kt create mode 100644 idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/nonLetters.kt_ignored create mode 100644 idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/notAtIdentifier.kt create mode 100644 idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/notTestFunction.kt create mode 100644 idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/unchanged.kt_ignored diff --git a/ChangeLog.md b/ChangeLog.md index 0ac76fd14e4..77985b7e256 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -258,6 +258,7 @@ Using 'this' as function argument in constructor of non-final class - [`KT-7492`](https://youtrack.jetbrains.com/issue/KT-7492) Support "Create abstract function/property" inside an abstract class - [`KT-10668`](https://youtrack.jetbrains.com/issue/KT-10668) Support "Create member/extension" corresponding to the extension receiver of enclosing function - [`KT-12553`](https://youtrack.jetbrains.com/issue/KT-12553) Show versions in inspection about different version of Kotlin plugin in Maven and IDE plugin +- [`KT-12489`](https://youtrack.jetbrains.com/issue/KT-12489) Implement intention to replace camel-case test function name with a space-separated one - [`KT-12730`](https://youtrack.jetbrains.com/issue/KT-12730) Warn about using different versions of Kotlin Gradle plugin and bundled compiler - [`KT-13173`](https://youtrack.jetbrains.com/issue/KT-13173) Handle more cases in "Add Const Modifier" Intention - [`KT-12628`](https://youtrack.jetbrains.com/issue/KT-12628) Quickfix for `invoke` operator unsafe calls diff --git a/idea/resources/intentionDescriptions/ConvertCamelCaseTestFunctionToSpacedIntention/after.kt.template b/idea/resources/intentionDescriptions/ConvertCamelCaseTestFunctionToSpacedIntention/after.kt.template new file mode 100644 index 00000000000..306191852a9 --- /dev/null +++ b/idea/resources/intentionDescriptions/ConvertCamelCaseTestFunctionToSpacedIntention/after.kt.template @@ -0,0 +1,5 @@ +class MyTest { + @Test fun `test two plus two equals four`() { + + } +} diff --git a/idea/resources/intentionDescriptions/ConvertCamelCaseTestFunctionToSpacedIntention/before.kt.template b/idea/resources/intentionDescriptions/ConvertCamelCaseTestFunctionToSpacedIntention/before.kt.template new file mode 100644 index 00000000000..8baeb555973 --- /dev/null +++ b/idea/resources/intentionDescriptions/ConvertCamelCaseTestFunctionToSpacedIntention/before.kt.template @@ -0,0 +1,5 @@ +class MyTest { + @Test fun testTwoPlusTwoEqualsFour() { + + } +} diff --git a/idea/resources/intentionDescriptions/ConvertCamelCaseTestFunctionToSpacedIntention/description.html b/idea/resources/intentionDescriptions/ConvertCamelCaseTestFunctionToSpacedIntention/description.html new file mode 100644 index 00000000000..de82b32bf35 --- /dev/null +++ b/idea/resources/intentionDescriptions/ConvertCamelCaseTestFunctionToSpacedIntention/description.html @@ -0,0 +1,5 @@ + + +This intention changes camel-case name of the given test function to space-separated one + + \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index d95b99f4f54..e5e56249116 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -1336,6 +1336,11 @@ Kotlin + + org.jetbrains.kotlin.idea.intentions.ConvertCamelCaseTestFunctionToSpacedIntention + Kotlin + + ( + KtNamedFunction::class.java, "Replace camel-case name with spaces" +) { + override fun applicabilityRange(element: KtNamedFunction): TextRange? { + val range = element.nameIdentifier?.textRange ?: return null + + val name = element.name ?: return null + val newName = decamelize(name) + if (newName == name) return null + + val lightMethod = element.toLightMethods().firstOrNull() ?: return null + if (!TestFrameworks.getInstance().isTestMethod(lightMethod)) return null + + text = "Rename to $newName" + + return range + } + + enum class Case { + LOWER, UPPER, OTHER + } + + private fun splitCamelName(name: String): List { + if (name === "") return emptyList() + + val result = SmartList() + var previousCase = Case.OTHER + var from = 0 + for (i in 0..name.length - 1) { + val c = name[i] + val currentCase = when { + Character.isUpperCase(c) -> Case.UPPER + Character.isLowerCase(c) -> Case.LOWER + else -> Case.OTHER + } + + when { + i == name.lastIndex -> result += name.substring(from) + i > 0 && currentCase != previousCase && currentCase != Case.LOWER -> { + result += name.substring(from, i) + from = i + } + } + + previousCase = currentCase + } + + return result + } + + private fun decamelize(name: String) = splitCamelName(name).joinToString(separator = " ") { it.decapitalizeSmart().trim() }.quoteIfNeeded() + + private fun startRename(element: KtNamedFunction, newName: String) { + RenameProcessor(element.project, element, newName, false, false).run() + } + + override fun startInWriteAction() = false + + override fun applyTo(element: KtNamedFunction, editor: Editor?) { + val nameIdentifier = element.nameIdentifier ?: return + val oldName = element.name ?: return + val oldId = oldName.quoteIfNeeded() + val newId = decamelize(oldName) + + if (editor != null) { + val builder = TemplateBuilderImpl(nameIdentifier) + builder.replaceElement(nameIdentifier, newId) + val template = runWriteAction { builder.buildInlineTemplate() } + TemplateManager.getInstance(element.project).startTemplate( + editor, + template, + object : TemplateEditingAdapter() { + private var chosenId: String = newId + private var range: TextRange? = null + + override fun beforeTemplateFinished(state: TemplateState?, template: Template?) { + val varName = (template as? TemplateImpl)?.getVariableNameAt(0) ?: return + chosenId = state?.getVariableValue(varName)?.text?.quoteIfNeeded() ?: return + range = state?.getVariableRange(varName) + } + + override fun templateFinished(template: Template?, brokenOff: Boolean) { + range?.let { + val doc = editor.document + runWriteAction { doc.replaceString(it.startOffset, it.endOffset, oldId) } + PsiDocumentManager.getInstance(element.project).commitDocument(doc) + } + + if (!brokenOff && chosenId != oldId) { + startRename(element, chosenId) + } + } + } + ) + } + else { + startRename(element, newId) + } + } +} \ No newline at end of file diff --git a/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/.intention b/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/.intention new file mode 100644 index 00000000000..7e37d249fa8 --- /dev/null +++ b/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.ConvertCamelCaseTestFunctionToSpacedIntention diff --git a/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/letters.kt_ignored b/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/letters.kt_ignored new file mode 100644 index 00000000000..205868cb33d --- /dev/null +++ b/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/letters.kt_ignored @@ -0,0 +1,12 @@ +// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar +// ERROR: Cannot access class 'java.lang.Class'. Check your module classpath for missing or conflicting dependencies +// ERROR: This annotation is not applicable to target 'member function' +import org.junit.Test + +class A { + @Test fun testTwoPlusTwoEqualsFour() {} +} + +fun test() { + A().testTwoPlusTwoEqualsFour() +} \ No newline at end of file diff --git a/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/nonFunction.kt b/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/nonFunction.kt new file mode 100644 index 00000000000..816ca69f2ca --- /dev/null +++ b/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/nonFunction.kt @@ -0,0 +1,4 @@ +// IS_APPLICABLE: false +class A { + val foo = 1 +} \ No newline at end of file diff --git a/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/nonLetters.kt_ignored b/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/nonLetters.kt_ignored new file mode 100644 index 00000000000..4cbf550fbb3 --- /dev/null +++ b/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/nonLetters.kt_ignored @@ -0,0 +1,12 @@ +// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar +// ERROR: Cannot access class 'java.lang.Class'. Check your module classpath for missing or conflicting dependencies +// ERROR: This annotation is not applicable to target 'member function' +import org.junit.Test + +class A { + @Test fun `testTwo + Two==Four`() {} +} + +fun test() { + A().`testTwo + Two==Four`() +} \ No newline at end of file diff --git a/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/notAtIdentifier.kt b/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/notAtIdentifier.kt new file mode 100644 index 00000000000..ecdd30125c1 --- /dev/null +++ b/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/notAtIdentifier.kt @@ -0,0 +1,4 @@ +// IS_APPLICABLE: false +class A { + fun foo() {} +} \ No newline at end of file diff --git a/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/notTestFunction.kt b/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/notTestFunction.kt new file mode 100644 index 00000000000..58fe2091692 --- /dev/null +++ b/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/notTestFunction.kt @@ -0,0 +1,4 @@ +// IS_APPLICABLE: false +class A { + fun foo() {} +} \ No newline at end of file diff --git a/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/unchanged.kt_ignored b/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/unchanged.kt_ignored new file mode 100644 index 00000000000..7825ab491ff --- /dev/null +++ b/idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/unchanged.kt_ignored @@ -0,0 +1,10 @@ +// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar +// ERROR: Cannot access class 'java.lang.Class'. Check your module classpath for missing or conflicting dependencies +// ERROR: This annotation is not applicable to target 'member function' +// IS_APPLICABLE: false + +import org.junit.Test + +class A { + @Test fun `foo bar`() {} +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/AbstractIntentionTest.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/AbstractIntentionTest.java index 88d89ae0ab9..95c80d0acda 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/AbstractIntentionTest.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/AbstractIntentionTest.java @@ -28,6 +28,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiFile; import com.intellij.refactoring.BaseRefactoringProcessor; import com.intellij.refactoring.util.CommonRefactoringUtil; +import com.intellij.testFramework.PlatformTestUtil; import com.intellij.util.PathUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.Convertor; @@ -145,6 +146,8 @@ public abstract class AbstractIntentionTest extends KotlinCodeInsightTestCase { ConfigLibraryUtil.configureKotlinRuntimeAndSdk(getModule(), PluginTestCaseBase.mockJdk()); } + ConfigLibraryUtil.configureLibrariesByDirective(getModule(), PlatformTestUtil.getCommunityPath(), fileText); + if (getFile() instanceof KtFile && !InTextDirectivesUtils.isDirectiveDefined(fileText, "// SKIP_ERRORS_BEFORE")) { DirectiveBasedActionUtils.INSTANCE.checkForUnexpectedErrors((KtFile) getFile()); } @@ -156,6 +159,7 @@ public abstract class AbstractIntentionTest extends KotlinCodeInsightTestCase { } } finally { + ConfigLibraryUtil.unconfigureLibrariesByDirective(myModule, fileText); if (isWithRuntime) { ConfigLibraryUtil.unConfigureKotlinRuntimeAndSdk(getModule(), getTestProjectJdk()); } diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 5deb1937732..37c19b34522 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -3509,6 +3509,33 @@ public class IntentionTestGenerated extends AbstractIntentionTest { } } + @TestMetadata("idea/testData/intentions/convertCamelCaseTestFunctionToSpaced") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ConvertCamelCaseTestFunctionToSpaced extends AbstractIntentionTest { + public void testAllFilesPresentInConvertCamelCaseTestFunctionToSpaced() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertCamelCaseTestFunctionToSpaced"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("nonFunction.kt") + public void testNonFunction() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/nonFunction.kt"); + doTest(fileName); + } + + @TestMetadata("notAtIdentifier.kt") + public void testNotAtIdentifier() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/notAtIdentifier.kt"); + doTest(fileName); + } + + @TestMetadata("notTestFunction.kt") + public void testNotTestFunction() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/notTestFunction.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/intentions/convertForEachToForLoop") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)