Skip to content

Commit 3ea093f

Browse files
Wojciech LiberdaWojciech Liberda
authored andcommitted
Enhanced String.replaceWithArgs to support non-positional format specifiers (%s, %d) when only one argument is provided
1 parent 7f012cb commit 3ea093f

File tree

2 files changed

+58
-4
lines changed

2 files changed

+58
-4
lines changed

components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringResourcesUtils.kt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,23 @@ import org.jetbrains.compose.resources.plural.PluralCategory
44
import kotlin.io.encoding.Base64
55
import kotlin.io.encoding.ExperimentalEncodingApi
66

7-
private val SimpleStringFormatRegex = Regex("""%(\d+)\$[ds]""")
8-
internal fun String.replaceWithArgs(args: List<String>) = SimpleStringFormatRegex.replace(this) { matchResult ->
9-
args[matchResult.groupValues[1].toInt() - 1]
7+
private val SimpleStringFormatRegex = Regex("""%(?:([1-9]\d*)\$)?[ds]""")
8+
internal fun String.replaceWithArgs(args: List<String>): String {
9+
if (!SimpleStringFormatRegex.containsMatchIn(this)) return this
10+
11+
return SimpleStringFormatRegex.replace(this) { match ->
12+
val placeholderNumber = match.groups[1]?.value?.toIntOrNull()
13+
val index = when {
14+
placeholderNumber != null -> placeholderNumber - 1
15+
args.size == 1 -> 0
16+
else -> {
17+
throw IllegalArgumentException(
18+
"Formatting failed: Non-positional placeholder '${match.value}' is ambiguous when multiple arguments are provided in \"$this\""
19+
)
20+
}
21+
}
22+
args[index]
23+
}
1024
}
1125

1226
internal sealed interface StringItem {

components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/StringFormatTest.kt

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,44 @@ class StringFormatTest {
180180
// Only the first argument should be used, ignoring the rest
181181
assertEquals("Hello Alice!", result)
182182
}
183-
}
183+
184+
@Test
185+
fun `replaceWithArgs handle single argument format`() {
186+
val template = "Hello %s!"
187+
val args = listOf("Alice")
188+
189+
val result = template.replaceWithArgs(args)
190+
191+
assertEquals("Hello Alice!", result)
192+
}
193+
194+
@Test
195+
fun `replaceWithArgs handle multiple placeholders for single argument`() {
196+
val template = "%s and %s are best friends!"
197+
val args = listOf("Alice")
198+
199+
val result = template.replaceWithArgs(args)
200+
201+
assertEquals("Alice and Alice are best friends!", result)
202+
}
203+
204+
@Test
205+
fun `replaceWithArgs throw exception when multiple different arguments with single placeholder format`() {
206+
val template = "Hello %s, you have %d new messages!"
207+
val args = listOf("Alice", "15")
208+
209+
assertFailsWith<IllegalArgumentException> {
210+
template.replaceWithArgs(args)
211+
}
212+
}
213+
214+
@Test
215+
fun `replaceWithArgs throw exception when mixing single and multiple placeholders format`() {
216+
val template = "Hello %1\$s, you have %s new messages!"
217+
val args = listOf("Alice", "15")
218+
219+
assertFailsWith<IllegalArgumentException> {
220+
template.replaceWithArgs(args)
221+
}
222+
}
223+
}

0 commit comments

Comments
 (0)