Allow renaming Java/Kotlin classes/packages during code generation (#499)

Adds a `rename` field to the Java/Kotlin code generators that allows renaming packages and classes during codegen.

* Add `--rename` flag to CLIs
* Add `rename` property to Gradle API
This commit is contained in:
Vladimir Matveev
2024-06-12 15:43:43 -07:00
committed by GitHub
parent b03530ed1f
commit d7a1778199
25 changed files with 1099 additions and 157 deletions

View File

@@ -0,0 +1,73 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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 org.pkl.commons
/**
* A helper class for translating names of Pkl modules to different names of classes and/or objects
* in the target language of a code generation execution.
*
* The `mapping` parameter is expected to contain valid prefixes of Pkl module names, with an
* optional dot at the end, and values should be valid class names in the language for which code
* generation is performed.
*
* If the rename patterns do not explicitly rename the class, the class name is capitalized.
*
* When computing the appropriate target name, the longest matching prefix is used.
*
* Prefix replacements are literal, and therefore dots are important. When renaming packages, in
* most cases, you must ensure that you have an ending dot on both sides of a mapping (except for
* the empty mapping, if you use it), otherwise you may get unexpected results:
* ```kotlin
* val mapper = NameMapper(
* mapOf(
* "com.foo." to "x", // Dot on the left only
* "org.bar" to "y.", // Dot on the right only
* "net.baz" to "z" // No dots
* )
* )
*
* assertThat(mapper.map("com.foo.bar")).isEqualTo("" to "xbar") // Target prefix merged into the suffix
* assertThat(mapper.map("org.bar.baz")).isEqualTo("y." to "Baz") // Double dot, invalid package name
* assertThat(mapper.map("net.baz.qux")).isEqualTo("z" to "Qux") // Looks okay, but...
* assertThat(mapper.map("net.bazqux")).isEqualTo("" to "zqux") // ...may cut the package name in the middle.
* ```
*/
class NameMapper(mapping: Map<String, String>) {
private val sortedMapping = mapping.toList().sortedBy { -it.first.length }
private fun doMap(sourceName: String): Pair<String, Boolean> {
for ((sourcePrefix, targetPrefix) in sortedMapping) {
if (sourceName.startsWith(sourcePrefix)) {
val rest = sourceName.substring(sourcePrefix.length)
val mapped = targetPrefix + rest
val wasClassRenamed =
!targetPrefix.endsWith('.') && (sourcePrefix.length - 1) >= sourceName.lastIndexOf('.')
return mapped to wasClassRenamed
}
}
return sourceName to false
}
fun map(sourceName: String): Pair<String, String> {
val (mappedName, wasClassRenamed) = doMap(sourceName)
val packageName = mappedName.substringBeforeLast(".", "")
val mappedClassName = mappedName.substringAfterLast(".")
val className =
if (wasClassRenamed) mappedClassName
else mappedClassName.replaceFirstChar { it.titlecaseChar() }
return packageName to className
}
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
*
* 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 org.pkl.commons
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
class NameMapperTest {
@Test
fun `empty prefixes everything`() {
val mapper = NameMapper(mapOf("" to "bar."))
assertThat(mapper.map("foo.bar.Baz")).isEqualTo("bar.foo.bar" to "Baz")
assertThat(mapper.map("Baz")).isEqualTo("bar" to "Baz")
}
@Test
fun `longest prefix wins`() {
val mapper = NameMapper(mapOf("bar." to "com.bar.", "bar.baz." to "foo.bar."))
assertThat(mapper.map("bar.baz.Buzzy")).isEqualTo("foo.bar" to "Buzzy")
}
@Test
fun `implicit uppercase classname`() {
val mapper = NameMapper(mapOf("foo." to "bar."))
assertThat(mapper.map("foo.bar.baz")).isEqualTo("bar.bar" to "Baz")
assertThat(mapper.map("foo.bar")).isEqualTo("bar" to "Bar")
assertThat(mapper.map("baz")).isEqualTo("" to "Baz")
assertThat(mapper.map("baz")).isEqualTo("" to "Baz")
}
@Test
fun `no implicit uppercased classname if explicitly renamed`() {
val mapper =
NameMapper(
mapOf(
"foo.bar" to "bar.bar",
"foo.c" to "foo.z",
"com.foo." to "x",
)
)
assertThat(mapper.map("foo.bar")).isEqualTo("bar" to "bar")
assertThat(mapper.map("foo.bar")).isEqualTo("bar" to "bar")
assertThat(mapper.map("foo.cow")).isEqualTo("foo" to "zow")
assertThat(mapper.map("com.foo.bar")).isEqualTo("" to "xbar")
}
}