Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[resources] Fix regex for placeholders to correctly match multi-digit placeholders. #5187

Merged
merged 1 commit into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import org.jetbrains.compose.resources.plural.PluralCategory
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi

private val SimpleStringFormatRegex = Regex("""%(\d)\$[ds]""")
private val SimpleStringFormatRegex = Regex("""%(\d+)\$[ds]""")
internal fun String.replaceWithArgs(args: List<String>) = SimpleStringFormatRegex.replace(this) { matchResult ->
args[matchResult.groupValues[1].toInt() - 1]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package org.jetbrains.compose.resources

import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

class StringFormatTest {

@Test
fun `replaceWithArgs replaces placeholders with corresponding arguments`() {
val template = "Hello %1\$s, you have %2\$d new messages!"
val args = listOf("Alice", "5")

val result = template.replaceWithArgs(args)

assertEquals("Hello Alice, you have 5 new messages!", result)
}

@Test
fun `replaceWithArgs works with multiple placeholders referring to the same argument`() {
val template = "%1\$s and %1\$s are best friends!"
val args = listOf("Alice")

val result = template.replaceWithArgs(args)

assertEquals("Alice and Alice are best friends!", result)
}

@Test
fun `replaceWithArgs works when placeholders are out of order`() {
val template = "Order: %2\$s comes after %1\$s"
val args = listOf("Alice", "Bob")

val result = template.replaceWithArgs(args)

assertEquals("Order: Bob comes after Alice", result)
}

@Test
fun `replaceWithArgs works when there are no placeholders`() {
val template = "No placeholders here!"
val args = emptyList<String>()

val result = template.replaceWithArgs(args)

assertEquals("No placeholders here!", result)
}

@Test
fun `replaceWithArgs throws exception when placeholders index is out of bounds`() {
val template = "Hello %1\$s, %2\$s!"
val args = listOf("Alice")

assertFailsWith<IndexOutOfBoundsException> {
template.replaceWithArgs(args)
}
}

@Test
fun `replaceWithArgs handles empty string template`() {
val template = ""
val args = listOf("Alice", "5")

val result = template.replaceWithArgs(args)

assertEquals("", result)
}

@Test
fun `replaceWithArgs handles templates with no matching args`() {
val template = "Hello %1\$s, you have %3\$s messages"
val args = listOf("Alice")

assertFailsWith<IndexOutOfBoundsException> {
template.replaceWithArgs(args)
}
}

@Test
fun `replaceWithArgs replaces multiple placeholders of the same index`() {
val template = "Repeat: %1\$s, %1\$s, and again %1\$s!"
val args = listOf("Echo")

val result = template.replaceWithArgs(args)

assertEquals("Repeat: Echo, Echo, and again Echo!", result)
}

@Test
fun `replaceWithArgs ensures %d and %s placeholders behave identically`() {
val template = "%1\$d, %1\$s, %2\$d, %2\$s"
val args = listOf("42", "hello")

val result = template.replaceWithArgs(args)

assertEquals("42, 42, hello, hello", result)
}

@Test
fun `replaceWithArgs handles 15 arguments correctly`() {
val template = "%1\$s, %2\$s, %3\$s, %4\$s, %5\$s, %6\$s, %7\$s, %8\$s, %9\$s, %10\$s, %11\$s, %12\$s, %13\$s, %14\$s, %15\$s!"
val args = listOf(
"arg1", "arg2", "arg3", "arg4", "arg5",
"arg6", "arg7", "arg8", "arg9", "arg10",
"arg11", "arg12", "arg13", "arg14", "arg15"
)

val result = template.replaceWithArgs(args)

assertEquals(
"arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15!",
result
)
}

@Test
fun `replaceWithArgs throws exception for template with missing argument index`() {
val template = "Hello %${'$'}s, how are you?"
val args = listOf("Alice")

val result = template.replaceWithArgs(args)

// Since the template doesn't properly specify a valid index (e.g., %1$s), it will replace nothing.
assertEquals("Hello %${'$'}s, how are you?", result)
}

@Test
fun `replaceWithArgs does not replace invalid placeholders`() {
val template = "Hello %1\$x, how are you?"
val args = listOf("Alice")

val result = template.replaceWithArgs(args)

// %1$x is not a valid placeholder, so the template should remain unchanged
assertEquals("Hello %1\$x, how are you?", result)
}

@Test
fun `replaceWithArgs throws exception for missing arguments`() {
val template = "Hello %1\$s, you have %2\$d messages!"
val args = listOf("Alice")

// An exception should be thrown because the second argument (%2$d) is missing
assertFailsWith<IndexOutOfBoundsException> {
template.replaceWithArgs(args)
}
}

@Test
fun `replaceWithArgs throws exception for unmatched placeholders`() {
val template = "Hello %1\$s, your rank is %3\$s"
val args = listOf("Alice", "1")

// The template has a %3$s placeholder, but there is no third argument
assertFailsWith<IndexOutOfBoundsException> {
template.replaceWithArgs(args)
}
}

@Test
fun `replaceWithArgs handles templates with invalid format`() {
val template = "This is %1\$"
val args = listOf("test")

val result = template.replaceWithArgs(args)

// The incomplete placeholder %1$ will not be replaced
assertEquals("This is %1\$", result)
}

@Test
fun `replaceWithArgs ignores extra arguments`() {
val template = "Hello %1\$s!"
val args = listOf("Alice", "ExtraData1", "ExtraData2")

val result = template.replaceWithArgs(args)

// Only the first argument should be used, ignoring the rest
assertEquals("Hello Alice!", result)
}
}