Skip to content

Commit

Permalink
Kotlin string literals are alive and accurate for dollars and multiline.
Browse files Browse the repository at this point in the history
  • Loading branch information
nedtwigg committed Jan 10, 2024
1 parent 1210ffa commit 6d619cd
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ internal object LiteralLong : LiteralFormat<Long>() {
}

private const val TRIPLE_QUOTE = "\"\"\""
private const val KOTLIN_DOLLAR = "${'$'}{'${'$'}'}"
private const val KOTLIN_DOLLAR = "\${'\$'}"

internal object LiteralString : LiteralFormat<String>() {
override fun encode(value: String, language: Language): String =
Expand All @@ -109,19 +109,35 @@ internal object LiteralString : LiteralFormat<String>() {
Language.JAVA_PRE15,
Language.JAVA -> singleLineJavaToSource(value)
Language.GROOVY,
Language.KOTLIN -> singleLineJavaToSource(value)
Language.KOTLIN -> singleLineKotlinGroovyToSource(value)
}
else
when (language) {
Language.GROOVY,
Language.SCALA,
Language.JAVA_PRE15 -> singleLineJavaToSource(value)
Language.JAVA -> multiLineJavaToSource(value)
Language.KOTLIN -> multiLineJavaToSource(value)
Language.GROOVY,
Language.KOTLIN -> multiLineKotlinToSource(value)
}
override fun parse(str: String, language: Language): String =
if (str.startsWith(TRIPLE_QUOTE)) multiLineJavaFromSource(str)
else singleLineJavaFromSource(str)
if (str.startsWith(TRIPLE_QUOTE))
when (language) {
Language.SCALA ->
throw UnsupportedOperationException(
"Selfie doesn't support triple-quoted strings in Scala")
Language.JAVA_PRE15,
Language.JAVA -> multiLineJavaFromSource(str)
Language.GROOVY,
Language.KOTLIN -> multiLineKotlinFromSource(str)
}
else
when (language) {
Language.SCALA,
Language.JAVA_PRE15,
Language.JAVA -> singleLineJavaFromSource(str)
Language.GROOVY,
Language.KOTLIN -> singleLineKotlinGroovyFromSource(str)
}
fun singleLineJavaToSource(value: String): String = singleLineToJavaIshSource(value, false)
fun singleLineKotlinGroovyToSource(value: String) = singleLineToJavaIshSource(value, true)
private fun singleLineToJavaIshSource(value: String, escapeDollars: Boolean): String {
Expand Down Expand Up @@ -151,6 +167,27 @@ internal object LiteralString : LiteralFormat<String>() {
private fun isControlChar(c: Char): Boolean {
return c in '\u0000'..'\u001F' || c == '\u007F'
}
fun multiLineKotlinToSource(arg: String): String {
val escapeDollars = arg.replace("$", KOTLIN_DOLLAR)
val escapeTripleQuotes = escapeDollars.replace(TRIPLE_QUOTE, "\${'\"'}${'"'}${'"'}")
val protectWhitespace =
escapeTripleQuotes.lines().joinToString("\n") { line ->
val protectTrailingWhitespace =
if (line.endsWith(" ")) {
line.dropLast(1) + "\${' '}"
} else if (line.endsWith("\t")) {
line.dropLast(1) + "\${'\t'}"
} else line
val protectLeadingWhitespace =
if (protectTrailingWhitespace.startsWith(" ")) {
"\${' '}" + protectTrailingWhitespace.drop(1)
} else if (protectTrailingWhitespace.startsWith("\t")) {
"\${'\t'}" + protectTrailingWhitespace.drop(1)
} else protectTrailingWhitespace
protectLeadingWhitespace
}
return "$TRIPLE_QUOTE$protectWhitespace$TRIPLE_QUOTE"
}
fun multiLineJavaToSource(arg: String): String {
val escapeBackslashes = arg.replace("\\", "\\\\")
val escapeTripleQuotes = escapeBackslashes.replace(TRIPLE_QUOTE, "\\\"\\\"\\\"")
Expand Down Expand Up @@ -266,6 +303,14 @@ internal object LiteralString : LiteralFormat<String>() {
}
}
}
fun multiLineKotlinFromSource(sourceWithQuotes: String): String {
check(sourceWithQuotes.startsWith("$TRIPLE_QUOTE"))
check(sourceWithQuotes.endsWith(TRIPLE_QUOTE))
val source =
sourceWithQuotes.substring(
TRIPLE_QUOTE.length, sourceWithQuotes.length - TRIPLE_QUOTE.length)
return inlineDollars(source)
}
}

internal object LiteralBoolean : LiteralFormat<Boolean>() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ class LiteralStringTest {
val actual = LiteralString.multiLineJavaToSource(value)
actual shouldBe expected.replace("'", "\"")
}
private val KOTLIN_DOLLAR = "s{'s'}".replace('s', '$')

@Test
fun multiLineKotlinToSource() {
multiLineKotlinToSource("1", "```1```")
multiLineKotlinToSource("$", "```$KOTLIN_DOLLAR```")
}
private fun multiLineKotlinToSource(value: String, expected: String) {
val actual = LiteralString.multiLineKotlinToSource(value)
actual shouldBe expected.replace("`", "\"")
}

@Test
fun singleLineJavaFromSource() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (C) 2024 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.diffplug.selfie.junit5

import kotlin.test.Test
import org.junit.jupiter.api.MethodOrderer
import org.junit.jupiter.api.Order
import org.junit.jupiter.api.TestMethodOrder
import org.junitpioneer.jupiter.DisableIfTestFails

@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
@DisableIfTestFails
class StringLiteralsKotlinTest : Harness("undertest-junit5") {
@Test @Order(1)
fun readFailsBecauseTodo() {
gradleReadSSFail()
}

@Test @Order(2)
fun writeSucceeds() {
gradleWriteSS()
}

@Test @Order(3)
fun nowReadSucceeds() {
gradleReadSS()
}

@Test @Order(4)
fun cleanup() {
ut_mirrorJava().restoreFromGit()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package undertest.junit5

import com.diffplug.selfie.Selfie.expectSelfie
import org.junit.jupiter.api.Test

class UT_StringLiteralsKotlinTest {
@Test fun empty() {
expectSelfie("").toBe_TODO()
}

@Test fun tabs() {
expectSelfie("\t\t\t").toBe_TODO()
}

@Test fun spaces() {
expectSelfie(" ").toBe_TODO()
}

@Test fun newlines() {
expectSelfie("\n").toBe_TODO()
expectSelfie("\n\n").toBe_TODO()
expectSelfie("\n\n\n").toBe_TODO()
}

@Test fun escapableCharacters() {
expectSelfie(" ' \" $ ").toBe_TODO()
expectSelfie(" ' \" $ \n \"\"\"\"\"\"\"\"\"\t").toBe_TODO()
}

@Test fun allOfIt() {
expectSelfie(" a\n" + "a \n" + "\t a \t\n").toBe_TODO()
}
}

0 comments on commit 6d619cd

Please sign in to comment.