Add intention to replace snake-case test function name with a space-separated

#KT-27143 Fixed
This commit is contained in:
Toshiaki Kameyama
2018-12-26 20:53:15 +09:00
committed by Mikhail Glukhikh
parent 5226ea5cda
commit 98810ba750
11 changed files with 168 additions and 50 deletions
@@ -1381,6 +1381,11 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.ConvertSnakeCaseTestFunctionToSpacedIntention</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.ReplaceSingleLineLetIntention</className>
<category>Kotlin</category>
@@ -0,0 +1,5 @@
class MyTest {
@Test fun <spot>`test two plus two equals four`</spot>() {
}
}
@@ -0,0 +1,5 @@
class MyTest {
@Test fun <spot>test_two_plus_two_equals_four</spot>() {
}
}
@@ -0,0 +1,5 @@
<html>
<body>
This intention changes a snake case name of the given test function to a space-separated one.
</body>
</html>
@@ -37,16 +37,29 @@ import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
import org.jetbrains.kotlin.util.capitalizeDecapitalize.decapitalizeSmart
import org.jetbrains.kotlin.utils.SmartList
class ConvertCamelCaseTestFunctionToSpacedIntention : SelfTargetingRangeIntention<KtNamedFunction>(
KtNamedFunction::class.java, "Replace camel-case name with spaces"
sealed class ConvertTestFunctionToSpacedIntention(case: String) : SelfTargetingRangeIntention<KtNamedFunction>(
KtNamedFunction::class.java, "Replace $case name with spaces"
) {
companion object {
private val SNAKE_CASE_REGEX = ".+_.+".toRegex()
}
abstract fun split(name: String): List<String>
abstract fun isApplicableName(name: String): Boolean
protected fun isSnakeCase(name: String): Boolean {
return name.contains(SNAKE_CASE_REGEX)
}
override fun applicabilityRange(element: KtNamedFunction): TextRange? {
val platform = element.platform
if (platform is CommonPlatform || platform is JsPlatform) return null
val range = element.nameIdentifier?.textRange ?: return null
val name = element.name ?: return null
val newName = decamelize(name)
if (!isApplicableName(name)) return null
val newName = convert(name)
if (newName == name.quoteIfNeeded()) return null
val lightMethod = element.toLightMethods().firstOrNull() ?: return null
@@ -57,11 +70,67 @@ class ConvertCamelCaseTestFunctionToSpacedIntention : SelfTargetingRangeIntentio
return range
}
private fun convert(name: String) = split(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 = convert(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)
}
}
}
class ConvertCamelCaseTestFunctionToSpacedIntention : ConvertTestFunctionToSpacedIntention("camel-case") {
override fun isApplicableName(name: String): Boolean {
return !isSnakeCase(name)
}
enum class Case {
LOWER, UPPER, OTHER
}
private fun splitCamelName(name: String): List<String> {
override fun split(name: String): List<String> {
if (name === "") return emptyList()
val result = SmartList<String>()
@@ -88,54 +157,14 @@ class ConvertCamelCaseTestFunctionToSpacedIntention : SelfTargetingRangeIntentio
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()
class ConvertSnakeCaseTestFunctionToSpacedIntention : ConvertTestFunctionToSpacedIntention("snake_case") {
override fun isApplicableName(name: String): Boolean {
return isSnakeCase(name)
}
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)
}
override fun split(name: String): List<String> {
return name.split("_").filter { it.isNotBlank() }
}
}
@@ -0,0 +1,12 @@
// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar
// IS_APPLICABLE: false
import org.junit.Test
class A {
@Test fun <caret>test_two_plus_two_equals_four() {}
}
fun test() {
A().test_two_plus_two_equals_four()
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.intentions.ConvertSnakeCaseTestFunctionToSpacedIntention
@@ -0,0 +1,8 @@
// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar
// IS_APPLICABLE: false
import org.junit.Test
class A {
@Test fun <caret>testTwoPlusTwoEqualsFour() {}
}
@@ -0,0 +1,10 @@
// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar
import org.junit.Test
class A {
@Test fun <caret>test_two_plus_two_equals_four() {}
}
fun test() {
A().test_two_plus_two_equals_four()
}
@@ -0,0 +1,10 @@
// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar
import org.junit.Test
class A {
@Test fun `test two plus two equals four`() {}
}
fun test() {
A().`test two plus two equals four`()
}
@@ -4804,6 +4804,11 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
runTest("idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/notTestFunction.kt");
}
@TestMetadata("snake.kt")
public void testSnake() throws Exception {
runTest("idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/snake.kt");
}
@TestMetadata("unchanged.kt")
public void testUnchanged() throws Exception {
runTest("idea/testData/intentions/convertCamelCaseTestFunctionToSpaced/unchanged.kt");
@@ -7014,6 +7019,29 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
@TestMetadata("idea/testData/intentions/convertSnakeCaseTestFunctionToSpaced")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ConvertSnakeCaseTestFunctionToSpaced extends AbstractIntentionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInConvertSnakeCaseTestFunctionToSpaced() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertSnakeCaseTestFunctionToSpaced"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("camel.kt")
public void testCamel() throws Exception {
runTest("idea/testData/intentions/convertSnakeCaseTestFunctionToSpaced/camel.kt");
}
@TestMetadata("snake.kt")
public void testSnake() throws Exception {
runTest("idea/testData/intentions/convertSnakeCaseTestFunctionToSpaced/snake.kt");
}
}
@TestMetadata("idea/testData/intentions/convertToAlso")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)