Skip to content

Commit b5c7a42

Browse files
authored
Migrating the Util.kt and KModifier from JVM to common (#2006)
* Migrating the Util.kt and KModifier to common * Apply spotlessApply to fix Copyright * Remove `enumSetOf` and use a regular set * Simplify hex operations in Util, keep them only in common * Apply spotlessApply to fix blank line
1 parent 5a11709 commit b5c7a42

File tree

6 files changed

+244
-49
lines changed

6 files changed

+244
-49
lines changed

kotlinpoet/src/jvmMain/kotlin/com/squareup/kotlinpoet/KModifier.kt renamed to kotlinpoet/src/commonMain/kotlin/com/squareup/kotlinpoet/KModifier.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import com.squareup.kotlinpoet.KModifier.INTERNAL
1919
import com.squareup.kotlinpoet.KModifier.PRIVATE
2020
import com.squareup.kotlinpoet.KModifier.PROTECTED
2121
import com.squareup.kotlinpoet.KModifier.PUBLIC
22-
import java.util.EnumSet
2322

2423
public enum class KModifier(
2524
internal val keyword: String,
@@ -89,4 +88,4 @@ public enum class KModifier(
8988
}
9089
}
9190

92-
internal val VISIBILITY_MODIFIERS: Set<KModifier> = EnumSet.of(PUBLIC, INTERNAL, PROTECTED, PRIVATE)
91+
internal val VISIBILITY_MODIFIERS: Set<KModifier> = setOf(PUBLIC, INTERNAL, PROTECTED, PRIVATE)

kotlinpoet/src/jvmMain/kotlin/com/squareup/kotlinpoet/Util.kt renamed to kotlinpoet/src/commonMain/kotlin/com/squareup/kotlinpoet/Util.kt

Lines changed: 43 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,17 @@
1515
*/
1616
package com.squareup.kotlinpoet
1717

18-
import com.squareup.kotlinpoet.CodeBlock.Companion.isPlaceholder
19-
import java.util.Collections
20-
2118
internal object NullAppendable : Appendable {
22-
override fun append(charSequence: CharSequence) = this
23-
override fun append(charSequence: CharSequence, start: Int, end: Int) = this
24-
override fun append(c: Char) = this
19+
override fun append(value: CharSequence?) = this
20+
override fun append(value: CharSequence?, startIndex: Int, endIndex: Int) = this
21+
override fun append(value: Char) = this
2522
}
2623

27-
internal fun <K, V> Map<K, V>.toImmutableMap(): Map<K, V> =
28-
Collections.unmodifiableMap(LinkedHashMap(this))
24+
internal expect fun <K, V> Map<K, V>.toImmutableMap(): Map<K, V>
2925

30-
internal fun <T> Collection<T>.toImmutableList(): List<T> =
31-
Collections.unmodifiableList(ArrayList(this))
26+
internal expect fun <T> Collection<T>.toImmutableList(): List<T>
3227

33-
internal fun <T> Collection<T>.toImmutableSet(): Set<T> =
34-
Collections.unmodifiableSet(LinkedHashSet(this))
28+
internal expect fun <T> Collection<T>.toImmutableSet(): Set<T>
3529

3630
internal inline fun <reified T : Enum<T>> Collection<T>.toEnumSet(): Set<T> =
3731
enumValues<T>().filterTo(mutableSetOf(), this::contains)
@@ -63,10 +57,16 @@ internal fun characterLiteralWithoutSingleQuotes(c: Char) = when {
6357
c == '\"' -> "\"" // \u0022: double quote (")
6458
c == '\'' -> "\\'" // \u0027: single quote (')
6559
c == '\\' -> "\\\\" // \u005c: backslash (\)
66-
c.isIsoControl -> String.format("\\u%04x", c.code)
60+
c.isIsoControl -> formatIsoControlCode(c.code)
6761
else -> c.toString()
6862
}
6963

64+
internal fun formatIsoControlCode(code: Int): String =
65+
"\\u${code.toHexStr().padStart(4, '0')}"
66+
67+
internal fun Int.toHexStr(): String =
68+
toUInt().toString(16)
69+
7070
internal fun escapeCharacterLiterals(s: String) = buildString {
7171
for (c in s) append(characterLiteralWithoutSingleQuotes(c))
7272
}
@@ -138,44 +138,36 @@ internal fun stringLiteralWithQuotes(
138138
}
139139
}
140140

141-
internal fun CodeBlock.ensureEndsWithNewLine() = trimTrailingNewLine('\n')
142-
143-
internal fun CodeBlock.trimTrailingNewLine(replaceWith: Char? = null) = if (isEmpty()) {
144-
this
145-
} else {
146-
with(toBuilder()) {
147-
val lastFormatPart = trim().formatParts.last()
148-
if (lastFormatPart.isPlaceholder && args.isNotEmpty()) {
149-
val lastArg = args.last()
150-
if (lastArg is String) {
151-
val trimmedArg = lastArg.trimEnd('\n')
152-
args[args.size - 1] = if (replaceWith != null) {
153-
trimmedArg + replaceWith
154-
} else {
155-
trimmedArg
156-
}
157-
}
158-
} else {
159-
formatParts[formatParts.lastIndexOf(lastFormatPart)] = lastFormatPart.trimEnd('\n')
160-
if (replaceWith != null) {
161-
formatParts += "$replaceWith"
162-
}
163-
}
164-
return@with build()
165-
}
166-
}
141+
// TODO Waiting for `CodeBlock` migration.
142+
// internal fun CodeBlock.ensureEndsWithNewLine()
167143

168-
private val IDENTIFIER_REGEX =
144+
// TODO Waiting for `CodeBlock` migration.
145+
// internal fun CodeBlock.trimTrailingNewLine(replaceWith: Char? = null)
146+
147+
/**
148+
* Will crash if used `IDENTIFIER_REGEX_VALUE.toRegex()` directly in WasmJs:
149+
* `PatternSyntaxException: No such character class`.
150+
*
151+
* It works in JS and JVM.
152+
*
153+
* For now:
154+
* - Keep the use of `Regex` in JVM and JS.
155+
* - And use `RegExp` directly in WasmJs for matching,
156+
* using it in a similar way as in JS.
157+
*
158+
* See also: [KT-71003](https://youtrack.jetbrains.com/issue/KT-71003)
159+
*/
160+
internal const val IDENTIFIER_REGEX_VALUE =
161+
// language=regexp
169162
(
170163
"((\\p{gc=Lu}+|\\p{gc=Ll}+|\\p{gc=Lt}+|\\p{gc=Lm}+|\\p{gc=Lo}+|\\p{gc=Nl}+)+" +
171164
"\\d*" +
172165
"\\p{gc=Lu}*\\p{gc=Ll}*\\p{gc=Lt}*\\p{gc=Lm}*\\p{gc=Lo}*\\p{gc=Nl}*)" +
173166
"|" +
174167
"(`[^\n\r`]+`)"
175168
)
176-
.toRegex()
177169

178-
internal val String.isIdentifier get() = IDENTIFIER_REGEX.matches(this)
170+
internal expect val String.isIdentifier: Boolean
179171

180172
// https://kotlinlang.org/docs/reference/keyword-reference.html
181173
internal val KEYWORDS = setOf(
@@ -317,7 +309,7 @@ internal fun String.escapeAsAlias(validate: Boolean = true): String {
317309

318310
val newAlias = StringBuilder("")
319311

320-
if (!Character.isJavaIdentifierStart(first())) {
312+
if (!first().isJavaIdentifierStart()) {
321313
newAlias.append('_')
322314
}
323315

@@ -327,8 +319,8 @@ internal fun String.escapeAsAlias(validate: Boolean = true): String {
327319
continue
328320
}
329321

330-
if (!Character.isJavaIdentifierPart(ch)) {
331-
newAlias.append("_U").append(Integer.toHexString(ch.code).padStart(4, '0'))
322+
if (!ch.isJavaIdentifierPart()) {
323+
newAlias.append("_U").append(ch.code.toHexStr().padStart(4, '0'))
332324
continue
333325
}
334326

@@ -348,8 +340,8 @@ private fun String.escapeIfAllCharactersAreUnderscore() = if (allCharactersAreUn
348340

349341
private fun String.escapeIfNotJavaIdentifier(): String {
350342
return if ((
351-
!Character.isJavaIdentifierStart(first()) ||
352-
drop(1).any { !Character.isJavaIdentifierPart(it) }
343+
!first().isJavaIdentifierStart() ||
344+
drop(1).any { !it.isJavaIdentifierPart() }
353345
) &&
354346
!alreadyEscaped()
355347
) {
@@ -362,3 +354,7 @@ private fun String.escapeIfNotJavaIdentifier(): String {
362354
internal fun String.escapeSegmentsIfNecessary(delimiter: Char = '.') = split(delimiter)
363355
.filter { it.isNotEmpty() }
364356
.joinToString(delimiter.toString()) { it.escapeIfNecessary() }
357+
358+
internal expect fun Char.isJavaIdentifierStart(): Boolean
359+
360+
internal expect fun Char.isJavaIdentifierPart(): Boolean
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright (C) 2024 Square, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.squareup.kotlinpoet
17+
18+
private val IDENTIFIER_REGEX = IDENTIFIER_REGEX_VALUE.toRegex()
19+
20+
internal actual val String.isIdentifier: Boolean get() = IDENTIFIER_REGEX.matches(this)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright (C) 2024 Square, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.squareup.kotlinpoet
17+
18+
import com.squareup.kotlinpoet.CodeBlock.Companion.isPlaceholder
19+
import java.util.Collections
20+
21+
internal actual fun <K, V> Map<K, V>.toImmutableMap(): Map<K, V> =
22+
Collections.unmodifiableMap(LinkedHashMap(this))
23+
24+
internal actual fun <T> Collection<T>.toImmutableList(): List<T> =
25+
Collections.unmodifiableList(ArrayList(this))
26+
27+
internal actual fun <T> Collection<T>.toImmutableSet(): Set<T> =
28+
Collections.unmodifiableSet(LinkedHashSet(this))
29+
30+
// TODO Waiting for `CodeBlock` migration.
31+
internal fun CodeBlock.ensureEndsWithNewLine() = trimTrailingNewLine('\n')
32+
33+
// TODO Waiting for `CodeBlock` migration.
34+
internal fun CodeBlock.trimTrailingNewLine(replaceWith: Char? = null) = if (isEmpty()) {
35+
this
36+
} else {
37+
with(toBuilder()) {
38+
val lastFormatPart = trim().formatParts.last()
39+
if (lastFormatPart.isPlaceholder && args.isNotEmpty()) {
40+
val lastArg = args.last()
41+
if (lastArg is String) {
42+
val trimmedArg = lastArg.trimEnd('\n')
43+
args[args.size - 1] = if (replaceWith != null) {
44+
trimmedArg + replaceWith
45+
} else {
46+
trimmedArg
47+
}
48+
}
49+
} else {
50+
formatParts[formatParts.lastIndexOf(lastFormatPart)] = lastFormatPart.trimEnd('\n')
51+
if (replaceWith != null) {
52+
formatParts += "$replaceWith"
53+
}
54+
}
55+
return@with build()
56+
}
57+
}
58+
59+
private val IDENTIFIER_REGEX = IDENTIFIER_REGEX_VALUE.toRegex()
60+
61+
internal actual val String.isIdentifier: Boolean
62+
get() = IDENTIFIER_REGEX.matches(this)
63+
64+
internal actual fun Char.isJavaIdentifierStart(): Boolean =
65+
Character.isJavaIdentifierStart(this)
66+
67+
internal actual fun Char.isJavaIdentifierPart(): Boolean =
68+
Character.isJavaIdentifierPart(this)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright (C) 2024 Square, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.squareup.kotlinpoet
17+
18+
internal actual fun <K, V> Map<K, V>.toImmutableMap(): Map<K, V> =
19+
toMap()
20+
21+
internal actual fun <T> Collection<T>.toImmutableList(): List<T> =
22+
toList()
23+
24+
internal actual fun <T> Collection<T>.toImmutableSet(): Set<T> =
25+
toSet()
26+
27+
internal actual fun Char.isJavaIdentifierStart(): Boolean {
28+
return isLetter() ||
29+
this in CharCategory.LETTER_NUMBER ||
30+
this == '$' ||
31+
this == '_'
32+
}
33+
34+
internal actual fun Char.isJavaIdentifierPart(): Boolean {
35+
// TODO
36+
// A character may be part of a Java identifier if any of the following conditions are true:
37+
// - it is a letter
38+
// - it is a currency symbol (such as '$')
39+
// - it is a connecting punctuation character (such as '_')
40+
// - it is a digit
41+
// - it is a numeric letter (such as a Roman numeral character)
42+
// - it is a combining mark
43+
// - it is a non-spacing mark
44+
// isIdentifierIgnorable returns true for the character.
45+
// Also missing here:
46+
// - a combining mark
47+
return isLetter() ||
48+
isDigit() ||
49+
this in CharCategory.LETTER_NUMBER ||
50+
this in CharCategory.NON_SPACING_MARK ||
51+
this == '_' ||
52+
this == '$' ||
53+
isIdentifierIgnorable()
54+
//
55+
}
56+
57+
internal fun Char.isIdentifierIgnorable(): Boolean {
58+
// The following Unicode characters are ignorable in a Java identifier or a Unicode identifier:
59+
// - ISO control characters that are not whitespace
60+
// - '\u0000' through '\u0008'
61+
// - '\u000E' through '\u001B'
62+
// - '\u007F' through '\u009F'
63+
// - all characters that have the FORMAT general category value
64+
return (
65+
isISOControl() && (
66+
this in '\u0000'..'\u0008' ||
67+
this in '\u000E'..'\u001B' ||
68+
this in '\u007F'..'\u009F'
69+
)
70+
) || this in CharCategory.FORMAT
71+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright (C) 2024 Square, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.squareup.kotlinpoet
17+
18+
internal actual val String.isIdentifier: Boolean
19+
get() {
20+
val regExp = RegExp(IDENTIFIER_REGEX_VALUE, "gu")
21+
regExp.reset()
22+
23+
val match = regExp.exec(this) ?: return false
24+
return match.index == 0 && regExp.lastIndex == length
25+
}
26+
27+
internal external interface RegExpMatch {
28+
val index: Int
29+
val length: Int
30+
}
31+
32+
internal external class RegExp(pattern: String, flags: String? = definedExternally) : JsAny {
33+
fun exec(str: String): RegExpMatch?
34+
override fun toString(): String
35+
36+
var lastIndex: Int
37+
}
38+
39+
internal fun RegExp.reset() {
40+
lastIndex = 0
41+
}

0 commit comments

Comments
 (0)