From 3d422b47cb5465a09ab70eb52bb8b97211e4ebe4 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Sat, 20 Feb 2016 14:05:13 +0100 Subject: [PATCH] Intention action to convert string to raw string (half of KT-5208) --- idea/src/META-INF/plugin.xml | 5 ++ .../intentions/ToRawStringLiteralIntention.kt | 80 +++++++++++++++++++ .../intentions/toRawStringLiteral/.intention | 1 + .../intentions/toRawStringLiteral/3quotes.kt | 2 + .../toRawStringLiteral/alreadyRaw.kt | 2 + .../intentions/toRawStringLiteral/dollar.kt | 1 + .../toRawStringLiteral/dollar.kt.after | 1 + .../intentions/toRawStringLiteral/dollar2.kt | 1 + .../toRawStringLiteral/dollar2.kt.after | 1 + .../toRawStringLiteral/quotesAndSlashes.kt | 1 + .../quotesAndSlashes.kt.after | 1 + .../intentions/toRawStringLiteral/simple.kt | 1 + .../toRawStringLiteral/simple.kt.after | 2 + .../toRawStringLiteral/specialChar.kt | 2 + .../toRawStringLiteral/tabCharacter.kt | 2 + .../toRawStringLiteral/trailingSpace.kt | 2 + .../toRawStringLiteral/trailingSpace2.kt | 1 + .../trailingSpace2.kt.after | 2 + .../toRawStringLiteral/trailingSpace3.kt | 2 + .../autoImports/delegateNoOperator.test | 1 + .../callExpression/callWithExtraArgs.kt | 1 + ...rrayOfTypeForJavaAnnotation.before.Main.kt | 1 + .../changeFunctionParameterType4.kt | 1 + .../intentions/IntentionTestGenerated.java | 75 +++++++++++++++++ 24 files changed, 189 insertions(+) create mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/ToRawStringLiteralIntention.kt create mode 100644 idea/testData/intentions/toRawStringLiteral/.intention create mode 100644 idea/testData/intentions/toRawStringLiteral/3quotes.kt create mode 100644 idea/testData/intentions/toRawStringLiteral/alreadyRaw.kt create mode 100644 idea/testData/intentions/toRawStringLiteral/dollar.kt create mode 100644 idea/testData/intentions/toRawStringLiteral/dollar.kt.after create mode 100644 idea/testData/intentions/toRawStringLiteral/dollar2.kt create mode 100644 idea/testData/intentions/toRawStringLiteral/dollar2.kt.after create mode 100644 idea/testData/intentions/toRawStringLiteral/quotesAndSlashes.kt create mode 100644 idea/testData/intentions/toRawStringLiteral/quotesAndSlashes.kt.after create mode 100644 idea/testData/intentions/toRawStringLiteral/simple.kt create mode 100644 idea/testData/intentions/toRawStringLiteral/simple.kt.after create mode 100644 idea/testData/intentions/toRawStringLiteral/specialChar.kt create mode 100644 idea/testData/intentions/toRawStringLiteral/tabCharacter.kt create mode 100644 idea/testData/intentions/toRawStringLiteral/trailingSpace.kt create mode 100644 idea/testData/intentions/toRawStringLiteral/trailingSpace2.kt create mode 100644 idea/testData/intentions/toRawStringLiteral/trailingSpace2.kt.after create mode 100644 idea/testData/intentions/toRawStringLiteral/trailingSpace3.kt diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index dffd95238db..aa30cb94ab4 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -1189,6 +1189,11 @@ Kotlin + + org.jetbrains.kotlin.idea.intentions.ToRawStringLiteralIntention + Kotlin + + ( + KtStringTemplateExpression::class.java, + "To raw string literal" +), LowPriorityAction { + override fun isApplicableTo(element: KtStringTemplateExpression): Boolean { + val text = element.text + if (text.startsWith("\"\"\"")) return false // already raw + + val escapeEntries = element.entries.filterIsInstance() + for (entry in escapeEntries) { + val c = entry.unescapedValue.singleOrNull() ?: return false + if (Character.isISOControl(c) && c != '\n' && c != '\r') return false + } + + val converted = convertContent(element) + return !converted.contains("\"\"\"") && !hasTrailingSpaces(converted) + } + + override fun applyTo(element: KtStringTemplateExpression, editor: Editor?) { + val text = convertContent(element) + element.replace(KtPsiFactory(element).createExpression("\"\"\"" + text + "\"\"\"")) + } + + private fun convertContent(element: KtStringTemplateExpression): String { + val text = buildString { + val entries = element.entries + for ((index, entry) in entries.withIndex()) { + val value = entry.value() + + if (value.endsWith("$") && index < entries.size - 1) { + val nextChar = entries[index + 1].value().first() + if (nextChar.isJavaIdentifierStart() || nextChar == '{') { + append("\${\"$\"}") + continue + } + } + + append(value) + } + } + return StringUtilRt.convertLineSeparators(text, "\n") + } + + private fun hasTrailingSpaces(text: String): Boolean { + var afterSpace = true + for (c in text) { + if ((c == '\n' || c == '\r') && afterSpace) return true + afterSpace = c == ' ' || c == '\t' + } + return false + } + + private fun KtStringTemplateEntry.value() = if (this is KtEscapeStringTemplateEntry) this.unescapedValue else text +} \ No newline at end of file diff --git a/idea/testData/intentions/toRawStringLiteral/.intention b/idea/testData/intentions/toRawStringLiteral/.intention new file mode 100644 index 00000000000..22fc2dca859 --- /dev/null +++ b/idea/testData/intentions/toRawStringLiteral/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.ToRawStringLiteralIntention diff --git a/idea/testData/intentions/toRawStringLiteral/3quotes.kt b/idea/testData/intentions/toRawStringLiteral/3quotes.kt new file mode 100644 index 00000000000..cca27e059fb --- /dev/null +++ b/idea/testData/intentions/toRawStringLiteral/3quotes.kt @@ -0,0 +1,2 @@ +// IS_APPLICABLE: false +val v = "\"\"\"Hello, world!\"\"\"" \ No newline at end of file diff --git a/idea/testData/intentions/toRawStringLiteral/alreadyRaw.kt b/idea/testData/intentions/toRawStringLiteral/alreadyRaw.kt new file mode 100644 index 00000000000..f7431f4bd20 --- /dev/null +++ b/idea/testData/intentions/toRawStringLiteral/alreadyRaw.kt @@ -0,0 +1,2 @@ +// IS_APPLICABLE: false +val v = """ """ \ No newline at end of file diff --git a/idea/testData/intentions/toRawStringLiteral/dollar.kt b/idea/testData/intentions/toRawStringLiteral/dollar.kt new file mode 100644 index 00000000000..fb3c1f0b4c5 --- /dev/null +++ b/idea/testData/intentions/toRawStringLiteral/dollar.kt @@ -0,0 +1 @@ +val v = "\$abc" \ No newline at end of file diff --git a/idea/testData/intentions/toRawStringLiteral/dollar.kt.after b/idea/testData/intentions/toRawStringLiteral/dollar.kt.after new file mode 100644 index 00000000000..ac3f220ea40 --- /dev/null +++ b/idea/testData/intentions/toRawStringLiteral/dollar.kt.after @@ -0,0 +1 @@ +val v = """${"$"}abc""" \ No newline at end of file diff --git a/idea/testData/intentions/toRawStringLiteral/dollar2.kt b/idea/testData/intentions/toRawStringLiteral/dollar2.kt new file mode 100644 index 00000000000..a656702a171 --- /dev/null +++ b/idea/testData/intentions/toRawStringLiteral/dollar2.kt @@ -0,0 +1 @@ +val v = " $\u0041 $ \${} $" \ No newline at end of file diff --git a/idea/testData/intentions/toRawStringLiteral/dollar2.kt.after b/idea/testData/intentions/toRawStringLiteral/dollar2.kt.after new file mode 100644 index 00000000000..baa9a2cfe1b --- /dev/null +++ b/idea/testData/intentions/toRawStringLiteral/dollar2.kt.after @@ -0,0 +1 @@ +val v = """ ${"$"}A $ ${"$"}{} $""" \ No newline at end of file diff --git a/idea/testData/intentions/toRawStringLiteral/quotesAndSlashes.kt b/idea/testData/intentions/toRawStringLiteral/quotesAndSlashes.kt new file mode 100644 index 00000000000..b5a16d90355 --- /dev/null +++ b/idea/testData/intentions/toRawStringLiteral/quotesAndSlashes.kt @@ -0,0 +1 @@ +val v = "\"Hello, world!\" \\ \'a\'" \ No newline at end of file diff --git a/idea/testData/intentions/toRawStringLiteral/quotesAndSlashes.kt.after b/idea/testData/intentions/toRawStringLiteral/quotesAndSlashes.kt.after new file mode 100644 index 00000000000..83e675cfcb9 --- /dev/null +++ b/idea/testData/intentions/toRawStringLiteral/quotesAndSlashes.kt.after @@ -0,0 +1 @@ +val v = """"Hello, world!" \ 'a'""" \ No newline at end of file diff --git a/idea/testData/intentions/toRawStringLiteral/simple.kt b/idea/testData/intentions/toRawStringLiteral/simple.kt new file mode 100644 index 00000000000..3a17c96f351 --- /dev/null +++ b/idea/testData/intentions/toRawStringLiteral/simple.kt @@ -0,0 +1 @@ +val v = "\"\\Hello,\nworld!\\\"" \ No newline at end of file diff --git a/idea/testData/intentions/toRawStringLiteral/simple.kt.after b/idea/testData/intentions/toRawStringLiteral/simple.kt.after new file mode 100644 index 00000000000..755d4a19631 --- /dev/null +++ b/idea/testData/intentions/toRawStringLiteral/simple.kt.after @@ -0,0 +1,2 @@ +val v = """"\Hello, +world!\"""" \ No newline at end of file diff --git a/idea/testData/intentions/toRawStringLiteral/specialChar.kt b/idea/testData/intentions/toRawStringLiteral/specialChar.kt new file mode 100644 index 00000000000..1580f7c1a04 --- /dev/null +++ b/idea/testData/intentions/toRawStringLiteral/specialChar.kt @@ -0,0 +1,2 @@ +// IS_APPLICABLE: false +val v = "\u0000" \ No newline at end of file diff --git a/idea/testData/intentions/toRawStringLiteral/tabCharacter.kt b/idea/testData/intentions/toRawStringLiteral/tabCharacter.kt new file mode 100644 index 00000000000..0fe023e9f78 --- /dev/null +++ b/idea/testData/intentions/toRawStringLiteral/tabCharacter.kt @@ -0,0 +1,2 @@ +// IS_APPLICABLE: false +val v = "\t" \ No newline at end of file diff --git a/idea/testData/intentions/toRawStringLiteral/trailingSpace.kt b/idea/testData/intentions/toRawStringLiteral/trailingSpace.kt new file mode 100644 index 00000000000..71a2782e9ee --- /dev/null +++ b/idea/testData/intentions/toRawStringLiteral/trailingSpace.kt @@ -0,0 +1,2 @@ +// IS_APPLICABLE: false +val v = "\"\\Hello, \nworld!\\\"" \ No newline at end of file diff --git a/idea/testData/intentions/toRawStringLiteral/trailingSpace2.kt b/idea/testData/intentions/toRawStringLiteral/trailingSpace2.kt new file mode 100644 index 00000000000..317c635b870 --- /dev/null +++ b/idea/testData/intentions/toRawStringLiteral/trailingSpace2.kt @@ -0,0 +1 @@ +val v = "Hello, world!\n " \ No newline at end of file diff --git a/idea/testData/intentions/toRawStringLiteral/trailingSpace2.kt.after b/idea/testData/intentions/toRawStringLiteral/trailingSpace2.kt.after new file mode 100644 index 00000000000..1cbc6e4d32f --- /dev/null +++ b/idea/testData/intentions/toRawStringLiteral/trailingSpace2.kt.after @@ -0,0 +1,2 @@ +val v = """Hello, world! + """ \ No newline at end of file diff --git a/idea/testData/intentions/toRawStringLiteral/trailingSpace3.kt b/idea/testData/intentions/toRawStringLiteral/trailingSpace3.kt new file mode 100644 index 00000000000..b796e8731c6 --- /dev/null +++ b/idea/testData/intentions/toRawStringLiteral/trailingSpace3.kt @@ -0,0 +1,2 @@ +// IS_APPLICABLE: false +val v = "\n Hello, world!" \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/delegateNoOperator.test b/idea/testData/quickfix/autoImports/delegateNoOperator.test index 9af6d8f94b8..6fc3097634e 100644 --- a/idea/testData/quickfix/autoImports/delegateNoOperator.test +++ b/idea/testData/quickfix/autoImports/delegateNoOperator.test @@ -1,6 +1,7 @@ // FILE: first.before.kt // "Import" "false" // ERROR: Missing 'getValue(Nothing?, KProperty<*>)' method on delegate of type 'String' +// ACTION: To raw string literal package b diff --git a/idea/testData/quickfix/createFromUsage/createClass/callExpression/callWithExtraArgs.kt b/idea/testData/quickfix/createFromUsage/createClass/callExpression/callWithExtraArgs.kt index e022d12783e..85930497230 100644 --- a/idea/testData/quickfix/createFromUsage/createClass/callExpression/callWithExtraArgs.kt +++ b/idea/testData/quickfix/createFromUsage/createClass/callExpression/callWithExtraArgs.kt @@ -3,6 +3,7 @@ // ACTION: Add parameter to constructor 'Foo' // ACTION: Create secondary constructor // ERROR: Too many arguments for public constructor Foo(a: Int) defined in Foo +// ACTION: To raw string literal class Foo(a: Int) diff --git a/idea/testData/quickfix/typeMismatch/addArrayOfTypeForJavaAnnotation.before.Main.kt b/idea/testData/quickfix/typeMismatch/addArrayOfTypeForJavaAnnotation.before.Main.kt index 3499c0c081f..0e195a4e94b 100644 --- a/idea/testData/quickfix/typeMismatch/addArrayOfTypeForJavaAnnotation.before.Main.kt +++ b/idea/testData/quickfix/typeMismatch/addArrayOfTypeForJavaAnnotation.before.Main.kt @@ -3,5 +3,6 @@ // ACTION: Create test // ACTION: Make internal // ACTION: Make private +// ACTION: To raw string literal @ArrAnn("123") class My \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/parameterTypeMismatch/changeFunctionParameterType4.kt b/idea/testData/quickfix/typeMismatch/parameterTypeMismatch/changeFunctionParameterType4.kt index bbc9aa20f83..2a86e92fd0d 100644 --- a/idea/testData/quickfix/typeMismatch/parameterTypeMismatch/changeFunctionParameterType4.kt +++ b/idea/testData/quickfix/typeMismatch/parameterTypeMismatch/changeFunctionParameterType4.kt @@ -1,4 +1,5 @@ // "Change parameter 'z' type of function 'foo' to '(Int) -> String'" "false" +// ACTION: To raw string literal fun foo(y: Int = 0, z: (Int) -> String = {""}) { foo { diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 6679a195e7e..6a38a1c2f28 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -9231,6 +9231,81 @@ public class IntentionTestGenerated extends AbstractIntentionTest { } } + @TestMetadata("idea/testData/intentions/toRawStringLiteral") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ToRawStringLiteral extends AbstractIntentionTest { + @TestMetadata("3quotes.kt") + public void test3quotes() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/toRawStringLiteral/3quotes.kt"); + doTest(fileName); + } + + public void testAllFilesPresentInToRawStringLiteral() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/toRawStringLiteral"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("alreadyRaw.kt") + public void testAlreadyRaw() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/toRawStringLiteral/alreadyRaw.kt"); + doTest(fileName); + } + + @TestMetadata("dollar.kt") + public void testDollar() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/toRawStringLiteral/dollar.kt"); + doTest(fileName); + } + + @TestMetadata("dollar2.kt") + public void testDollar2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/toRawStringLiteral/dollar2.kt"); + doTest(fileName); + } + + @TestMetadata("quotesAndSlashes.kt") + public void testQuotesAndSlashes() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/toRawStringLiteral/quotesAndSlashes.kt"); + doTest(fileName); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/toRawStringLiteral/simple.kt"); + doTest(fileName); + } + + @TestMetadata("specialChar.kt") + public void testSpecialChar() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/toRawStringLiteral/specialChar.kt"); + doTest(fileName); + } + + @TestMetadata("tabCharacter.kt") + public void testTabCharacter() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/toRawStringLiteral/tabCharacter.kt"); + doTest(fileName); + } + + @TestMetadata("trailingSpace.kt") + public void testTrailingSpace() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/toRawStringLiteral/trailingSpace.kt"); + doTest(fileName); + } + + @TestMetadata("trailingSpace2.kt") + public void testTrailingSpace2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/toRawStringLiteral/trailingSpace2.kt"); + doTest(fileName); + } + + @TestMetadata("trailingSpace3.kt") + public void testTrailingSpace3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/toRawStringLiteral/trailingSpace3.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/intentions/usePropertyAccessSyntax") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)