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 92% rename from kotlinpoet/src/jvmMain/kotlin/com/squareup/kotlinpoet/NameAllocator.kt rename to kotlinpoet/src/commonMain/kotlin/com/squareup/kotlinpoet/NameAllocator.kt index a6c570b9e..73f6dd317 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,8 @@ public class NameAllocator private constructor( */ @JvmOverloads public fun newName( suggestion: String, - tag: Any = UUID.randomUUID().toString(), + // 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)) { @@ -154,18 +157,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..302f42034 --- /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) ushr 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 >= 0x10000) 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)")