From b9475d574bf318950894316a7bca5ff7df3205a1 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Wed, 20 Nov 2024 20:17:03 +0800 Subject: [PATCH 1/4] Migrate NameAllocator from JVM to common --- .../com/squareup/kotlinpoet/CodePoint.kt | 33 +++++++++++ .../com/squareup/kotlinpoet/NameAllocator.kt | 16 +++--- .../com/squareup/kotlinpoet/CodePoint.js.kt | 51 +++++++++++++++++ .../com/squareup/kotlinpoet/ClassName.kt | 4 +- .../com/squareup/kotlinpoet/CodePoint.jvm.kt | 41 ++++++++++++++ .../squareup/kotlinpoet/CodePoint.nonJvm.kt | 55 +++++++++++++++++++ .../squareup/kotlinpoet/CodePoint.wasmJs.kt | 54 ++++++++++++++++++ 7 files changed, 245 insertions(+), 9 deletions(-) create mode 100644 kotlinpoet/src/commonMain/kotlin/com/squareup/kotlinpoet/CodePoint.kt rename kotlinpoet/src/{jvmMain => commonMain}/kotlin/com/squareup/kotlinpoet/NameAllocator.kt (93%) create mode 100644 kotlinpoet/src/jsMain/kotlin/com/squareup/kotlinpoet/CodePoint.js.kt create mode 100644 kotlinpoet/src/jvmMain/kotlin/com/squareup/kotlinpoet/CodePoint.jvm.kt create mode 100644 kotlinpoet/src/nonJvmMain/kotlin/com/squareup/kotlinpoet/CodePoint.nonJvm.kt create mode 100644 kotlinpoet/src/wasmJsMain/kotlin/com/squareup/kotlinpoet/CodePoint.wasmJs.kt diff --git a/kotlinpoet/src/commonMain/kotlin/com/squareup/kotlinpoet/CodePoint.kt b/kotlinpoet/src/commonMain/kotlin/com/squareup/kotlinpoet/CodePoint.kt new file mode 100644 index 000000000..0479320f9 --- /dev/null +++ b/kotlinpoet/src/commonMain/kotlin/com/squareup/kotlinpoet/CodePoint.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 Square, Inc. + * + * 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.squareup.kotlinpoet + +import kotlin.jvm.JvmInline + +@JvmInline +internal value class CodePoint(val code: Int) + +internal expect fun String.codePointAt(index: Int): CodePoint + +internal expect fun CodePoint.isLowerCase(): Boolean +internal expect fun CodePoint.isUpperCase(): Boolean + +internal expect fun CodePoint.isJavaIdentifierStart(): Boolean +internal expect fun CodePoint.isJavaIdentifierPart(): Boolean + +internal expect fun CodePoint.charCount(): Int + +internal expect fun StringBuilder.appendCodePoint(codePoint: CodePoint): StringBuilder diff --git a/kotlinpoet/src/jvmMain/kotlin/com/squareup/kotlinpoet/NameAllocator.kt b/kotlinpoet/src/commonMain/kotlin/com/squareup/kotlinpoet/NameAllocator.kt similarity index 93% rename from kotlinpoet/src/jvmMain/kotlin/com/squareup/kotlinpoet/NameAllocator.kt rename to kotlinpoet/src/commonMain/kotlin/com/squareup/kotlinpoet/NameAllocator.kt index a6c570b9e..6586756d2 100644 --- a/kotlinpoet/src/jvmMain/kotlin/com/squareup/kotlinpoet/NameAllocator.kt +++ b/kotlinpoet/src/commonMain/kotlin/com/squareup/kotlinpoet/NameAllocator.kt @@ -15,7 +15,9 @@ */ package com.squareup.kotlinpoet -import java.util.UUID +import kotlin.jvm.JvmOverloads +import kotlin.random.Random +import kotlin.random.nextULong /** * Assigns Kotlin identifier names to avoid collisions, keywords, and invalid characters. To use, @@ -118,7 +120,7 @@ public class NameAllocator private constructor( */ @JvmOverloads public fun newName( suggestion: String, - tag: Any = UUID.randomUUID().toString(), + tag: Any = Random.nextULong().toString(), // TODO Since Kotlin 2.0.20, it's possible to use kotlin.uuid.Uuid ): String { var result = toJavaIdentifier(suggestion) while (!allocatedNames.add(result)) { @@ -154,18 +156,18 @@ private fun toJavaIdentifier(suggestion: String) = buildString { while (i < suggestion.length) { val codePoint = suggestion.codePointAt(i) if (i == 0 && - !Character.isJavaIdentifierStart(codePoint) && - Character.isJavaIdentifierPart(codePoint) + !codePoint.isJavaIdentifierStart() && + codePoint.isJavaIdentifierPart() ) { append("_") } - val validCodePoint: Int = if (Character.isJavaIdentifierPart(codePoint)) { + val validCodePoint: CodePoint = if (codePoint.isJavaIdentifierPart()) { codePoint } else { - '_'.code + CodePoint('_'.code) } appendCodePoint(validCodePoint) - i += Character.charCount(codePoint) + i += codePoint.charCount() } } diff --git a/kotlinpoet/src/jsMain/kotlin/com/squareup/kotlinpoet/CodePoint.js.kt b/kotlinpoet/src/jsMain/kotlin/com/squareup/kotlinpoet/CodePoint.js.kt new file mode 100644 index 000000000..6789369ca --- /dev/null +++ b/kotlinpoet/src/jsMain/kotlin/com/squareup/kotlinpoet/CodePoint.js.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 Square, Inc. + * + * 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.squareup.kotlinpoet + +internal actual fun String.codePointAt(index: Int): CodePoint { + val code = jsCodePointAt(this, index) + return CodePoint(code) +} + +internal actual fun CodePoint.isLowerCase(): Boolean { + // TODO Will there be a problem? Is there a better way? + val str = jsFromCodePoint(this.code) + + if (str.length != 1) { + return false + } + + return str.first().isLowerCase() +} + +internal actual fun CodePoint.isUpperCase(): Boolean { + // TODO Will there be a problem? Is there a better way? + val str = jsFromCodePoint(this.code) + + if (str.length != 1) { + return false + } + + return str.first().isUpperCase() +} + +@Suppress("unused") +private fun jsCodePointAt(str: String, index: Int): Int = + js("str.codePointAt(index)").unsafeCast() + +@Suppress("unused") +private fun jsFromCodePoint(code: Int): String = + js("String.fromCodePoint(code)").toString() diff --git a/kotlinpoet/src/jvmMain/kotlin/com/squareup/kotlinpoet/ClassName.kt b/kotlinpoet/src/jvmMain/kotlin/com/squareup/kotlinpoet/ClassName.kt index 5eeb21b0a..59ce4bf8e 100644 --- a/kotlinpoet/src/jvmMain/kotlin/com/squareup/kotlinpoet/ClassName.kt +++ b/kotlinpoet/src/jvmMain/kotlin/com/squareup/kotlinpoet/ClassName.kt @@ -220,7 +220,7 @@ public class ClassName internal constructor( // Add the package name, like "java.util.concurrent", or "" for no package. var p = 0 - while (p < classNameString.length && Character.isLowerCase(classNameString.codePointAt(p))) { + while (p < classNameString.length && classNameString.codePointAt(p).isLowerCase()) { p = classNameString.indexOf('.', p) + 1 require(p != 0) { "couldn't make a guess for $classNameString" } } @@ -228,7 +228,7 @@ public class ClassName internal constructor( // Add the class names, like "Map" and "Entry". for (part in classNameString.substring(p).split('.')) { - require(part.isNotEmpty() && Character.isUpperCase(part.codePointAt(0))) { + require(part.isNotEmpty() && part.codePointAt(0).isUpperCase()) { "couldn't make a guess for $classNameString" } diff --git a/kotlinpoet/src/jvmMain/kotlin/com/squareup/kotlinpoet/CodePoint.jvm.kt b/kotlinpoet/src/jvmMain/kotlin/com/squareup/kotlinpoet/CodePoint.jvm.kt new file mode 100644 index 000000000..ff02a2c6e --- /dev/null +++ b/kotlinpoet/src/jvmMain/kotlin/com/squareup/kotlinpoet/CodePoint.jvm.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 Square, Inc. + * + * 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.squareup.kotlinpoet + +import kotlin.text.codePointAt as codePointAtKt + +internal actual fun String.codePointAt(index: Int): CodePoint = + CodePoint(codePointAtKt(index)) + +internal actual fun CodePoint.isLowerCase(): Boolean = + Character.isLowerCase(code) + +internal actual fun CodePoint.isUpperCase(): Boolean = + Character.isUpperCase(code) + +internal actual fun CodePoint.isJavaIdentifierStart(): Boolean = + Character.isJavaIdentifierStart(code) + +internal actual fun CodePoint.isJavaIdentifierPart(): Boolean = + Character.isJavaIdentifierPart(code) + +internal actual fun CodePoint.charCount(): Int { + return Character.charCount(code) +} + +internal actual fun StringBuilder.appendCodePoint(codePoint: CodePoint): StringBuilder { + return appendCodePoint(codePoint.code) +} diff --git a/kotlinpoet/src/nonJvmMain/kotlin/com/squareup/kotlinpoet/CodePoint.nonJvm.kt b/kotlinpoet/src/nonJvmMain/kotlin/com/squareup/kotlinpoet/CodePoint.nonJvm.kt new file mode 100644 index 000000000..f2b6eb9af --- /dev/null +++ b/kotlinpoet/src/nonJvmMain/kotlin/com/squareup/kotlinpoet/CodePoint.nonJvm.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 Square, Inc. + * + * 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.squareup.kotlinpoet + +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ +internal actual fun StringBuilder.appendCodePoint(codePoint: CodePoint): StringBuilder { + // Copied from StringBuilder.kt, + // TODO Is this correct? + val code = codePoint.code + if (code <= Char.MAX_VALUE.code) { + append(code.toChar()) + } else { + append(Char.MIN_HIGH_SURROGATE + ((code - 0x10000) shr 10)) + append(Char.MIN_LOW_SURROGATE + (code and 0x3ff)) + } + return this +} + +internal actual fun CodePoint.isJavaIdentifierStart(): Boolean { + // TODO How check Java identifier start use code point? + if (charCount() == 1) { + return Char(code).isJavaIdentifierStart() + } + + return true +} + +internal actual fun CodePoint.isJavaIdentifierPart(): Boolean { + // TODO How check Java identifier part use code point? + if (charCount() == 1) { + return Char(code).isJavaIdentifierPart() + } + + return true +} + +internal actual fun CodePoint.charCount(): Int { + return if (code >= 0x010000) 2 else 1 +} diff --git a/kotlinpoet/src/wasmJsMain/kotlin/com/squareup/kotlinpoet/CodePoint.wasmJs.kt b/kotlinpoet/src/wasmJsMain/kotlin/com/squareup/kotlinpoet/CodePoint.wasmJs.kt new file mode 100644 index 000000000..22bd4d1ed --- /dev/null +++ b/kotlinpoet/src/wasmJsMain/kotlin/com/squareup/kotlinpoet/CodePoint.wasmJs.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 Square, Inc. + * + * 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.squareup.kotlinpoet + +internal actual fun String.codePointAt(index: Int): CodePoint { + val str = this + val code = jsCodePointAt(str, index) + return CodePoint(code) +} + +internal actual fun CodePoint.isLowerCase(): Boolean { + // TODO Will there be a problem? Is there a better way? + val code = this.code + val str = jsFromCodePoint(code) + + if (str.length != 1) { + return false + } + + return str.first().isLowerCase() +} + +internal actual fun CodePoint.isUpperCase(): Boolean { + // TODO Will there be a problem? Is there a better way? + val code = this.code + val str = jsFromCodePoint(code) + + if (str.length != 1) { + return false + } + + return str.first().isUpperCase() +} + +@Suppress("UNUSED_PARAMETER") +private fun jsCodePointAt(str: String, index: Int): Int = + js("str.codePointAt(index)") + +@Suppress("UNUSED_PARAMETER") +private fun jsFromCodePoint(code: Int): String = + js("String.fromCodePoint(code)") From 96383f5f8f5179154cb2ddcc1b5ac669fb26c7ec Mon Sep 17 00:00:00 2001 From: ForteScarlet <1149159218@qq.com> Date: Thu, 21 Nov 2024 09:08:56 +0800 Subject: [PATCH 2/4] CodePoint use unsigned `ushr` Co-authored-by: Jake Wharton --- .../kotlin/com/squareup/kotlinpoet/CodePoint.nonJvm.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinpoet/src/nonJvmMain/kotlin/com/squareup/kotlinpoet/CodePoint.nonJvm.kt b/kotlinpoet/src/nonJvmMain/kotlin/com/squareup/kotlinpoet/CodePoint.nonJvm.kt index f2b6eb9af..7d42f3b30 100644 --- a/kotlinpoet/src/nonJvmMain/kotlin/com/squareup/kotlinpoet/CodePoint.nonJvm.kt +++ b/kotlinpoet/src/nonJvmMain/kotlin/com/squareup/kotlinpoet/CodePoint.nonJvm.kt @@ -26,7 +26,7 @@ internal actual fun StringBuilder.appendCodePoint(codePoint: CodePoint): StringB if (code <= Char.MAX_VALUE.code) { append(code.toChar()) } else { - append(Char.MIN_HIGH_SURROGATE + ((code - 0x10000) shr 10)) + append(Char.MIN_HIGH_SURROGATE + ((code - 0x10000) ushr 10)) append(Char.MIN_LOW_SURROGATE + (code and 0x3ff)) } return this From 096dd4c2928172ff20b9086cec5dfb718fb4a093 Mon Sep 17 00:00:00 2001 From: ForteScarlet <1149159218@qq.com> Date: Thu, 21 Nov 2024 09:09:49 +0800 Subject: [PATCH 3/4] Remove leading 0 Co-authored-by: Jake Wharton --- .../kotlin/com/squareup/kotlinpoet/CodePoint.nonJvm.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinpoet/src/nonJvmMain/kotlin/com/squareup/kotlinpoet/CodePoint.nonJvm.kt b/kotlinpoet/src/nonJvmMain/kotlin/com/squareup/kotlinpoet/CodePoint.nonJvm.kt index 7d42f3b30..302f42034 100644 --- a/kotlinpoet/src/nonJvmMain/kotlin/com/squareup/kotlinpoet/CodePoint.nonJvm.kt +++ b/kotlinpoet/src/nonJvmMain/kotlin/com/squareup/kotlinpoet/CodePoint.nonJvm.kt @@ -51,5 +51,5 @@ internal actual fun CodePoint.isJavaIdentifierPart(): Boolean { } internal actual fun CodePoint.charCount(): Int { - return if (code >= 0x010000) 2 else 1 + return if (code >= 0x10000) 2 else 1 } From 59ba6a32e0c1989263095ef43455665fee40fb5e Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Thu, 21 Nov 2024 10:49:53 +0800 Subject: [PATCH 4/4] Better random tag effect --- .../commonMain/kotlin/com/squareup/kotlinpoet/NameAllocator.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kotlinpoet/src/commonMain/kotlin/com/squareup/kotlinpoet/NameAllocator.kt b/kotlinpoet/src/commonMain/kotlin/com/squareup/kotlinpoet/NameAllocator.kt index 6586756d2..73f6dd317 100644 --- a/kotlinpoet/src/commonMain/kotlin/com/squareup/kotlinpoet/NameAllocator.kt +++ b/kotlinpoet/src/commonMain/kotlin/com/squareup/kotlinpoet/NameAllocator.kt @@ -120,7 +120,8 @@ public class NameAllocator private constructor( */ @JvmOverloads public fun newName( suggestion: String, - tag: Any = Random.nextULong().toString(), // TODO Since Kotlin 2.0.20, it's possible to use kotlin.uuid.Uuid + // TODO It's possible to use `kotlin.uuid.Uuid` when it's stable + tag: Any = Random.nextULong().toString(16).padStart(16, '0'), ): String { var result = toJavaIdentifier(suggestion) while (!allocatedNames.add(result)) {