Skip to content

Commit

Permalink
Use FQ names for generated hint files to avoid collisions (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
r0adkll authored Sep 13, 2024
1 parent 4bab568 commit e39b94b
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 19 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Fixed explicit parent components with @MergeComponent components - [#52](https://github.com/r0adkll/kimchi/pull/52)
- Fixed non-primitive custom keys not generating correctly - [#55](https://github.com/r0adkll/kimchi/pull/55)
- Fixed name collisions with generated hint classes - [#56](https://github.com/r0adkll/kimchi/pull/56)

## [0.3.1] - 2024-09-10

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package com.r0adkll.kimchi.util.ksp

import com.google.devtools.ksp.getAllSuperTypes
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSName
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.ksp.toClassName

Expand All @@ -16,3 +17,6 @@ public fun KSClassDeclaration.implements(className: ClassName): Boolean {
it.toClassName() == className
}
}

public val KSClassDeclaration.requireQualifiedName: KSName
get() = qualifiedName!!
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (C) 2024 r0adkll
// SPDX-License-Identifier: Apache-2.0
package com.r0adkll.kimchi.util.ksp

import com.google.devtools.ksp.symbol.KSName

/**
* Convert a [KSName] to a URL-safe string that can be used as a file name
* @receiver [KSName]
* @return a url safe name with '.' replaced with '_' characters
*/
public fun KSName.asUrlSafeString(): String = asString().replace(".", "_")
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.r0adkll.kimchi.REFERENCE_SUFFIX
import com.r0adkll.kimchi.SCOPE_SUFFIX
import com.r0adkll.kimchi.util.buildFile
import com.r0adkll.kimchi.util.ksp.asUrlSafeString
import com.r0adkll.kimchi.util.ksp.requireQualifiedName
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.KModifier
Expand Down Expand Up @@ -65,10 +67,10 @@ internal abstract class HintSymbolProcessor(
private fun process(
element: KSClassDeclaration,
): FileSpec {
val fileName = element.simpleName.asString()
// TODO: In other examples using the fq name for file names has been known to hit length limits
// so we should look into using a hash instead.
val fileName = element.requireQualifiedName.asUrlSafeString()
val className = element.toClassName()
val propertyName = element.qualifiedName!!.asString()
.replace(".", "_")

validate(element)

Expand All @@ -79,7 +81,7 @@ internal abstract class HintSymbolProcessor(
addProperty(
PropertySpec
.builder(
name = propertyName + REFERENCE_SUFFIX,
name = fileName + REFERENCE_SUFFIX,
type = KClass::class.asClassName().parameterizedBy(className),
)
.initializer("%T::class", className)
Expand All @@ -91,7 +93,7 @@ internal abstract class HintSymbolProcessor(
addProperty(
PropertySpec
.builder(
name = propertyName + SCOPE_SUFFIX,
name = fileName + SCOPE_SUFFIX,
type = KClass::class.asClassName().parameterizedBy(scope),
)
.initializer("%T::class", scope)
Expand Down
34 changes: 21 additions & 13 deletions compiler/src/test/kotlin/com/r0adkll/kimchi/TestUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
package com.r0adkll.kimchi

import com.r0adkll.kimchi.util.capitalized
import com.squareup.kotlinpoet.asClassName
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
Expand All @@ -22,7 +23,19 @@ fun Class<*>.getHintScope(hintPackage: String): KClass<*>? =
fun Class<*>.contributedProperties(
hintPackageName: String,
): List<KClass<*>>? {
val clazz = topLevelClass(hintPackageName) ?: return null
// The capitalize() comes from kotlinc's implicit handling of file names -> class names. It will
// always, unless otherwise instructed via `@file:JvmName`, capitalize its facade class.
val className = kotlin.asClassName()
.canonicalName
.replace(".", "_")
.capitalized()
.plus("Kt")

val clazz = try {
classLoader.loadClass("$hintPackageName.$className")
} catch (e: ClassNotFoundException) {
return null
}

return clazz.declaredFields
.sortedBy { it.name }
Expand All @@ -33,25 +46,20 @@ fun Class<*>.contributedProperties(
fun Class<*>.topLevelFunctions(
packageName: String,
): List<KFunction<*>>? {
val clazz = topLevelClass(packageName) ?: return null

return clazz.declaredMethods
.sortedBy { it.name }
.mapNotNull { method -> method.kotlinFunction }
}

private fun Class<*>.topLevelClass(
packageName: String,
): Class<*>? {
// The capitalize() comes from kotlinc's implicit handling of file names -> class names. It will
// always, unless otherwise instructed via `@file:JvmName`, capitalize its facade class.
val className = kotlin.asClassName()
.simpleName
.capitalized()
.plus("Kt")

return try {
val clazz = try {
classLoader.loadClass("$packageName.$className")
} catch (e: ClassNotFoundException) {
null
return null
}

return clazz.declaredMethods
.sortedBy { it.name }
.mapNotNull { method -> method.kotlinFunction }
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import com.tschuchort.compiletesting.KotlinCompilation
import java.io.File
import kotlin.reflect.KClass
import org.intellij.lang.annotations.Language
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.CleanupMode
import org.junit.jupiter.api.io.TempDir
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource
Expand All @@ -24,7 +26,7 @@ import strikt.assertions.isEqualTo

class HintSymbolProcessorTest {

@TempDir
@TempDir(cleanup = CleanupMode.ON_SUCCESS)
lateinit var workingDir: File

@ParameterizedTest
Expand All @@ -44,6 +46,30 @@ class HintSymbolProcessorTest {
}
}

@Test
fun `Duplicate class names do NOT cause a collision`() {
println(workingDir.absolutePath)
compileKimchiWithTestSources(
"""
package kimchi
import com.r0adkll.kimchi.annotations.ContributesTo
interface Outer1 {
@ContributesTo(TestScope::class)
interface Inner
}
interface Outer2 {
@ContributesTo(TestScope::class)
interface Inner
}
""".trimIndent(),
workingDir = workingDir,
expectExitCode = KotlinCompilation.ExitCode.OK,
)
}

companion object {
@JvmStatic
fun hintTestParameters(): List<HintTest> = listOf(
Expand Down

0 comments on commit e39b94b

Please sign in to comment.