diff --git a/docs/mutable-utils/styles/jetbrains-mono.css b/docs/mutable-utils/styles/jetbrains-mono.css deleted file mode 100644 index 9a0f06f..0000000 --- a/docs/mutable-utils/styles/jetbrains-mono.css +++ /dev/null @@ -1,17 +0,0 @@ -@font-face{ - font-family: 'JetBrains Mono'; - src: url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/web/JetBrainsMono-Regular.eot') format('embedded-opentype'), - url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/webfonts/JetBrainsMono-Regular.woff2') format('woff2'), - url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/ttf/JetBrainsMono-Regular.ttf') format('truetype'); - font-weight: normal; - font-style: normal; -} - -@font-face{ - font-family: 'JetBrains Mono'; - src: url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/web/JetBrainsMono-Bold.eot') format('embedded-opentype'), - url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/webfonts/JetBrainsMono-Bold.woff2') format('woff2'), - url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/ttf/JetBrainsMono-Bold.ttf') format('truetype'); - font-weight: bold; - font-style: bold; -} \ No newline at end of file diff --git a/kopykat-ksp/src/main/kotlin/at/kopyk/CopyConstructors.kt b/kopykat-ksp/src/main/kotlin/at/kopyk/CopyConstructors.kt index 40175bd..47e3a41 100644 --- a/kopykat-ksp/src/main/kotlin/at/kopyk/CopyConstructors.kt +++ b/kopykat-ksp/src/main/kotlin/at/kopyk/CopyConstructors.kt @@ -11,6 +11,7 @@ import at.kopyk.utils.fullName import at.kopyk.utils.getPrimaryConstructorProperties import at.kopyk.utils.lang.takeIfInstanceOf import at.kopyk.utils.minimal +import at.kopyk.utils.sanitizedName import com.google.devtools.ksp.getVisibility import com.google.devtools.ksp.symbol.KSAnnotated import com.google.devtools.ksp.symbol.KSAnnotation @@ -75,7 +76,7 @@ private fun TypeCompileScope.copyConstructorFileSpec( to.properties.toList().zipByName(from.getAllProperties().toList()) { to, from -> propertyDefinition(others, from, to) }.filterNotNull() - addReturn("${to.baseName}(${propertyAssignments.joinToString()})") + addReturn("${to.sanitizedName}(${propertyAssignments.joinToString()})") } } } @@ -94,10 +95,10 @@ private fun propertyDefinition( ): String? { val fromType = from.typeDeclaration val toType = to.typeDeclaration - val propertyName = from.baseName + val propertyName = from.sanitizedName return when { fromType == null || toType == null -> null - others.hasCopyConstructor(fromType, toType) -> "$propertyName = ${toType.baseName}(from.$propertyName)" + others.hasCopyConstructor(fromType, toType) -> "$propertyName = ${toType.sanitizedName}(from.$propertyName)" else -> "$propertyName = from.$propertyName" } } diff --git a/kopykat-ksp/src/main/kotlin/at/kopyk/CopyMap.kt b/kopykat-ksp/src/main/kotlin/at/kopyk/CopyMap.kt index 66e773a..75a71f7 100644 --- a/kopykat-ksp/src/main/kotlin/at/kopyk/CopyMap.kt +++ b/kopykat-ksp/src/main/kotlin/at/kopyk/CopyMap.kt @@ -14,6 +14,7 @@ import at.kopyk.utils.fullName import at.kopyk.utils.lang.joinWithWhen import at.kopyk.utils.lang.mapRun import at.kopyk.utils.lang.onEachRun +import at.kopyk.utils.sanitizedName import at.kopyk.utils.typeCategory import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.ksp.toKModifier @@ -33,7 +34,7 @@ internal val TypeCompileScope.copyMapFunctionKt: FileSpec defaultValue = "{ it }", ) } - .mapRun { "$baseName = $baseName(this, this.$baseName)" } + .mapRun { "$sanitizedName = $sanitizedName(this, this.$sanitizedName)" } .run { addReturn(repeatOnSubclasses(joinToString(), "copy")) } } } diff --git a/kopykat-ksp/src/main/kotlin/at/kopyk/MutableCopy.kt b/kopykat-ksp/src/main/kotlin/at/kopyk/MutableCopy.kt index 0cacc17..ffe3d12 100644 --- a/kopykat-ksp/src/main/kotlin/at/kopyk/MutableCopy.kt +++ b/kopykat-ksp/src/main/kotlin/at/kopyk/MutableCopy.kt @@ -24,15 +24,20 @@ import at.kopyk.utils.lang.forEachRun import at.kopyk.utils.lang.joinWithWhen import at.kopyk.utils.mutable import at.kopyk.utils.onKnownCategory +import at.kopyk.utils.sanitizedName import at.kopyk.utils.typeCategory +import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.ParameterizedTypeName +import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.ksp.toKModifier internal val TypeCompileScope.mutableCopyKt: FileSpec get() = buildFile(target.mutable.reflectionName()) { + addImports() addGeneratedMarker() addDslMarkerClass() addMutableCopy() @@ -45,6 +50,31 @@ internal val TypeCompileScope.mutableCopyKt: FileSpec } } +internal fun FileCompilerScope.addImports() { + with(element) { + val targetPkg = target.packageName + mutationInfo.forEach { (declaration, mutationInfo) -> + if (declaration.type.resolve().hasMutableCopy()) { + mutationInfo.className.usedPackages.forEach { propertyPkg -> + if (propertyPkg != targetPkg) { + file.addImport(propertyPkg, "freeze", "toMutable") + } + } + } + } + } +} + +@Suppress("RecursivePropertyAccessor") +internal val TypeName.usedPackages: Set + get() = + when (this) { + is ClassName -> setOf(this.packageName) + is ParameterizedTypeName -> + this.rawType.usedPackages + this.typeArguments.flatMap { it.usedPackages } + else -> emptySet() + } + internal fun FileCompilerScope.addMutableCopy() { with(element) { file.addClass(target.mutable) { @@ -117,10 +147,10 @@ internal fun FileCompilerScope.addCopyClosure() { private fun FileCompilerScope.addRetrofittedCopyFunction() { with(element) { addCopyFunction { - properties.forEachRun { addParameter(name = baseName, type = typeName, defaultValue = "this.$baseName") } + properties.forEachRun { addParameter(name = baseName, type = typeName, defaultValue = "this.$sanitizedName") } addReturn( sealedTypes.joinWithWhen { type -> - "is ${type.fullName} -> this.copy(${properties.joinToString { "${it.baseName} = ${it.baseName}" }})" + "is ${type.fullName} -> this.copy(${properties.joinToString { "${it.sanitizedName} = ${it.sanitizedName}" }})" }, ) } diff --git a/kopykat-ksp/src/main/kotlin/at/kopyk/utils/KspExtensions.kt b/kopykat-ksp/src/main/kotlin/at/kopyk/utils/KspExtensions.kt index d3f1662..59b8c89 100644 --- a/kopykat-ksp/src/main/kotlin/at/kopyk/utils/KspExtensions.kt +++ b/kopykat-ksp/src/main/kotlin/at/kopyk/utils/KspExtensions.kt @@ -6,7 +6,10 @@ import com.google.devtools.ksp.symbol.KSDeclarationContainer import com.google.devtools.ksp.symbol.KSPropertyDeclaration internal val KSDeclaration.baseName: String - get() = simpleName.asString().sanitize() + get() = simpleName.asString() + +internal val KSDeclaration.sanitizedName: String + get() = baseName.sanitize() internal val KSDeclaration.fullName: String get() = qualifiedName?.asString() ?: simpleName.asString() @@ -36,10 +39,13 @@ private fun KSClassDeclaration.hasPrimaryProperty(property: KSPropertyDeclaratio /** * Sanitizes each delimited section if it matches with Kotlin reserved keywords. */ -private fun String.sanitize( +internal fun String.sanitize( delimiter: String = ".", prefix: String = "", -) = splitToSequence(delimiter).joinToString(delimiter, prefix) { if (it in KOTLIN_KEYWORDS) "`$it`" else it } +): String = + splitToSequence(delimiter).joinToString(delimiter, prefix) { + if (it in KOTLIN_KEYWORDS) "`$it`" else it + } private val KOTLIN_KEYWORDS = setOf( diff --git a/kopykat-ksp/src/main/kotlin/at/kopyk/utils/TypeCompileScope.kt b/kopykat-ksp/src/main/kotlin/at/kopyk/utils/TypeCompileScope.kt index 9cd4a00..bc4d8c6 100644 --- a/kopykat-ksp/src/main/kotlin/at/kopyk/utils/TypeCompileScope.kt +++ b/kopykat-ksp/src/main/kotlin/at/kopyk/utils/TypeCompileScope.kt @@ -57,7 +57,7 @@ internal sealed interface TypeCompileScope : KSDeclaration, LoggerScope { fun KSPropertyDeclaration.toAssignment( wrapper: (String) -> String, source: String? = null, - ): String = "$baseName = ${wrapper("${source ?: ""}$baseName")}" + ): String = "$sanitizedName = ${wrapper("${source ?: ""}$sanitizedName")}" fun Sequence>>.joinAsAssignmentsWithMutation( wrapper: MutationInfo.(String) -> String, diff --git a/kopykat-ksp/src/test/kotlin/at/kopyk/CopyMapTest.kt b/kopykat-ksp/src/test/kotlin/at/kopyk/CopyMapTest.kt index aea0287..b6dcab3 100644 --- a/kopykat-ksp/src/test/kotlin/at/kopyk/CopyMapTest.kt +++ b/kopykat-ksp/src/test/kotlin/at/kopyk/CopyMapTest.kt @@ -163,4 +163,15 @@ class CopyMapTest { |val r = p2.second """.evals("r" to 2) } + + @Test + fun `keyword as fields, issue #93`() { + """ + |data class Person(val `in`: String, val `out`: Int) + | + |val p1 = Person("Alex", 1) + |val p2 = p1.copyMap(`out` = { it + 1 }) + |val r = p2.`out` + """.evals("r" to 2) + } } diff --git a/kopykat-ksp/src/test/kotlin/at/kopyk/MutableCopyTest.kt b/kopykat-ksp/src/test/kotlin/at/kopyk/MutableCopyTest.kt index a3929aa..2d463ae 100644 --- a/kopykat-ksp/src/test/kotlin/at/kopyk/MutableCopyTest.kt +++ b/kopykat-ksp/src/test/kotlin/at/kopyk/MutableCopyTest.kt @@ -224,4 +224,15 @@ class MutableCopyTest { |val r = p2.d """.evals("r" to listOf("e", "f")) } + + @Test + fun `keyword as fields, issue #93`() { + """ + |data class Person(val `in`: String, val `out`: Int) + | + |val p1 = Person("Alex", 1) + |val p2 = p1.copy { `out` = `out` + 1 } + |val r = p2.`out` + """.evals("r" to 2) + } }