Introduce Parameter: Support extraction of string template fragments

This commit is contained in:
Alexey Sedunov
2015-11-18 15:15:32 +03:00
parent 6e8acc6453
commit 1b162e0294
34 changed files with 295 additions and 15 deletions
@@ -38,14 +38,13 @@ import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.idea.core.moveInsideParenthesesAndReplaceWith
import org.jetbrains.kotlin.idea.core.refactoring.removeTemplateEntryBracesIfPossible
import org.jetbrains.kotlin.idea.core.refactoring.runRefactoringWithPostprocessing
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringBundle
import org.jetbrains.kotlin.idea.refactoring.changeSignature.*
import org.jetbrains.kotlin.idea.refactoring.introduce.KotlinIntroduceHandlerBase
import org.jetbrains.kotlin.idea.refactoring.introduce.*
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
import org.jetbrains.kotlin.idea.refactoring.introduce.selectElementsWithTargetParent
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHintByKey
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.application.executeCommand
@@ -205,9 +204,10 @@ public open class KotlinIntroduceParameterHandler(
val helper: KotlinIntroduceParameterHelper = KotlinIntroduceParameterHelper.Default
): KotlinIntroduceHandlerBase() {
open fun invoke(project: Project, editor: Editor, expression: KtExpression, targetParent: KtNamedDeclaration) {
val context = expression.analyze()
val physicalExpression = expression.substringContextOrThis
val context = physicalExpression.analyze()
val expressionType = context.getType(expression)
val expressionType = expression.extractableSubstringInfo?.type ?: context.getType(physicalExpression)
if (expressionType == null) {
showErrorHint(project, editor, "Expression has no type", INTRODUCE_PARAMETER)
return
@@ -246,7 +246,7 @@ public open class KotlinIntroduceParameterHandler(
val occurrencesToReplace = expression.toRange()
.match(body, KotlinPsiUnifier.DEFAULT)
.filterNot {
val textRange = it.range.getTextRange()
val textRange = it.range.getPhysicalTextRange()
forbiddenRanges.any { it.intersects(textRange) }
}
.mapNotNull {
@@ -267,7 +267,10 @@ public open class KotlinIntroduceParameterHandler(
val haveLambdaArgumentsToReplace = occurrencesToReplace.any {
it.elements.any { it is KtFunctionLiteralExpression && it.parent is KtFunctionLiteralArgument }
}
val inplaceIsAvailable = editor.settings.isVariableInplaceRenameEnabled && !isTestMode && !haveLambdaArgumentsToReplace
val inplaceIsAvailable = editor.settings.isVariableInplaceRenameEnabled
&& !isTestMode
&& !haveLambdaArgumentsToReplace
&& expression.extractableSubstringInfo == null
val originalExpression = KtPsiUtil.safeDeparenthesize(expression)
val psiFactory = KtPsiFactory(project)
@@ -286,14 +289,17 @@ public open class KotlinIntroduceParameterHandler(
occurrenceReplacer = {
val expressionToReplace = it.elements.single() as KtExpression
val replacingExpression = psiFactory.createExpression(newParameterName)
if (expressionToReplace.isFunctionLiteralOutsideParentheses()) {
expressionToReplace
.getStrictParentOfType<KtFunctionLiteralArgument>()!!
.moveInsideParenthesesAndReplaceWith(replacingExpression, context)
}
else {
expressionToReplace.replace(replacingExpression)
val substringInfo = expressionToReplace.extractableSubstringInfo
val result = when {
expressionToReplace.isFunctionLiteralOutsideParentheses() -> {
expressionToReplace
.getStrictParentOfType<KtFunctionLiteralArgument>()!!
.moveInsideParenthesesAndReplaceWith(replacingExpression, context)
}
substringInfo != null -> substringInfo.replaceWith(replacingExpression)
else -> expressionToReplace.replaced(replacingExpression)
}
result.removeTemplateEntryBracesIfPossible()
}
)
)
@@ -0,0 +1,3 @@
fun foo(param: Int): String {
return "<selection>abc${pa</selection>ram}def"
}
@@ -0,0 +1 @@
Cannot perform refactoring without an expression
@@ -0,0 +1,3 @@
fun foo(param: Int): String {
return "<selection>abc$pa</selection>ram"
}
@@ -0,0 +1 @@
Cannot perform refactoring without an expression
@@ -0,0 +1,3 @@
fun foo(a: Int): String {
return "<selection>abc$a\</selection>ndef"
}
@@ -0,0 +1 @@
Cannot perform refactoring without an expression
@@ -0,0 +1,6 @@
fun foo(param: Int): String {
val x = "xyfalsez"
val y = "xyFalsez"
val z = false
return "ab<selection>false</selection>def"
}
@@ -0,0 +1,6 @@
fun foo(param: Int, b: Boolean = false): String {
val x = "xy${b}z"
val y = "xyFalsez"
val z = false
return "ab${b}def"
}
@@ -0,0 +1,7 @@
fun foo(param: Int): String {
val x = "a1234_"
val y = "-4123a"
val z = "+1243a"
val u = 123
return "ab<selection>123</selection>def"
}
@@ -0,0 +1,7 @@
fun foo(param: Int, i: Int = 123): String {
val x = "a${i}4_"
val y = "-4${i}a"
val z = "+1243a"
val u = 123
return "ab${i}def"
}
@@ -0,0 +1,6 @@
fun foo(param: Int): String {
val x = "atrue123"
val x = "aTRUE123"
val z = true
return "ab<selection>true</selection>def"
}
@@ -0,0 +1,6 @@
fun foo(param: Int, b: Boolean = true): String {
val x = "a${b}123"
val x = "aTRUE123"
val z = true
return "ab${b}def"
}
@@ -0,0 +1,6 @@
fun foo(a: Int): String {
val x = "abc$a"
val y = "abc${a}"
val z = "abc{$a}def"
return "<selection>abc$a</selection>" + "def"
}
@@ -0,0 +1,6 @@
fun foo(a: Int, s: String = "abc$a"): String {
val x = s
val y = s
val z = "abc{$a}def"
return s + "def"
}
@@ -0,0 +1,6 @@
fun foo(a: Int): String {
val x = "-${a + 1}"
val y = "x${a + 1}y"
val z = "x${a - 1}y"
return "abc<selection>${a + 1}</selection>def"
}
@@ -0,0 +1,6 @@
fun foo(a: Int, i: Int = a + 1): String {
val x = "-$i"
val y = "x${i}y"
val z = "x${a - 1}y"
return "abc${i}def"
}
@@ -0,0 +1,6 @@
fun foo(a: Int): String {
val x = "-$a"
val y = "x${a}y"
val z = "x$ay"
return "abc<selection>${a}</selection>def"
}
@@ -0,0 +1,6 @@
fun foo(a: Int, i: Int = a): String {
val x = "-$i"
val y = "x${i}y"
val z = "x$ay"
return "abc${i}def"
}
@@ -0,0 +1,5 @@
fun foo(a: Int): String {
val x = "+cd$a:${a + 1}efg"
val y = "+cd$a${a + 1}efg"
return "ab<selection>cd$a:${a + 1}ef</selection>"
}
@@ -0,0 +1,5 @@
fun foo(a: Int, s: String = "cd$a:${a + 1}ef"): String {
val x = "+${s}g"
val y = "+cd$a${a + 1}efg"
return "ab$s"
}
@@ -0,0 +1,5 @@
fun foo(a: Int): String {
val x = "_c$a:${a + 1}d_"
val y = "_$a:${a + 1}d_"
return "ab<selection>c$a:${a + 1}d</selection>ef"
}
@@ -0,0 +1,5 @@
fun foo(a: Int, s: String = "c$a:${a + 1}d"): String {
val x = "_${s}_"
val y = "_$a:${a + 1}d_"
return "ab${s}ef"
}
@@ -0,0 +1,5 @@
fun foo(a: Int): String {
val x = "_ab$a:${a + 1}cd__"
val y = "_a$a:${a + 1}cd__"
return "<selection>ab$a:${a + 1}cd</selection>ef"
}
@@ -0,0 +1,5 @@
fun foo(a: Int, s: String = "ab$a:${a + 1}cd"): String {
val x = "_${s}__"
val y = "_a$a:${a + 1}cd__"
return "${s}ef"
}
@@ -0,0 +1,9 @@
fun foo(a: Int): String {
val x = """_c$a
:${a + 1}d_"""
val y = "_$a:${a + 1}d_"
val z = """_c$a:${a + 1}d_"""
val u = "_c$a\n:${a + 1}d_"
return """ab<selection>c$a
:${a + 1}d</selection>ef"""
}
@@ -0,0 +1,8 @@
fun foo(a: Int, s: String = """c$a
:${a + 1}d"""): String {
val x = """_${s}_"""
val y = "_$a:${a + 1}d_"
val z = """_c$a:${a + 1}d_"""
val u = "_c$a\n:${a + 1}d_"
return """ab${s}ef"""
}
@@ -0,0 +1,6 @@
fun foo(a: Int): String {
val x = "xabc$a"
val y = "${a}abcx"
val z = "xacb$a"
return "<selection>abc</selection>def"
}
@@ -0,0 +1,6 @@
fun foo(a: Int, s: String = "abc"): String {
val x = "x$s$a"
val y = "${a}${s}x"
val z = "xacb$a"
return "${s}def"
}
@@ -0,0 +1,6 @@
fun foo(a: Int): String {
val x = "xcd$a"
val y = "${a}cdx"
val z = "xcf$a"
return "ab<selection>cd</selection>ef"
}
@@ -0,0 +1,6 @@
fun foo(a: Int, s: String = "cd"): String {
val x = "x$s$a"
val y = "${a}${s}x"
val z = "xcf$a"
return "ab${s}ef"
}
@@ -0,0 +1,6 @@
fun foo(a: Int): String {
val x = "xdef$a"
val y = "${a}defx"
val z = "xddf$a"
return "abc<selection>def</selection>"
}
@@ -0,0 +1,6 @@
fun foo(a: Int, s: String = "def"): String {
val x = "x$s$a"
val y = "${a}${s}x"
val z = "xddf$a"
return "abc$s"
}
@@ -3294,6 +3294,111 @@ public class ExtractionTestGenerated extends AbstractExtractionTest {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/while.kt");
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("idea/testData/refactoring/introduceParameter/stringTemplates")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class StringTemplates extends AbstractExtractionTest {
public void testAllFilesPresentInStringTemplates() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/introduceParameter/stringTemplates"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("brokenEntryWithBlockExpr.kt")
public void testBrokenEntryWithBlockExpr() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/stringTemplates/brokenEntryWithBlockExpr.kt");
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("brokenEntryWithExpr.kt")
public void testBrokenEntryWithExpr() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/stringTemplates/brokenEntryWithExpr.kt");
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("brokenEscapeEntry.kt")
public void testBrokenEscapeEntry() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/stringTemplates/brokenEscapeEntry.kt");
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("extractFalse.kt")
public void testExtractFalse() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/stringTemplates/extractFalse.kt");
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("extractIntegerLiteral.kt")
public void testExtractIntegerLiteral() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/stringTemplates/extractIntegerLiteral.kt");
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("extractTrue.kt")
public void testExtractTrue() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/stringTemplates/extractTrue.kt");
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("fullContent.kt")
public void testFullContent() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/stringTemplates/fullContent.kt");
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("fullEntryWithBlockExpr.kt")
public void testFullEntryWithBlockExpr() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/stringTemplates/fullEntryWithBlockExpr.kt");
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("fullEntryWithSimpleName.kt")
public void testFullEntryWithSimpleName() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/stringTemplates/fullEntryWithSimpleName.kt");
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("multipleEntriesWithPrefix.kt")
public void testMultipleEntriesWithPrefix() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/stringTemplates/multipleEntriesWithPrefix.kt");
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("multipleEntriesWithSubstring.kt")
public void testMultipleEntriesWithSubstring() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/stringTemplates/multipleEntriesWithSubstring.kt");
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("multipleEntriesWithSuffix.kt")
public void testMultipleEntriesWithSuffix() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/stringTemplates/multipleEntriesWithSuffix.kt");
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("rawTemplateWithSubstring.kt")
public void testRawTemplateWithSubstring() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/stringTemplates/rawTemplateWithSubstring.kt");
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("singleEntryPrefix.kt")
public void testSingleEntryPrefix() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/stringTemplates/singleEntryPrefix.kt");
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("singleEntrySubstring.kt")
public void testSingleEntrySubstring() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/stringTemplates/singleEntrySubstring.kt");
doIntroduceSimpleParameterTest(fileName);
}
@TestMetadata("singleEntrySuffix.kt")
public void testSingleEntrySuffix() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/stringTemplates/singleEntrySuffix.kt");
doIntroduceSimpleParameterTest(fileName);
}
}
}
@TestMetadata("idea/testData/refactoring/introduceLambdaParameter")