From 490fe833f5e72fe3c7f6a83b4abc2b6bf8c8f8ef Mon Sep 17 00:00:00 2001 From: Doug Mealing Date: Sat, 20 Jun 2026 02:07:28 -0400 Subject: [PATCH] fix(codegen-kotlin): import cross-package Table objects in MetadataStartupValidator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit KotlinValidatorGenerator emitted bare `Table` references in the generated MetadataStartupValidator registry, but the Table objects live in their entity's own package (`partylore.arc.ActiveStoryArcTable`, …) while the validator is emitted into a single configured package. Any entity in a different package than the validator produced an "unresolved reference" compile error — i.e. every multi-package project. The validator now imports each Table object whose package differs from its own, mirroring the cross-package import handling in KotlinExposedTableGenerator. Adds a multi-package regression test; the existing single-package test is unchanged (no imports emitted when packages match). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../kotlin/KotlinValidatorGenerator.kt | 19 +++++++--- .../kotlin/KotlinValidatorGeneratorTest.kt | 35 +++++++++++++++++++ 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/server/java/codegen-kotlin/src/main/kotlin/com/metaobjects/generator/kotlin/KotlinValidatorGenerator.kt b/server/java/codegen-kotlin/src/main/kotlin/com/metaobjects/generator/kotlin/KotlinValidatorGenerator.kt index fa368c9fa..56386efe9 100644 --- a/server/java/codegen-kotlin/src/main/kotlin/com/metaobjects/generator/kotlin/KotlinValidatorGenerator.kt +++ b/server/java/codegen-kotlin/src/main/kotlin/com/metaobjects/generator/kotlin/KotlinValidatorGenerator.kt @@ -43,16 +43,23 @@ open class KotlinValidatorGenerator : MultiFileDirectGeneratorBase() .filter { !KotlinGenUtil.isAbstractEntity(it) } .filter { it.children.any { c -> c is RdbSource } } .map { entity -> - val shortName = PackageMapping.splitFqn(entity.name).second - entity.name to "${shortName}Table" + val (tablePkg, shortName) = PackageMapping.splitFqn(entity.name) + // (metadata FQN, Table object name, Table's Kotlin package) + Triple(entity.name, "${shortName}Table", tablePkg) } emitValidator(pkg, entries, outRoot) emitHelper(pkg, outRoot) } - protected open fun emitValidator(pkg: String, entries: List>, outRoot: Path) { - val registry = entries.joinToString(",\n ") { (fqn, table) -> "\"$fqn\" to $table" } + protected open fun emitValidator(pkg: String, entries: List>, outRoot: Path) { + val registry = entries.joinToString(",\n ") { (fqn, table, _) -> "\"$fqn\" to $table" } + // The Table objects live in their entity's own package; import any that + // are NOT in this validator's package or the bare reference won't resolve. + val tableImports = entries + .filter { (_, _, tablePkg) -> tablePkg.isNotEmpty() && tablePkg != pkg } + .map { (_, table, tablePkg) -> "$tablePkg.$table" } + .toSortedSet() val source = buildString { if (pkg.isNotEmpty()) { @@ -60,7 +67,9 @@ open class KotlinValidatorGenerator : MultiFileDirectGeneratorBase() } append("import com.metaobjects.loader.MetaDataLoader\n") append("import com.metaobjects.metadata.ktx.metaObjectOrNull\n") - append("import org.jetbrains.exposed.sql.Table\n\n") + append("import org.jetbrains.exposed.sql.Table\n") + for (imp in tableImports) append("import $imp\n") + append("\n") append("/**\n") append(" * GENERATED — runtime drift gate. Call [validate] from a Spring `@PostConstruct` or\n") append(" * `ApplicationReadyEvent` listener to fail-fast when generated Tables drift from metadata.\n") diff --git a/server/java/codegen-kotlin/src/test/kotlin/com/metaobjects/generator/kotlin/KotlinValidatorGeneratorTest.kt b/server/java/codegen-kotlin/src/test/kotlin/com/metaobjects/generator/kotlin/KotlinValidatorGeneratorTest.kt index 96c0ff9d5..f3db10d60 100644 --- a/server/java/codegen-kotlin/src/test/kotlin/com/metaobjects/generator/kotlin/KotlinValidatorGeneratorTest.kt +++ b/server/java/codegen-kotlin/src/test/kotlin/com/metaobjects/generator/kotlin/KotlinValidatorGeneratorTest.kt @@ -46,4 +46,39 @@ class KotlinValidatorGeneratorTest { outDir.toFile().deleteRecursively() } } + + /** Entities in different packages than the validator: their Table objects must be imported. */ + private val multiPkgFixture = """{ + "metadata.root": { "children": [ + { "object.entity": { "name": "acme::blog::Author", "children": [ + { "field.long": { "name": "id" } }, + { "source.rdb": { "@table": "authors" } }, + { "identity.primary": { "name": "pk", "@fields": ["id"] } } + ] } }, + { "object.entity": { "name": "acme::shop::Order", "children": [ + { "field.long": { "name": "id" } }, + { "source.rdb": { "@table": "orders" } }, + { "identity.primary": { "name": "pk", "@fields": ["id"] } } + ] } } + ] } + }""".trimIndent() + + @Test fun `imports Table objects that live in a different package than the validator`() { + val outDir = Files.createTempDirectory("kvld-") + try { + val gen = KotlinValidatorGenerator() + gen.setArgs(mapOf("outputDir" to outDir.toString(), "packageName" to "acme")) + gen.execute(loadString("multi", multiPkgFixture)) + + val vSrc = Files.readString(outDir.resolve("acme/MetadataStartupValidator.kt")) + assertTrue("import acme.blog.AuthorTable" in vSrc, + "expected cross-package import for AuthorTable; saw:\n$vSrc") + assertTrue("import acme.shop.OrderTable" in vSrc, + "expected cross-package import for OrderTable; saw:\n$vSrc") + assertTrue("\"acme::blog::Author\" to AuthorTable" in vSrc, vSrc) + assertTrue("\"acme::shop::Order\" to OrderTable" in vSrc, vSrc) + } finally { + outDir.toFile().deleteRecursively() + } + } }