Skip to content

Commit f9cda5b

Browse files
authored
Keyword sanitization for Lens code generation (arrow-kt#2924) (arrow-kt#2925)
1 parent 50c80d5 commit f9cda5b

File tree

9 files changed

+127
-6
lines changed

9 files changed

+127
-6
lines changed

arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/domain.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ data class ADT(val pckg: KSName, val declaration: KSClassDeclaration, val target
99
val sourceClassName = declaration.qualifiedNameOrSimpleName
1010
val sourceName = declaration.simpleName.asString().replaceFirstChar { it.lowercase(Locale.getDefault()) }
1111
val simpleName = declaration.nameWithParentClass
12-
val packageName = pckg.asString()
12+
val packageName = pckg.asSanitizedString()
1313
val visibilityModifierName = when (declaration.companionObject?.getVisibility()) {
1414
Visibility.INTERNAL -> "internal"
1515
else -> "public"

arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/processor.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ internal fun evalAnnotatedPrismElement(
8080
}
8181

8282
internal val KSDeclaration.qualifiedNameOrSimpleName: String
83-
get() = (qualifiedName ?: simpleName).asString()
83+
get() = (qualifiedName ?: simpleName).asSanitizedString()
8484

8585
internal fun KSClassDeclaration.sealedSubclassFqNameList(): List<String> =
8686
getSealedSubclasses().mapNotNull { it.qualifiedName?.asString() }.toList()
@@ -135,10 +135,10 @@ internal fun KSClassDeclaration.getConstructorTypesNames(): List<String> =
135135

136136
internal fun KSType.qualifiedString(): String = when (declaration) {
137137
is KSTypeParameter -> {
138-
val n = declaration.simpleName.asString()
138+
val n = declaration.simpleName.asSanitizedString()
139139
if (isMarkedNullable) "$n?" else n
140140
}
141-
else -> when (val qname = declaration.qualifiedName?.asString()) {
141+
else -> when (val qname = declaration.qualifiedName?.asSanitizedString()) {
142142
null -> toString()
143143
else -> {
144144
val withArgs = when {
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,108 @@
11
package arrow.optics.plugin.internals
22

3+
import com.google.devtools.ksp.symbol.KSName
4+
35
/**
46
* From Eugenio's https://github.com/Takhion/kotlin-metadata If this [isNotBlank] then it adds the
57
* optional [prefix] and [postfix].
68
*/
79
fun String.plusIfNotBlank(prefix: String = "", postfix: String = "") =
810
if (isNotBlank()) "$prefix${this}$postfix" else this
11+
12+
/**
13+
* Sanitizes each delimited section if it matches with Kotlin reserved keywords.
14+
*/
15+
fun KSName.asSanitizedString(delimiter: String = ".", separator: String = delimiter) =
16+
asString().splitToSequence(delimiter).joinToString(separator) { if (kotlinKeywords.contains(it)) "`$it`" else it }
17+
18+
19+
private val kotlinKeywords = setOf(
20+
// Hard keywords
21+
"as",
22+
"break",
23+
"class",
24+
"continue",
25+
"do",
26+
"else",
27+
"false",
28+
"for",
29+
"fun",
30+
"if",
31+
"in",
32+
"interface",
33+
"is",
34+
"null",
35+
"object",
36+
"package",
37+
"return",
38+
"super",
39+
"this",
40+
"throw",
41+
"true",
42+
"try",
43+
"typealias",
44+
"typeof",
45+
"val",
46+
"var",
47+
"when",
48+
"while",
49+
50+
// Soft keywords
51+
"by",
52+
"catch",
53+
"constructor",
54+
"delegate",
55+
"dynamic",
56+
"field",
57+
"file",
58+
"finally",
59+
"get",
60+
"import",
61+
"init",
62+
"param",
63+
"property",
64+
"receiver",
65+
"set",
66+
"setparam",
67+
"where",
68+
69+
// Modifier keywords
70+
"actual",
71+
"abstract",
72+
"annotation",
73+
"companion",
74+
"const",
75+
"crossinline",
76+
"data",
77+
"enum",
78+
"expect",
79+
"external",
80+
"final",
81+
"infix",
82+
"inline",
83+
"inner",
84+
"internal",
85+
"lateinit",
86+
"noinline",
87+
"open",
88+
"operator",
89+
"out",
90+
"override",
91+
"private",
92+
"protected",
93+
"public",
94+
"reified",
95+
"sealed",
96+
"suspend",
97+
"tailrec",
98+
"value",
99+
"vararg",
100+
101+
// These aren't keywords anymore but still break some code if unescaped.
102+
// https://youtrack.jetbrains.com/issue/KT-52315
103+
"header",
104+
"impl",
105+
106+
// Other reserved keywords
107+
"yield",
108+
)

arrow-libs/optics/arrow-optics-ksp-plugin/src/test/kotlin/arrow/optics/plugin/DSLTests.kt

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class DSLTests {
77
@Test
88
fun `DSL is generated for complex model with Every`() {
99
"""
10+
|$`package`
1011
|$imports
1112
|$dslModel
1213
|$dslValues
@@ -19,6 +20,7 @@ class DSLTests {
1920
@Test
2021
fun `DSL is generated for complex model with At`() {
2122
"""
23+
|$`package`
2224
|$imports
2325
|$dslModel
2426
|$dslValues
@@ -32,6 +34,7 @@ class DSLTests {
3234
// it's important to keep the 'Source' name for the class,
3335
// because files in the test are named 'Source.kt'
3436
"""
37+
|$`package`
3538
|$imports
3639
|
3740
|@optics

arrow-libs/optics/arrow-optics-ksp-plugin/src/test/kotlin/arrow/optics/plugin/IsoTests.kt

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class IsoTests {
99
@Test
1010
fun `Isos will be generated for data class`() {
1111
"""
12+
|$`package`
1213
|$imports
1314
|@optics
1415
|data class IsoData(
@@ -23,6 +24,7 @@ class IsoTests {
2324
@Test
2425
fun `Isos will be generated for generic data class`() {
2526
"""
27+
|$`package`
2628
|$imports
2729
|@optics
2830
|data class IsoData<A>(
@@ -37,6 +39,7 @@ class IsoTests {
3739
@Test
3840
fun `Isos will be generated for data class with secondary constructors`() {
3941
"""
42+
|$`package`
4043
|$imports
4144
|@optics
4245
|data class IsoSecondaryConstructor(val fieldNumber: Int, val fieldString: String) {
@@ -52,6 +55,7 @@ class IsoTests {
5255
@Test
5356
fun `Iso generation requires companion object declaration`() {
5457
"""
58+
|$`package`
5559
|$imports
5660
|@optics
5761
|data class IsoNoCompanion(
@@ -63,6 +67,7 @@ class IsoTests {
6367
@Test
6468
fun `Isos cannot be generated for huge classes`() {
6569
"""
70+
|$`package`
6671
|$imports
6772
|@optics
6873
|data class IsoXXL(
@@ -92,6 +97,6 @@ class IsoTests {
9297
|) {
9398
| companion object
9499
|}
95-
""".failsWith { it.contains("IsoXXL".isoTooBigErrorMessage) }
100+
""".failsWith { it.contains("${`package`.removePrefix("package ")}.IsoXXL".isoTooBigErrorMessage) }
96101
}
97102
}

arrow-libs/optics/arrow-optics-ksp-plugin/src/test/kotlin/arrow/optics/plugin/LensTests.kt

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package arrow.optics.plugin
22

3-
import arrow.optics.plugin.internals.typeParametersErrorMessage
43
import org.junit.jupiter.api.Test
54

65
class LensTests {
76

87
@Test
98
fun `Lenses will be generated for data class`() {
109
"""
10+
|$`package`
1111
|$imports
1212
|@optics
1313
|data class LensData(
@@ -22,6 +22,7 @@ class LensTests {
2222
@Test
2323
fun `Lenses will be generated for data class with secondary constructors`() {
2424
"""
25+
|$`package`
2526
|$imports
2627
|@optics
2728
|data class LensesSecondaryConstructor(val fieldNumber: Int, val fieldString: String) {
@@ -37,6 +38,7 @@ class LensTests {
3738
@Test
3839
fun `Lenses which mentions imported elements`() {
3940
"""
41+
|$`package`
4042
|$imports
4143
|
4244
|@optics
@@ -52,6 +54,7 @@ class LensTests {
5254
@Test
5355
fun `Lenses which mentions type arguments`() {
5456
"""
57+
|$`package`
5558
|$imports
5659
|@optics
5760
|data class OpticsTest<A>(val field: A) {
@@ -66,6 +69,7 @@ class LensTests {
6669
@Test
6770
fun `Lenses for nested classes`() {
6871
"""
72+
|$`package`
6973
|$imports
7074
|@optics
7175
|data class LensData(val field1: String) {
@@ -84,6 +88,7 @@ class LensTests {
8488
@Test
8589
fun `Lenses for nested classes with repeated names (#2718)`() {
8690
"""
91+
|$`package`
8792
|$imports
8893
|@optics
8994
|data class LensData(val field1: String) {

arrow-libs/optics/arrow-optics-ksp-plugin/src/test/kotlin/arrow/optics/plugin/OptionalTests.kt

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class OptionalTests {
77
@Test
88
fun `Optional will be generated for data class`() {
99
"""
10+
|$`package`
1011
|$imports
1112
|@optics
1213
|data class OptionalData(
@@ -21,6 +22,7 @@ class OptionalTests {
2122
@Test
2223
fun `Optional will be generated for generic data class`() {
2324
"""
25+
|$`package`
2426
|$imports
2527
|@optics
2628
|data class OptionalData<A>(
@@ -35,6 +37,7 @@ class OptionalTests {
3537
@Test
3638
fun `Optional will be generated for data class with secondary constructors`() {
3739
"""
40+
|$`package`
3841
|$imports
3942
|@optics
4043
|data class OptionalSecondaryConstructor(val fieldNumber: Int?, val fieldString: String?) {

arrow-libs/optics/arrow-optics-ksp-plugin/src/test/kotlin/arrow/optics/plugin/PrismTests.kt

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class PrismTests {
77
@Test
88
fun `Prism will be generated for sealed class`() {
99
"""
10+
|$`package`
1011
|$imports
1112
|@optics
1213
|sealed class PrismSealed(val field: String, val nullable: String?) {
@@ -22,6 +23,7 @@ class PrismTests {
2223
@Test
2324
fun `Prism will be generated for generic sealed class`() {
2425
"""
26+
|$`package`
2527
|$imports
2628
|@optics
2729
|sealed class PrismSealed<A,B>(val field: A, val nullable: B?) {
@@ -37,6 +39,7 @@ class PrismTests {
3739
@Test
3840
fun `Prism will not be generated for sealed class if DSL Target is specified`() {
3941
"""
42+
|$`package`
4043
|$imports
4144
|@optics([OpticsTarget.DSL])
4245
|sealed class PrismSealed(val field: String, val nullable: String?) {

arrow-libs/optics/arrow-optics-ksp-plugin/src/test/kotlin/arrow/optics/plugin/Utils.kt

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package arrow.optics.plugin
22

3+
const val `package` = "package `if`.`this`.`object`.`is`.`finally`.`null`.`expect`.`annotation`"
4+
35
const val imports =
46
"""
57
import arrow.core.None

0 commit comments

Comments
 (0)