aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Santiago Rivera <danysantiago@google.com>2023-02-07 17:05:51 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-02-07 17:05:51 +0000
commit3a0794f490651afc8490a95f1a66a78526bed5b1 (patch)
tree32591b66c39ac18e0ab8aa8387dcfc6e07812cb5
parentfe5ac83ed28793b359d3f844ab46015ca4f30fa1 (diff)
parent3870e30a312431f6651dbd26812563e4214cef00 (diff)
downloadkotlinpoet-android14-d1-s6-release.tar.gz
Initial import of kotlinpoet from upstream master am: 8b6cde9a0e am: 920796b32b am: 3870e30a31frc_340821000frc_340819280frc_340819220frc_340819190frc_340819030frc_340819020frc_340819010frc_340818170frc_340818110android-vts-14.0_r4android-vts-14.0_r3android-vts-14.0_r2android-vts-14.0_r1android-security-14.0.0_r9android-security-14.0.0_r8android-security-14.0.0_r7android-security-14.0.0_r6android-security-14.0.0_r5android-security-14.0.0_r4android-security-14.0.0_r3android-security-14.0.0_r2android-security-14.0.0_r1android-platform-14.0.0_r7android-platform-14.0.0_r6android-platform-14.0.0_r5android-platform-14.0.0_r4android-platform-14.0.0_r3android-platform-14.0.0_r2android-platform-14.0.0_r1android-cts-14.0_r4android-cts-14.0_r3android-cts-14.0_r2android-cts-14.0_r1android-14.0.0_r9android-14.0.0_r8android-14.0.0_r7android-14.0.0_r6android-14.0.0_r5android-14.0.0_r45android-14.0.0_r44android-14.0.0_r43android-14.0.0_r42android-14.0.0_r41android-14.0.0_r40android-14.0.0_r4android-14.0.0_r39android-14.0.0_r38android-14.0.0_r3android-14.0.0_r28android-14.0.0_r27android-14.0.0_r26android-14.0.0_r25android-14.0.0_r24android-14.0.0_r23android-14.0.0_r22android-14.0.0_r21android-14.0.0_r20android-14.0.0_r2android-14.0.0_r19android-14.0.0_r18android-14.0.0_r17android-14.0.0_r16android-14.0.0_r15android-14.0.0_r14android-14.0.0_r13android-14.0.0_r12android-14.0.0_r11android-14.0.0_r10android-14.0.0_r1aml_wif_341410080aml_wif_341310010aml_wif_341110010aml_wif_341011010aml_wif_340913010aml_uwb_341310300aml_uwb_341310030aml_uwb_341111010aml_uwb_341011000aml_tet_341411060aml_tet_341310230aml_tet_341112070aml_tet_341010040aml_tet_340913030aml_swc_341312300aml_swc_341312020aml_swc_341111000aml_swc_341011020aml_swc_340922010aml_sta_341410000aml_sta_341311010aml_sta_341114000aml_sta_341111000aml_sta_341010020aml_sta_340912000aml_sta_340911000aml_sdk_341410000aml_sdk_341110080aml_sdk_341110000aml_sdk_341010000aml_sdk_340912010aml_rkp_341311000aml_rkp_341114000aml_rkp_341015010aml_rkp_341012000aml_res_341410010aml_res_341311030aml_res_341110000aml_res_340912000aml_per_341410020aml_per_341311000aml_per_341110020aml_per_341110010aml_per_341011100aml_per_341011020aml_per_340916010aml_neu_341010080aml_neu_341010000aml_net_341411030aml_net_341311010aml_net_341310020aml_net_341111030aml_net_341014000aml_net_340913000aml_mpr_341411070aml_mpr_341313030aml_mpr_341111030aml_mpr_341111020aml_mpr_341015090aml_mpr_341015030aml_mpr_340919000aml_med_341312300aml_med_341312020aml_med_341111000aml_med_341011000aml_med_340922010aml_ips_340914280aml_ips_340914200aml_ips_340914000aml_hef_341415040aml_hef_341311010aml_hef_341114030aml_ext_341414010aml_ext_341317010aml_ext_341131030aml_ext_341027030aml_doc_341312010aml_doc_341112000aml_doc_341012000aml_doc_340916000aml_con_341310090aml_con_341110000aml_cbr_341410010aml_cbr_341311010aml_cbr_341110000aml_cbr_341011000aml_cbr_340914000aml_ase_341410000aml_ase_341310010aml_ase_341113000aml_ase_340913000aml_art_341411300aml_art_341311100aml_art_341110110aml_art_341110060aml_art_341010050aml_art_340915060aml_ads_341413000aml_ads_341316030aml_ads_341131050aml_ads_341027030aml_ads_340915050aml_adb_340912530aml_adb_340912350aml_adb_340912200aml_adb_340912000android14-tests-releaseandroid14-security-releaseandroid14-s2-releaseandroid14-s1-releaseandroid14-releaseandroid14-qpr1-s2-releaseandroid14-qpr1-releaseandroid14-platform-releaseandroid14-mainline-wifi-releaseandroid14-mainline-uwb-releaseandroid14-mainline-tethering-releaseandroid14-mainline-sdkext-releaseandroid14-mainline-resolv-releaseandroid14-mainline-permission-releaseandroid14-mainline-os-statsd-releaseandroid14-mainline-networking-releaseandroid14-mainline-mediaprovider-releaseandroid14-mainline-media-swcodec-releaseandroid14-mainline-media-releaseandroid14-mainline-healthfitness-releaseandroid14-mainline-extservices-releaseandroid14-mainline-cellbroadcast-releaseandroid14-mainline-art-releaseandroid14-mainline-appsearch-releaseandroid14-mainline-adservices-releaseandroid14-mainline-adbd-releaseandroid14-gsiandroid14-devandroid14-d2-s5-releaseandroid14-d2-s4-releaseandroid14-d2-s3-releaseandroid14-d2-s2-releaseandroid14-d2-s1-releaseandroid14-d2-releaseandroid14-d1-s7-releaseandroid14-d1-s6-releaseandroid14-d1-s5-releaseandroid14-d1-s4-releaseandroid14-d1-s3-releaseandroid14-d1-s2-releaseandroid14-d1-s1-releaseandroid14-d1-release
Original change: https://android-review.googlesource.com/c/platform/external/kotlinpoet/+/2413152 Change-Id: Ic663a7bfaf78e703948aa55c9b9555d87a035ebd Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--.editorconfig12
-rw-r--r--.gitattributes4
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.md20
-rw-r--r--.github/ISSUE_TEMPLATE/feature_request.md20
-rw-r--r--.github/workflows/build.yml97
-rw-r--r--.github/workflows/mkdocs-requirements.txt19
-rw-r--r--.gitignore24
-rw-r--r--Android.bp14
l---------LICENSE1
-rw-r--r--LICENSE.txt202
-rw-r--r--METADATA17
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--OWNERS3
-rw-r--r--README.md23
-rw-r--r--RELEASING.md26
-rw-r--r--build.gradle.kts136
-rw-r--r--docs/changelog.md588
-rw-r--r--docs/contributing.md15
-rw-r--r--docs/css/app.css30
-rw-r--r--docs/images/icon-square.pngbin0 -> 1469 bytes
-rw-r--r--docs/index.md1541
-rw-r--r--docs/interop-javapoet.md54
-rw-r--r--docs/interop-kotlinx-metadata.md84
-rw-r--r--docs/interop-ksp.md136
-rw-r--r--gradle.properties19
-rw-r--r--gradle/libs.versions.toml51
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin0 -> 60756 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties5
-rwxr-xr-xgradlew240
-rw-r--r--gradlew.bat91
-rw-r--r--interop/javapoet/api/javapoet.api22
-rw-r--r--interop/javapoet/build.gradle.kts28
-rw-r--r--interop/javapoet/gradle.properties4
-rw-r--r--interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/K2jInterop.kt147
-rw-r--r--interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/KotlinPoetJavaPoetPreview.kt30
-rw-r--r--interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/PoetInterop.kt27
-rw-r--r--interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/j2kInterop.kt150
-rw-r--r--interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/typeAliases.kt62
-rw-r--r--interop/javapoet/src/test/kotlin/com/squareup/kotlinpoet/javapoet/PoetInteropTest.kt197
-rw-r--r--interop/kotlinx-metadata/api/kotlinx-metadata.api369
-rw-r--r--interop/kotlinx-metadata/build.gradle.kts44
-rw-r--r--interop/kotlinx-metadata/gradle.properties4
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/Flags.kt338
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/KotlinPoetMetadata.kt112
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ClassInspectorUtil.kt241
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ElementsClassInspector.kt585
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/JvmDescriptorUtils.kt194
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/Optional.kt25
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ReflectiveClassInspector.kt604
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ClassInspector.kt117
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ConstructorData.kt67
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ContainerData.kt104
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/FieldData.kt64
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmFieldModifier.kt40
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmMethodModifier.kt36
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmModifier.kt33
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KmTypes.kt294
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecs.kt948
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/MethodData.kt91
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/PropertyData.kt88
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/kmAnnotations.kt79
-rw-r--r--interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFile.kt96
-rw-r--r--interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFileTest.kt394
-rw-r--r--interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmNameWithKtFacadeFile.kt20
-rw-r--r--interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KmAnnotationsTest.kt260
-rw-r--r--interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecsTest.kt2250
-rw-r--r--interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/MultiClassInspectorTest.kt119
-rw-r--r--interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/NoJvmNameFacadeFile.kt18
-rw-r--r--interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/ReflectiveClassInspectorTest.kt90
-rw-r--r--interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/classinspectors/ClassInspectorUtilTest.kt96
-rw-r--r--interop/ksp/api/ksp.api64
-rw-r--r--interop/ksp/build.gradle.kts28
-rw-r--r--interop/ksp/gradle.properties4
-rw-r--r--interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Annotations.kt130
-rw-r--r--interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/KsClassDeclarations.kt24
-rw-r--r--interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/KsTypes.kt190
-rw-r--r--interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Modifiers.kt89
-rw-r--r--interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/OriginatingKSFiles.kt172
-rw-r--r--interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/TypeParameterResolver.kt95
-rw-r--r--interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Visibilities.kt35
-rw-r--r--interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/utils.kt68
-rw-r--r--interop/ksp/test-processor/build.gradle.kts36
-rw-r--r--interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessor.kt172
-rw-r--r--interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessorProvider.kt28
-rw-r--r--interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TypeAliasUnwrapping.kt68
-rw-r--r--interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/exampleAnnotations.kt55
-rw-r--r--interop/ksp/test-processor/src/test/kotlin/com/squareup/kotlinpoet/ksp/test/processor/KsTypesTest.kt86
-rw-r--r--interop/ksp/test-processor/src/test/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessorTest.kt497
-rw-r--r--kotlinpoet/api/kotlinpoet.api1164
-rw-r--r--kotlinpoet/build.gradle.kts52
-rw-r--r--kotlinpoet/gradle.properties4
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/AnnotationSpec.kt269
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/ClassName.kt277
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeBlock.kt522
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt778
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/ContextReceivable.kt54
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/DelicateKotlinPoetApi.kt32
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/Dynamic.kt31
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt29
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/FileSpec.kt551
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt669
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/Import.kt33
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/KModifier.kt92
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/KOperator.kt47
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt125
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/LineWrapper.kt175
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/MemberName.kt168
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/NameAllocator.kt139
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/OriginatingElementsHolder.kt49
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/ParameterSpec.kt257
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/ParameterizedTypeName.kt254
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt342
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/Taggable.kt158
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeAliasSpec.kt158
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeName.kt347
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeSpec.kt901
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeVariableName.kt276
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/Util.kt311
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/WildcardTypeName.kt136
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/jvm/JvmAnnotations.kt135
-rw-r--r--kotlinpoet/src/main/java/com/squareup/kotlinpoet/tags/TypeAliasTag.kt25
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/AbstractTypesTest.kt226
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/AnnotatedTypeNameTest.kt140
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/AnnotationSpecTest.kt601
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/AssertThrows.kt32
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/ClassNameTest.kt223
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/CodeBlockTest.kt632
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/CrossplatformTest.kt238
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/DelegatedConstructorCallTest.kt180
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/ExpectDeclarationsTest.kt81
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/ExternalDeclarationsTest.kt182
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileReadingTest.kt64
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileSpecTest.kt1215
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileWritingTest.kt307
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt1219
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/JavaClassWithArrayValueAnnotation.java18
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/KotlinPoetTest.kt1375
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt195
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/LineWrapperTest.kt232
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/LineWrappingTest.kt218
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/MemberNameTest.kt612
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/NameAllocatorTest.kt100
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/ParameterSpecTest.kt180
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/ParameterizedTypeNameTest.kt164
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt769
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/StringsTest.kt75
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/TaggableTest.kt130
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/TestFiler.kt128
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeAliasSpecTest.kt251
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeNameKotlinTest.kt92
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeNameTest.java161
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeSpecTest.kt5364
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeVariableNameTest.kt254
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypesEclipseTest.kt139
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypesTest.kt33
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/UtilTest.kt151
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/ValueTypeSpecTest.kt256
-rw-r--r--kotlinpoet/src/test/java/com/squareup/kotlinpoet/jvm/JvmAnnotationsTest.kt1215
-rw-r--r--mkdocs.yml69
-rw-r--r--renovate.json12
-rw-r--r--settings.gradle.kts29
161 files changed, 39922 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..3ac56613
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,12 @@
+root = true
+
+[*]
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.{kt,kts}]
+ij_kotlin_imports_layout = *
+ij_kotlin_allow_trailing_comma = true
+ij_kotlin_allow_trailing_comma_on_call_site = true
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..27fc7c29
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+* text=auto eol=lf
+
+*.bat text eol=crlf
+*.jar binary \ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000..cbe36f66
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,20 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+A sample Github project that reproduces the problem is ideal. Alternatively, please provide a failing unit test that triggers the bug.
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000..11fc491e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: enhancement
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..46c31895
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,97 @@
+name: Build
+
+on: [push, pull_request]
+
+env:
+ GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle.daemon=false -Dkotlin.incremental=false"
+
+jobs:
+ jvm:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Validate Gradle Wrapper
+ uses: gradle/wrapper-validation-action@v1
+
+ - name: Configure JDK
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'zulu'
+ java-version: 18
+
+ - name: Test
+ run: ./gradlew build
+
+ build-docs:
+ runs-on: ubuntu-latest
+ if: github.repository == 'square/kotlinpoet' && github.ref != 'refs/heads/master'
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Configure JDK
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'zulu'
+ java-version: 18
+
+ - name: Prep docs
+ run: ./gradlew dokkaHtml
+
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: 3.8
+
+ - name: Build mkdocs
+ run: |
+ pip3 install -r .github/workflows/mkdocs-requirements.txt
+ mkdocs build
+
+ publish:
+ runs-on: ubuntu-latest
+ if: github.repository == 'square/kotlinpoet' && github.ref == 'refs/heads/master'
+ needs:
+ - jvm
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Configure JDK
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'zulu'
+ java-version: 18
+
+ - name: Upload Artifacts
+ run: ./gradlew publish
+ env:
+ ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
+ ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
+
+ - name: Prep docs
+ run: ./gradlew dokkaHtml
+
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: 3.8
+
+ - name: Build mkdocs
+ run: |
+ pip3 install -r .github/workflows/mkdocs-requirements.txt
+ mkdocs build
+
+ - name: Deploy 🚀
+ if: success() && github.ref == 'refs/heads/master'
+ uses: JamesIves/github-pages-deploy-action@releases/v3
+ with:
+ GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
+ BRANCH: gh-pages # The branch the action should deploy to.
+ FOLDER: site # The folder the action should deploy.
+ SINGLE_COMMIT: true
diff --git a/.github/workflows/mkdocs-requirements.txt b/.github/workflows/mkdocs-requirements.txt
new file mode 100644
index 00000000..67c640f2
--- /dev/null
+++ b/.github/workflows/mkdocs-requirements.txt
@@ -0,0 +1,19 @@
+click==8.1.3
+future==0.18.3
+Jinja2==3.1.2
+livereload==2.6.3
+lunr==0.6.2
+Markdown==3.3.7 # See https://github.com/mkdocs/mkdocs/issues/2892.
+MarkupSafe==2.1.2
+mkdocs==1.4.2
+mkdocs-macros-plugin==0.7.0
+mkdocs-material==9.0.9
+mkdocs-material-extensions==1.1.1
+Pygments==2.14.0
+pymdown-extensions==9.9.2
+python-dateutil==2.8.2
+PyYAML==6.0
+repackage==0.7.3
+six==1.16.0
+termcolor==2.2.0
+tornado==6.2
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..75e7808e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+.classpath
+.gradle
+.project
+.settings
+eclipsebin
+local.properties
+
+bin
+gen
+build
+out
+lib
+reports
+
+.idea
+*.iml
+classes
+
+# Mkdocs files
+docs/1.x/*
+
+obj
+
+.DS_Store
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 00000000..ef85457e
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,14 @@
+package {
+ default_applicable_licenses: ["external_kotlinpoet_license"],
+}
+
+license {
+ name: "external_kotlinpoet_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "LICENSE.txt",
+ ],
+}
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 00000000..85de3d45
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+LICENSE.txt \ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 00000000..62589edd
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ https://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/METADATA b/METADATA
new file mode 100644
index 00000000..95ed8d4d
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,17 @@
+name: "kotlinpoet"
+description:
+ "A Kotlin API for generating .kt source files"
+
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://square.github.io/kotlinpoet/"
+ }
+ url {
+ type: GIT
+ value: "https://github.com/square/kotlinpoet.git"
+ }
+ version: "c99ed244c6e4c7f8594f75566bb19ec62fa9c80d"
+ last_upgrade_date { year: 2023 month: 1 day: 31 }
+ license_type: NOTICE
+}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 00000000..22812017
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,3 @@
+krzysio@google.com
+danysantiago@google.com
+aurimas@google.com
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..90ee21b7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+KotlinPoet
+==========
+
+`KotlinPoet` is a Kotlin and Java API for generating `.kt` source files.
+
+### [square.github.io/kotlinpoet](https://square.github.io/kotlinpoet)
+
+License
+-------
+
+ Copyright 2017 Square, Inc.
+
+ 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.
diff --git a/RELEASING.md b/RELEASING.md
new file mode 100644
index 00000000..f987b6eb
--- /dev/null
+++ b/RELEASING.md
@@ -0,0 +1,26 @@
+Releasing
+=========
+
+ 1. Change the version in `gradle.properties` to a non-SNAPSHOT version.
+ 2. Update `docs/changelog.md` for the impending release.
+ 3. `git commit -am "Prepare for release X.Y.Z."` (where X.Y.Z is the new version)
+ 4. `./gradlew clean publish --no-daemon --no-parallel`.
+ 5. Visit [Sonatype Nexus][sonatype] and ensure there's only one staging repository.
+ 6. `./gradlew closeAndReleaseRepository`.
+ 7. `git tag -a X.Y.Z -m "Version X.Y.Z"` (where X.Y.Z is the new version).
+ 8. Update `gradle.properties` to the next SNAPSHOT version.
+ 9. `git commit -am "Prepare next development version."`.
+ 10. `git push && git push --tags`.
+
+If steps 5-6 fail, drop the Sonatype repo, fix the problem, commit, and start again at step 4.
+
+
+Prerequisites
+-------------
+
+In `~/.gradle/gradle.properties`, set the following:
+
+ * `ORG_GRADLE_PROJECT_mavenCentralUsername` - Sonatype username for releasing to `com.squareup`.
+ * `ORG_GRADLE_PROJECT_mavenCentralPassword` - Sonatype password for releasing to `com.squareup`.
+
+ [sonatype]: https://s01.oss.sonatype.org/
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 00000000..421de38b
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * 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.
+ */
+import com.diffplug.gradle.spotless.SpotlessExtension
+import org.jetbrains.dokka.gradle.DokkaTask
+import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+ alias(libs.plugins.kotlin.jvm) apply false
+ alias(libs.plugins.ksp) apply false
+ alias(libs.plugins.dokka) apply false
+ alias(libs.plugins.spotless) apply false
+ alias(libs.plugins.mavenPublish) apply false
+ alias(libs.plugins.kotlinBinaryCompatibilityValidator)
+}
+
+allprojects {
+ group = property("GROUP") as String
+ version = property("VERSION_NAME") as String
+
+ repositories {
+ mavenCentral()
+ }
+}
+
+subprojects {
+ tasks.withType<KotlinCompile> {
+ kotlinOptions {
+ freeCompilerArgs += listOf("-opt-in=kotlin.RequiresOptIn")
+ }
+ }
+ // Ensure "org.gradle.jvm.version" is set to "8" in Gradle metadata.
+ tasks.withType<JavaCompile> {
+ sourceCompatibility = JavaVersion.VERSION_1_8.toString()
+ targetCompatibility = JavaVersion.VERSION_1_8.toString()
+ }
+
+ apply(plugin = "org.jetbrains.kotlin.jvm")
+ if ("test" !in name && buildFile.exists()) {
+ apply(plugin = "org.jetbrains.dokka")
+ apply(plugin = "com.vanniktech.maven.publish")
+ configure<KotlinProjectExtension> {
+ explicitApi()
+ }
+ afterEvaluate {
+ tasks.named<DokkaTask>("dokkaHtml") {
+ val projectFolder = project.path.trim(':').replace(':', '-')
+ outputDirectory.set(rootProject.rootDir.resolve("docs/1.x/$projectFolder"))
+ dokkaSourceSets.configureEach {
+ skipDeprecated.set(true)
+ }
+ }
+ }
+ }
+
+ apply(plugin = "com.diffplug.spotless")
+ configure<SpotlessExtension> {
+ kotlin {
+ target("**/*.kt")
+ ktlint(libs.versions.ktlint.get()).editorConfigOverride(
+ mapOf("ktlint_standard_filename" to "disabled"),
+ )
+ trimTrailingWhitespace()
+ endWithNewline()
+
+ licenseHeader(
+ """
+ |/*
+ | * Copyright (C) ${'$'}YEAR Square, Inc.
+ | *
+ | * 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.
+ | */
+ """.trimMargin()
+ )
+ }
+ }
+
+ // Copied from https://github.com/square/retrofit/blob/master/retrofit/build.gradle#L28.
+ // Create a test task for each supported JDK.
+ for (majorVersion in 8..18) {
+ // Adoptium JDK 9 cannot extract on Linux or Mac OS.
+ if (majorVersion == 9) continue
+ // Started causing build failures in late 2022, e.g.:
+ // https://github.com/square/kotlinpoet/actions/runs/3816320722/jobs/6531532305.
+ if (majorVersion == 10) continue
+
+ val jdkTest = tasks.register<Test>("testJdk$majorVersion") {
+ val javaToolchains = project.extensions.getByType(JavaToolchainService::class)
+ javaLauncher.set(javaToolchains.launcherFor {
+ languageVersion.set(JavaLanguageVersion.of(majorVersion))
+ })
+
+ description = "Runs the test suite on JDK $majorVersion"
+ group = LifecycleBasePlugin.VERIFICATION_GROUP
+
+ // Copy inputs from normal Test task.
+ val testTask = tasks.getByName<Test>("test")
+ classpath = testTask.classpath
+ testClassesDirs = testTask.testClassesDirs
+ }
+ tasks.named("check").configure {
+ dependsOn(jdkTest)
+ }
+ }
+}
+
+apiValidation {
+ nonPublicMarkers += "com.squareup.kotlinpoet.ExperimentalKotlinPoetApi"
+ ignoredProjects += listOf(
+ "interop", // Empty middle package
+ "test-processor" // Test only
+ )
+}
diff --git a/docs/changelog.md b/docs/changelog.md
new file mode 100644
index 00000000..05680e65
--- /dev/null
+++ b/docs/changelog.md
@@ -0,0 +1,588 @@
+Change Log
+==========
+
+## Version 1.12.0
+
+_2022-06-13_
+
+Thanks to [@WhosNickDoglio][WhosNickDoglio], [@sullis][sullis], [@DRSchlaubi][DRSchlaubi],
+[@martinbonnin][martinbonnin], [@seriouslyhypersonic][seriouslyhypersonic], [@ephemient][ephemient],
+[@dkilmer][dkilmer], [@aksh1618][aksh1618], [@zsqw123][zsqw123], [@roihershberg][roihershberg] for
+contributing to this release.
+
+ * New: Kotlin 1.7.0.
+ * New: Add support for context receivers.
+ * New: Add support for external property getter.
+ * New: `interop-ksp` API promoted to stable.
+ * Fix: Resolve enum constants when emitting types.
+ * Fix: Fix type argument mapping when processing typealiases with KSP.
+ * Fix: Properly unwrap `KSTypeAlias` with an unused type parameter.
+ * Fix: Unwrap nested `KSTypeAlias`-es recursively.
+ * Fix: Add support for context receivers `@PropertySpec` and fix issues with annotations.
+ * Fix: Treat `header` and `impl` as keywords (workaround for KT-52315).
+ * Fix: Use `%N` instead of `%L` for annotation arg names so keywords are handled.
+ * Fix: Improve handling of long `return` expressions.
+
+## Version 1.11.0
+
+_2022-03-24_
+
+Thanks to [@liujingxing][liujingxing] and [@BoD][BoD] for contributing to this release.
+
+* New: Kotlin scripting support in `FileSpec`.
+
+```kotlin
+val spec = FileSpec.scriptBuilder("Taco")
+ .addStatement("println(%S)", "hello world!")
+ .addKotlinDefaultImports()
+ .build()
+```
+
+Generates a `Taco.kts` file with the following contents:
+
+```kotlin
+println("hello world!")
+```
+
+* New: Emit trailing commas for multi-line parameters and annotations.
+* New: Add `KSAnnotation.toAnnotationSpec()`.
+* New: Add `Unit` and `CharSequence` conversions in `javapoet-interop`.
+* New: Add support for default imports in `FileSpec`.
+ * This is particularly oriented at scripting support, but can also be used in non-script files.
+* New: Update to Kotlin 1.6.10.
+* Fix: Fail compilation if you only pass one string to `ClassName`.
+* Fix: Inline `val` property if its getter is `inline`.
+* Fix: Add `yield` to the list of reserved keywords.
+* Fix: Enforce only allowed parameter modifiers in `ParameterSpec` (i.e. `crossinline`, `vararg`, and `noinline`).
+* Fix: Fix `CodeBlock`s in class delegation getting `toString()`'d instead of participating in code writing.
+* Fix: Error when attempting to convert KSP error types (i.e. if `KSType.isError` is true) to `TypeName`.
+
+## Version 1.10.2
+
+_2021-10-22_
+
+Thanks to [@glureau][glureau] and [@goooler][goooler] for contributing to this release.
+
+* New: Switch `AnnotationSpec.get()` to use the `arrayOf()` syntax instead of `[]`.
+* Fix: Don't wrap aliasing imports with long package names.
+* Fix: Don't wrap type names inside line comments.
+* Fix: Ignore Java's `@Deprecated` annotations on synthetic methods for annotations.
+
+## Version 1.10.1
+
+_2021-09-21_
+
+Thanks to [@evant][evant] for contributing to this release.
+
+ * Fix: Correct generation of typealiases with type args in KSP interop.
+ * Fix: Add missing default `TypeParameterResolver.EMPTY` argument to
+ `fun KSTypeArgument.toTypeName` in KSP interop.
+
+## Version 1.10.0
+
+_2021-09-20_
+
+Thanks to [@martinbonnin][martinbonnin], [@idanakav][idanakav], [@goooler][goooler], and
+[@anandwana001][anandwana001] for contributing to this release.
+
+ * New: Add a new [KSP][ksp] interop artifact. See [docs][ksp-interop-docs] for more details.
+ * New: Add a new [JavaPoet][javapoet] interop artifact. See [docs][javapoet-interop-docs] for more
+ details.
+ * New: Allow copying a `ParameterizedTypeName` with new type arguments via new `copy()` overload.
+ * kotlinx-metadata artifacts have been consolidated to a single `com.squareup:kotlinpoet-metadata`
+ maven artifact. The previous `kotlinpoet-metadata-*` subartifacts are no longer published.
+ * New: `TypeNameAliasTag` has been moved to KotlinPoet's main artifact under `TypeAliasTag`, for
+ reuse with KSP interop.
+ * `ImmutableKm*` classes have been removed. They were deemed to be a needless abstraction over the base `kotlinx-metadata` Km types. All usages of these should be substituted with their non-immutable base types.
+ * Fix: Fix self-referencing type variables in metadata parsing.
+ * Fix: Use delicate APIs rather than noisy logging ones when converting annotation mirrors in
+ `AnnotationSpec.get`.
+ * Fix: Update error message when metadata cannot be read to a more actionable one.
+ * Fix: Avoid escaping already escaped strings.
+ * Add docs about `kotlin-reflect` usage.
+ * Avoid using kotlin-reflect for looking up `Unit` types where possible.
+ * Test all the way up to JDK 17.
+ * Update Kotlin to 1.5.31.
+
+## Version 1.9.0
+
+_2021-06-22_
+
+ * New: Kotlin 1.5.10.
+ * New: Previously deprecated API to interop with Java reflection and Mirror API have been
+ un-deprecated and marked with `@DelicateKotlinPoetApi` annotation.
+ * New: `CodeBlock.Builder.withIndent` helper function.
+ * New: Allow changing initializers and default values in `ParameterSpec.Builder` and
+ `PropertySpec.Builder` after they were set.
+ * New: `MemberName.isExtension` property that instructs KotlinPoet to always import the member,
+ even if conflicting declarations are present in the same scope.
+ * Fix: Escape member names that only contain underscores.
+ * Fix: Always emit an empty primary constructor if it was set via `TypeSpec.primaryConstructor`.
+
+## Version 1.8.0
+
+_2021-03-29_
+
+ * New: Kotlin 1.4.31.
+ * New: Add `KModifier.VALUE` to support `value class` declarations.
+ * New: Allow using a custom `ClassLoader` with `ReflectiveClassInspector`.
+ * New: Update to kotlinx-metadata 0.2.0.
+ * Fix: Ensure `ImmutableKmProperty.toMutable()` copies `fieldSignature`.
+ * Fix: Prevent name clashes between an imported `MemberName` and a member in current scope.
+ * Fix: Prevent name clashes between a type and a supertype with the same name.
+ * Fix: Don't generate empty body for `expect` and `external` functions.
+ * Fix: Don't allow `expect` or `external` classes to initialize supertypes.
+ * Fix: Disallow delegate constructor calls in `external` classes.
+ * Fix: Allow non-public primary constructors inside inline/value classes.
+ * Fix: Allow init blocks inside inline/value classes.
+ * Fix: Omit redundant `abstract` modifiers on members inside interfaces
+
+## Version 1.7.2
+
+_2020-10-20_
+
+ * New: Detect expression bodies with `return·` and `throw·` prefixes.
+ * Fix: Omit visibility modifiers on custom accessors.
+
+## Version 1.7.1
+
+_2020-10-15_
+
+ * Fix: 1.7.0 was published using JDK 11 which set `"org.gradle.jvm.version"` to `"11"` in Gradle
+ metadata, making it impossible to use the library on earlier Java versions (see
+ [#999][issue-999]). 1.7.1 is published with JDK 8, which fixes the problem.
+
+## Version 1.7.0
+
+_2020-10-14_
+
+ * New: Kotlin 1.4.10.
+ * New: Generated code is now compatible with the [explicit API mode][explicit-api-mode] by default.
+ * New: Escape soft and modifier keywords, in addition to hard keywords.
+ * New: Improve enum constants generation for cleaner diffs.
+ * New: Disallow setters on immutable properties.
+ * New: Ensure trailing new lines in expression bodies.
+ * New: Ensure trailing new lines after parameterless custom setters.
+ * Fix: Don't auto-convert properties with custom accessors to primary constructor properties.
+ * Fix: Don't allow parameterless setters with body.
+ * Fix: Prevent auto-wrapping spaces inside escaped keywords.
+
+## Version 1.6.0
+
+_2020-05-28_
+
+ * New: Deprecate Mirror API integrations.
+
+ Mirror API integrations, such as `TypeElement.asClassName()` and
+ `FunSpec.overriding(ExecutableElement)`, are being deprecated in this release. These KotlinPoet
+ APIs are most often used in annotation processors. Since kapt runs annotation processors over
+ stubs, which are Java files, a lot of the Kotlin-specific information gets lost in translation
+ and cannot be accessed by KotlinPoet through the Mirror API integrations. Examples include:
+
+ - Alias types, such as `kotlin.String`, get converted to their JVM representations, such as
+ `java.lang.String`.
+ - Type nullability information is not accessible.
+ - `suspend` functions are seen as simple functions with an additional `Continuation` parameter.
+
+ The correct solution is to switch to [KotlinPoet-metadata][kotlinpoet-metadata] or
+ [KotlinPoet-metadata-specs][kotlinpoet-metadata-specs] API, which fetches Kotlin-specific
+ information from the `@Metadata` annotation and produces correct KotlinPoet Specs. We may explore
+ adding new metadata-based alternatives to the deprecated APIs in the future.
+
+ * New: Kotlin 1.3.72.
+ * New: Improve `MemberName` to support operator overloading.
+ * New: Support generics in `AnnotationSpec`.
+ * New: Add support for functional interfaces.
+ * New: Make more `FunSpec.Builder` members public for easier mutation.
+ * Fix: Properly propagate implicit type and function modifiers in nested declarations.
+ * Fix: Properly escape type names containing `$` character.
+ * Fix: Don't emit `LambdaTypeName` annotations twice.
+ * Fix: Preserve tags in `TypeName.copy()`.
+
+## Version 1.5.0
+
+_2020-01-09_
+
+ KotlinPoet now targets JDK8, which means that executing a build that includes KotlinPoet as a
+ dependency on a machine with an older version of JDK installed won't work. **This has no effect on
+ the code that KotlinPoet produces**: the code can still be compiled against JDK6, as long as it
+ doesn't use any features that were introduced in newer releases.
+
+ * New: Kotlin 1.3.61.
+ * New: Add support for processing FileFacades in KotlinPoet-metadata.
+ * New: Add support for inner nested and companion objects on annotation classes.
+ * New: Improve error messages for mismatched open/close statement characters.
+ * New: Tag `AnnotationSpec`s with the annotation mirror when available.
+ * New: Include annotations on enum entries when creating `TypeSpec`s from metadata.
+ * Fix: Fix metadata parsing for types.
+ * Fix: Allow file names that are Kotlin keywords.
+ * Fix: Properly escape type alias names with backticks.
+ * Fix: Allow creating `TypeSpec`s with names that can be escaped with backticks.
+ * Fix: Properly escape enum constant names with backticks.
+ * Fix: Maintain proper ordering of properties and initializers when emitting a `TypeSpec`.
+ **Note**: with this change, any properties declared after any initializer blocks will not be
+ added to the primary constructor and will instead be emitted inside the `TypeSpec` body.
+ * Fix: Don't emit a leading new line if type KDoc is empty but parameter KDocs are present.
+ * Fix: Ensure KotlinPoet-metadata resolves package names properly.
+
+ ## Version 1.4.4
+
+_2019-11-16_
+
+ * Fix: Support reified inline types in KotlinPoet-metadata.
+
+## Version 1.4.3
+
+_2019-10-30_
+
+ * Fix: Don't emit stubs for abstract functions in KotlinPoet-metadata.
+
+## Version 1.4.2
+
+_2019-10-28_
+
+ * Fix: Properly handle abstract elements in KotlinPoet-metadata.
+ * Fix: Properly handle typealiases in KotlinPoet-metadata.
+ * Fix: Properly render % symbols at the end of KDocs.
+
+## Version 1.4.1
+
+_2019-10-18_
+
+ * New: Add annotations support to `TypeAliasSpec`.
+ * New: Read type annotations from Kotlin `Metadata`.
+ * New: Introduce `ImmutableKmDeclarationContainer`.
+ * Fix: Use full package name for shading `auto-common`.
+ * Fix: Support reading self-type variables (e.g. `Asset<A : Asset<A>>`) from Kotlin `Metadata`.
+
+## Version 1.4.0
+
+_2019-09-24_
+
+ * New: This release introduces the new KotlinPoet-metadata API that makes it easy to introspect
+ Kotlin types and build KotlinPoet Specs based on that data.
+
+ The strategy for type introspection is driven by `ClassInspector`, which is a basic interface for
+ looking up JVM information about a given Class. This optionally is used by the
+ `toTypeSpec()`/`toFileSpec()` APIs in `kotlinpoet-metadata-specs` artifact to inform about
+ Classes with information that isn’t present in metadata (overrides, JVM modifiers, etc). There
+ are two batteries-included implementations available in `ReflectiveClassInspector`
+ (for reflection) and `ElementsClassInspector` (for the javax Elements API in annotation
+ processing). These implementations are available through their respective
+ `kotlinpoet-classinspector-*` artifacts. For more information refer to the
+ [KotlinPoet-metadata-specs README][kotlinpoet-metadata-specs].
+
+ At the time of this release the API is in experimental mode and has to be opted into via the
+ `KotlinPoetMetadataPreview` annotation.
+
+ * New: Kotlin 1.3.50.
+ * New: A new constructor to simplify creation of `ParameterSpec` instances.
+ * New: New `ClassName` constructors.
+ * New: `TypeName` and subclasses can now store tags.
+ * New: Optional parameters added to `toBuilder()` methods of most Specs.
+ * New: `List` overrides for Spec methods that accept `vararg`s.
+ * New: `CodeBlock.Builder.clear()` helper method.
+ * New: `FunSpec.Builder.clearBody()` helper method.
+ * Fix: Properly escape enum constant names.
+ * Fix: Ensure trailing newlines in KDoc and function bodies.
+ * Fix: `TypeVariableName`s with empty bounds will now default to `Any?`.
+ * Fix: Don't emit parens for primary constructors.
+ * Fix: `ClassName`s with empty simple names are not allowed anymore.
+ * Fix: Throw if names contain illegal characters that can't be escaped with backticks.
+
+## Version 1.3.0
+
+_2019-05-30_
+
+ * New: Don't inline annotations in the primary constructor.
+ * New: Force new lines when emitting primary constructors.
+ * New: Support using MemberNames as arguments to %N.
+ * New: Add more ClassName constants: ClassName.STRING, ClassName.LIST, etc.
+ * New: Add ClassName.constructorReference() and MemberName.reference().
+ * New: Make %N accept MemberNames.
+ * New: Escape spaces in import aliases.
+ * New: Escape spaces in ClassNames.
+ * New: Escape spaces in MemberNames.
+ * New: Escape imports containing spaces.
+ * New: Escape package name containing spaces.
+ * New: Use 2-space indents.
+ * New: Only indent one level on annotation values.
+ * Fix: Pass only unique originating elements to Filer.
+ * Fix: Fix bug with MemberNames in same package nested inside a class.
+
+## Version 1.2.0
+
+_2019-03-28_
+
+ * New: Add writeTo(Filer) and originating element API.
+ * New: Make *Spec types taggable.
+ * New: Make FunSpec.Builder#addCode take vararg Any?.
+ * Fix: Import members from default package.
+ * Fix: Add non-wrapping spaces in control flow creation methods.
+ * Fix: Named "value" argument being omitted in annotation array types.
+
+## Version 1.1.0
+
+_2019-02-28_
+
+ * New: Kotlin 1.3.21.
+ * New: Support referencing members using `%M` and `MemberName` type.
+ * New: Add extensions for getting a `MemberName` from a `ClassName`, `KClass` and `Class`.
+ * New: Allow passing `CodeBlock`s as arguments to `%P`.
+ * New: Allow interface delegation for objects.
+ * Fix: Don't emit visible whitespace in `toString()`.
+ * Fix: Prevent line wrapping in weird places inside function signature.
+ * Fix: No line wrapping between val and property name.
+ * Fix: Allow passing line prefix into `LineWrapper` to enable proper line wrapping in KDoc.
+ * Fix: Add newline for `TypeSpec` Kdoc with no tags.
+ * Fix: Add newline for remaining Specs.
+ * Fix: Fix kdoc formatting for property getter/setters.
+ * Fix: Don't wrap single line comments inside `FunSpec`.
+ * Fix: Add non-wrapping package name.
+ * Fix: Remove n^2 algorithm in `CodeWriter.resolve()` by precomputing all of the nested simple names of a `TypeSpec`.
+ * Fix: Fix edge case with empty enum classes.
+ * Fix: Fix Nullable Type Parameter handling in `KType.asTypeName()`.
+ * Fix: Fix incorrect long comment wrapping in `FileSpec`.
+ * Fix: Attach primary constructor param/property KDoc to the element vs emitting it inside the type header.
+
+## Version 1.0.1
+
+_2019-01-02_
+
+ * New: Allow enums without constants.
+ * New: Improved formatting of TypeSpec KDoc.
+ * New: Support @property and @param KDoc tags in TypeSpec.
+ * Fix: Use pre-formatted strings for arguments to %P.
+
+## Version 1.0.0
+
+_2018-12-10_
+
+ * New: Kotlin 1.3.11.
+ * Fix: Prevent wrapping in import statements.
+
+## Version 1.0.0-RC3
+
+_2018-11-28_
+
+ * New: Kotlin 1.3.10.
+ * New: Add `%P` placeholder for string templates.
+ * New: Add support for receiver kdoc.
+ * New: Avoid emitting `Unit` as return type.
+ * New: Add support for empty setters.
+ * New: Add checks for inline classes.
+ * New: Escape property and variable names if keywords.
+ * New: Replace `%>`, `%<`, `%[`, `%]` placeholders with `⇥`, `⇤`, `«`, `»`.
+ * New: Replace `%W` with space, and add `·` as a non-breaking space.
+ * New: Change `TypeName` to sealed class.
+ * New: Documentation improvements.
+ * New: Replace `TypeName` modifier methods with `copy()`.
+ * New: Rename members of `WildcardTypeName` to match with the producer/consumer generics model.
+ * New: Rename `TypeName.nullable` into `TypeName.isNullable`.
+ * New: Rename `LambdaTypeName.suspending` into `LambdaTypeName.isSuspending`.
+ * New: Rename `TypeVariableName.reified` into `TypeVariableName.isReified`.
+ * Fix: Emit star-projection only for types with `Any?` upper bound.
+ * Fix: Fold property with escaped name.
+
+## Version 1.0.0-RC2
+
+_2018-10-22_
+
+ * New: Kotlin 1.2.71.
+ * New: README improvements.
+ * New: Allow opening braces and params in `beginControlFlow()`.
+ * New: Add KDoc to `ParameterSpec`, collapse into parent KDoc.
+ * New: Support `TypeVariable`s in `PropertySpec`.
+ * New: Add parens for annotated types in `LambdaTypeName`.
+ * New: Improve error messaging and documentation for inline properties.
+ * New: Allow sealed classes to declare abstract properties.
+ * New: Added `buildCodeBlock()` helper function.
+ * New: Allow using `CodeBlock`s with statements as property initializers and default parameter values.
+ * New: Rename `NameAllocator.clone()` into `NameAllocator.copy().
+ * New: Rename `TypeName.asNonNullable()` to `TypeName.asNonNull()`.
+ * New: Remove `PropertySpec.varBuilder()` (use `mutable()` instead).
+ * New: Allow importing top-level members in default package.
+ * New: Add overloads to add KDoc to return type.
+ * Fix: Distinguishing `IntArray` and `Array<Int>` when creating `TypeName`.
+ * Fix: Use `TypeName` instead of `ClassName` as parameter type of `plusParameter()`.
+ * Fix: Keep type-parameter variance when constructing `TypeName` from `KType`.
+ * Fix: Don't validate modifiers when merging properties with primary constructor parameters.
+ * Fix: Escape $ characters in formatted strings.
+ * Fix: `FileSpec.Builder` blank package and subfolder fix.
+ * Fix: Append new line at end of parameter KDoc.
+ * Fix: Add parameter KDoc in `toBuilder()`.
+
+## Version 1.0.0-RC1
+
+_2018-07-16_
+
+ * New: Escape keywords in imports and canonical class names.
+ * New: Improve `external` support.
+ * New: Extensions for `KType` and `KTypeParameter`.
+ * New: Add builder methods to simplify adding common kotlin.jvm annotations.
+ * New: Enums are able to have companion objects.
+ * New: Add missing primaryConstructor & companionObject to `TypeSpec#toBuilder()`.
+ * New: Make subtype checking vals inside Kind public.
+ * New: Escape (class/property/function/variable) names automatically if they contain space, hyphen, or other symbols.
+ * New: Improve `ParameterizedTypeName` API.
+ * New: Add `WildcardTypeName.STAR` constant.
+ * New: Expose mutable builder properties and move their validations to build-time.
+ * Fix: Use regular indents for parameter lists.
+ * Fix: Inline annotations on properties defined in primary constructor.
+ * Fix: Use `Any?` as the default type variable bounds.
+ * Fix: Fix importing annotated `TypeName`.
+ * Fix: If any primary constructor property has KDoc, put properties on new lines.
+ * Fix: Properly emit where block in type signature.
+ * Fix: Avoid type name collisions in primary constructor.
+ * Fix: Remove implicit `TypeVariable` bound when more bounds are added.
+ * Fix: Combine annotations and modifiers from constructor params and properties.
+ * Fix: Replace delegate constructor args along with the constructor.
+
+## Version 0.7.0
+
+_2018-02-16_
+
+ * New: Increase indent to 4 spaces.
+ * New: Delegate super interfaces as constructor parameters.
+ * New: Support `PropertySpec`s as `CodeBlock` literals.
+ * New: Support KDoc for `TypeAliasSpec`.
+ * New: Allow for adding an initializer block inside a companion object.
+ * New: Escape name in `ParameterSpec` which is also a keyword.
+ * New: Escape names in statements.
+ * New: Set com.squareup.kotlinpoet as automatic module name.
+ * New: Support suspending lambda types.
+ * New: Support named `LambdaTypeName` parameters.
+ * New: Support dynamic type.
+ * New: Disallow wildcard imports.
+ * New: Depend on Kotlin 1.2.21.
+ * Fix: Correct handling of super-classes/interfaces on anonymous classes.
+ * Fix: Fix boundary filtering to `Any?`.
+ * Fix: Wrap long property initializers.
+ * Fix: Fix formatting and indentation of parameter lists.
+
+## Version 0.6.0
+
+_2017-11-03_
+
+ * New: Support lambda extensions.
+ * New: Support renames in imports like `import bar.Bar as bBar`.
+ * New: Support extension and inline properties.
+ * New: Support reified types.
+ * New: Expose enclosed types inside `LambdaTypeName`.
+ * New: Depend on Kotlin Kotlin 1.1.51.
+ * New: Improved API and formatting of annotations.
+ * New: Improved multiplatform support.
+ * Fix: Escape function and package names if they are a Kotlin keyword.
+ * Fix: Properly format WildcardTypeName's class declaration.
+
+
+## Version 0.5.0
+
+_2017-09-13_
+
+ * New: Rename `addFun()` to `addFunction()`.
+ * New: Rename `KotlinFile` to `FileSpec`.
+ * New: Rename `KotlinFile.addFileAnnotation()` to `addAnnotation()`.
+ * New: Rename `KotlinFile.addFileComment()` to `addComment()`.
+ * New: Support cross-platform code, including `HEADER` and `IMPL` modifiers.
+ * New: Support type variables for type aliases.
+ * New: Support constructor delegation.
+ * New: Support named companion objects.
+ * New: Depend on Kotlin 1.1.4-3.
+ * Fix: Format one parameter per line when there are more than two parameters.
+ * Fix: Don't emit braces when the constructor body is empty.
+ * Fix: Do not invoke superclass constructor when no primary constructor.
+ * Fix: Enforce the right modifiers on functions.
+
+
+## Version 0.4.0
+
+_2017-08-08_
+
+ * New: Change KotlinPoet's extensions like `asClassName()` to be top-level functions.
+ * New: Add declaration-site variance support.
+ * New: Improve handling of single expression bodies.
+ * New: Support file annotations.
+ * New: Support imports from the top-level file.
+ * New: Accept superclass constructor parameters.
+ * New: Support primary constructors using the `constructor` keyword.
+ * Fix: Don't emit setter parameter types.
+ * Fix: Support Kotlin keywords in `NameAllocator`.
+ * Fix: Emit the right default parameters for primary constructors.
+ * Fix: Format annotations properly when used as parameters.
+ * Fix: Recognize imports when emitting nullable types.
+ * Fix: Call through to the superclass constructor when superclass has a no-args constructor.
+ * Fix: Omit class braces if all properties are declared in primary constructor.
+ * Fix: Don't emit empty class bodies.
+ * Fix: Emit the right syntax for declaring multiple generic type constraints.
+ * Fix: Support properties on objects, companions and interfaces.
+ * Fix: Use `AnnotationSpec` for throws.
+
+
+## Version 0.3.0
+
+_2017-06-11_
+
+ * New: Objects and companion objects.
+ * New: `TypeAliasSpec` to create type aliases.
+ * New: `LambdaTypeName` to create lambda types.
+ * New: Collapse property declarations into constructor params.
+ * New: Extension and invoke functions for creating type names: `Runnable::class.asClassName()`.
+ * New: Basic support for expression bodies.
+ * New: Basic support for custom accessors.
+ * New: Remove `Filer` writing and originating elements concept. These stem from `javac` annotation
+ processors.
+ * Fix: Generate valid annotation classes.
+ * Fix: Use `KModifier` for varargs.
+ * Fix: Use `ParameterizedTypeName` for array types.
+ * Fix: Extract Kotlin name from `KClass` instead of Java name.
+ * Fix: Emit valid class literals: `Double::class` instead of `Double.class`.
+ * Fix: Emit modifiers in the expected order.
+ * Fix: Emit the correct syntax for enum classes and overridden members.
+
+
+## Version 0.2.0
+
+_2017-05-21_
+
+ * New: Flip API signatures to be (name, type) instead of (type, name).
+ * New: Support for nullable types.
+ * New: Support delegated properties.
+ * New: Extension functions.
+ * New: Support top-level properties.
+ * Fix: Inheritance should use `:` instead of `extends` and `implements`.
+ * Fix: Make initializerBlock emit `init {}`.
+
+
+## Version 0.1.0
+
+_2017-05-16_
+
+ * Initial public release.
+
+ [kotlinpoet-metadata]: ../kotlinpoet_metadata
+ [kotlinpoet-metadata-specs]: ../kotlinpoet_metadata_specs
+ [explicit-api-mode]: https://kotlinlang.org/docs/reference/whatsnew14.html#explicit-api-mode-for-library-authors
+ [issue-999]: https://github.com/square/kotlinpoet/issues/999
+ [ksp]: https://github.com/google/ksp
+ [ksp-interop-docs]: https://square.github.io/kotlinpoet/interop-ksp/
+ [javapoet]: https://github.com/square/javapoet
+ [javapoet-interop-docs]: https://square.github.io/kotlinpoet/interop-javapoet/
+
+ [martinbonnin]: https://github.com/martinbonnin
+ [idanakav]: https://github.com/idanakav
+ [goooler]: https://github.com/goooler
+ [anandwana001]: https://github.com/anandwana001
+ [evant]: https://github.com/evant
+ [glureau]: https://github.com/glureau
+ [liujingxing]: https://github.com/liujingxing
+ [BoD]: https://github.com/BoD
+ [WhosNickDoglio]: https://github.com/WhosNickDoglio
+ [sullis]: https://github.com/sullis
+ [DRSchlaubi]: https://github.com/DRSchlaubi
+ [seriouslyhypersonic]: https://github.com/seriouslyhypersonic
+ [ephemient]: https://github.com/ephemient
+ [dkilmer]: https://github.com/dkilmer
+ [aksh1618]: https://github.com/aksh1618
+ [zsqw123]: https://github.com/zsqw123
+ [roihershberg]: https://github.com/roihershberg
diff --git a/docs/contributing.md b/docs/contributing.md
new file mode 100644
index 00000000..74108a8c
--- /dev/null
+++ b/docs/contributing.md
@@ -0,0 +1,15 @@
+Contributing
+============
+
+If you would like to contribute code you can do so through GitHub by forking
+the repository and sending a pull request.
+
+When submitting code, please make every effort to follow existing conventions
+and style in order to keep the code as readable as possible. Please also make
+sure your code compiles by running `./gradlew clean build`.
+
+Before your code can be accepted into the project you must also sign the
+[Individual Contributor License Agreement (CLA)][1].
+
+
+ [1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
diff --git a/docs/css/app.css b/docs/css/app.css
new file mode 100644
index 00000000..a982ae6f
--- /dev/null
+++ b/docs/css/app.css
@@ -0,0 +1,30 @@
+@font-face {
+ font-family: cash-market;
+ src: url("https://cash-f.squarecdn.com/static/fonts/cash-market/v2/CashMarket-Regular.woff2") format("woff2");
+ font-weight: 400;
+ font-style: normal
+}
+
+@font-face {
+ font-family: cash-market;
+ src: url("https://cash-f.squarecdn.com/static/fonts/cash-market/v2/CashMarket-Medium.woff2") format("woff2");
+ font-weight: 500;
+ font-style: normal
+}
+
+@font-face {
+ font-family: cash-market;
+ src: url("https://cash-f.squarecdn.com/static/fonts/cash-market/v2/CashMarket-Bold.woff2") format("woff2");
+ font-weight: 700;
+ font-style: normal
+}
+
+body, input {
+ font-family: cash-market,"Helvetica Neue",helvetica,sans-serif;
+}
+
+.md-typeset h1, .md-typeset h2, .md-typeset h3, .md-typeset h4 {
+ font-family: cash-market,"Helvetica Neue",helvetica,sans-serif;
+ line-height: normal;
+ font-weight: bold;
+}
diff --git a/docs/images/icon-square.png b/docs/images/icon-square.png
new file mode 100644
index 00000000..bdc98d1c
--- /dev/null
+++ b/docs/images/icon-square.png
Binary files differ
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 00000000..4b8ec1a1
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,1541 @@
+KotlinPoet
+==========
+
+`KotlinPoet` is a Kotlin and Java API for generating `.kt` source files.
+
+Source file generation can be useful when doing things such as annotation processing or interacting
+with metadata files (e.g., database schemas, protocol formats). By generating code, you eliminate
+the need to write boilerplate while also keeping a single source of truth for the metadata.
+
+### Example
+
+Here's a `HelloWorld` file:
+
+```kotlin
+class Greeter(val name: String) {
+ fun greet() {
+ println("""Hello, $name""")
+ }
+}
+
+fun main(vararg args: String) {
+ Greeter(args[0]).greet()
+}
+```
+
+And this is the code to generate it with KotlinPoet:
+
+```kotlin
+val greeterClass = ClassName("", "Greeter")
+val file = FileSpec.builder("", "HelloWorld")
+ .addType(
+ TypeSpec.classBuilder("Greeter")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("name", String::class)
+ .build()
+ )
+ .addProperty(
+ PropertySpec.builder("name", String::class)
+ .initializer("name")
+ .build()
+ )
+ .addFunction(
+ FunSpec.builder("greet")
+ .addStatement("println(%P)", "Hello, \$name")
+ .build()
+ )
+ .build()
+ )
+ .addFunction(
+ FunSpec.builder("main")
+ .addParameter("args", String::class, VARARG)
+ .addStatement("%T(args[0]).greet()", greeterClass)
+ .build()
+ )
+ .build()
+
+file.writeTo(System.out)
+```
+
+The [KDoc][kdoc] catalogs the complete KotlinPoet API, which is inspired by [JavaPoet][javapoet].
+
+**Note:** In order to maximize portability, KotlinPoet generates code with explicit visibility
+modifiers. This ensures compatibility with both standard Kotlin projects as well as projects
+using [explicit API mode](https://kotlinlang.org/docs/whatsnew14.html#explicit-api-mode-for-library-authors).
+Examples in this file omit those modifiers for brevity.
+
+### Code & Control Flow
+
+Most of KotlinPoet's API uses immutable Kotlin objects. There's also builders, method chaining
+and varargs to make the API friendly. KotlinPoet offers models for Kotlin files (`FileSpec`),
+classes, interfaces & objects (`TypeSpec`), type aliases (`TypeAliasSpec`),
+properties (`PropertySpec`), functions & constructors (`FunSpec`), parameters (`ParameterSpec`) and
+annotations (`AnnotationSpec`).
+
+But the _body_ of methods and constructors is not modeled. There's no expression class, no
+statement class or syntax tree nodes. Instead, KotlinPoet uses strings for code blocks, and you can
+take advantage of Kotlin's multiline strings to make this look nice:
+
+```kotlin
+val main = FunSpec.builder("main")
+ .addCode("""
+ |var total = 0
+ |for (i in 0 until 10) {
+ | total += i
+ |}
+ |""".trimMargin())
+ .build()
+```
+
+Which generates this:
+
+```kotlin
+fun main() {
+ var total = 0
+ for (i in 0 until 10) {
+ total += i
+ }
+}
+```
+
+There are additional APIs to assist with newlines, braces and indentation:
+
+```kotlin
+val main = FunSpec.builder("main")
+ .addStatement("var total = 0")
+ .beginControlFlow("for (i in 0 until 10)")
+ .addStatement("total += i")
+ .endControlFlow()
+ .build()
+```
+
+This example is lame because the generated code is constant! Suppose instead of just adding 0 to 10,
+we want to make the operation and range configurable. Here's a method that generates a method:
+
+```kotlin
+private fun computeRange(name: String, from: Int, to: Int, op: String): FunSpec {
+ return FunSpec.builder(name)
+ .returns(Int::class)
+ .addStatement("var result = 1")
+ .beginControlFlow("for (i in $from until $to)")
+ .addStatement("result = result $op i")
+ .endControlFlow()
+ .addStatement("return result")
+ .build()
+}
+```
+
+And here's what we get when we call `computeRange("multiply10to20", 10, 20, "*")`:
+
+```kotlin
+fun multiply10to20(): kotlin.Int {
+ var result = 1
+ for (i in 10 until 20) {
+ result = result * i
+ }
+ return result
+}
+```
+
+Methods generating methods! And since KotlinPoet generates source instead of bytecode, you can
+read through it to make sure it's right.
+
+### %S for Strings
+
+When emitting code that includes string literals, we can use **`%S`** to emit a **string**, complete
+with wrapping quotation marks and escaping. Here's a program that emits 3 methods, each of which
+returns its own name:
+
+```kotlin
+fun main(args: Array<String>) {
+ val helloWorld = TypeSpec.classBuilder("HelloWorld")
+ .addFunction(whatsMyNameYo("slimShady"))
+ .addFunction(whatsMyNameYo("eminem"))
+ .addFunction(whatsMyNameYo("marshallMathers"))
+ .build()
+
+ val kotlinFile = FileSpec.builder("com.example.helloworld", "HelloWorld")
+ .addType(helloWorld)
+ .build()
+
+ kotlinFile.writeTo(System.out)
+}
+
+private fun whatsMyNameYo(name: String): FunSpec {
+ return FunSpec.builder(name)
+ .returns(String::class)
+ .addStatement("return %S", name)
+ .build()
+}
+```
+
+In this case, using `%S` gives us quotation marks:
+
+```kotlin
+class HelloWorld {
+ fun slimShady(): String = "slimShady"
+
+ fun eminem(): String = "eminem"
+
+ fun marshallMathers(): String = "marshallMathers"
+}
+```
+
+### %P for String Templates
+
+`%S` also handles the escaping of dollar signs (`$`), to avoid inadvertent creation of string
+templates, which may fail to compile in generated code:
+
+```kotlin
+val stringWithADollar = "Your total is " + "$" + "50"
+val funSpec = FunSpec.builder("printTotal")
+ .returns(String::class)
+ .addStatement("return %S", stringWithADollar)
+ .build()
+```
+
+produces:
+
+```kotlin
+fun printTotal(): String = "Your total is ${'$'}50"
+```
+
+If you need to generate string templates, use `%P`, which doesn't escape dollars:
+
+```kotlin
+val amount = 50
+val stringWithADollar = "Your total is " + "$" + "amount"
+val funSpec = FunSpec.builder("printTotal")
+ .returns(String::class)
+ .addStatement("return %P", stringWithADollar)
+ .build()
+```
+
+produces:
+
+```kotlin
+fun printTotal(): String = "Your total is $amount"
+```
+
+You can also use `CodeBlock`s as arguments to `%P`, which is handy when you need to reference
+importable types or members inside the string template:
+
+```kotlin
+val file = FileSpec.builder("com.example", "Digits")
+ .addFunction(
+ FunSpec.builder("print")
+ .addParameter("digits", IntArray::class)
+ .addStatement("println(%P)", buildCodeBlock {
+ val contentToString = MemberName("kotlin.collections", "contentToString")
+ add("These are the digits: \${digits.%M()}", contentToString)
+ })
+ .build()
+ )
+ .build()
+println(file)
+```
+
+The snippet above will produce the following output, handling the imports properly:
+
+```kotlin
+package com.example
+
+import kotlin.IntArray
+import kotlin.collections.contentToString
+
+fun print(digits: IntArray) {
+ println("""These are the digits: ${digits.contentToString()}""")
+}
+```
+
+### %T for Types
+
+KotlinPoet has rich built-in support for types, including automatic generation of `import`
+statements. Just use **`%T`** to reference **types**:
+
+```kotlin
+val today = FunSpec.builder("today")
+ .returns(Date::class)
+ .addStatement("return %T()", Date::class)
+ .build()
+
+val helloWorld = TypeSpec.classBuilder("HelloWorld")
+ .addFunction(today)
+ .build()
+
+val kotlinFile = FileSpec.builder("com.example.helloworld", "HelloWorld")
+ .addType(helloWorld)
+ .build()
+
+kotlinFile.writeTo(System.out)
+```
+
+That generates the following `.kt` file, complete with the necessary `import`:
+
+```kotlin
+package com.example.helloworld
+
+import java.util.Date
+
+class HelloWorld {
+ fun today(): Date = Date()
+}
+```
+
+We passed `Date::class` to reference a class that just-so-happens to be available when we're
+generating code. This doesn't need to be the case. Here's a similar example, but this one
+references a class that doesn't exist (yet):
+
+```kotlin
+val hoverboard = ClassName("com.mattel", "Hoverboard")
+
+val tomorrow = FunSpec.builder("tomorrow")
+ .returns(hoverboard)
+ .addStatement("return %T()", hoverboard)
+ .build()
+```
+
+And that not-yet-existent class is imported as well:
+
+```kotlin
+package com.example.helloworld
+
+import com.mattel.Hoverboard
+
+class HelloWorld {
+ fun tomorrow(): Hoverboard = Hoverboard()
+}
+```
+
+The `ClassName` type is very important, and you'll need it frequently when you're using KotlinPoet.
+It can identify any _declared_ class. Declared types are just the beginning of Kotlin's rich type
+system: we also have arrays, parameterized types, wildcard types, lambda types and type variables.
+KotlinPoet has classes for building each of these:
+
+```kotlin
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+
+val hoverboard = ClassName("com.mattel", "Hoverboard")
+val list = ClassName("kotlin.collections", "List")
+val arrayList = ClassName("kotlin.collections", "ArrayList")
+val listOfHoverboards = list.parameterizedBy(hoverboard)
+val arrayListOfHoverboards = arrayList.parameterizedBy(hoverboard)
+
+val thing = ClassName("com.misc", "Thing")
+val array = ClassName("kotlin", "Array")
+val producerArrayOfThings = array.parameterizedBy(WildcardTypeName.producerOf(thing))
+
+val beyond = FunSpec.builder("beyond")
+ .returns(listOfHoverboards)
+ .addStatement("val result = %T()", arrayListOfHoverboards)
+ .addStatement("result += %T()", hoverboard)
+ .addStatement("result += %T()", hoverboard)
+ .addStatement("result += %T()", hoverboard)
+ .addStatement("return result")
+ .build()
+
+val printThings = FunSpec.builder("printThings")
+ .addParameter("things", producerArrayOfThings)
+ .addStatement("println(things)")
+ .build()
+```
+
+KotlinPoet will decompose each type and import its components where possible.
+
+```kotlin
+package com.example.helloworld
+
+import com.mattel.Hoverboard
+import com.misc.Thing
+import kotlin.Array
+import kotlin.collections.ArrayList
+import kotlin.collections.List
+
+class HelloWorld {
+ fun beyond(): List<Hoverboard> {
+ val result = ArrayList<Hoverboard>()
+ result += Hoverboard()
+ result += Hoverboard()
+ result += Hoverboard()
+ return result
+ }
+
+ fun printThings(things: Array<out Thing>) {
+ println(things)
+ }
+}
+```
+
+#### Nullable Types
+
+KotlinPoet supports nullable types. To convert a `TypeName` into its nullable counterpart, use the
+`copy()` method with `nullable` parameter set to `true`:
+
+```kotlin
+val java = PropertySpec.builder("java", String::class.asTypeName().copy(nullable = true))
+ .mutable()
+ .addModifiers(KModifier.PRIVATE)
+ .initializer("null")
+ .build()
+
+val helloWorld = TypeSpec.classBuilder("HelloWorld")
+ .addProperty(java)
+ .addProperty("kotlin", String::class, KModifier.PRIVATE)
+ .build()
+```
+
+generates:
+
+```kotlin
+class HelloWorld {
+ private var java: String? = null
+
+ private val kotlin: String
+}
+```
+
+### %M for Members
+
+Similar to types, KotlinPoet has a special placeholder for **members** (functions and properties),
+which comes handy when your code needs to access top-level members and members declared inside
+objects. Use **`%M`** to reference members, pass an instance of `MemberName` as the argument for the
+placeholder, and KotlinPoet will handle imports automatically:
+
+```kotlin
+val createTaco = MemberName("com.squareup.tacos", "createTaco")
+val isVegan = MemberName("com.squareup.tacos", "isVegan")
+val file = FileSpec.builder("com.squareup.example", "TacoTest")
+ .addFunction(
+ FunSpec.builder("main")
+ .addStatement("val taco = %M()", createTaco)
+ .addStatement("println(taco.%M)", isVegan)
+ .build()
+ )
+ .build()
+println(file)
+```
+
+The code above generates the following file:
+
+```kotlin
+package com.squareup.example
+
+import com.squareup.tacos.createTaco
+import com.squareup.tacos.isVegan
+
+fun main() {
+ val taco = createTaco()
+ println(taco.isVegan)
+}
+```
+
+As you can see, it's also possible to use `%M` to reference extension functions and properties. You
+just need to make sure the member can be imported without simple name collisions, otherwise
+importing will fail and the code generator output will not pass compilation. There's a way to work
+around such cases though - use `FileSpec.addAliasedImport()` to create an alias for a clashing
+`MemberName`:
+
+```kotlin
+val createTaco = MemberName("com.squareup.tacos", "createTaco")
+val createCake = MemberName("com.squareup.cakes", "createCake")
+val isTacoVegan = MemberName("com.squareup.tacos", "isVegan")
+val isCakeVegan = MemberName("com.squareup.cakes", "isVegan")
+val file = FileSpec.builder("com.squareup.example", "Test")
+ .addAliasedImport(isTacoVegan, "isTacoVegan")
+ .addAliasedImport(isCakeVegan, "isCakeVegan")
+ .addFunction(
+ FunSpec.builder("main")
+ .addStatement("val taco = %M()", createTaco)
+ .addStatement("val cake = %M()", createCake)
+ .addStatement("println(taco.%M)", isTacoVegan)
+ .addStatement("println(cake.%M)", isCakeVegan)
+ .build()
+ )
+ .build()
+println(file)
+```
+
+KotlinPoet will produce an aliased import for `com.squareup.tacos2.isVegan`:
+
+```kotlin
+package com.squareup.example
+
+import com.squareup.cakes.createCake
+import com.squareup.tacos.createTaco
+import com.squareup.cakes.isVegan as isCakeVegan
+import com.squareup.tacos.isVegan as isTacoVegan
+
+fun main() {
+ val taco = createTaco()
+ val cake = createCake()
+ println(taco.isTacoVegan)
+ println(cake.isCakeVegan)
+}
+```
+
+#### MemberName and operators
+
+MemberName also supports operators, you can use `MemberName(String, KOperator)`
+or `MemberName(ClassName, KOperator)` to import and reference operators.
+
+```kotlin
+val taco = ClassName("com.squareup.tacos", "Taco")
+val meat = ClassName("com.squareup.tacos.ingredient", "Meat")
+val iterator = MemberName("com.squareup.tacos.internal", KOperator.ITERATOR)
+val minusAssign = MemberName("com.squareup.tacos.internal", KOperator.MINUS_ASSIGN)
+val file = FileSpec.builder("com.example", "Test")
+ .addFunction(
+ FunSpec.builder("makeTacoHealthy")
+ .addParameter("taco", taco)
+ .beginControlFlow("for (ingredient %M taco)", iterator)
+ .addStatement("if (ingredient is %T) taco %M ingredient", meat, minusAssign)
+ .endControlFlow()
+ .addStatement("return taco")
+ .build()
+ )
+ .build()
+println(file)
+```
+
+KotlinPoet will import the extension operator functions and emit the operator.
+
+```kotlin
+package com.example
+
+import com.squareup.tacos.Taco
+import com.squareup.tacos.ingredient.Meat
+import com.squareup.tacos.internal.iterator
+import com.squareup.tacos.internal.minusAssign
+
+fun makeTacoHealthy(taco: Taco) {
+ for (ingredient in taco) {
+ if (ingredient is Meat) taco -= ingredient
+ }
+ return taco
+}
+
+```
+
+### %N for Names
+
+Generated code is often self-referential. Use **`%N`** to refer to another generated declaration by
+its name. Here's a method that calls another:
+
+```kotlin
+fun byteToHex(b: Int): String {
+ val result = CharArray(2)
+ result[0] = hexDigit((b ushr 4) and 0xf)
+ result[1] = hexDigit(b and 0xf)
+ return String(result)
+}
+
+fun hexDigit(i: Int): Char {
+ return (if (i < 10) i + '0'.toInt() else i - 10 + 'a'.toInt()).toChar()
+}
+```
+
+When generating the code above, we pass the `hexDigit()` method as an argument to the `byteToHex()`
+method using `%N`:
+
+```kotlin
+val hexDigit = FunSpec.builder("hexDigit")
+ .addParameter("i", Int::class)
+ .returns(Char::class)
+ .addStatement("return (if (i < 10) i + '0'.toInt() else i - 10 + 'a'.toInt()).toChar()")
+ .build()
+
+val byteToHex = FunSpec.builder("byteToHex")
+ .addParameter("b", Int::class)
+ .returns(String::class)
+ .addStatement("val result = CharArray(2)")
+ .addStatement("result[0] = %N((b ushr 4) and 0xf)", hexDigit)
+ .addStatement("result[1] = %N(b and 0xf)", hexDigit)
+ .addStatement("return String(result)")
+ .build()
+```
+
+Another handy feature that `%N` provides is automatically escaping names that contain illegal
+identifier characters with double ticks. Suppose your code creates a `MemberName` with a Kotlin
+keyword as the simple name:
+
+```kotlin
+val taco = ClassName("com.squareup.tacos", "Taco")
+val packager = ClassName("com.squareup.tacos", "TacoPackager")
+val file = FileSpec.builder("com.example", "Test")
+ .addFunction(
+ FunSpec.builder("packageTacos")
+ .addParameter("tacos", LIST.parameterizedBy(taco))
+ .addParameter("packager", packager)
+ .addStatement("packager.%N(tacos)", packager.member("package"))
+ .build()
+ )
+ .build()
+```
+
+`%N` will escape the name for you, ensuring that the output will pass compilation:
+
+```kotlin
+package com.example
+
+import com.squareup.tacos.Taco
+import com.squareup.tacos.TacoPackager
+import kotlin.collections.List
+
+fun packageTacos(tacos: List<Taco>, packager: TacoPackager) {
+ packager.`package`(tacos)
+}
+```
+
+### %L for Literals
+
+Although Kotlin's string templates usually work well in cases when you want to include literals into
+generated code, KotlinPoet offers additional syntax inspired-by but incompatible-with
+[`String.format()`][formatter]. It accepts **`%L`** to emit a **literal** value in the output. This
+works just like `Formatter`'s `%s`:
+
+```kotlin
+private fun computeRange(name: String, from: Int, to: Int, op: String): FunSpec {
+ return FunSpec.builder(name)
+ .returns(Int::class)
+ .addStatement("var result = 0")
+ .beginControlFlow("for (i in %L until %L)", from, to)
+ .addStatement("result = result %L i", op)
+ .endControlFlow()
+ .addStatement("return result")
+ .build()
+}
+```
+
+Literals are emitted directly to the output code with no escaping. Arguments for literals may be
+strings, primitives, and a few KotlinPoet types described below.
+
+### Code block format strings
+
+Code blocks may specify the values for their placeholders in a few ways. Only one style may be used
+for each operation on a code block.
+
+#### Relative Arguments
+
+Pass an argument value for each placeholder in the format string to `CodeBlock.add()`. In each
+example, we generate code to say "I ate 3 tacos"
+
+```kotlin
+CodeBlock.builder().add("I ate %L %L", 3, "tacos")
+```
+
+#### Positional Arguments
+
+Place an integer index (1-based) before the placeholder in the format string to specify which
+argument to use.
+
+```kotlin
+CodeBlock.builder().add("I ate %2L %1L", "tacos", 3)
+```
+
+#### Named Arguments
+
+Use the syntax `%argumentName:X` where `X` is the format character and call `CodeBlock.addNamed()`
+with a map containing all argument keys in the format string. Argument names use characters in
+`a-z`, `A-Z`, `0-9`, and `_`, and must start with a lowercase character.
+
+```kotlin
+val map = LinkedHashMap<String, Any>()
+map += "food" to "tacos"
+map += "count" to 3
+CodeBlock.builder().addNamed("I ate %count:L %food:L", map)
+```
+
+### Functions
+
+All of the above functions have a code body. Use `KModifier.ABSTRACT` to get a function without any
+body. This is only legal if it is enclosed by an abstract class or an interface.
+
+```kotlin
+val flux = FunSpec.builder("flux")
+ .addModifiers(KModifier.ABSTRACT, KModifier.PROTECTED)
+ .build()
+
+val helloWorld = TypeSpec.classBuilder("HelloWorld")
+ .addModifiers(KModifier.ABSTRACT)
+ .addFunction(flux)
+ .build()
+```
+
+Which generates this:
+
+```kotlin
+abstract class HelloWorld {
+ protected abstract fun flux()
+}
+```
+
+The other modifiers work where permitted.
+
+Methods also have parameters, varargs, KDoc, annotations, type variables, return type and receiver
+type for extension functions. All of these are configured with `FunSpec.Builder`.
+
+#### Extension functions
+
+Extension functions can be generated by specifying a `receiver`.
+
+```kotlin
+val square = FunSpec.builder("square")
+ .receiver(Int::class)
+ .returns(Int::class)
+ .addStatement("var s = this * this")
+ .addStatement("return s")
+ .build()
+```
+
+Which outputs:
+
+```kotlin
+fun Int.square(): Int {
+ val s = this * this
+ return s
+}
+```
+
+#### Single-expression functions
+
+KotlinPoet can recognize single-expression functions and print them out properly. It treats
+each function with a body that starts with `return` as a single-expression function:
+
+```kotlin
+val abs = FunSpec.builder("abs")
+ .addParameter("x", Int::class)
+ .returns(Int::class)
+ .addStatement("return if (x < 0) -x else x")
+ .build()
+```
+
+Which outputs:
+
+```kotlin
+fun abs(x: Int): Int = if (x < 0) -x else x
+```
+
+#### Default function arguments
+
+Consider the example below.
+Function argument `b` has a default value of 0 to avoid overloading this function.
+
+```kotlin
+fun add(a: Int, b: Int = 0) {
+ print("a + b = ${a + b}")
+}
+```
+
+Use the `defaultValue()` builder function to declare default value for a function argument.
+
+```kotlin
+FunSpec.builder("add")
+ .addParameter("a", Int::class)
+ .addParameter(
+ ParameterSpec.builder("b", Int::class)
+ .defaultValue("%L", 0)
+ .build()
+ )
+ .addStatement("print(\"a + b = ${a + b}\")")
+ .build()
+```
+
+#### Spaces wrap by default!
+
+In order to provide meaningful formatting, KotlinPoet would replace spaces, found in blocks of code,
+with new line symbols, in cases when the line of code exceeds the length limit. Let's take this
+function for example:
+
+```kotlin
+val funSpec = FunSpec.builder("foo")
+ .addStatement("return (100..10000).map { number -> number * number }.map { number -> number.toString() }.also { string -> println(string) }")
+ .build()
+```
+
+Depending on where it's found in the file, it may end up being printed out like this:
+
+```kotlin
+fun foo() = (100..10000).map { number -> number * number }.map { number -> number.toString() }.also
+{ string -> println(string) }
+```
+
+Unfortunately this code is broken: the compiler expects `also` and `{` to be on the same line.
+KotlinPoet is unable to understand the context of the expression and fix the formatting for you, but
+there's a trick you can use to declare a non-breaking space - use the `·` symbol where you would
+otherwise use a space. Let's apply this to our example:
+
+```kotlin
+val funSpec = FunSpec.builder("foo")
+ .addStatement("return (100..10000).map·{ number -> number * number }.map·{ number -> number.toString() }.also·{ string -> println(string) }")
+ .build()
+```
+
+This will now produce the following result:
+
+```kotlin
+fun foo() = (100..10000).map { number -> number * number }.map { number ->
+ number.toString()
+}.also { string -> println(string) }
+```
+
+The code is now correct and will compile properly. It still doesn't look perfect - you can play with
+replacing other spaces in the code block with `·` symbols to achieve better formatting.
+
+### Constructors
+
+`FunSpec` is a slight misnomer; it can also be used for constructors:
+
+```kotlin
+val flux = FunSpec.constructorBuilder()
+ .addParameter("greeting", String::class)
+ .addStatement("this.%N = %N", "greeting", "greeting")
+ .build()
+
+val helloWorld = TypeSpec.classBuilder("HelloWorld")
+ .addProperty("greeting", String::class, KModifier.PRIVATE)
+ .addFunction(flux)
+ .build()
+```
+
+Which generates this:
+
+```kotlin
+class HelloWorld {
+ private val greeting: String
+
+ constructor(greeting: String) {
+ this.greeting = greeting
+ }
+}
+```
+
+For the most part, constructors work just like methods. When emitting code, KotlinPoet will place
+constructors before methods in the output file.
+
+Often times you'll need to generate the primary constructor for a class:
+
+```kotlin
+val helloWorld = TypeSpec.classBuilder("HelloWorld")
+ .primaryConstructor(flux)
+ .addProperty("greeting", String::class, KModifier.PRIVATE)
+ .build()
+```
+
+This code, however, generates the following:
+
+```kotlin
+class HelloWorld(greeting: String) {
+ private val greeting: String
+
+ init {
+ this.greeting = greeting
+ }
+}
+```
+
+By default, KotlinPoet won't merge primary constructor parameters and properties, even if they share
+the same name. To achieve the effect, you have to tell KotlinPoet that the property is initialized
+via the constructor parameter:
+
+```kotlin
+val flux = FunSpec.constructorBuilder()
+ .addParameter("greeting", String::class)
+ .build()
+
+val helloWorld = TypeSpec.classBuilder("HelloWorld")
+ .primaryConstructor(flux)
+ .addProperty(
+ PropertySpec.builder("greeting", String::class)
+ .initializer("greeting")
+ .addModifiers(KModifier.PRIVATE)
+ .build()
+ )
+ .build()
+```
+
+Now we're getting the following output:
+
+```kotlin
+class HelloWorld(private val greeting: String)
+```
+
+Notice that KotlinPoet omits `{}` for classes with empty bodies.
+
+### Parameters
+
+Declare parameters on methods and constructors with either `ParameterSpec.builder()` or
+`FunSpec`'s convenient `addParameter()` API:
+
+```kotlin
+val android = ParameterSpec.builder("android", String::class)
+ .defaultValue("\"pie\"")
+ .build()
+
+val welcomeOverlords = FunSpec.builder("welcomeOverlords")
+ .addParameter(android)
+ .addParameter("robot", String::class)
+ .build()
+```
+
+The code above generates:
+
+```kotlin
+fun welcomeOverlords(android: String = "pie", robot: String) {
+}
+```
+
+The extended `Builder` form is necessary when the parameter has annotations (such as `@Inject`).
+
+### Properties
+
+Like parameters, properties can be created either with builders or by using convenient helper
+methods:
+
+```kotlin
+val android = PropertySpec.builder("android", String::class)
+ .addModifiers(KModifier.PRIVATE)
+ .build()
+
+val helloWorld = TypeSpec.classBuilder("HelloWorld")
+ .addProperty(android)
+ .addProperty("robot", String::class, KModifier.PRIVATE)
+ .build()
+```
+
+Which generates:
+
+```kotlin
+class HelloWorld {
+ private val android: String
+
+ private val robot: String
+}
+```
+
+The extended `Builder` form is necessary when a field has KDoc, annotations, or a field
+initializer. Field initializers use the same [`String.format()`][formatter]-like syntax as the code
+blocks above:
+
+```kotlin
+val android = PropertySpec.builder("android", String::class)
+ .addModifiers(KModifier.PRIVATE)
+ .initializer("%S + %L", "Oreo v.", 8.1)
+ .build()
+```
+
+Which generates:
+
+```kotlin
+private val android: String = "Oreo v." + 8.1
+```
+
+By default `PropertySpec.Builder` produces `val` properties. Use `mutable()` if you need a
+`var`:
+
+```kotlin
+val android = PropertySpec.builder("android", String::class)
+ .mutable()
+ .addModifiers(KModifier.PRIVATE)
+ .initializer("%S + %L", "Oreo v.", 8.1)
+ .build()
+```
+
+#### Inline properties
+
+The way KotlinPoet models inline properties deserves special mention. The following snippet of code:
+
+```kotlin
+val android = PropertySpec.builder("android", String::class)
+ .mutable()
+ .addModifiers(KModifier.INLINE)
+ .build()
+```
+
+will produce an error:
+
+```
+java.lang.IllegalArgumentException: KotlinPoet doesn't allow setting the inline modifier on
+properties. You should mark either the getter, the setter, or both inline.
+```
+
+Indeed, a property marked with `inline` should have at least one accessor which will be inlined by
+the compiler. Let's add a getter to this property:
+
+```kotlin
+val android = PropertySpec.builder("android", String::class)
+ .mutable()
+ .getter(
+ FunSpec.getterBuilder()
+ .addModifiers(KModifier.INLINE)
+ .addStatement("return %S", "foo")
+ .build()
+ )
+ .build()
+```
+
+The result is the following:
+
+```kotlin
+var android: kotlin.String
+ inline get() = "foo"
+```
+
+Now, what if we wanted to add a non-inline setter to the property above? We can do so without
+modifying any of the code we wrote previously:
+
+```kotlin
+val android = PropertySpec.builder("android", String::class)
+ .mutable()
+ .getter(
+ FunSpec.getterBuilder()
+ .addModifiers(KModifier.INLINE)
+ .addStatement("return %S", "foo")
+ .build()
+ )
+ .setter(
+ FunSpec.setterBuilder()
+ .addParameter("value", String::class)
+ .build()
+ )
+ .build()
+```
+
+We get the expected result:
+
+```kotlin
+var android: kotlin.String
+ inline get() = "foo"
+ set(`value`) {
+ }
+```
+
+Finally, if we go back and add `KModifier.INLINE` to the setter, KotlinPoet can wrap it nicely and
+produce the following result:
+
+```kotlin
+inline var android: kotlin.String
+ get() = "foo"
+ set(`value`) {
+ }
+```
+
+Removing the modifier from either the getter or the setter will unwrap the expression back.
+
+If, on the other hand, KotlinPoet had allowed marking a property `inline` directly, the programmer
+would have had to manually add/remove the modifier whenever the state of the accessors changes in
+order to get correct and compilable output. We're solving this problem by making accessors the
+source of truth for the `inline` modifier.
+
+### Interfaces
+
+KotlinPoet has no trouble with interfaces. Note that interface methods must always be `ABSTRACT`.
+The modifier is necessary when defining the interface:
+
+```kotlin
+val helloWorld = TypeSpec.interfaceBuilder("HelloWorld")
+ .addProperty("buzz", String::class)
+ .addFunction(
+ FunSpec.builder("beep")
+ .addModifiers(KModifier.ABSTRACT)
+ .build()
+ )
+ .build()
+```
+
+But these modifiers are omitted when the code is generated. These are the default so we don't need
+to include them for `kotlinc`'s benefit!
+
+```kotlin
+interface HelloWorld {
+ val buzz: String
+
+ fun beep()
+}
+```
+
+Kotlin 1.4 adds support for functional interfaces via `fun interface` syntax. To create this in
+KotlinPoet, use `TypeSpec.funInterfaceBuilder()`.
+
+```kotlin
+val helloWorld = TypeSpec.funInterfaceBuilder("HelloWorld")
+ .addFunction(
+ FunSpec.builder("beep")
+ .addModifiers(KModifier.ABSTRACT)
+ .build()
+ )
+ .build()
+
+// Generates...
+fun interface HelloWorld {
+ fun beep()
+}
+```
+
+### Objects
+
+KotlinPoet supports objects:
+
+```kotlin
+val helloWorld = TypeSpec.objectBuilder("HelloWorld")
+ .addProperty(
+ PropertySpec.builder("buzz", String::class)
+ .initializer("%S", "buzz")
+ .build()
+ )
+ .addFunction(
+ FunSpec.builder("beep")
+ .addStatement("println(%S)", "Beep!")
+ .build()
+ )
+ .build()
+```
+
+Similarly, you can create companion objects and add them to classes using `addType()`:
+
+```kotlin
+val companion = TypeSpec.companionObjectBuilder()
+ .addProperty(
+ PropertySpec.builder("buzz", String::class)
+ .initializer("%S", "buzz")
+ .build()
+ )
+ .addFunction(
+ FunSpec.builder("beep")
+ .addStatement("println(%S)", "Beep!")
+ .build()
+ )
+ .build()
+
+val helloWorld = TypeSpec.classBuilder("HelloWorld")
+ .addType(companion)
+ .build()
+```
+
+You can provide an optional name for a companion object.
+
+### Enums
+
+Use `enumBuilder` to create the enum type, and `addEnumConstant()` for each value:
+
+```kotlin
+val helloWorld = TypeSpec.enumBuilder("Roshambo")
+ .addEnumConstant("ROCK")
+ .addEnumConstant("SCISSORS")
+ .addEnumConstant("PAPER")
+ .build()
+```
+
+To generate this:
+
+```kotlin
+enum class Roshambo {
+ ROCK,
+
+ SCISSORS,
+
+ PAPER
+}
+```
+
+Fancy enums are supported, where the enum values override methods or call a superclass constructor.
+Here's a comprehensive example:
+
+```kotlin
+val helloWorld = TypeSpec.enumBuilder("Roshambo")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("handsign", String::class)
+ .build()
+ )
+ .addEnumConstant(
+ "ROCK", TypeSpec.anonymousClassBuilder()
+ .addSuperclassConstructorParameter("%S", "fist")
+ .addFunction(
+ FunSpec.builder("toString")
+ .addModifiers(KModifier.OVERRIDE)
+ .addStatement("return %S", "avalanche!")
+ .returns(String::class)
+ .build()
+ )
+ .build()
+ )
+ .addEnumConstant(
+ "SCISSORS", TypeSpec.anonymousClassBuilder()
+ .addSuperclassConstructorParameter("%S", "peace")
+ .build()
+ )
+ .addEnumConstant(
+ "PAPER", TypeSpec.anonymousClassBuilder()
+ .addSuperclassConstructorParameter("%S", "flat")
+ .build()
+ )
+ .addProperty(
+ PropertySpec.builder("handsign", String::class, KModifier.PRIVATE)
+ .initializer("handsign")
+ .build()
+ )
+ .build()
+```
+
+Which generates this:
+
+```kotlin
+enum class Roshambo(private val handsign: String) {
+ ROCK("fist") {
+ override fun toString(): String = "avalanche!"
+ },
+
+ SCISSORS("peace"),
+
+ PAPER("flat");
+}
+```
+
+### Anonymous Inner Classes
+
+In the enum code, we used `TypeSpec.anonymousClassBuilder()`. Anonymous inner classes can also be
+used in code blocks. They are values that can be referenced with `%L`:
+
+```kotlin
+val comparator = TypeSpec.anonymousClassBuilder()
+ .addSuperinterface(Comparator::class.parameterizedBy(String::class))
+ .addFunction(
+ FunSpec.builder("compare")
+ .addModifiers(KModifier.OVERRIDE)
+ .addParameter("a", String::class)
+ .addParameter("b", String::class)
+ .returns(Int::class)
+ .addStatement("return %N.length - %N.length", "a", "b")
+ .build()
+ )
+ .build()
+
+val helloWorld = TypeSpec.classBuilder("HelloWorld")
+ .addFunction(
+ FunSpec.builder("sortByLength")
+ .addParameter("strings", List::class.parameterizedBy(String::class))
+ .addStatement("%N.sortedWith(%L)", "strings", comparator)
+ .build()
+ )
+ .build()
+```
+
+This generates a method that contains a class that contains a method:
+
+```kotlin
+class HelloWorld {
+ fun sortByLength(strings: List<String>) {
+ strings.sortedWith(object : Comparator<String> {
+ override fun compare(a: String, b: String): Int = a.length - b.length
+ })
+ }
+}
+```
+
+One particularly tricky part of defining anonymous inner classes is the arguments to the superclass
+constructor. To pass them use `TypeSpec.Builder`'s `addSuperclassConstructorParameter()` method.
+
+### Annotations
+
+Simple annotations are easy:
+
+```kotlin
+val test = FunSpec.builder("test string equality")
+ .addAnnotation(Test::class)
+ .addStatement("assertThat(%1S).isEqualTo(%1S)", "foo")
+ .build()
+```
+
+Which generates this function with an `@Test` annotation:
+
+```kotlin
+@Test
+fun `test string equality`() {
+ assertThat("foo").isEqualTo("foo")
+}
+```
+
+Use `AnnotationSpec.builder()` to set properties on annotations:
+
+```kotlin
+val logRecord = FunSpec.builder("recordEvent")
+ .addModifiers(KModifier.ABSTRACT)
+ .addAnnotation(
+ AnnotationSpec.builder(Headers::class)
+ .addMember("accept = %S", "application/json; charset=utf-8")
+ .addMember("userAgent = %S", "Square Cash")
+ .build()
+ )
+ .addParameter("logRecord", LogRecord::class)
+ .returns(LogReceipt::class)
+ .build()
+```
+
+Which generates this annotation with `accept` and `userAgent` properties:
+
+```kotlin
+@Headers(
+ accept = "application/json; charset=utf-8",
+ userAgent = "Square Cash"
+)
+abstract fun recordEvent(logRecord: LogRecord): LogReceipt
+```
+
+When you get fancy, annotation values can be annotations themselves. Use `%L` for embedded
+annotations:
+
+```kotlin
+val headerList = ClassName("", "HeaderList")
+val header = ClassName("", "Header")
+val logRecord = FunSpec.builder("recordEvent")
+ .addModifiers(KModifier.ABSTRACT)
+ .addAnnotation(
+ AnnotationSpec.builder(headerList)
+ .addMember(
+ "[\n⇥%L,\n%L⇤\n]",
+ AnnotationSpec.builder(header)
+ .addMember("name = %S", "Accept")
+ .addMember("value = %S", "application/json; charset=utf-8")
+ .build(),
+ AnnotationSpec.builder(header)
+ .addMember("name = %S", "User-Agent")
+ .addMember("value = %S", "Square Cash")
+ .build()
+ )
+ .build()
+ )
+ .addParameter("logRecord", logRecordName)
+ .returns(logReceipt)
+ .build()
+```
+
+Which generates this:
+
+```kotlin
+@HeaderList(
+ [
+ Header(name = "Accept", value = "application/json; charset=utf-8"),
+ Header(name = "User-Agent", value = "Square Cash")
+ ]
+)
+abstract fun recordEvent(logRecord: LogRecord): LogReceipt
+```
+
+KotlinPoet supports use-site targets for annotations:
+
+```kotlin
+val utils = FileSpec.builder("com.example", "Utils")
+ .addAnnotation(
+ AnnotationSpec.builder(JvmName::class)
+ .useSiteTarget(UseSiteTarget.FILE)
+ .build()
+ )
+ .addFunction(
+ FunSpec.builder("abs")
+ .receiver(Int::class)
+ .returns(Int::class)
+ .addStatement("return if (this < 0) -this else this")
+ .build()
+ )
+ .build()
+```
+
+Will output this:
+
+```kotlin
+@file:JvmName
+
+package com.example
+
+import kotlin.Int
+import kotlin.jvm.JvmName
+
+fun Int.abs(): Int = if (this < 0) -this else this
+```
+
+### Type Aliases
+
+KotlinPoet provides API for creating Type Aliases, which supports simple class names, parameterized
+types and lambdas:
+
+```kotlin
+val k = TypeVariableName("K")
+val t = TypeVariableName("T")
+
+val fileTable = Map::class.asClassName()
+ .parameterizedBy(k, Set::class.parameterizedBy(File::class))
+
+val predicate = LambdaTypeName.get(
+ parameters = arrayOf(t),
+ returnType = Boolean::class.asClassName()
+)
+val helloWorld = FileSpec.builder("com.example", "HelloWorld")
+ .addTypeAlias(TypeAliasSpec.builder("Word", String::class).build())
+ .addTypeAlias(
+ TypeAliasSpec.builder("FileTable", fileTable)
+ .addTypeVariable(k)
+ .build()
+ )
+ .addTypeAlias(
+ TypeAliasSpec.builder("Predicate", predicate)
+ .addTypeVariable(t)
+ .build()
+ )
+ .build()
+```
+
+Which generates the following:
+
+```kotlin
+package com.example
+
+import java.io.File
+import kotlin.Boolean
+import kotlin.String
+import kotlin.collections.Map
+import kotlin.collections.Set
+
+typealias Word = String
+
+typealias FileTable<K> = Map<K, Set<File>>
+
+typealias Predicate<T> = (T) -> Boolean
+```
+
+### Callable References
+
+[Callable references](https://kotlinlang.org/docs/reference/reflection.html#callable-references) to
+constructors, functions, and properties may be emitted via:
+
+- `ClassName.constructorReference()` for constructors
+- `MemberName.reference()` for functions and properties
+
+For example,
+
+```kotlin
+val helloClass = ClassName("com.example.hello", "Hello")
+val worldFunction: MemberName = helloClass.member("world")
+val byeProperty: MemberName = helloClass.nestedClass("World").member("bye")
+
+val factoriesFun = FunSpec.builder("factories")
+ .addStatement("val hello = %L", helloClass.constructorReference())
+ .addStatement("val world = %L", worldFunction.reference())
+ .addStatement("val bye = %L", byeProperty.reference())
+ .build()
+
+FileSpec.builder("com.example", "HelloWorld")
+ .addFunction(factoriesFun)
+ .build()
+```
+
+would generate:
+
+```kotlin
+package com.example
+
+import com.example.hello.Hello
+
+fun factories() {
+ val hello = ::Hello
+ val world = Hello::world
+ val bye = Hello.World::bye
+}
+```
+
+Top-level classes and members with conflicting names may require aliased imports, as with
+[member names](#m-for-members).
+
+kotlin-reflect
+--------
+
+To generate source code from
+any [`KType`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-type/), including
+information that's not accessible to the builtin reflection APIs, KotlinPoet depends
+on [kotlin-reflect](https://kotlinlang.org/docs/reflection.html#jvm-dependency). `kotlin-reflect`
+can read the metadata of your classes and access this extra information. KotlinPoet can for an
+example, read the type parameters and
+their [variance](https://kotlinlang.org/docs/generics.html#variance) from a generic `KType` and
+generate appropriate source code.
+
+`kotlin-reflect` is a relatively big dependency though and in some cases it is desirable to remove
+it from the final executable to save some space and/or simplify the proguard/R8 setup (for example
+for a Gradle plugin that generates Kotlin code). It is possible to do so and still use most of the
+KotlinPoet APIs:
+
+```kotlin
+dependencies {
+ implementation("com.squareup:kotlinpoet:<version>") {
+ exclude(module = "kotlin-reflect")
+ }
+}
+```
+
+The main APIs that require `kotlin-reflect`
+are [`KType.asTypeName()`](https://square.github.io/kotlinpoet/1.x/kotlinpoet/kotlinpoet/com.squareup.kotlinpoet/as-type-name.html)
+and [`typeNameOf<T>()`](https://square.github.io/kotlinpoet/1.x/kotlinpoet/kotlinpoet/com.squareup.kotlinpoet/type-name-of.html).
+If you're calling one of these without `kotlin-reflect` in the classpath and the type is generic
+or has annotations you will get a crash.
+
+You can replace it with code that passes type parameters or annotations explicitly and doesn't
+need `kotlin-reflect`. For example:
+
+```kotlin
+// Replace
+// kotlin-reflect needed
+val typeName = typeNameOf<List<Int?>>()
+
+// With
+// kotlin-reflect not needed
+val typeName =
+ List::class.asClassName().parameterizedBy(Int::class.asClassName().copy(nullable = true))
+```
+
+Download
+--------
+
+![Maven Central][version-shield]
+
+Download [the latest .jar][dl] or depend via Maven:
+
+```xml
+<dependency>
+ <groupId>com.squareup</groupId>
+ <artifactId>kotlinpoet</artifactId>
+ <version>[version]</version>
+</dependency>
+```
+
+or Gradle:
+
+```groovy
+implementation("com.squareup:kotlinpoet:[version]")
+```
+
+Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap].
+
+
+License
+-------
+
+ Copyright 2017 Square, Inc.
+
+ 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.
+
+
+ [dl]: https://search.maven.org/remote_content?g=com.squareup&a=kotlinpoet&v=LATEST
+ [version-shield]: https://img.shields.io/maven-central/v/com.squareup/kotlinpoet
+ [snap]: https://s01.oss.sonatype.org/content/repositories/snapshots/com/squareup/kotlinpoet/
+ [kdoc]: https://square.github.io/kotlinpoet/1.x/kotlinpoet/kotlinpoet/com.squareup.kotlinpoet/
+ [javapoet]: https://github.com/square/javapoet/
+ [formatter]: https://developer.android.com/reference/java/util/Formatter.html
diff --git a/docs/interop-javapoet.md b/docs/interop-javapoet.md
new file mode 100644
index 00000000..6b41f0d5
--- /dev/null
+++ b/docs/interop-javapoet.md
@@ -0,0 +1,54 @@
+JavaPoet Extensions for KotlinPoet
+==================================
+
+`interop:javapoet` is an interop API for converting [JavaPoet](https://github.com/squareup/javapoet)
+types to KotlinPoet types. This is particularly useful for projects that support code gen in
+multiple languages and want to easily be able to jump between.
+
+Note that this API is currently in preview and subject to API changes. Usage of them requires opting
+in to the `@KotlinPoetJavaPoetPreview` annotation.
+
+### Examples
+
+**Typealiases for common conflicting type names**
+
+```kotlin
+// Points to com.squareup.kotlinpoet.TypeName
+KTypeName
+// Points to com.squareup.javapoet.TypeName
+JTypeName
+```
+
+**Convert between a `JTypeName` and `KTypeName`**
+
+Most usages of these can run through the `toKTypeName()` and `toJTypeName()` extensions.
+
+```kotlin
+val jType = JTypeName.get("com.example", "Taco")
+
+// Returns a KotlinPoet `ClassName` of value `com.example.Taco`
+val kType = jType.toKTypeName()
+
+// Returns a JavaPoet `ClassName` of value `com.example.Taco`
+val jType2 = kType.toJTypeName()
+```
+
+### Intrinsics
+
+Kotlin supports a number of intrinsic types that live in the `kotlin` package, such as primitives,
+`List`, `String`, `IntArray`, etc. Where possible, interop will best-effort attempt to convert to
+the idiomatic Kotlin type when converting from the Java type.
+
+### Lossy Conversions
+
+Kotlin has more expressive types in some regards. These cannot be simply expressed in JavaPoet and
+are subject to lossy conversions.
+
+Examples include:
+
+- Nullability
+ - Nullable types in Kotlin will appear as normal types in JavaPoet.
+- Collection mutability
+ - Immutable Kotlin collections will convert to their standard (mutable) Java analogs.
+ - Java collections will convert to _immutable_ Kotlin analogs, erring on the side of safety in generated public APIs
+- Unsigned types
diff --git a/docs/interop-kotlinx-metadata.md b/docs/interop-kotlinx-metadata.md
new file mode 100644
index 00000000..58a470a6
--- /dev/null
+++ b/docs/interop-kotlinx-metadata.md
@@ -0,0 +1,84 @@
+KotlinPoet-metadata
+===================
+
+`interop:kotlinx-metadata` is an API for working with Kotlin `@Metadata` annotations. Its API
+sits atop [kotlinx-metadata](https://github.com/JetBrains/kotlin/tree/master/libraries/kotlinx-metadata/jvm),
+offering extensions for its types + JVM metadata information. This can be used to read
+Kotlin language semantics off of `Class` or `TypeElement` `@Metadata` annotations.
+
+### Example
+
+```kotlin
+data class Taco(val seasoning: String, val soft: Boolean) {
+ fun prepare() {
+
+ }
+}
+
+val kmClass = Taco::class.toKmClass()
+
+// Now you can access misc information about Taco from a Kotlin lens
+println(kmClass.name)
+kmClass.properties.forEach { println(it.name) }
+kmClass.functions.forEach { println(it.name) }
+```
+
+### Flags
+
+There are a number of boolean flags available to types as well under `Flags.kt`. These read the
+underlying kotlinx-metadata `Flags` property.
+
+Using the Taco example above, we can glean certain information:
+
+```kotlin
+println("Is class? ${kmClass.isClass}")
+println("Is data class? ${kmClass.isData}")
+```
+
+### Interop with KotlinPoet
+
+`interop:kotlinx-metadata` offers an API for converting core kotlinx-metadata `Km` types to
+KotlinPoet source representations of their APIs. This includes full type resolution, signatures,
+enclosed elements, and general stub source representations of the underlying API.
+
+### Example
+
+```kotlin
+data class Taco(val seasoning: String, val soft: Boolean) {
+ fun prepare() {
+ }
+}
+
+val typeSpec = Taco::class.toTypeSpec()
+
+// Or FileSpec
+val fileSpec = Taco::class.toFileSpec()
+```
+
+### Source representation
+
+The generated representations are a _best effort_ representation of the underlying source code.
+This means that synthetic elements will be excluded from generation. Kotlin-specific language
+features like lambdas or delegation will be coerced to their idiomatic source form.
+
+To aid with this, `toTypeSpec()` and `toFileSpec()` accept optional `ClassInspector` instances
+to assist in parsing/understanding the underlying JVM code. This is important for things like
+annotations, companion objects, certain JVM modifiers, overrides, and more. While it is optional,
+represented sources can be incomplete without this information available. Reflective and javax
+`Elements` implementations are available under the
+`com.squareup.kotlinpoet.metadata.classinspectors` package.
+
+Generated sources are solely _stub_ implementations, meaning implementation details of elements
+like functions, property getters, and delegated properties are simply stubbed with `TODO()`
+placeholders.
+
+### Known limitations
+
+- Only `KotlinClassMetadata.Class` and `KotlinClassMetadata.FileFacade` are supported for now. No support for `SyntheticClass`, `MultiFileClassFacade`, or `MultiFileClassPart`
+- `@JvmOverloads` annotations are only supported with `ElementsClassInspector` and not reflection.
+- Non-const literal values are only supported with `ElementsClassInspector` and not reflection.
+- ClassInspector data sourced from `synthetic` constructs are only supported with
+ `ReflectiveClassInspector` and not elements. This is because the javax Elements API does not model
+ synthetic constructs. This can yield some missing information, like static companion object properties
+ or `property:` site target annotations.
+- Annotations annotated with `AnnotationRetention.SOURCE` are not parsable in reflection nor javax elements.
diff --git a/docs/interop-ksp.md b/docs/interop-ksp.md
new file mode 100644
index 00000000..78c752f8
--- /dev/null
+++ b/docs/interop-ksp.md
@@ -0,0 +1,136 @@
+KSP Extensions for KotlinPoet
+==============
+
+`interop:ksp` is an interop API for converting
+[Kotlin Symbol Processing][ksp] (KSP) types to KotlinPoet types and
+writing to KSP `CodeGenerator`.
+
+```kotlin
+dependencies {
+ implementation("com.squareup:kotlinpoet-ksp:<version>")
+}
+```
+
+### Examples
+
+Examples are based on reading the following property as a `KSProperty`:
+
+```kotlin
+class Taco {
+ internal inline val seasoning: String get() = "spicy"
+}
+```
+
+**Convert a `KSType` to a `TypeName`**
+
+```kotlin
+// returns a `ClassName` of value `kotlin.String`
+seasoningKsProperty.type.toTypeName()
+```
+
+**Convert a `Modifier` to a `KModifier`**
+
+```kotlin
+// returns `[KModifier.INLINE]`
+seasoningKsProperty.modifiers.mapNotNull { it.toKModifier() }
+```
+
+**Convert a `Visibility` to a `KModifier`**
+
+```kotlin
+// returns `KModifier.INTERNAL`
+seasoningKsProperty.getVisibility().toKModifier()
+```
+
+**Write to `CodeGenerator`**
+
+To write a `FileSpec` to a KSP `CodeGenerator`, simply call the `FileSpec.writeTo(CodeGenerator, ...)`
+extension function.
+
+```kotlin
+fileSpec.writeTo(codeGenerator)
+```
+
+### Type Parameters
+
+Type parameters can be declared on classes, functions, and typealiases. These parameters are then
+available to all of its enclosed elements. In order for these elements to resolve these in KSP, you
+must be able to reference these type parameters by their _index_.
+
+In `kotlinpoet-ksp` this is orchestrated by the `TypeParameterResolver` API, which can be passed
+into most `toTypeName()` (or similar) functions to give them access to enclosing type parameters
+that they may reference.
+
+The canonical way to create an instance of this is to call `toTypeParameterResolver()` on a
+`List<KSTypeParameter>`.
+
+Consider the following class and function
+
+```kotlin
+abstract class Taco<T> {
+ abstract val seasoning: T
+}
+```
+
+To properly resolve the type of `seasoning`, we need to pass the class `TypeParameterResolver` to
+`toTypeName()` so that it can properly resolve it.
+
+```kotlin
+val classTypeParams = ksClassDeclaration.typeParameters.toTypeParameterResolver()
+// returns `T`
+val seasoningType = seasoningKsProperty.type.toTypeName(classTypeParams)
+```
+
+`TypeParameterResolver` is also composable to allow for multi-level nesting. `toTypeParameterResolver()`
+has an optional `parent` parameter to provide a parent instance.
+
+Consider our previous example again, but this time with a function that defines its own type parameters.
+
+```kotlin
+class Taco<T> {
+ fun <E> getShellOfType(param1: E, param2: T) {
+
+ }
+}
+```
+
+To resolve its parameters, we need to create a `TypeParameterResolver` from the function's
+`typeParameters` and _compose_ it with the enclosing class's type parameters as a `parent`.
+
+```kotlin
+val classTypeParams = ksClassDeclaration.typeParameters.toTypeParameterResolver()
+val functionTypeParams = ksFunction.typeParameters.toTypeParameterResolver(parent = classTypeParams)
+// returns `[E, T]`
+val seasoningType = ksFunction.parameterTypes.map { it.toTypeName(functionTypeParams) }
+```
+
+### Incremental Processing
+
+KSP supports [incremental processing][incremental] as
+long as symbol processors properly indicate originating files in generated new files and whether or
+not they are `aggregating`. `kotlinpoet-ksp` supports this via `OriginatingKSFiles`, which is a simple
+API that sits atop KotlinPoet's `Taggable` API. To use this, simply add relevant originating files to
+any `TypeSpec`, `TypeAliasSpec`, `PropertySpec`, or `FunSpec` builders.
+
+```kotlin
+val functionBuilder = FunSpec.builder("sayHello")
+ .addOriginatingKSFile(sourceKsFile)
+ .build()
+```
+
+Like KotlinPoet's _originating elements_ support for javac annotation processors, calling the
+`FileSpec.writeTo(CodeGenerator, ...)` function will automatically collect and de-dupe these originating
+`KSFile` references and automatically assemble them in the underlying `Dependencies` for KSP's reference.
+
+Optionally you can define your own collection of files and pass them to the `writeTo` function, but usually
+you don't need to do this manually.
+
+Lastly - `FileSpec.writeTo(CodeGenerator, ...)` also requires you to specify if your processor is
+_aggregating_ or not via required parameter by the same name.
+
+### TypeAlias Handling
+
+For `typealias` types, KSP interop will store a `TypeAliasTag` in the `TypeName`'s tags with a reference to the abbreviated type. This can be useful for APIs that want to resolve all un-aliased types.
+
+ [ksp]: https://github.com/google/ksp
+ [incremental]: https://github.com/google/ksp/blob/main/docs/incremental.md
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 00000000..f0543f01
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,19 @@
+org.gradle.jvmargs='-Dfile.encoding=UTF-8'
+
+GROUP=com.squareup
+VERSION_NAME=1.13.0-SNAPSHOT
+
+POM_URL=https://github.com/square/kotlinpoet
+POM_SCM_URL=https://github.com/square/kotlinpoet
+POM_SCM_CONNECTION=scm:git:https://github.com/square/kotlinpoet.git
+POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/square/kotlinpoet.git
+
+POM_LICENCE_NAME=The Apache Software License, Version 2.0
+POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt
+POM_LICENCE_DIST=repo
+
+POM_DEVELOPER_ID=square
+POM_DEVELOPER_NAME=Square, Inc.
+
+SONATYPE_HOST=S01
+RELEASE_SIGNING_ENABLED=true
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 00000000..523b9bc0
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,51 @@
+# Copyright (C) 2021 Square, Inc.
+#
+# 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.
+
+[versions]
+kotlin = "1.7.22"
+kct = "1.4.9"
+ksp = "1.7.22-1.0.8"
+ktlint = "0.48.2"
+
+[plugins]
+kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
+dokka = { id = "org.jetbrains.dokka", version = "1.7.20" }
+ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
+spotless = { id = "com.diffplug.spotless", version = "6.14.0" }
+mavenPublish = { id = "com.vanniktech.maven.publish", version = "0.24.0" }
+kotlinBinaryCompatibilityValidator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version = "0.12.1" }
+
+[libraries]
+autoCommon = { module = "com.google.auto:auto-common", version = "1.2.1" }
+guava = { module = "com.google.guava:guava", version = "31.1-jre" }
+javapoet = "com.squareup:javapoet:1.13.0"
+
+autoService = "com.google.auto.service:auto-service-annotations:1.0.1"
+autoService-ksp = "dev.zacsweers.autoservice:auto-service-ksp:1.0.0"
+
+kotlin-compilerEmbeddable = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlin" }
+kotlin-annotationProcessingEmbeddable = { module = "org.jetbrains.kotlin:kotlin-annotation-processing-embeddable", version.ref = "kotlin" }
+kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
+kotlin-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
+kotlin-metadata = { module = "org.jetbrains.kotlinx:kotlinx-metadata-jvm", version = "0.5.0" }
+
+ksp = { module = "com.google.devtools.ksp:symbol-processing", version.ref = "ksp" }
+ksp-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
+
+truth = { module = "com.google.truth:truth", version = "1.1.3" }
+compileTesting = { module = "com.google.testing.compile:compile-testing", version = "0.21.0" }
+jimfs = { module = "com.google.jimfs:jimfs", version = "1.2" }
+ecj = { module = "org.eclipse.jdt.core.compiler:ecj", version = "4.6.1" }
+kotlinCompileTesting = { module = "com.github.tschuchortdev:kotlin-compile-testing", version.ref = "kct" }
+kotlinCompileTesting-ksp = { module = "com.github.tschuchortdev:kotlin-compile-testing-ksp", version.ref = "kct" }
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..249e5832
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..8fad3f5a
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 00000000..a69d9cb6
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,240 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# 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.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 00000000..f127cfd4
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,91 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/interop/javapoet/api/javapoet.api b/interop/javapoet/api/javapoet.api
new file mode 100644
index 00000000..80ac2d96
--- /dev/null
+++ b/interop/javapoet/api/javapoet.api
@@ -0,0 +1,22 @@
+public final class com/squareup/kotlinpoet/javapoet/J2kInteropKt {
+ public static final fun toKClassName (Lcom/squareup/javapoet/ClassName;)Lcom/squareup/kotlinpoet/ClassName;
+ public static final fun toKParameterizedTypeName (Lcom/squareup/javapoet/ParameterizedTypeName;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public static final fun toKTypeName (Lcom/squareup/javapoet/TypeName;)Lcom/squareup/kotlinpoet/TypeName;
+ public static final fun toKTypeVariableName (Lcom/squareup/javapoet/TypeVariableName;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static final fun toKWildcardTypeName (Lcom/squareup/javapoet/WildcardTypeName;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+}
+
+public final class com/squareup/kotlinpoet/javapoet/K2jInteropKt {
+ public static final fun toJClassName (Lcom/squareup/kotlinpoet/ClassName;Z)Lcom/squareup/javapoet/TypeName;
+ public static synthetic fun toJClassName$default (Lcom/squareup/kotlinpoet/ClassName;ZILjava/lang/Object;)Lcom/squareup/javapoet/TypeName;
+ public static final fun toJParameterizedOrArrayTypeName (Lcom/squareup/kotlinpoet/ParameterizedTypeName;)Lcom/squareup/javapoet/TypeName;
+ public static final fun toJParameterizedTypeName (Lcom/squareup/kotlinpoet/ParameterizedTypeName;)Lcom/squareup/javapoet/ParameterizedTypeName;
+ public static final fun toJTypeName (Lcom/squareup/kotlinpoet/TypeName;Z)Lcom/squareup/javapoet/TypeName;
+ public static synthetic fun toJTypeName$default (Lcom/squareup/kotlinpoet/TypeName;ZILjava/lang/Object;)Lcom/squareup/javapoet/TypeName;
+ public static final fun toJTypeVariableName (Lcom/squareup/kotlinpoet/TypeVariableName;)Lcom/squareup/javapoet/TypeVariableName;
+ public static final fun toJWildcardTypeName (Lcom/squareup/kotlinpoet/WildcardTypeName;)Lcom/squareup/javapoet/WildcardTypeName;
+}
+
+public abstract interface annotation class com/squareup/kotlinpoet/javapoet/KotlinPoetJavaPoetPreview : java/lang/annotation/Annotation {
+}
+
diff --git a/interop/javapoet/build.gradle.kts b/interop/javapoet/build.gradle.kts
new file mode 100644
index 00000000..9899d9cc
--- /dev/null
+++ b/interop/javapoet/build.gradle.kts
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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.
+ */
+
+tasks.jar {
+ manifest {
+ attributes("Automatic-Module-Name" to "com.squareup.kotlinpoet.javapoet")
+ }
+}
+
+dependencies {
+ api(project(":kotlinpoet"))
+ api(libs.javapoet)
+ testImplementation(libs.kotlin.junit)
+ testImplementation(libs.truth)
+}
diff --git a/interop/javapoet/gradle.properties b/interop/javapoet/gradle.properties
new file mode 100644
index 00000000..9584afad
--- /dev/null
+++ b/interop/javapoet/gradle.properties
@@ -0,0 +1,4 @@
+POM_ARTIFACT_ID=kotlinpoet-javapoet
+POM_NAME=KotlinPoet (JavaPoet Interop)
+POM_DESCRIPTION=Extensions for interop with JavaPoet.
+POM_PACKAGING=jar
diff --git a/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/K2jInterop.kt b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/K2jInterop.kt
new file mode 100644
index 00000000..82d7374a
--- /dev/null
+++ b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/K2jInterop.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.javapoet
+
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.ANY
+import com.squareup.kotlinpoet.ARRAY
+import com.squareup.kotlinpoet.BOOLEAN
+import com.squareup.kotlinpoet.BOOLEAN_ARRAY
+import com.squareup.kotlinpoet.BYTE
+import com.squareup.kotlinpoet.BYTE_ARRAY
+import com.squareup.kotlinpoet.CHAR
+import com.squareup.kotlinpoet.CHAR_ARRAY
+import com.squareup.kotlinpoet.CHAR_SEQUENCE
+import com.squareup.kotlinpoet.DOUBLE
+import com.squareup.kotlinpoet.DOUBLE_ARRAY
+import com.squareup.kotlinpoet.Dynamic
+import com.squareup.kotlinpoet.ENUM
+import com.squareup.kotlinpoet.FLOAT
+import com.squareup.kotlinpoet.FLOAT_ARRAY
+import com.squareup.kotlinpoet.INT
+import com.squareup.kotlinpoet.INT_ARRAY
+import com.squareup.kotlinpoet.LIST
+import com.squareup.kotlinpoet.LONG
+import com.squareup.kotlinpoet.LONG_ARRAY
+import com.squareup.kotlinpoet.LambdaTypeName
+import com.squareup.kotlinpoet.MAP
+import com.squareup.kotlinpoet.MUTABLE_LIST
+import com.squareup.kotlinpoet.MUTABLE_MAP
+import com.squareup.kotlinpoet.MUTABLE_SET
+import com.squareup.kotlinpoet.SET
+import com.squareup.kotlinpoet.SHORT
+import com.squareup.kotlinpoet.SHORT_ARRAY
+import com.squareup.kotlinpoet.STAR
+import com.squareup.kotlinpoet.STRING
+import com.squareup.kotlinpoet.U_BYTE
+import com.squareup.kotlinpoet.U_BYTE_ARRAY
+import com.squareup.kotlinpoet.U_INT
+import com.squareup.kotlinpoet.U_INT_ARRAY
+import com.squareup.kotlinpoet.U_LONG
+import com.squareup.kotlinpoet.U_LONG_ARRAY
+import com.squareup.kotlinpoet.U_SHORT
+import com.squareup.kotlinpoet.U_SHORT_ARRAY
+
+@KotlinPoetJavaPoetPreview
+public fun KClassName.toJClassName(boxIfPrimitive: Boolean = false): JTypeName {
+ return when (copy(nullable = false)) {
+ BOOLEAN -> JTypeName.BOOLEAN.boxIfPrimitive(boxIfPrimitive || isNullable)
+ BYTE, U_BYTE -> JTypeName.BYTE.boxIfPrimitive(boxIfPrimitive || isNullable)
+ CHAR -> JTypeName.CHAR.boxIfPrimitive(boxIfPrimitive || isNullable)
+ SHORT, U_SHORT -> JTypeName.SHORT.boxIfPrimitive(boxIfPrimitive || isNullable)
+ INT, U_INT -> JTypeName.INT.boxIfPrimitive(boxIfPrimitive || isNullable)
+ LONG, U_LONG -> JTypeName.LONG.boxIfPrimitive(boxIfPrimitive || isNullable)
+ FLOAT -> JTypeName.FLOAT.boxIfPrimitive(boxIfPrimitive || isNullable)
+ DOUBLE -> JTypeName.DOUBLE.boxIfPrimitive(boxIfPrimitive || isNullable)
+ ANY -> JTypeName.OBJECT
+ CHAR_SEQUENCE -> PoetInterop.CN_JAVA_CHAR_SEQUENCE
+ STRING -> PoetInterop.CN_JAVA_STRING
+ LIST, MUTABLE_LIST -> PoetInterop.CN_JAVA_LIST
+ SET, MUTABLE_SET -> PoetInterop.CN_JAVA_SET
+ MAP, MUTABLE_MAP -> PoetInterop.CN_JAVA_MAP
+ BOOLEAN_ARRAY -> ArrayTypeName.of(JTypeName.BOOLEAN)
+ BYTE_ARRAY, U_BYTE_ARRAY -> ArrayTypeName.of(JTypeName.BYTE)
+ CHAR_ARRAY -> ArrayTypeName.of(JTypeName.CHAR)
+ SHORT_ARRAY, U_SHORT_ARRAY -> ArrayTypeName.of(JTypeName.SHORT)
+ INT_ARRAY, U_INT_ARRAY -> ArrayTypeName.of(JTypeName.INT)
+ LONG_ARRAY, U_LONG_ARRAY -> ArrayTypeName.of(JTypeName.LONG)
+ FLOAT_ARRAY -> ArrayTypeName.of(JTypeName.FLOAT)
+ DOUBLE_ARRAY -> ArrayTypeName.of(JTypeName.DOUBLE)
+ ENUM -> PoetInterop.CN_JAVA_ENUM
+ else -> {
+ if (simpleNames.size == 1) {
+ JClassName.get(packageName, simpleName)
+ } else {
+ JClassName.get(packageName, simpleNames.first(), *simpleNames.drop(1).toTypedArray())
+ }
+ }
+ }
+}
+
+@KotlinPoetJavaPoetPreview
+public fun KParameterizedTypeName.toJParameterizedOrArrayTypeName(): JTypeName {
+ return when (rawType) {
+ ARRAY -> {
+ val componentType = typeArguments.firstOrNull()?.toJTypeName()
+ ?: throw IllegalStateException("Array with no type! $this")
+ ArrayTypeName.of(componentType)
+ }
+ else -> {
+ JParameterizedTypeName.get(
+ rawType.toJClassName() as JClassName,
+ *typeArguments.map { it.toJTypeName(boxIfPrimitive = true) }.toTypedArray(),
+ )
+ }
+ }
+}
+
+@KotlinPoetJavaPoetPreview
+public fun KParameterizedTypeName.toJParameterizedTypeName(): JParameterizedTypeName {
+ check(rawType != ARRAY) {
+ "Array type! JavaPoet arrays are a custom TypeName. Use this function only for things you know are not arrays"
+ }
+ return toJParameterizedOrArrayTypeName() as JParameterizedTypeName
+}
+
+@KotlinPoetJavaPoetPreview
+public fun KTypeVariableName.toJTypeVariableName(): JTypeVariableName {
+ return JTypeVariableName.get(name, *bounds.map { it.toJTypeName(boxIfPrimitive = true) }.toTypedArray())
+}
+
+@KotlinPoetJavaPoetPreview
+public fun KWildcardTypeName.toJWildcardTypeName(): JWildcardTypeName {
+ return if (this == STAR) {
+ JWildcardTypeName.subtypeOf(TypeName.OBJECT)
+ } else if (inTypes.size == 1) {
+ JWildcardTypeName.supertypeOf(inTypes[0].toJTypeName())
+ } else {
+ JWildcardTypeName.subtypeOf(outTypes[0].toJTypeName())
+ }
+}
+
+@KotlinPoetJavaPoetPreview
+public fun KTypeName.toJTypeName(boxIfPrimitive: Boolean = false): JTypeName {
+ return when (this) {
+ is KClassName -> toJClassName(boxIfPrimitive)
+ Dynamic -> throw IllegalStateException("Not applicable in Java!")
+ // TODO should we return a ParameterizedTypeName of the KFunction?
+ is LambdaTypeName -> throw IllegalStateException("Not applicable in Java!")
+ is KParameterizedTypeName -> toJParameterizedOrArrayTypeName()
+ is KTypeVariableName -> toJTypeVariableName()
+ is KWildcardTypeName -> toJWildcardTypeName()
+ }
+}
diff --git a/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/KotlinPoetJavaPoetPreview.kt b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/KotlinPoetJavaPoetPreview.kt
new file mode 100644
index 00000000..7fcfd922
--- /dev/null
+++ b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/KotlinPoetJavaPoetPreview.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.javapoet
+
+import kotlin.annotation.AnnotationTarget.CLASS
+import kotlin.annotation.AnnotationTarget.FUNCTION
+import kotlin.annotation.AnnotationTarget.PROPERTY
+import kotlin.annotation.AnnotationTarget.TYPEALIAS
+
+/**
+ * Indicates that a given API is part of the experimental KotlinPoet JavaPoet support and is
+ * subject to API changes.
+ */
+@RequiresOptIn
+@Retention(AnnotationRetention.BINARY)
+@Target(CLASS, FUNCTION, PROPERTY, TYPEALIAS)
+public annotation class KotlinPoetJavaPoetPreview
diff --git a/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/PoetInterop.kt b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/PoetInterop.kt
new file mode 100644
index 00000000..005e828e
--- /dev/null
+++ b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/PoetInterop.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.javapoet
+
+/** Various JavaPoet and KotlinPoet representations of some common types. */
+@OptIn(KotlinPoetJavaPoetPreview::class)
+internal object PoetInterop {
+ internal val CN_JAVA_CHAR_SEQUENCE = JClassName.get("java.lang", "CharSequence")
+ internal val CN_JAVA_STRING = JClassName.get("java.lang", "String")
+ internal val CN_JAVA_LIST = JClassName.get("java.util", "List")
+ internal val CN_JAVA_SET = JClassName.get("java.util", "Set")
+ internal val CN_JAVA_MAP = JClassName.get("java.util", "Map")
+ internal val CN_JAVA_ENUM = JClassName.get("java.lang", "Enum")
+}
diff --git a/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/j2kInterop.kt b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/j2kInterop.kt
new file mode 100644
index 00000000..7cb8fdb4
--- /dev/null
+++ b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/j2kInterop.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.javapoet
+
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.kotlinpoet.ANY
+import com.squareup.kotlinpoet.ARRAY
+import com.squareup.kotlinpoet.BOOLEAN
+import com.squareup.kotlinpoet.BYTE
+import com.squareup.kotlinpoet.BYTE_ARRAY
+import com.squareup.kotlinpoet.CHAR
+import com.squareup.kotlinpoet.CHAR_ARRAY
+import com.squareup.kotlinpoet.CHAR_SEQUENCE
+import com.squareup.kotlinpoet.DOUBLE
+import com.squareup.kotlinpoet.DOUBLE_ARRAY
+import com.squareup.kotlinpoet.ENUM
+import com.squareup.kotlinpoet.FLOAT
+import com.squareup.kotlinpoet.FLOAT_ARRAY
+import com.squareup.kotlinpoet.INT
+import com.squareup.kotlinpoet.INT_ARRAY
+import com.squareup.kotlinpoet.LIST
+import com.squareup.kotlinpoet.LONG
+import com.squareup.kotlinpoet.LONG_ARRAY
+import com.squareup.kotlinpoet.MAP
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.SET
+import com.squareup.kotlinpoet.SHORT
+import com.squareup.kotlinpoet.SHORT_ARRAY
+import com.squareup.kotlinpoet.STAR
+import com.squareup.kotlinpoet.STRING
+
+@KotlinPoetJavaPoetPreview
+public fun JClassName.toKClassName(): KClassName {
+ return when (this) {
+ JTypeName.BOOLEAN.box() -> BOOLEAN
+ JTypeName.BYTE.box() -> BYTE
+ JTypeName.CHAR.box() -> CHAR
+ JTypeName.SHORT.box() -> SHORT
+ JTypeName.INT.box() -> INT
+ JTypeName.LONG.box() -> LONG
+ JTypeName.FLOAT.box() -> FLOAT
+ JTypeName.DOUBLE.box() -> DOUBLE
+ JTypeName.OBJECT -> ANY
+ PoetInterop.CN_JAVA_CHAR_SEQUENCE -> CHAR_SEQUENCE
+ PoetInterop.CN_JAVA_STRING -> STRING
+ PoetInterop.CN_JAVA_LIST -> LIST
+ PoetInterop.CN_JAVA_SET -> SET
+ PoetInterop.CN_JAVA_MAP -> MAP
+ PoetInterop.CN_JAVA_ENUM -> ENUM
+ else -> {
+ if (simpleNames().size == 1) {
+ KClassName(packageName(), simpleName())
+ } else {
+ KClassName(packageName(), simpleNames().first(), *simpleNames().drop(1).toTypedArray())
+ }
+ }
+ }
+}
+
+@KotlinPoetJavaPoetPreview
+public fun JParameterizedTypeName.toKParameterizedTypeName(): KParameterizedTypeName {
+ return rawType.toKClassName()
+ .parameterizedBy(*typeArguments.map { it.toKTypeName() }.toTypedArray())
+}
+
+@KotlinPoetJavaPoetPreview
+public fun JTypeVariableName.toKTypeVariableName(): KTypeVariableName {
+ return if (bounds.isEmpty()) {
+ KTypeVariableName(name)
+ } else {
+ KTypeVariableName(name, *bounds.map { it.toKTypeName() }.toTypedArray())
+ }
+}
+
+@KotlinPoetJavaPoetPreview
+public fun JWildcardTypeName.toKWildcardTypeName(): KWildcardTypeName {
+ return if (lowerBounds.size == 1) {
+ KWildcardTypeName.consumerOf(lowerBounds.first().toKTypeName())
+ } else {
+ when (val upperBound = upperBounds[0]) {
+ TypeName.OBJECT -> STAR
+ else -> KWildcardTypeName.producerOf(upperBound.toKTypeName())
+ }
+ }
+}
+
+@KotlinPoetJavaPoetPreview
+public fun JTypeName.toKTypeName(): KTypeName {
+ return when (this) {
+ is JClassName -> toKClassName()
+ is JParameterizedTypeName -> toKParameterizedTypeName()
+ is JTypeVariableName -> toKTypeVariableName()
+ is JWildcardTypeName -> toKWildcardTypeName()
+ is ArrayTypeName -> {
+ when (componentType) {
+ JTypeName.BYTE -> BYTE_ARRAY
+ JTypeName.CHAR -> CHAR_ARRAY
+ JTypeName.SHORT -> SHORT_ARRAY
+ JTypeName.INT -> INT_ARRAY
+ JTypeName.LONG -> LONG_ARRAY
+ JTypeName.FLOAT -> FLOAT_ARRAY
+ JTypeName.DOUBLE -> DOUBLE_ARRAY
+ else -> ARRAY.parameterizedBy(componentType.toKTypeName())
+ }
+ }
+ else -> when (unboxIfBoxedPrimitive()) {
+ JTypeName.BOOLEAN -> BOOLEAN
+ JTypeName.BYTE -> BYTE
+ JTypeName.CHAR -> CHAR
+ JTypeName.SHORT -> SHORT
+ JTypeName.INT -> INT
+ JTypeName.LONG -> LONG
+ JTypeName.FLOAT -> FLOAT
+ JTypeName.DOUBLE -> DOUBLE
+ else -> error("Unrecognized type $this")
+ }
+ }
+}
+
+@OptIn(KotlinPoetJavaPoetPreview::class)
+internal fun JTypeName.unboxIfBoxedPrimitive(): JTypeName {
+ return if (isBoxedPrimitive) {
+ unbox()
+ } else {
+ this
+ }
+}
+
+@OptIn(KotlinPoetJavaPoetPreview::class)
+internal fun JTypeName.boxIfPrimitive(extraCondition: Boolean = true): JTypeName {
+ return if (extraCondition && isPrimitive && !isBoxedPrimitive) {
+ box()
+ } else {
+ this
+ }
+}
diff --git a/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/typeAliases.kt b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/typeAliases.kt
new file mode 100644
index 00000000..4982fef7
--- /dev/null
+++ b/interop/javapoet/src/main/kotlin/com/squareup/kotlinpoet/javapoet/typeAliases.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.javapoet
+
+/*
+ * Useful typealiases for colliding names
+ */
+
+@KotlinPoetJavaPoetPreview
+public typealias KTypeName = com.squareup.kotlinpoet.TypeName
+
+@KotlinPoetJavaPoetPreview
+public typealias KClassName = com.squareup.kotlinpoet.ClassName
+
+@KotlinPoetJavaPoetPreview
+public typealias KTypeVariableName = com.squareup.kotlinpoet.TypeVariableName
+
+@KotlinPoetJavaPoetPreview
+public typealias KParameterizedTypeName = com.squareup.kotlinpoet.ParameterizedTypeName
+
+@KotlinPoetJavaPoetPreview
+public typealias KWildcardTypeName = com.squareup.kotlinpoet.WildcardTypeName
+
+@KotlinPoetJavaPoetPreview
+public typealias KTypeSpec = com.squareup.kotlinpoet.TypeSpec
+
+@KotlinPoetJavaPoetPreview
+public typealias KAnnotationSpec = com.squareup.kotlinpoet.AnnotationSpec
+
+@KotlinPoetJavaPoetPreview
+public typealias JTypeName = com.squareup.javapoet.TypeName
+
+@KotlinPoetJavaPoetPreview
+public typealias JClassName = com.squareup.javapoet.ClassName
+
+@KotlinPoetJavaPoetPreview
+public typealias JTypeVariableName = com.squareup.javapoet.TypeVariableName
+
+@KotlinPoetJavaPoetPreview
+public typealias JParameterizedTypeName = com.squareup.javapoet.ParameterizedTypeName
+
+@KotlinPoetJavaPoetPreview
+public typealias JWildcardTypeName = com.squareup.javapoet.WildcardTypeName
+
+@KotlinPoetJavaPoetPreview
+public typealias JTypeSpec = com.squareup.javapoet.TypeSpec
+
+@KotlinPoetJavaPoetPreview
+public typealias JAnnotationSpec = com.squareup.javapoet.AnnotationSpec
diff --git a/interop/javapoet/src/test/kotlin/com/squareup/kotlinpoet/javapoet/PoetInteropTest.kt b/interop/javapoet/src/test/kotlin/com/squareup/kotlinpoet/javapoet/PoetInteropTest.kt
new file mode 100644
index 00000000..b622e6b4
--- /dev/null
+++ b/interop/javapoet/src/test/kotlin/com/squareup/kotlinpoet/javapoet/PoetInteropTest.kt
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.javapoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.kotlinpoet.ANY
+import com.squareup.kotlinpoet.ARRAY
+import com.squareup.kotlinpoet.BOOLEAN
+import com.squareup.kotlinpoet.BYTE
+import com.squareup.kotlinpoet.CHAR
+import com.squareup.kotlinpoet.DOUBLE
+import com.squareup.kotlinpoet.ENUM
+import com.squareup.kotlinpoet.FLOAT
+import com.squareup.kotlinpoet.INT
+import com.squareup.kotlinpoet.INT_ARRAY
+import com.squareup.kotlinpoet.LIST
+import com.squareup.kotlinpoet.LONG
+import com.squareup.kotlinpoet.MAP
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.SET
+import com.squareup.kotlinpoet.SHORT
+import com.squareup.kotlinpoet.STAR
+import com.squareup.kotlinpoet.STRING
+import com.squareup.kotlinpoet.U_BYTE
+import com.squareup.kotlinpoet.U_INT
+import com.squareup.kotlinpoet.U_LONG
+import com.squareup.kotlinpoet.U_SHORT
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.typeNameOf
+import org.junit.Test
+
+@OptIn(KotlinPoetJavaPoetPreview::class)
+class PoetInteropTest {
+
+ @Test
+ fun classNamesMatch() {
+ val kotlinPoetCN = PoetInteropTest::class.asClassName()
+ val javapoetCN = kotlinPoetCN.toJClassName()
+
+ assertThat(javapoetCN.toKTypeName()).isEqualTo(kotlinPoetCN)
+ assertThat(JClassName.get(PoetInteropTest::class.java)).isEqualTo(javapoetCN)
+ }
+
+ @Test
+ fun nestedClassNamesMatch() {
+ val kotlinPoetCN = PoetInteropTest::class.asClassName().nestedClass("Foo").nestedClass("Bar")
+ val javapoetCN = kotlinPoetCN.toJClassName()
+
+ assertThat(javapoetCN.toKTypeName()).isEqualTo(kotlinPoetCN)
+ assertThat(JClassName.get(PoetInteropTest::class.java).nestedClass("Foo").nestedClass("Bar"))
+ .isEqualTo(javapoetCN)
+ }
+
+ @Test
+ fun kotlinIntrinsicsMapCorrectlyToJava() {
+ // To Java
+ assertThat(LIST.toJTypeName()).isEqualTo(PoetInterop.CN_JAVA_LIST)
+ assertThat(SET.toJTypeName()).isEqualTo(PoetInterop.CN_JAVA_SET)
+ assertThat(MAP.toJTypeName()).isEqualTo(PoetInterop.CN_JAVA_MAP)
+ assertThat(STRING.toJTypeName()).isEqualTo(PoetInterop.CN_JAVA_STRING)
+ assertThat(ANY.toJTypeName()).isEqualTo(JTypeName.OBJECT)
+
+ // To Kotlin
+ assertThat(PoetInterop.CN_JAVA_LIST.toKTypeName()).isEqualTo(LIST)
+ assertThat(PoetInterop.CN_JAVA_SET.toKTypeName()).isEqualTo(SET)
+ assertThat(PoetInterop.CN_JAVA_MAP.toKTypeName()).isEqualTo(MAP)
+ assertThat(PoetInterop.CN_JAVA_STRING.toKTypeName()).isEqualTo(STRING)
+ assertThat(JTypeName.OBJECT.toKTypeName()).isEqualTo(ANY)
+ }
+
+ @Test
+ fun boxIfPrimitiveRequestReturnsBoxedPrimitive() {
+ assertThat(BOOLEAN.toJTypeName(boxIfPrimitive = true)).isEqualTo(JTypeName.BOOLEAN.box())
+ assertThat(BYTE.toJTypeName(boxIfPrimitive = true)).isEqualTo(JTypeName.BYTE.box())
+ assertThat(CHAR.toJTypeName(boxIfPrimitive = true)).isEqualTo(JTypeName.CHAR.box())
+ assertThat(SHORT.toJTypeName(boxIfPrimitive = true)).isEqualTo(JTypeName.SHORT.box())
+ assertThat(INT.toJTypeName(boxIfPrimitive = true)).isEqualTo(JTypeName.INT.box())
+ assertThat(LONG.toJTypeName(boxIfPrimitive = true)).isEqualTo(JTypeName.LONG.box())
+ assertThat(FLOAT.toJTypeName(boxIfPrimitive = true)).isEqualTo(JTypeName.FLOAT.box())
+ assertThat(DOUBLE.toJTypeName(boxIfPrimitive = true)).isEqualTo(JTypeName.DOUBLE.box())
+ }
+
+ @Test
+ fun primitivesAreUnboxedByDefault() {
+ assertThat(BOOLEAN.toJTypeName()).isEqualTo(JTypeName.BOOLEAN)
+ assertThat(BYTE.toJTypeName()).isEqualTo(JTypeName.BYTE)
+ assertThat(CHAR.toJTypeName()).isEqualTo(JTypeName.CHAR)
+ assertThat(SHORT.toJTypeName()).isEqualTo(JTypeName.SHORT)
+ assertThat(INT.toJTypeName()).isEqualTo(JTypeName.INT)
+ assertThat(LONG.toJTypeName()).isEqualTo(JTypeName.LONG)
+ assertThat(FLOAT.toJTypeName()).isEqualTo(JTypeName.FLOAT)
+ assertThat(DOUBLE.toJTypeName()).isEqualTo(JTypeName.DOUBLE)
+ }
+
+ @Test
+ fun nullablePrimitiveBoxedByDefault() {
+ assertThat(BOOLEAN.copy(nullable = true).toJTypeName()).isEqualTo(JTypeName.BOOLEAN.box())
+ assertThat(BYTE.copy(nullable = true).toJTypeName()).isEqualTo(JTypeName.BYTE.box())
+ assertThat(CHAR.copy(nullable = true).toJTypeName()).isEqualTo(JTypeName.CHAR.box())
+ assertThat(SHORT.copy(nullable = true).toJTypeName()).isEqualTo(JTypeName.SHORT.box())
+ assertThat(INT.copy(nullable = true).toJTypeName()).isEqualTo(JTypeName.INT.box())
+ assertThat(LONG.copy(nullable = true).toJTypeName()).isEqualTo(JTypeName.LONG.box())
+ assertThat(FLOAT.copy(nullable = true).toJTypeName()).isEqualTo(JTypeName.FLOAT.box())
+ assertThat(DOUBLE.copy(nullable = true).toJTypeName()).isEqualTo(JTypeName.DOUBLE.box())
+ }
+
+ @Test
+ fun arrayTypesConversion() {
+ assertThat(ARRAY.parameterizedBy(INT).toJParameterizedOrArrayTypeName())
+ .isEqualTo(ArrayTypeName.of(JTypeName.INT))
+ assertThat(ARRAY.parameterizedBy(INT.copy(nullable = true)).toJParameterizedOrArrayTypeName())
+ .isEqualTo(ArrayTypeName.of(JTypeName.INT.box()))
+ assertThat(ArrayTypeName.of(JTypeName.INT).toKTypeName()).isEqualTo(INT_ARRAY)
+ assertThat(ArrayTypeName.of(JTypeName.INT.box()).toKTypeName())
+ .isEqualTo(ARRAY.parameterizedBy(INT))
+ }
+
+ class GenericType<T>
+
+ @Test
+ fun wildcards() {
+ val inKType = typeNameOf<GenericType<in String>>()
+ val superJType = JParameterizedTypeName.get(
+ JClassName.get(GenericType::class.java),
+ JWildcardTypeName.supertypeOf(String::class.java),
+ )
+ assertThat(inKType.toJTypeName()).isEqualTo(superJType)
+ assertThat(superJType.toKTypeName()).isEqualTo(inKType)
+
+ val outKType = typeNameOf<GenericType<out String>>()
+ val extendsJType = JParameterizedTypeName.get(
+ JClassName.get(GenericType::class.java),
+ JWildcardTypeName.subtypeOf(String::class.java),
+ )
+ assertThat(outKType.toJTypeName()).isEqualTo(extendsJType)
+ assertThat(extendsJType.toKTypeName()).isEqualTo(outKType)
+
+ val star = typeNameOf<GenericType<*>>()
+ val extendsObjectJType = JParameterizedTypeName.get(
+ JClassName.get(GenericType::class.java),
+ JWildcardTypeName.subtypeOf(JTypeName.OBJECT),
+ )
+ assertThat(star.toJTypeName()).isEqualTo(extendsObjectJType)
+ assertThat(extendsObjectJType.toKTypeName()).isEqualTo(star)
+ assertThat(STAR.toJTypeName()).isEqualTo(JWildcardTypeName.subtypeOf(JTypeName.OBJECT))
+ assertThat(JWildcardTypeName.subtypeOf(JTypeName.OBJECT).toKTypeName()).isEqualTo(STAR)
+ }
+
+ @Test
+ fun complex() {
+ val complexType = typeNameOf<Map<String?, List<MutableMap<Int, IntArray>>>>()
+ val jType = JParameterizedTypeName.get(
+ JClassName.get(Map::class.java),
+ JClassName.get(String::class.java),
+ JParameterizedTypeName.get(
+ JClassName.get(List::class.java),
+ JParameterizedTypeName.get(
+ JClassName.get(Map::class.java),
+ JClassName.INT.box(),
+ ArrayTypeName.of(JClassName.INT),
+ ),
+ ),
+ )
+ assertThat(complexType.toJTypeName()).isEqualTo(jType)
+
+ assertThat(jType.toKTypeName())
+ .isEqualTo(typeNameOf<Map<String, List<MutableMap<Int, IntArray>>>>())
+ }
+
+ @Test
+ fun uTypesAreJustNormalTypesInJava() {
+ assertThat(U_BYTE.toJTypeName()).isEqualTo(JTypeName.BYTE)
+ assertThat(U_SHORT.toJTypeName()).isEqualTo(JTypeName.SHORT)
+ assertThat(U_INT.toJTypeName()).isEqualTo(JTypeName.INT)
+ assertThat(U_LONG.toJTypeName()).isEqualTo(JTypeName.LONG)
+ }
+
+ @Test
+ fun enums() {
+ assertThat(ENUM.toJTypeName()).isEqualTo(PoetInterop.CN_JAVA_ENUM)
+ assertThat(PoetInterop.CN_JAVA_ENUM.toKTypeName()).isEqualTo(ENUM)
+ }
+}
diff --git a/interop/kotlinx-metadata/api/kotlinx-metadata.api b/interop/kotlinx-metadata/api/kotlinx-metadata.api
new file mode 100644
index 00000000..96f47c0c
--- /dev/null
+++ b/interop/kotlinx-metadata/api/kotlinx-metadata.api
@@ -0,0 +1,369 @@
+public final class com/squareup/kotlinpoet/metadata/FlagsKt {
+ public static final fun getDeclaresDefaultValue (Lkotlinx/metadata/KmValueParameter;)Z
+ public static final fun getGetterPropertyAccessorFlags (Lkotlinx/metadata/KmProperty;)Ljava/util/Set;
+ public static final fun getHasAnnotations (I)Z
+ public static final fun getHasConstant (Lkotlinx/metadata/KmProperty;)Z
+ public static final fun getHasGetter (Lkotlinx/metadata/KmProperty;)Z
+ public static final fun getHasSetter (Lkotlinx/metadata/KmProperty;)Z
+ public static final fun getPropertyAccessorFlags (I)Ljava/util/Set;
+ public static final fun getSetterPropertyAccessorFlags (Lkotlinx/metadata/KmProperty;)Ljava/util/Set;
+ public static final fun isAbstract (I)Z
+ public static final fun isAnnotation (Lkotlinx/metadata/KmClass;)Z
+ public static final fun isAnnotationClass (I)Z
+ public static final fun isClass (I)Z
+ public static final fun isClass (Lkotlinx/metadata/KmClass;)Z
+ public static final fun isCompanionObject (Lkotlinx/metadata/KmClass;)Z
+ public static final fun isCompanionObjectClass (I)Z
+ public static final fun isConst (Lkotlinx/metadata/KmProperty;)Z
+ public static final fun isCrossInline (Lkotlinx/metadata/KmValueParameter;)Z
+ public static final fun isData (Lkotlinx/metadata/KmClass;)Z
+ public static final fun isDataClass (I)Z
+ public static final fun isDeclaration (Lkotlinx/metadata/KmFunction;)Z
+ public static final fun isDeclaration (Lkotlinx/metadata/KmProperty;)Z
+ public static final fun isDeclarationFunction (I)Z
+ public static final fun isDelegated (Lkotlinx/metadata/KmProperty;)Z
+ public static final fun isDelegation (Lkotlinx/metadata/KmFunction;)Z
+ public static final fun isDelegation (Lkotlinx/metadata/KmProperty;)Z
+ public static final fun isDelegationFunction (I)Z
+ public static final fun isEnum (Lkotlinx/metadata/KmClass;)Z
+ public static final fun isEnumClass (I)Z
+ public static final fun isEnumEntry (Lkotlinx/metadata/KmClass;)Z
+ public static final fun isEnumEntryClass (I)Z
+ public static final fun isExpect (Lkotlinx/metadata/KmClass;)Z
+ public static final fun isExpect (Lkotlinx/metadata/KmFunction;)Z
+ public static final fun isExpect (Lkotlinx/metadata/KmProperty;)Z
+ public static final fun isExpectClass (I)Z
+ public static final fun isExpectFunction (I)Z
+ public static final fun isExternal (Lkotlinx/metadata/KmClass;)Z
+ public static final fun isExternal (Lkotlinx/metadata/KmFunction;)Z
+ public static final fun isExternal (Lkotlinx/metadata/KmProperty;)Z
+ public static final fun isExternalClass (I)Z
+ public static final fun isExternalFunction (I)Z
+ public static final fun isFakeOverride (Lkotlinx/metadata/KmFunction;)Z
+ public static final fun isFakeOverride (Lkotlinx/metadata/KmProperty;)Z
+ public static final fun isFakeOverrideFunction (I)Z
+ public static final fun isFakeOverrideProperty (I)Z
+ public static final fun isFinal (I)Z
+ public static final fun isFun (I)Z
+ public static final fun isFun (Lkotlinx/metadata/KmClass;)Z
+ public static final fun isInfix (Lkotlinx/metadata/KmFunction;)Z
+ public static final fun isInfixFunction (I)Z
+ public static final fun isInline (Lkotlinx/metadata/KmFunction;)Z
+ public static final fun isInlineFunction (I)Z
+ public static final fun isInner (Lkotlinx/metadata/KmClass;)Z
+ public static final fun isInnerClass (I)Z
+ public static final fun isInterface (I)Z
+ public static final fun isInterface (Lkotlinx/metadata/KmClass;)Z
+ public static final fun isInternal (I)Z
+ public static final fun isLateinit (Lkotlinx/metadata/KmProperty;)Z
+ public static final fun isLocal (I)Z
+ public static final fun isNoInline (Lkotlinx/metadata/KmValueParameter;)Z
+ public static final fun isNullable (Lkotlinx/metadata/KmType;)Z
+ public static final fun isNullableType (I)Z
+ public static final fun isObject (Lkotlinx/metadata/KmClass;)Z
+ public static final fun isObjectClass (I)Z
+ public static final fun isOpen (I)Z
+ public static final fun isOperator (Lkotlinx/metadata/KmFunction;)Z
+ public static final fun isOperatorFunction (I)Z
+ public static final fun isPrimary (Lkotlinx/metadata/KmConstructor;)Z
+ public static final fun isPrimaryConstructor (I)Z
+ public static final fun isPrivate (I)Z
+ public static final fun isPrivate_to_this (I)Z
+ public static final fun isPropertyAccessorExternal (I)Z
+ public static final fun isPropertyAccessorInline (I)Z
+ public static final fun isPropertyAccessorNotDefault (I)Z
+ public static final fun isProtected (I)Z
+ public static final fun isPublic (I)Z
+ public static final fun isReified (Lkotlinx/metadata/KmTypeParameter;)Z
+ public static final fun isSealed (I)Z
+ public static final fun isSecondary (Lkotlinx/metadata/KmConstructor;)Z
+ public static final fun isSuspend (Lkotlinx/metadata/KmFunction;)Z
+ public static final fun isSuspend (Lkotlinx/metadata/KmType;)Z
+ public static final fun isSuspendFunction (I)Z
+ public static final fun isSuspendType (I)Z
+ public static final fun isSynthesized (Lkotlinx/metadata/KmFunction;)Z
+ public static final fun isSynthesized (Lkotlinx/metadata/KmProperty;)Z
+ public static final fun isSynthesizedFunction (I)Z
+ public static final fun isTailRec (Lkotlinx/metadata/KmFunction;)Z
+ public static final fun isTailRecFunction (I)Z
+ public static final fun isVal (Lkotlinx/metadata/KmProperty;)Z
+ public static final fun isValue (Lkotlinx/metadata/KmClass;)Z
+ public static final fun isValueClass (I)Z
+ public static final fun isVar (Lkotlinx/metadata/KmProperty;)Z
+}
+
+public final class com/squareup/kotlinpoet/metadata/KotlinPoetMetadata {
+ public static final fun readKotlinClassMetadata (Lkotlin/Metadata;)Lkotlinx/metadata/jvm/KotlinClassMetadata;
+ public static final fun toKmClass (Ljava/lang/Class;)Lkotlinx/metadata/KmClass;
+ public static final fun toKmClass (Ljavax/lang/model/element/TypeElement;)Lkotlinx/metadata/KmClass;
+ public static final fun toKmClass (Lkotlin/Metadata;)Lkotlinx/metadata/KmClass;
+ public static final fun toKmClass (Lkotlin/reflect/KClass;)Lkotlinx/metadata/KmClass;
+}
+
+public abstract interface annotation class com/squareup/kotlinpoet/metadata/KotlinPoetMetadataPreview : java/lang/annotation/Annotation {
+}
+
+public final class com/squareup/kotlinpoet/metadata/PropertyAccessorFlag : java/lang/Enum {
+ public static final field IS_EXTERNAL Lcom/squareup/kotlinpoet/metadata/PropertyAccessorFlag;
+ public static final field IS_INLINE Lcom/squareup/kotlinpoet/metadata/PropertyAccessorFlag;
+ public static final field IS_NOT_DEFAULT Lcom/squareup/kotlinpoet/metadata/PropertyAccessorFlag;
+ public static fun valueOf (Ljava/lang/String;)Lcom/squareup/kotlinpoet/metadata/PropertyAccessorFlag;
+ public static fun values ()[Lcom/squareup/kotlinpoet/metadata/PropertyAccessorFlag;
+}
+
+public final class com/squareup/kotlinpoet/metadata/classinspectors/ElementsClassInspector : com/squareup/kotlinpoet/metadata/specs/ClassInspector {
+ public static final field Companion Lcom/squareup/kotlinpoet/metadata/classinspectors/ElementsClassInspector$Companion;
+ public synthetic fun <init> (Ljavax/lang/model/util/Elements;Ljavax/lang/model/util/Types;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun containerData (Lkotlinx/metadata/KmDeclarationContainer;Lcom/squareup/kotlinpoet/ClassName;Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/metadata/specs/ContainerData;
+ public static final fun create (Ljavax/lang/model/util/Elements;Ljavax/lang/model/util/Types;)Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;
+ public fun declarationContainerFor (Lcom/squareup/kotlinpoet/ClassName;)Lkotlinx/metadata/KmDeclarationContainer;
+ public fun enumEntry (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;)Lcom/squareup/kotlinpoet/metadata/specs/EnumEntryData;
+ public fun getSupportsNonRuntimeRetainedAnnotations ()Z
+ public fun isInterface (Lcom/squareup/kotlinpoet/ClassName;)Z
+ public fun methodExists (Lcom/squareup/kotlinpoet/ClassName;Lkotlinx/metadata/jvm/JvmMethodSignature;)Z
+}
+
+public final class com/squareup/kotlinpoet/metadata/classinspectors/ElementsClassInspector$Companion {
+ public final fun create (Ljavax/lang/model/util/Elements;Ljavax/lang/model/util/Types;)Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;
+}
+
+public final class com/squareup/kotlinpoet/metadata/classinspectors/ReflectiveClassInspector : com/squareup/kotlinpoet/metadata/specs/ClassInspector {
+ public static final field Companion Lcom/squareup/kotlinpoet/metadata/classinspectors/ReflectiveClassInspector$Companion;
+ public synthetic fun <init> (Ljava/lang/ClassLoader;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun containerData (Lkotlinx/metadata/KmDeclarationContainer;Lcom/squareup/kotlinpoet/ClassName;Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/metadata/specs/ContainerData;
+ public static final fun create (Ljava/lang/ClassLoader;)Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;
+ public fun declarationContainerFor (Lcom/squareup/kotlinpoet/ClassName;)Lkotlinx/metadata/KmDeclarationContainer;
+ public fun enumEntry (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;)Lcom/squareup/kotlinpoet/metadata/specs/EnumEntryData;
+ public fun getSupportsNonRuntimeRetainedAnnotations ()Z
+ public fun isInterface (Lcom/squareup/kotlinpoet/ClassName;)Z
+ public fun methodExists (Lcom/squareup/kotlinpoet/ClassName;Lkotlinx/metadata/jvm/JvmMethodSignature;)Z
+}
+
+public final class com/squareup/kotlinpoet/metadata/classinspectors/ReflectiveClassInspector$Companion {
+ public final fun create (Ljava/lang/ClassLoader;)Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;
+ public static synthetic fun create$default (Lcom/squareup/kotlinpoet/metadata/classinspectors/ReflectiveClassInspector$Companion;Ljava/lang/ClassLoader;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/ClassData : com/squareup/kotlinpoet/metadata/specs/ContainerData {
+ public fun <init> (Lkotlinx/metadata/KmClass;Lcom/squareup/kotlinpoet/ClassName;Ljava/util/Collection;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)V
+ public final fun component1 ()Lkotlinx/metadata/KmClass;
+ public final fun component2 ()Lcom/squareup/kotlinpoet/ClassName;
+ public final fun component3 ()Ljava/util/Collection;
+ public final fun component4 ()Ljava/util/Map;
+ public final fun component5 ()Ljava/util/Map;
+ public final fun component6 ()Ljava/util/Map;
+ public final fun copy (Lkotlinx/metadata/KmClass;Lcom/squareup/kotlinpoet/ClassName;Ljava/util/Collection;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Lcom/squareup/kotlinpoet/metadata/specs/ClassData;
+ public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/metadata/specs/ClassData;Lkotlinx/metadata/KmClass;Lcom/squareup/kotlinpoet/ClassName;Ljava/util/Collection;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/metadata/specs/ClassData;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun getAnnotations ()Ljava/util/Collection;
+ public final fun getClassName ()Lcom/squareup/kotlinpoet/ClassName;
+ public final fun getConstructors ()Ljava/util/Map;
+ public fun getDeclarationContainer ()Lkotlinx/metadata/KmClass;
+ public synthetic fun getDeclarationContainer ()Lkotlinx/metadata/KmDeclarationContainer;
+ public fun getMethods ()Ljava/util/Map;
+ public fun getProperties ()Ljava/util/Map;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public abstract interface class com/squareup/kotlinpoet/metadata/specs/ClassInspector {
+ public abstract fun containerData (Lkotlinx/metadata/KmDeclarationContainer;Lcom/squareup/kotlinpoet/ClassName;Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/metadata/specs/ContainerData;
+ public abstract fun declarationContainerFor (Lcom/squareup/kotlinpoet/ClassName;)Lkotlinx/metadata/KmDeclarationContainer;
+ public abstract fun enumEntry (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;)Lcom/squareup/kotlinpoet/metadata/specs/EnumEntryData;
+ public abstract fun getSupportsNonRuntimeRetainedAnnotations ()Z
+ public abstract fun isInterface (Lcom/squareup/kotlinpoet/ClassName;)Z
+ public abstract fun methodExists (Lcom/squareup/kotlinpoet/ClassName;Lkotlinx/metadata/jvm/JvmMethodSignature;)Z
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/ClassInspectorKt {
+ public static final fun classFor (Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;Lcom/squareup/kotlinpoet/ClassName;)Lkotlinx/metadata/KmClass;
+ public static final fun containerData (Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;Lcom/squareup/kotlinpoet/ClassName;Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/metadata/specs/ContainerData;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/ConstructorData {
+ public static final field Companion Lcom/squareup/kotlinpoet/metadata/specs/ConstructorData$Companion;
+ public fun <init> (Ljava/util/List;Ljava/util/Map;ZLjava/util/Set;Ljava/util/List;)V
+ public final fun component2 ()Ljava/util/Map;
+ public final fun component3 ()Z
+ public final fun component4 ()Ljava/util/Set;
+ public final fun component5 ()Ljava/util/List;
+ public final fun copy (Ljava/util/List;Ljava/util/Map;ZLjava/util/Set;Ljava/util/List;)Lcom/squareup/kotlinpoet/metadata/specs/ConstructorData;
+ public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/metadata/specs/ConstructorData;Ljava/util/List;Ljava/util/Map;ZLjava/util/Set;Ljava/util/List;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/metadata/specs/ConstructorData;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getAllAnnotations ()Ljava/util/Collection;
+ public final fun getExceptions ()Ljava/util/List;
+ public final fun getJvmModifiers ()Ljava/util/Set;
+ public final fun getParameterAnnotations ()Ljava/util/Map;
+ public fun hashCode ()I
+ public final fun isSynthetic ()Z
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/ConstructorData$Companion {
+ public final fun getEMPTY ()Lcom/squareup/kotlinpoet/metadata/specs/ConstructorData;
+}
+
+public abstract interface class com/squareup/kotlinpoet/metadata/specs/ContainerData {
+ public abstract fun getAnnotations ()Ljava/util/Collection;
+ public abstract fun getDeclarationContainer ()Lkotlinx/metadata/KmDeclarationContainer;
+ public abstract fun getMethods ()Ljava/util/Map;
+ public abstract fun getProperties ()Ljava/util/Map;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/EnumEntryData {
+ public fun <init> (Lkotlinx/metadata/KmClass;Ljava/util/Collection;)V
+ public final fun component1 ()Lkotlinx/metadata/KmClass;
+ public final fun component2 ()Ljava/util/Collection;
+ public final fun copy (Lkotlinx/metadata/KmClass;Ljava/util/Collection;)Lcom/squareup/kotlinpoet/metadata/specs/EnumEntryData;
+ public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/metadata/specs/EnumEntryData;Lkotlinx/metadata/KmClass;Ljava/util/Collection;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/metadata/specs/EnumEntryData;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getAnnotations ()Ljava/util/Collection;
+ public final fun getDeclarationContainer ()Lkotlinx/metadata/KmClass;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/FieldData {
+ public static final field Companion Lcom/squareup/kotlinpoet/metadata/specs/FieldData$Companion;
+ public fun <init> (Ljava/util/List;ZLjava/util/Set;Lcom/squareup/kotlinpoet/CodeBlock;)V
+ public final fun component2 ()Z
+ public final fun component3 ()Ljava/util/Set;
+ public final fun component4 ()Lcom/squareup/kotlinpoet/CodeBlock;
+ public final fun copy (Ljava/util/List;ZLjava/util/Set;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/metadata/specs/FieldData;
+ public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/metadata/specs/FieldData;Ljava/util/List;ZLjava/util/Set;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/metadata/specs/FieldData;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getAllAnnotations ()Ljava/util/Collection;
+ public final fun getConstant ()Lcom/squareup/kotlinpoet/CodeBlock;
+ public final fun getJvmModifiers ()Ljava/util/Set;
+ public fun hashCode ()I
+ public final fun isSynthetic ()Z
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/FieldData$Companion {
+ public final fun getSYNTHETIC ()Lcom/squareup/kotlinpoet/metadata/specs/FieldData;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/FileData : com/squareup/kotlinpoet/metadata/specs/ContainerData {
+ public fun <init> (Lkotlinx/metadata/KmPackage;Ljava/util/Collection;Ljava/util/Map;Ljava/util/Map;Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;)V
+ public synthetic fun <init> (Lkotlinx/metadata/KmPackage;Ljava/util/Collection;Ljava/util/Map;Ljava/util/Map;Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Lkotlinx/metadata/KmPackage;
+ public final fun component2 ()Ljava/util/Collection;
+ public final fun component3 ()Ljava/util/Map;
+ public final fun component4 ()Ljava/util/Map;
+ public final fun component5 ()Lcom/squareup/kotlinpoet/ClassName;
+ public final fun component6 ()Ljava/lang/String;
+ public final fun copy (Lkotlinx/metadata/KmPackage;Ljava/util/Collection;Ljava/util/Map;Ljava/util/Map;Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;)Lcom/squareup/kotlinpoet/metadata/specs/FileData;
+ public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/metadata/specs/FileData;Lkotlinx/metadata/KmPackage;Ljava/util/Collection;Ljava/util/Map;Ljava/util/Map;Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/metadata/specs/FileData;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun getAnnotations ()Ljava/util/Collection;
+ public final fun getClassName ()Lcom/squareup/kotlinpoet/ClassName;
+ public synthetic fun getDeclarationContainer ()Lkotlinx/metadata/KmDeclarationContainer;
+ public fun getDeclarationContainer ()Lkotlinx/metadata/KmPackage;
+ public final fun getFileName ()Ljava/lang/String;
+ public final fun getJvmName ()Ljava/lang/String;
+ public fun getMethods ()Ljava/util/Map;
+ public fun getProperties ()Ljava/util/Map;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public class com/squareup/kotlinpoet/metadata/specs/JvmFieldModifier : java/lang/Enum, com/squareup/kotlinpoet/metadata/specs/JvmModifier {
+ public static final field STATIC Lcom/squareup/kotlinpoet/metadata/specs/JvmFieldModifier;
+ public static final field TRANSIENT Lcom/squareup/kotlinpoet/metadata/specs/JvmFieldModifier;
+ public static final field VOLATILE Lcom/squareup/kotlinpoet/metadata/specs/JvmFieldModifier;
+ public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun annotationSpec ()Lcom/squareup/kotlinpoet/AnnotationSpec;
+ public static fun valueOf (Ljava/lang/String;)Lcom/squareup/kotlinpoet/metadata/specs/JvmFieldModifier;
+ public static fun values ()[Lcom/squareup/kotlinpoet/metadata/specs/JvmFieldModifier;
+}
+
+public class com/squareup/kotlinpoet/metadata/specs/JvmMethodModifier : java/lang/Enum, com/squareup/kotlinpoet/metadata/specs/JvmModifier {
+ public static final field DEFAULT Lcom/squareup/kotlinpoet/metadata/specs/JvmMethodModifier;
+ public static final field STATIC Lcom/squareup/kotlinpoet/metadata/specs/JvmMethodModifier;
+ public static final field SYNCHRONIZED Lcom/squareup/kotlinpoet/metadata/specs/JvmMethodModifier;
+ public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun annotationSpec ()Lcom/squareup/kotlinpoet/AnnotationSpec;
+ public static fun valueOf (Ljava/lang/String;)Lcom/squareup/kotlinpoet/metadata/specs/JvmMethodModifier;
+ public static fun values ()[Lcom/squareup/kotlinpoet/metadata/specs/JvmMethodModifier;
+}
+
+public abstract interface class com/squareup/kotlinpoet/metadata/specs/JvmModifier {
+ public abstract fun annotationSpec ()Lcom/squareup/kotlinpoet/AnnotationSpec;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/JvmModifier$DefaultImpls {
+ public static fun annotationSpec (Lcom/squareup/kotlinpoet/metadata/specs/JvmModifier;)Lcom/squareup/kotlinpoet/AnnotationSpec;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/KmTypesKt {
+ public static final fun isExtensionType (Lkotlinx/metadata/KmType;)Z
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecs {
+ public static final fun getPackageName (Ljavax/lang/model/element/Element;)Ljava/lang/String;
+ public static final fun toFileSpec (Ljava/lang/Class;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;)Lcom/squareup/kotlinpoet/FileSpec;
+ public static final fun toFileSpec (Ljavax/lang/model/element/TypeElement;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;)Lcom/squareup/kotlinpoet/FileSpec;
+ public static final fun toFileSpec (Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;)Lcom/squareup/kotlinpoet/FileSpec;
+ public static final fun toFileSpec (Lkotlinx/metadata/KmClass;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/FileSpec;
+ public static final fun toFileSpec (Lkotlinx/metadata/KmPackage;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/FileSpec;
+ public static synthetic fun toFileSpec$default (Ljava/lang/Class;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec;
+ public static synthetic fun toFileSpec$default (Ljavax/lang/model/element/TypeElement;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec;
+ public static synthetic fun toFileSpec$default (Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec;
+ public static synthetic fun toFileSpec$default (Lkotlinx/metadata/KmClass;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;Lcom/squareup/kotlinpoet/ClassName;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec;
+ public static final fun toTypeSpec (Ljava/lang/Class;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;)Lcom/squareup/kotlinpoet/TypeSpec;
+ public static final fun toTypeSpec (Ljavax/lang/model/element/TypeElement;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;)Lcom/squareup/kotlinpoet/TypeSpec;
+ public static final fun toTypeSpec (Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;)Lcom/squareup/kotlinpoet/TypeSpec;
+ public static final fun toTypeSpec (Lkotlinx/metadata/KmClass;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec;
+ public static synthetic fun toTypeSpec$default (Ljava/lang/Class;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec;
+ public static synthetic fun toTypeSpec$default (Ljavax/lang/model/element/TypeElement;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec;
+ public static synthetic fun toTypeSpec$default (Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec;
+ public static synthetic fun toTypeSpec$default (Lkotlinx/metadata/KmClass;Lcom/squareup/kotlinpoet/metadata/specs/ClassInspector;Lcom/squareup/kotlinpoet/ClassName;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/MethodData {
+ public static final field Companion Lcom/squareup/kotlinpoet/metadata/specs/MethodData$Companion;
+ public fun <init> (Ljava/util/List;Ljava/util/Map;ZLjava/util/Set;ZLjava/util/List;)V
+ public final fun allAnnotations (Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;Z)Ljava/util/Collection;
+ public static synthetic fun allAnnotations$default (Lcom/squareup/kotlinpoet/metadata/specs/MethodData;Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;ZILjava/lang/Object;)Ljava/util/Collection;
+ public final fun component2 ()Ljava/util/Map;
+ public final fun component3 ()Z
+ public final fun component4 ()Ljava/util/Set;
+ public final fun component5 ()Z
+ public final fun component6 ()Ljava/util/List;
+ public final fun copy (Ljava/util/List;Ljava/util/Map;ZLjava/util/Set;ZLjava/util/List;)Lcom/squareup/kotlinpoet/metadata/specs/MethodData;
+ public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/metadata/specs/MethodData;Ljava/util/List;Ljava/util/Map;ZLjava/util/Set;ZLjava/util/List;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/metadata/specs/MethodData;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getExceptions ()Ljava/util/List;
+ public final fun getJvmModifiers ()Ljava/util/Set;
+ public final fun getParameterAnnotations ()Ljava/util/Map;
+ public fun hashCode ()I
+ public final fun isOverride ()Z
+ public final fun isSynthetic ()Z
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/MethodData$Companion {
+ public final fun getEMPTY ()Lcom/squareup/kotlinpoet/metadata/specs/MethodData;
+ public final fun getSYNTHETIC ()Lcom/squareup/kotlinpoet/metadata/specs/MethodData;
+}
+
+public final class com/squareup/kotlinpoet/metadata/specs/PropertyData {
+ public fun <init> (Ljava/util/List;Lcom/squareup/kotlinpoet/metadata/specs/FieldData;Lcom/squareup/kotlinpoet/metadata/specs/MethodData;Lcom/squareup/kotlinpoet/metadata/specs/MethodData;Z)V
+ public final fun component2 ()Lcom/squareup/kotlinpoet/metadata/specs/FieldData;
+ public final fun component3 ()Lcom/squareup/kotlinpoet/metadata/specs/MethodData;
+ public final fun component4 ()Lcom/squareup/kotlinpoet/metadata/specs/MethodData;
+ public final fun component5 ()Z
+ public final fun copy (Ljava/util/List;Lcom/squareup/kotlinpoet/metadata/specs/FieldData;Lcom/squareup/kotlinpoet/metadata/specs/MethodData;Lcom/squareup/kotlinpoet/metadata/specs/MethodData;Z)Lcom/squareup/kotlinpoet/metadata/specs/PropertyData;
+ public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/metadata/specs/PropertyData;Ljava/util/List;Lcom/squareup/kotlinpoet/metadata/specs/FieldData;Lcom/squareup/kotlinpoet/metadata/specs/MethodData;Lcom/squareup/kotlinpoet/metadata/specs/MethodData;ZILjava/lang/Object;)Lcom/squareup/kotlinpoet/metadata/specs/PropertyData;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getAllAnnotations ()Ljava/util/Collection;
+ public final fun getFieldData ()Lcom/squareup/kotlinpoet/metadata/specs/FieldData;
+ public final fun getGetterData ()Lcom/squareup/kotlinpoet/metadata/specs/MethodData;
+ public final fun getSetterData ()Lcom/squareup/kotlinpoet/metadata/specs/MethodData;
+ public fun hashCode ()I
+ public final fun isJvmField ()Z
+ public final fun isOverride ()Z
+ public fun toString ()Ljava/lang/String;
+}
+
diff --git a/interop/kotlinx-metadata/build.gradle.kts b/interop/kotlinx-metadata/build.gradle.kts
new file mode 100644
index 00000000..df75e1ad
--- /dev/null
+++ b/interop/kotlinx-metadata/build.gradle.kts
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * 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.
+ */
+
+tasks.jar {
+ manifest {
+ attributes("Automatic-Module-Name" to "com.squareup.kotlinpoet.metadata")
+ }
+}
+
+tasks.compileTestKotlin {
+ kotlinOptions {
+ freeCompilerArgs = listOf(
+ "-Xjvm-default=all",
+ "-opt-in=com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview",
+ )
+ }
+}
+
+dependencies {
+ implementation(libs.autoCommon)
+ implementation(libs.guava)
+ api(libs.kotlin.metadata)
+ api(project(":kotlinpoet"))
+
+ testImplementation(libs.kotlin.junit)
+ testImplementation(libs.truth)
+ testImplementation(libs.compileTesting)
+ testImplementation(libs.kotlinCompileTesting)
+ testImplementation(libs.kotlin.annotationProcessingEmbeddable)
+ testImplementation(libs.kotlin.compilerEmbeddable)
+}
diff --git a/interop/kotlinx-metadata/gradle.properties b/interop/kotlinx-metadata/gradle.properties
new file mode 100644
index 00000000..dba6fb43
--- /dev/null
+++ b/interop/kotlinx-metadata/gradle.properties
@@ -0,0 +1,4 @@
+POM_ARTIFACT_ID=kotlinpoet-metadata
+POM_NAME=KotlinPoet (Kotlin Metadata Extensions)
+POM_DESCRIPTION=Extensions for reading Kotlin metadata from Metadata annotations.
+POM_PACKAGING=jar
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/Flags.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/Flags.kt
new file mode 100644
index 00000000..4745dda0
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/Flags.kt
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * 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.
+ */
+@file:Suppress("unused")
+
+package com.squareup.kotlinpoet.metadata
+
+import kotlinx.metadata.Flag
+import kotlinx.metadata.Flags
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmConstructor
+import kotlinx.metadata.KmFunction
+import kotlinx.metadata.KmProperty
+import kotlinx.metadata.KmType
+import kotlinx.metadata.KmTypeParameter
+import kotlinx.metadata.KmValueParameter
+
+// Common flags for any element with flags.
+@KotlinPoetMetadataPreview
+public val Flags.hasAnnotations: Boolean get() = Flag.HAS_ANNOTATIONS(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isAbstract: Boolean get() = Flag.IS_ABSTRACT(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isFinal: Boolean get() = Flag.IS_FINAL(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isInternal: Boolean get() = Flag.IS_INTERNAL(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isLocal: Boolean get() = Flag.IS_LOCAL(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isOpen: Boolean get() = Flag.IS_OPEN(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isPrivate: Boolean get() = Flag.IS_PRIVATE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isPrivate_to_this: Boolean get() = Flag.IS_PRIVATE_TO_THIS(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isProtected: Boolean get() = Flag.IS_PROTECTED(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isPublic: Boolean get() = Flag.IS_PUBLIC(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isSealed: Boolean get() = Flag.IS_SEALED(this)
+
+// Type flags.
+@KotlinPoetMetadataPreview
+public val Flags.isNullableType: Boolean get() = Flag.Type.IS_NULLABLE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isSuspendType: Boolean get() = Flag.Type.IS_SUSPEND(this)
+
+// Class flags.
+@KotlinPoetMetadataPreview
+public val Flags.isAnnotationClass: Boolean get() = Flag.Class.IS_ANNOTATION_CLASS(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isClass: Boolean get() = Flag.Class.IS_CLASS(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isCompanionObjectClass: Boolean get() = Flag.Class.IS_COMPANION_OBJECT(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isDataClass: Boolean get() = Flag.Class.IS_DATA(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isEnumClass: Boolean get() = Flag.Class.IS_ENUM_CLASS(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isEnumEntryClass: Boolean get() = Flag.Class.IS_ENUM_ENTRY(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isExpectClass: Boolean get() = Flag.Class.IS_EXPECT(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isExternalClass: Boolean get() = Flag.Class.IS_EXTERNAL(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isValueClass: Boolean get() = Flag.Class.IS_VALUE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isInnerClass: Boolean get() = Flag.Class.IS_INNER(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isObjectClass: Boolean get() = Flag.Class.IS_OBJECT(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isInterface: Boolean get() = Flag.Class.IS_INTERFACE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isFun: Boolean get() = Flag.Class.IS_FUN(this)
+
+@KotlinPoetMetadataPreview
+public val KmClass.isAnnotation: Boolean get() = flags.isAnnotationClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isClass: Boolean get() = flags.isClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isCompanionObject: Boolean get() = flags.isCompanionObjectClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isData: Boolean get() = flags.isDataClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isEnum: Boolean get() = flags.isEnumClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isEnumEntry: Boolean get() = flags.isEnumEntryClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isExpect: Boolean get() = flags.isExpectClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isExternal: Boolean get() = flags.isExternalClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isValue: Boolean get() = flags.isValueClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isInner: Boolean get() = flags.isInnerClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isObject: Boolean get() = flags.isObjectClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isInterface: Boolean get() = flags.isInterface
+
+@KotlinPoetMetadataPreview
+public val KmClass.isFun: Boolean get() = flags.isFun
+
+@KotlinPoetMetadataPreview
+public val KmType.isSuspend: Boolean get() = flags.isSuspendType
+
+@KotlinPoetMetadataPreview
+public val KmType.isNullable: Boolean get() = flags.isNullableType
+
+// Constructor flags.
+@KotlinPoetMetadataPreview
+public val Flags.isPrimaryConstructor: Boolean get() = !Flag.Constructor.IS_SECONDARY(this)
+
+@KotlinPoetMetadataPreview
+public val KmConstructor.isPrimary: Boolean get() = flags.isPrimaryConstructor
+
+@KotlinPoetMetadataPreview
+public val KmConstructor.isSecondary: Boolean get() = !isPrimary
+
+// Function flags.
+@KotlinPoetMetadataPreview
+public val Flags.isDeclarationFunction: Boolean get() = Flag.Function.IS_DECLARATION(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isFakeOverrideFunction: Boolean get() = Flag.Function.IS_FAKE_OVERRIDE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isDelegationFunction: Boolean get() = Flag.Function.IS_DELEGATION(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isSynthesizedFunction: Boolean get() = Flag.Function.IS_SYNTHESIZED(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isOperatorFunction: Boolean get() = Flag.Function.IS_OPERATOR(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isInfixFunction: Boolean get() = Flag.Function.IS_INFIX(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isInlineFunction: Boolean get() = Flag.Function.IS_INLINE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isTailRecFunction: Boolean get() = Flag.Function.IS_TAILREC(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isExternalFunction: Boolean get() = Flag.Function.IS_EXTERNAL(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isSuspendFunction: Boolean get() = Flag.Function.IS_SUSPEND(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isExpectFunction: Boolean get() = Flag.Function.IS_EXPECT(this)
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isDeclaration: Boolean get() = flags.isDeclarationFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isFakeOverride: Boolean get() = flags.isFakeOverrideFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isDelegation: Boolean get() = flags.isDelegationFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isSynthesized: Boolean get() = flags.isSynthesizedFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isOperator: Boolean get() = flags.isOperatorFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isInfix: Boolean get() = flags.isInfixFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isInline: Boolean get() = flags.isInlineFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isTailRec: Boolean get() = flags.isTailRecFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isExternal: Boolean get() = flags.isExternalFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isSuspend: Boolean get() = flags.isSuspendFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isExpect: Boolean get() = flags.isExpectFunction
+
+// Parameter flags.
+@KotlinPoetMetadataPreview
+public val KmValueParameter.declaresDefaultValue: Boolean get() =
+ Flag.ValueParameter.DECLARES_DEFAULT_VALUE(flags)
+
+@KotlinPoetMetadataPreview
+public val KmValueParameter.isCrossInline: Boolean get() = Flag.ValueParameter.IS_CROSSINLINE(flags)
+
+@KotlinPoetMetadataPreview
+public val KmValueParameter.isNoInline: Boolean get() = Flag.ValueParameter.IS_NOINLINE(flags)
+
+// Property flags.
+@KotlinPoetMetadataPreview
+public val Flags.isFakeOverrideProperty: Boolean get() = Flag.Property.IS_FAKE_OVERRIDE(this)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.hasConstant: Boolean get() = Flag.Property.HAS_CONSTANT(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.hasGetter: Boolean get() = Flag.Property.HAS_GETTER(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.hasSetter: Boolean get() = Flag.Property.HAS_SETTER(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isConst: Boolean get() = Flag.Property.IS_CONST(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isDeclaration: Boolean get() = Flag.Property.IS_DECLARATION(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isDelegated: Boolean get() = Flag.Property.IS_DELEGATED(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isDelegation: Boolean get() = Flag.Property.IS_DELEGATION(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isExpect: Boolean get() = Flag.Property.IS_EXPECT(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isExternal: Boolean get() = Flag.Property.IS_EXTERNAL(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isFakeOverride: Boolean get() = flags.isFakeOverrideProperty
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isLateinit: Boolean get() = Flag.Property.IS_LATEINIT(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isSynthesized: Boolean get() = Flag.Property.IS_SYNTHESIZED(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isVar: Boolean get() = Flag.Property.IS_VAR(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isVal: Boolean get() = !isVar
+
+// Property Accessor Flags
+@KotlinPoetMetadataPreview
+public val Flags.isPropertyAccessorExternal: Boolean
+ get() = Flag.PropertyAccessor.IS_EXTERNAL(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isPropertyAccessorInline: Boolean
+ get() = Flag.PropertyAccessor.IS_INLINE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isPropertyAccessorNotDefault: Boolean
+ get() = Flag.PropertyAccessor.IS_NOT_DEFAULT(this)
+
+// TypeParameter flags.
+@KotlinPoetMetadataPreview
+public val KmTypeParameter.isReified: Boolean get() = Flag.TypeParameter.IS_REIFIED(flags)
+
+// Property Accessor Flags
+public enum class PropertyAccessorFlag {
+ IS_EXTERNAL,
+ IS_INLINE,
+ IS_NOT_DEFAULT,
+}
+
+@KotlinPoetMetadataPreview
+public val KmProperty.setterPropertyAccessorFlags: Set<PropertyAccessorFlag>
+ get() = setterFlags.propertyAccessorFlags
+
+@KotlinPoetMetadataPreview
+public val KmProperty.getterPropertyAccessorFlags: Set<PropertyAccessorFlag>
+ get() = getterFlags.propertyAccessorFlags
+
+@KotlinPoetMetadataPreview
+public val Flags.propertyAccessorFlags: Set<PropertyAccessorFlag>
+ get() = setOf {
+ if (Flag.PropertyAccessor.IS_EXTERNAL(this@propertyAccessorFlags)) {
+ add(PropertyAccessorFlag.IS_EXTERNAL)
+ }
+ if (Flag.PropertyAccessor.IS_INLINE(this@propertyAccessorFlags)) {
+ add(PropertyAccessorFlag.IS_INLINE)
+ }
+ if (Flag.PropertyAccessor.IS_NOT_DEFAULT(this@propertyAccessorFlags)) {
+ add(PropertyAccessorFlag.IS_NOT_DEFAULT)
+ }
+ }
+
+internal inline fun <E> setOf(body: MutableSet<E>.() -> Unit): Set<E> {
+ return mutableSetOf<E>().apply(body).toSet()
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/KotlinPoetMetadata.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/KotlinPoetMetadata.kt
new file mode 100644
index 00000000..6cb3c91d
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/KotlinPoetMetadata.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * 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.
+ */
+@file:JvmName("KotlinPoetMetadata")
+@file:Suppress("unused")
+
+package com.squareup.kotlinpoet.metadata
+
+import javax.lang.model.element.TypeElement
+import kotlin.annotation.AnnotationTarget.CLASS
+import kotlin.annotation.AnnotationTarget.FUNCTION
+import kotlin.annotation.AnnotationTarget.PROPERTY
+import kotlin.reflect.KClass
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.jvm.KotlinClassHeader
+import kotlinx.metadata.jvm.KotlinClassMetadata
+
+/**
+ * Indicates that a given API is part of the experimental KotlinPoet metadata support. This exists
+ * because kotlinx-metadata is not a stable API, and will remain in place until it is.
+ */
+@RequiresOptIn
+@Retention(AnnotationRetention.BINARY)
+@Target(CLASS, FUNCTION, PROPERTY)
+public annotation class KotlinPoetMetadataPreview
+
+/** @return a new [KmClass] representation of the Kotlin metadata for [this] class. */
+@KotlinPoetMetadataPreview
+public fun KClass<*>.toKmClass(): KmClass = java.toKmClass()
+
+/** @return a new [KmClass] representation of the Kotlin metadata for [this] class. */
+@KotlinPoetMetadataPreview
+public fun Class<*>.toKmClass(): KmClass = readMetadata(::getAnnotation).toKmClass()
+
+/** @return a new [KmClass] representation of the Kotlin metadata for [this] type. */
+@KotlinPoetMetadataPreview
+public fun TypeElement.toKmClass(): KmClass = readMetadata(::getAnnotation).toKmClass()
+
+@KotlinPoetMetadataPreview
+public fun Metadata.toKmClass(): KmClass {
+ return toKotlinClassMetadata<KotlinClassMetadata.Class>()
+ .toKmClass()
+}
+
+@KotlinPoetMetadataPreview
+public inline fun <reified T : KotlinClassMetadata> Metadata.toKotlinClassMetadata(): T {
+ val expectedType = T::class
+ val metadata = readKotlinClassMetadata()
+ return when (expectedType) {
+ KotlinClassMetadata.Class::class -> {
+ check(metadata is KotlinClassMetadata.Class)
+ metadata as T
+ }
+ KotlinClassMetadata.FileFacade::class -> {
+ check(metadata is KotlinClassMetadata.FileFacade)
+ metadata as T
+ }
+ KotlinClassMetadata.SyntheticClass::class ->
+ throw UnsupportedOperationException("SyntheticClass isn't supported yet!")
+ KotlinClassMetadata.MultiFileClassFacade::class ->
+ throw UnsupportedOperationException("MultiFileClassFacade isn't supported yet!")
+ KotlinClassMetadata.MultiFileClassPart::class ->
+ throw UnsupportedOperationException("MultiFileClassPart isn't supported yet!")
+ KotlinClassMetadata.Unknown::class ->
+ throw RuntimeException("Recorded unknown metadata type! $metadata")
+ else -> TODO("Unrecognized KotlinClassMetadata type: $expectedType")
+ }
+}
+
+/**
+ * Returns the [KotlinClassMetadata] this represents. In general you should only use this function
+ * when you don't know what the underlying [KotlinClassMetadata] subtype is, otherwise you should
+ * use one of the more direct functions like [toKmClass].
+ */
+@KotlinPoetMetadataPreview
+public fun Metadata.readKotlinClassMetadata(): KotlinClassMetadata {
+ val metadata = KotlinClassMetadata.read(asClassHeader())
+ checkNotNull(metadata) {
+ "Could not parse metadata! Try bumping kotlinpoet and/or kotlinx-metadata version."
+ }
+ return metadata
+}
+
+private inline fun readMetadata(lookup: ((Class<Metadata>) -> Metadata?)): Metadata {
+ return checkNotNull(lookup.invoke(Metadata::class.java)) {
+ "No Metadata annotation found! Must be Kotlin code built with the standard library on the classpath."
+ }
+}
+
+private fun Metadata.asClassHeader(): KotlinClassHeader {
+ return KotlinClassHeader(
+ kind = kind,
+ metadataVersion = metadataVersion,
+ data1 = data1,
+ data2 = data2,
+ extraString = extraString,
+ packageName = packageName,
+ extraInt = extraInt,
+ )
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ClassInspectorUtil.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ClassInspectorUtil.kt
new file mode 100644
index 00000000..d9caa834
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ClassInspectorUtil.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.classinspectors
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FIELD
+import com.squareup.kotlinpoet.CHAR_SEQUENCE
+import com.squareup.kotlinpoet.COLLECTION
+import com.squareup.kotlinpoet.COMPARABLE
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.ITERABLE
+import com.squareup.kotlinpoet.LIST
+import com.squareup.kotlinpoet.MAP
+import com.squareup.kotlinpoet.MAP_ENTRY
+import com.squareup.kotlinpoet.MUTABLE_COLLECTION
+import com.squareup.kotlinpoet.MUTABLE_ITERABLE
+import com.squareup.kotlinpoet.MUTABLE_LIST
+import com.squareup.kotlinpoet.MUTABLE_MAP
+import com.squareup.kotlinpoet.MUTABLE_MAP_ENTRY
+import com.squareup.kotlinpoet.MUTABLE_SET
+import com.squareup.kotlinpoet.SET
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.joinToCode
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.isConst
+import com.squareup.kotlinpoet.metadata.specs.ClassInspector
+import java.util.Collections
+import java.util.TreeSet
+import kotlinx.metadata.KmProperty
+import kotlinx.metadata.isLocal
+import org.jetbrains.annotations.NotNull
+import org.jetbrains.annotations.Nullable
+
+@KotlinPoetMetadataPreview
+internal object ClassInspectorUtil {
+ val JVM_NAME: ClassName = JvmName::class.asClassName()
+ private val JVM_FIELD = JvmField::class.asClassName()
+ internal val JVM_FIELD_SPEC = AnnotationSpec.builder(JVM_FIELD).build()
+ internal val JVM_SYNTHETIC = JvmSynthetic::class.asClassName()
+ internal val JVM_SYNTHETIC_SPEC = AnnotationSpec.builder(JVM_SYNTHETIC).build()
+ internal val JAVA_DEPRECATED = java.lang.Deprecated::class.asClassName()
+ private val JVM_TRANSIENT = Transient::class.asClassName()
+ private val JVM_VOLATILE = Volatile::class.asClassName()
+ private val IMPLICIT_FIELD_ANNOTATIONS = setOf(
+ JVM_FIELD,
+ JVM_TRANSIENT,
+ JVM_VOLATILE,
+ )
+ private val NOT_NULL = NotNull::class.asClassName()
+ private val NULLABLE = Nullable::class.asClassName()
+ private val EXTENSION_FUNCTION_TYPE = ExtensionFunctionType::class.asClassName()
+ private val KOTLIN_INTRINSIC_ANNOTATIONS = setOf(
+ NOT_NULL,
+ NULLABLE,
+ EXTENSION_FUNCTION_TYPE,
+ )
+
+ val KOTLIN_INTRINSIC_INTERFACES: Set<ClassName> = setOf(
+ CHAR_SEQUENCE,
+ COMPARABLE,
+ ITERABLE,
+ COLLECTION,
+ LIST,
+ SET,
+ MAP,
+ MAP_ENTRY,
+ MUTABLE_ITERABLE,
+ MUTABLE_COLLECTION,
+ MUTABLE_LIST,
+ MUTABLE_SET,
+ MUTABLE_MAP,
+ MUTABLE_MAP_ENTRY,
+ )
+
+ private val KOTLIN_NULLABILITY_ANNOTATIONS = setOf(
+ "org.jetbrains.annotations.NotNull",
+ "org.jetbrains.annotations.Nullable",
+ )
+
+ fun filterOutNullabilityAnnotations(
+ annotations: List<AnnotationSpec>,
+ ): List<AnnotationSpec> {
+ return annotations.filterNot {
+ val typeName = it.typeName
+ return@filterNot typeName is ClassName &&
+ typeName.canonicalName in KOTLIN_NULLABILITY_ANNOTATIONS
+ }
+ }
+
+ /** @return a [CodeBlock] representation of a [literal] value. */
+ fun codeLiteralOf(literal: Any): CodeBlock {
+ return when (literal) {
+ is String -> CodeBlock.of("%S", literal)
+ is Long -> CodeBlock.of("%LL", literal)
+ is Float -> CodeBlock.of("%LF", literal)
+ else -> CodeBlock.of("%L", literal)
+ }
+ }
+
+ /**
+ * Infers if [property] is a jvm field and should be annotated as such given the input
+ * parameters.
+ */
+ fun computeIsJvmField(
+ property: KmProperty,
+ classInspector: ClassInspector,
+ isCompanionObject: Boolean,
+ hasGetter: Boolean,
+ hasSetter: Boolean,
+ hasField: Boolean,
+ ): Boolean {
+ return if (!hasGetter &&
+ !hasSetter &&
+ hasField &&
+ !property.isConst
+ ) {
+ !(classInspector.supportsNonRuntimeRetainedAnnotations && !isCompanionObject)
+ } else {
+ false
+ }
+ }
+
+ /**
+ * @return a new collection of [AnnotationSpecs][AnnotationSpec] with sorting and de-duping
+ * input annotations from [body].
+ */
+ fun createAnnotations(
+ siteTarget: UseSiteTarget? = null,
+ body: MutableCollection<AnnotationSpec>.() -> Unit,
+ ): Collection<AnnotationSpec> {
+ val result = mutableSetOf<AnnotationSpec>()
+ .apply(body)
+ .filterNot { spec ->
+ spec.typeName in KOTLIN_INTRINSIC_ANNOTATIONS
+ }
+ val withUseSiteTarget = if (siteTarget != null) {
+ result.map {
+ if (!(siteTarget == FIELD && it.typeName in IMPLICIT_FIELD_ANNOTATIONS)) {
+ // Some annotations are implicitly only for FIELD, so don't emit those site targets
+ it.toBuilder().useSiteTarget(siteTarget).build()
+ } else {
+ it
+ }
+ }
+ } else {
+ result
+ }
+
+ val sorted = withUseSiteTarget.toTreeSet()
+
+ return Collections.unmodifiableCollection(sorted)
+ }
+
+ /**
+ * @return a [@Throws][Throws] [AnnotationSpec] representation of a given collection of
+ * [exceptions].
+ */
+ fun createThrowsSpec(
+ exceptions: Collection<TypeName>,
+ useSiteTarget: UseSiteTarget? = null,
+ ): AnnotationSpec {
+ return AnnotationSpec.builder(Throws::class)
+ .addMember(
+ "exceptionClasses = %L",
+ exceptions.map { CodeBlock.of("%T::class", it) }
+ .joinToCode(prefix = "[", suffix = "]"),
+ )
+ .useSiteTarget(useSiteTarget)
+ .build()
+ }
+
+ /**
+ * Best guesses a [ClassName] as represented in Metadata's [kotlinx.metadata.ClassName], where
+ * package names in this name are separated by '/' and class names are separated by '.'.
+ *
+ * For example: `"org/foo/bar/Baz.Nested"`.
+ *
+ * Local classes are prefixed with ".", but for KotlinPoetMetadataSpecs' use case we don't deal
+ * with those.
+ */
+ fun createClassName(kotlinMetadataName: String): ClassName {
+ require(!kotlinMetadataName.isLocal) {
+ "Local/anonymous classes are not supported!"
+ }
+ // Top-level: package/of/class/MyClass
+ // Nested A: package/of/class/MyClass.NestedClass
+ val simpleName = kotlinMetadataName.substringAfterLast(
+ '/', // Drop the package name, e.g. "package/of/class/"
+ '.', // Drop any enclosing classes, e.g. "MyClass."
+ )
+ val packageName = kotlinMetadataName.substringBeforeLast(
+ delimiter = "/",
+ missingDelimiterValue = "",
+ )
+ val simpleNames = kotlinMetadataName.removeSuffix(simpleName)
+ .removeSuffix(".") // Trailing "." if any
+ .removePrefix(packageName)
+ .removePrefix("/")
+ .let {
+ if (it.isNotEmpty()) {
+ it.split(".")
+ } else {
+ // Don't split, otherwise we end up with an empty string as the first element!
+ emptyList()
+ }
+ }
+ .plus(simpleName)
+
+ return ClassName(
+ packageName = packageName.replace("/", "."),
+ simpleNames = simpleNames,
+ )
+ }
+
+ fun Iterable<AnnotationSpec>.toTreeSet(): TreeSet<AnnotationSpec> {
+ return TreeSet<AnnotationSpec>(compareBy { it.toString() }).apply {
+ addAll(this@toTreeSet)
+ }
+ }
+
+ private fun String.substringAfterLast(vararg delimiters: Char): String {
+ val index = lastIndexOfAny(delimiters)
+ return if (index == -1) this else substring(index + 1, length)
+ }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ElementsClassInspector.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ElementsClassInspector.kt
new file mode 100644
index 00000000..d4f77860
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ElementsClassInspector.kt
@@ -0,0 +1,585 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.classinspectors
+
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.auto.common.Visibility
+import com.google.common.collect.LinkedHashMultimap
+import com.google.common.collect.SetMultimap
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.asTypeName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.JAVA_DEPRECATED
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.JVM_NAME
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.filterOutNullabilityAnnotations
+import com.squareup.kotlinpoet.metadata.hasAnnotations
+import com.squareup.kotlinpoet.metadata.hasConstant
+import com.squareup.kotlinpoet.metadata.isAnnotation
+import com.squareup.kotlinpoet.metadata.isCompanionObject
+import com.squareup.kotlinpoet.metadata.isConst
+import com.squareup.kotlinpoet.metadata.isDeclaration
+import com.squareup.kotlinpoet.metadata.isSynthesized
+import com.squareup.kotlinpoet.metadata.isValue
+import com.squareup.kotlinpoet.metadata.readKotlinClassMetadata
+import com.squareup.kotlinpoet.metadata.specs.ClassData
+import com.squareup.kotlinpoet.metadata.specs.ClassInspector
+import com.squareup.kotlinpoet.metadata.specs.ConstructorData
+import com.squareup.kotlinpoet.metadata.specs.ContainerData
+import com.squareup.kotlinpoet.metadata.specs.EnumEntryData
+import com.squareup.kotlinpoet.metadata.specs.FieldData
+import com.squareup.kotlinpoet.metadata.specs.FileData
+import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier
+import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.TRANSIENT
+import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.VOLATILE
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.DEFAULT
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.STATIC
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.SYNCHRONIZED
+import com.squareup.kotlinpoet.metadata.specs.KM_CONSTRUCTOR_COMPARATOR
+import com.squareup.kotlinpoet.metadata.specs.KM_FUNCTION_COMPARATOR
+import com.squareup.kotlinpoet.metadata.specs.KM_PROPERTY_COMPARATOR
+import com.squareup.kotlinpoet.metadata.specs.MethodData
+import com.squareup.kotlinpoet.metadata.specs.PropertyData
+import com.squareup.kotlinpoet.metadata.toKmClass
+import java.util.TreeMap
+import java.util.concurrent.ConcurrentHashMap
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind.INTERFACE
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.PackageElement
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.TypeKind
+import javax.lang.model.util.ElementFilter
+import javax.lang.model.util.Elements
+import javax.lang.model.util.Types
+import kotlin.LazyThreadSafetyMode.NONE
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmDeclarationContainer
+import kotlinx.metadata.KmPackage
+import kotlinx.metadata.jvm.JvmFieldSignature
+import kotlinx.metadata.jvm.JvmMethodSignature
+import kotlinx.metadata.jvm.KotlinClassMetadata
+import kotlinx.metadata.jvm.fieldSignature
+import kotlinx.metadata.jvm.getterSignature
+import kotlinx.metadata.jvm.setterSignature
+import kotlinx.metadata.jvm.signature
+import kotlinx.metadata.jvm.syntheticMethodForAnnotations
+
+private typealias ElementsModifier = javax.lang.model.element.Modifier
+
+/**
+ * An [Elements]-based implementation of [ClassInspector].
+ */
+@KotlinPoetMetadataPreview
+public class ElementsClassInspector private constructor(
+ private val elements: Elements,
+ private val types: Types,
+) : ClassInspector {
+ private val typeElementCache = ConcurrentHashMap<ClassName, Optional<TypeElement>>()
+ private val methodCache = ConcurrentHashMap<Pair<TypeElement, String>, Optional<ExecutableElement>>()
+ private val variableElementCache = ConcurrentHashMap<Pair<TypeElement, String>, Optional<VariableElement>>()
+ private val jvmNameType = elements.getTypeElement(JVM_NAME.canonicalName)
+ private val jvmNameName = ElementFilter.methodsIn(jvmNameType.enclosedElements)
+ .first { it.simpleName.toString() == "name" }
+
+ private fun lookupTypeElement(className: ClassName): TypeElement? {
+ return typeElementCache.getOrPut(className) {
+ elements.getTypeElement(className.canonicalName).toOptional()
+ }.nullableValue
+ }
+
+ override val supportsNonRuntimeRetainedAnnotations: Boolean = true
+
+ override fun declarationContainerFor(className: ClassName): KmDeclarationContainer {
+ val typeElement = lookupTypeElement(className)
+ ?: error("No type element found for: $className.")
+
+ val metadata = typeElement.getAnnotation(Metadata::class.java)
+ return when (val kotlinClassMetadata = metadata.readKotlinClassMetadata()) {
+ is KotlinClassMetadata.Class -> kotlinClassMetadata.toKmClass()
+ is KotlinClassMetadata.FileFacade -> kotlinClassMetadata.toKmPackage()
+ else -> TODO("Not implemented yet: ${kotlinClassMetadata.javaClass.simpleName}")
+ }
+ }
+
+ override fun isInterface(className: ClassName): Boolean {
+ if (className in ClassInspectorUtil.KOTLIN_INTRINSIC_INTERFACES) {
+ return true
+ }
+ return lookupTypeElement(className)?.kind == INTERFACE
+ }
+
+ private fun TypeElement.lookupField(fieldSignature: JvmFieldSignature): VariableElement? {
+ val signatureString = fieldSignature.asString()
+ return variableElementCache.getOrPut(this to signatureString) {
+ ElementFilter.fieldsIn(enclosedElements)
+ .find { signatureString == it.jvmFieldSignature(types) }.toOptional()
+ }.nullableValue
+ }
+
+ private fun lookupMethod(
+ className: ClassName,
+ methodSignature: JvmMethodSignature,
+ elementFilter: (Iterable<Element>) -> List<ExecutableElement>,
+ ): ExecutableElement? {
+ return lookupTypeElement(className)?.lookupMethod(methodSignature, elementFilter)
+ }
+
+ private fun TypeElement.lookupMethod(
+ methodSignature: JvmMethodSignature,
+ elementFilter: (Iterable<Element>) -> List<ExecutableElement>,
+ ): ExecutableElement? {
+ val signatureString = methodSignature.asString()
+ return methodCache.getOrPut(this to signatureString) {
+ elementFilter(enclosedElements)
+ .find { signatureString == it.jvmMethodSignature(types) }.toOptional()
+ }.nullableValue
+ }
+
+ private fun VariableElement.jvmModifiers(isJvmField: Boolean): Set<JvmFieldModifier> {
+ return modifiers.mapNotNullTo(mutableSetOf()) {
+ when {
+ it == ElementsModifier.TRANSIENT -> TRANSIENT
+ it == ElementsModifier.VOLATILE -> VOLATILE
+ !isJvmField && it == ElementsModifier.STATIC -> JvmFieldModifier.STATIC
+ else -> null
+ }
+ }
+ }
+
+ private fun VariableElement.annotationSpecs(): List<AnnotationSpec> {
+ @Suppress("DEPRECATION")
+ return filterOutNullabilityAnnotations(
+ annotationMirrors.map { AnnotationSpec.get(it) },
+ )
+ }
+
+ private fun ExecutableElement.jvmModifiers(): Set<JvmMethodModifier> {
+ return modifiers.mapNotNullTo(mutableSetOf()) {
+ when (it) {
+ ElementsModifier.SYNCHRONIZED -> SYNCHRONIZED
+ ElementsModifier.STATIC -> STATIC
+ ElementsModifier.DEFAULT -> DEFAULT
+ else -> null
+ }
+ }
+ }
+
+ private fun ExecutableElement.annotationSpecs(): List<AnnotationSpec> {
+ @Suppress("DEPRECATION")
+ return filterOutNullabilityAnnotations(
+ annotationMirrors.map { AnnotationSpec.get(it) },
+ )
+ }
+
+ private fun ExecutableElement.exceptionTypeNames(): List<TypeName> {
+ @Suppress("DEPRECATION")
+ return thrownTypes.map { it.asTypeName() }
+ }
+
+ override fun enumEntry(enumClassName: ClassName, memberName: String): EnumEntryData {
+ val enumType = lookupTypeElement(enumClassName)
+ ?: error("No type element found for: $enumClassName.")
+ val enumTypeAsType = enumType.asType()
+ val member = typeElementCache.getOrPut(enumClassName.nestedClass(memberName)) {
+ ElementFilter.typesIn(enumType.enclosedElements)
+ .asSequence()
+ .filter { types.isSubtype(enumTypeAsType, it.superclass) }
+ .find { it.simpleName.contentEquals(memberName) }.toOptional()
+ }.nullableValue
+ val declarationContainer = member?.getAnnotation(Metadata::class.java)?.toKmClass()
+
+ val entry = ElementFilter.fieldsIn(enumType.enclosedElements)
+ .find { it.simpleName.contentEquals(memberName) }
+ ?: error("Could not find the enum entry for: $enumClassName")
+
+ return EnumEntryData(
+ declarationContainer = declarationContainer,
+ annotations = entry.annotationSpecs(),
+ )
+ }
+
+ private fun VariableElement.constantValue(): CodeBlock? {
+ return constantValue?.let(ClassInspectorUtil::codeLiteralOf)
+ }
+
+ override fun methodExists(className: ClassName, methodSignature: JvmMethodSignature): Boolean {
+ return lookupMethod(className, methodSignature, ElementFilter::methodsIn) != null
+ }
+
+ /**
+ * Detects whether [this] given method is overridden in [type].
+ *
+ * Adapted and simplified from AutoCommon's private
+ * [MoreElements.getLocalAndInheritedMethods] methods implementations for detecting
+ * overrides.
+ */
+ private fun ExecutableElement.isOverriddenIn(type: TypeElement): Boolean {
+ val methodMap = LinkedHashMultimap.create<String, ExecutableElement>()
+ type.getAllMethods(MoreElements.getPackage(type), methodMap)
+ // Find methods that are overridden using `Elements.overrides`. We reduce the performance
+ // impact by:
+ // (a) grouping methods by name, since a method cannot override another method with a
+ // different name. Since we know the target name, we just inspect the methods with
+ // that name.
+ // (b) making sure that methods in ancestor types precede those in descendant types,
+ // which means we only have to check a method against the ones that follow it in
+ // that order. Below, this means we just need to find the index of our target method
+ // and compare against only preceding ones.
+ val methodList = methodMap.asMap()[simpleName.toString()]?.toList()
+ ?: return false
+ val signature = jvmMethodSignature(types)
+ return methodList.asSequence()
+ .filter { it.jvmMethodSignature(types) == signature }
+ .take(1)
+ .any { elements.overrides(this, it, type) }
+ }
+
+ /**
+ * Add to [methodsAccumulator] the instance methods from [this] that are visible to code in
+ * the package [pkg]. This means all the instance methods from [this] itself and all
+ * instance methods it inherits from its ancestors, except private methods and
+ * package-private methods in other packages. This method does not take overriding into
+ * account, so it will add both an ancestor method and a descendant method that overrides
+ * it. [methodsAccumulator] is a multimap from a method name to all of the methods with
+ * that name, including methods that override or overload one another. Within those
+ * methods, those in ancestor types always precede those in descendant types.
+ *
+ * Adapted from AutoCommon's private [MoreElements.getLocalAndInheritedMethods] methods'
+ * implementations, before overridden methods are stripped.
+ */
+ private fun TypeElement.getAllMethods(
+ pkg: PackageElement,
+ methodsAccumulator: SetMultimap<String, ExecutableElement>,
+ ) {
+ for (superInterface in interfaces) {
+ MoreTypes.asTypeElement(superInterface).getAllMethods(pkg, methodsAccumulator)
+ }
+ if (superclass.kind != TypeKind.NONE) {
+ // Visit the superclass after superinterfaces so we will always see the implementation of a
+ // method after any interfaces that declared it.
+ MoreTypes.asTypeElement(superclass).getAllMethods(pkg, methodsAccumulator)
+ }
+ for (method in ElementFilter.methodsIn(enclosedElements)) {
+ if (ElementsModifier.STATIC !in method.modifiers &&
+ ElementsModifier.FINAL !in method.modifiers &&
+ ElementsModifier.PRIVATE !in method.modifiers &&
+ method.isVisibleFrom(pkg)
+ ) {
+ methodsAccumulator.put(method.simpleName.toString(), method)
+ }
+ }
+ }
+
+ private fun ExecutableElement.isVisibleFrom(pkg: PackageElement): Boolean {
+ // We use Visibility.ofElement rather than [MoreElements.effectiveVisibilityOfElement]
+ // because it doesn't really matter whether the containing class is visible. If you
+ // inherit a public method then you have a public method, regardless of whether you
+ // inherit it from a public class.
+ return when (Visibility.ofElement(this)) {
+ Visibility.PRIVATE -> false
+ Visibility.DEFAULT -> MoreElements.getPackage(this) == pkg
+ else -> true
+ }
+ }
+
+ override fun containerData(
+ declarationContainer: KmDeclarationContainer,
+ className: ClassName,
+ parentClassName: ClassName?,
+ ): ContainerData {
+ val typeElement: TypeElement = lookupTypeElement(className) ?: error("No class found for: $className.")
+ val isCompanionObject = when (declarationContainer) {
+ is KmClass -> {
+ declarationContainer.isCompanionObject
+ }
+ is KmPackage -> {
+ false
+ }
+ else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}")
+ }
+
+ // Should only be called if parentName has been null-checked
+ val classIfCompanion by lazy(NONE) {
+ if (isCompanionObject && parentClassName != null) {
+ lookupTypeElement(parentClassName)
+ ?: error("No class found for: $parentClassName.")
+ } else {
+ typeElement
+ }
+ }
+
+ val propertyData = declarationContainer.properties
+ .asSequence()
+ .filter { it.isDeclaration }
+ .filterNot { it.isSynthesized }
+ .associateWithTo(TreeMap(KM_PROPERTY_COMPARATOR)) { property ->
+ val isJvmField = ClassInspectorUtil.computeIsJvmField(
+ property = property,
+ classInspector = this,
+ isCompanionObject = isCompanionObject,
+ hasGetter = property.getterSignature != null,
+ hasSetter = property.setterSignature != null,
+ hasField = property.fieldSignature != null,
+ )
+
+ val fieldData = property.fieldSignature?.let fieldDataLet@{ fieldSignature ->
+ // Check the field in the parent first. For const/static/jvmField elements, these only
+ // exist in the parent and we want to check that if necessary to avoid looking up a
+ // non-existent field in the companion.
+ val parentModifiers = if (isCompanionObject && parentClassName != null) {
+ classIfCompanion.lookupField(fieldSignature)?.jvmModifiers(isJvmField).orEmpty()
+ } else {
+ emptySet()
+ }
+
+ val isStatic = JvmFieldModifier.STATIC in parentModifiers
+
+ // TODO we looked up field once, let's reuse it
+ val classForOriginalField = typeElement.takeUnless {
+ isCompanionObject &&
+ (property.isConst || isJvmField || isStatic)
+ } ?: classIfCompanion
+
+ val field = classForOriginalField.lookupField(fieldSignature)
+ ?: return@fieldDataLet FieldData.SYNTHETIC
+ val constant = if (property.hasConstant) {
+ val fieldWithConstant = classIfCompanion.takeIf { it != typeElement }?.let {
+ if (it.kind.isInterface) {
+ field
+ } else {
+ // const properties are relocated to the enclosing class
+ it.lookupField(fieldSignature)
+ ?: return@fieldDataLet FieldData.SYNTHETIC
+ }
+ } ?: field
+ fieldWithConstant.constantValue()
+ } else {
+ null
+ }
+
+ val jvmModifiers = field.jvmModifiers(isJvmField) + parentModifiers
+
+ FieldData(
+ annotations = field.annotationSpecs(),
+ isSynthetic = false,
+ jvmModifiers = jvmModifiers.filterNotTo(mutableSetOf()) {
+ // JvmField companion objects don't need JvmStatic, it's implicit
+ isCompanionObject && isJvmField && it == JvmFieldModifier.STATIC
+ },
+ constant = constant,
+ )
+ }
+
+ val getterData = property.getterSignature?.let { getterSignature ->
+ val method = classIfCompanion.lookupMethod(getterSignature, ElementFilter::methodsIn)
+ method?.methodData(
+ typeElement = typeElement,
+ hasAnnotations = property.getterFlags.hasAnnotations,
+ jvmInformationMethod = classIfCompanion.takeIf { it != typeElement }
+ ?.lookupMethod(getterSignature, ElementFilter::methodsIn)
+ ?: method,
+ )
+ ?: return@let MethodData.SYNTHETIC
+ }
+
+ val setterData = property.setterSignature?.let { setterSignature ->
+ val method = classIfCompanion.lookupMethod(setterSignature, ElementFilter::methodsIn)
+ method?.methodData(
+ typeElement = typeElement,
+ hasAnnotations = property.setterFlags.hasAnnotations,
+ jvmInformationMethod = classIfCompanion.takeIf { it != typeElement }
+ ?.lookupMethod(setterSignature, ElementFilter::methodsIn)
+ ?: method,
+ knownIsOverride = getterData?.isOverride,
+ )
+ ?: return@let MethodData.SYNTHETIC
+ }
+
+ val annotations = mutableListOf<AnnotationSpec>()
+ if (property.flags.hasAnnotations) {
+ property.syntheticMethodForAnnotations?.let { annotationsHolderSignature ->
+ val method = typeElement.lookupMethod(annotationsHolderSignature, ElementFilter::methodsIn)
+ ?: return@let MethodData.SYNTHETIC
+ annotations += method.annotationSpecs()
+ // Cover for https://github.com/square/kotlinpoet/issues/1046
+ .filterNot { it.typeName == JAVA_DEPRECATED }
+ }
+ }
+
+ // If a field is static in a companion object, remove the modifier and add the annotation
+ // directly on the top level. Otherwise this will generate `@field:JvmStatic`, which is
+ // not legal
+ var finalFieldData = fieldData
+ fieldData?.jvmModifiers?.let { modifiers ->
+ if (isCompanionObject && JvmFieldModifier.STATIC in modifiers) {
+ finalFieldData = fieldData.copy(
+ jvmModifiers = fieldData.jvmModifiers
+ .filterNotTo(LinkedHashSet()) { it == JvmFieldModifier.STATIC },
+ )
+ annotations += AnnotationSpec.builder(
+ JVM_STATIC,
+ ).build()
+ }
+ }
+
+ PropertyData(
+ annotations = annotations,
+ fieldData = finalFieldData,
+ getterData = getterData,
+ setterData = setterData,
+ isJvmField = isJvmField,
+ )
+ }
+
+ val methodData = declarationContainer.functions
+ .associateWithTo(TreeMap(KM_FUNCTION_COMPARATOR)) { kmFunction ->
+ val signature = kmFunction.signature
+ if (signature != null) {
+ val method = typeElement.lookupMethod(signature, ElementFilter::methodsIn)
+ method?.methodData(
+ typeElement = typeElement,
+ hasAnnotations = kmFunction.flags.hasAnnotations,
+ jvmInformationMethod = classIfCompanion.takeIf { it != typeElement }
+ ?.lookupMethod(signature, ElementFilter::methodsIn)
+ ?: method,
+ )
+ ?: return@associateWithTo MethodData.SYNTHETIC
+ } else {
+ MethodData.EMPTY
+ }
+ }
+
+ when (declarationContainer) {
+ is KmClass -> {
+ val constructorData = declarationContainer.constructors
+ .associateWithTo(TreeMap(KM_CONSTRUCTOR_COMPARATOR)) { kmConstructor ->
+ if (declarationContainer.isAnnotation || declarationContainer.isValue) {
+ //
+ // Annotations are interfaces in bytecode, but kotlin metadata will still report a
+ // constructor signature
+ //
+ // Inline classes have no constructors at runtime
+ //
+ return@associateWithTo ConstructorData.EMPTY
+ }
+ val signature = kmConstructor.signature
+ if (signature != null) {
+ val constructor = typeElement.lookupMethod(signature, ElementFilter::constructorsIn)
+ ?: return@associateWithTo ConstructorData.EMPTY
+ ConstructorData(
+ annotations = if (kmConstructor.flags.hasAnnotations) {
+ constructor.annotationSpecs()
+ } else {
+ emptyList()
+ },
+ parameterAnnotations = constructor.parameters.indexedAnnotationSpecs(),
+ isSynthetic = false,
+ jvmModifiers = constructor.jvmModifiers(),
+ exceptions = constructor.exceptionTypeNames(),
+ )
+ } else {
+ ConstructorData.EMPTY
+ }
+ }
+ return ClassData(
+ declarationContainer = declarationContainer,
+ className = className,
+ annotations = if (declarationContainer.flags.hasAnnotations) {
+ ClassInspectorUtil.createAnnotations {
+ @Suppress("DEPRECATION")
+ addAll(typeElement.annotationMirrors.map { AnnotationSpec.get(it) })
+ }
+ } else {
+ emptyList()
+ },
+ properties = propertyData,
+ constructors = constructorData,
+ methods = methodData,
+ )
+ }
+ is KmPackage -> {
+ // There's no flag for checking if there are annotations, so we just eagerly check in this
+ // case. All annotations on this class are file: site targets in source. This includes
+ // @JvmName.
+ var jvmName: String? = null
+ val fileAnnotations = ClassInspectorUtil.createAnnotations(FILE) {
+ addAll(
+ typeElement.annotationMirrors.map {
+ if (it.annotationType == jvmNameType) {
+ val nameValue = requireNotNull(it.elementValues[jvmNameName]) {
+ "No name property found on $it"
+ }
+ jvmName = nameValue.value as String
+ }
+ @Suppress("DEPRECATION")
+ AnnotationSpec.get(it)
+ },
+ )
+ }
+ return FileData(
+ declarationContainer = declarationContainer,
+ annotations = fileAnnotations,
+ properties = propertyData,
+ methods = methodData,
+ className = className,
+ jvmName = jvmName,
+ )
+ }
+ else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}")
+ }
+ }
+
+ private fun List<VariableElement>.indexedAnnotationSpecs(): Map<Int, Collection<AnnotationSpec>> {
+ return withIndex().associate { (index, parameter) ->
+ index to ClassInspectorUtil.createAnnotations { addAll(parameter.annotationSpecs()) }
+ }
+ }
+
+ private fun ExecutableElement.methodData(
+ typeElement: TypeElement,
+ hasAnnotations: Boolean,
+ jvmInformationMethod: ExecutableElement = this,
+ knownIsOverride: Boolean? = null,
+ ): MethodData {
+ return MethodData(
+ annotations = if (hasAnnotations) annotationSpecs() else emptyList(),
+ parameterAnnotations = parameters.indexedAnnotationSpecs(),
+ isSynthetic = false,
+ jvmModifiers = jvmInformationMethod.jvmModifiers(),
+ isOverride = knownIsOverride ?: isOverriddenIn(typeElement),
+ exceptions = exceptionTypeNames(),
+ )
+ }
+
+ public companion object {
+ /** @return an [Elements]-based implementation of [ClassInspector]. */
+ @JvmStatic
+ @KotlinPoetMetadataPreview
+ public fun create(elements: Elements, types: Types): ClassInspector {
+ return ElementsClassInspector(elements, types)
+ }
+
+ private val JVM_STATIC = JvmStatic::class.asClassName()
+ }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/JvmDescriptorUtils.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/JvmDescriptorUtils.kt
new file mode 100644
index 00000000..235e5e87
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/JvmDescriptorUtils.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.classinspectors
+
+import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.NestingKind
+import javax.lang.model.element.QualifiedNameable
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.ArrayType
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.ErrorType
+import javax.lang.model.type.ExecutableType
+import javax.lang.model.type.NoType
+import javax.lang.model.type.NullType
+import javax.lang.model.type.PrimitiveType
+import javax.lang.model.type.TypeKind.BOOLEAN
+import javax.lang.model.type.TypeKind.BYTE
+import javax.lang.model.type.TypeKind.CHAR
+import javax.lang.model.type.TypeKind.DOUBLE
+import javax.lang.model.type.TypeKind.FLOAT
+import javax.lang.model.type.TypeKind.INT
+import javax.lang.model.type.TypeKind.LONG
+import javax.lang.model.type.TypeKind.SHORT
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.type.TypeVariable
+import javax.lang.model.type.WildcardType
+import javax.lang.model.util.AbstractTypeVisitor6
+import javax.lang.model.util.Types
+import kotlinx.metadata.jvm.JvmFieldSignature
+import kotlinx.metadata.jvm.JvmMethodSignature
+
+/*
+ * Adapted from
+ * - https://github.com/Takhion/kotlin-metadata/blob/e6de126575ad6ca10b093129b7c30d000c9b0c37/lib/src/main/kotlin/me/eugeniomarletti/kotlin/metadata/jvm/JvmDescriptorUtils.kt
+ * - https://github.com/Takhion/kotlin-metadata/pull/13
+ */
+
+/**
+ * For reference, see the [JVM specification, section 4.2](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.2).
+ *
+ * @return the name of this [Element] in its "internal form".
+ */
+internal val Element.internalName: String
+ get() = when (this) {
+ is TypeElement -> {
+ when (nestingKind) {
+ NestingKind.TOP_LEVEL ->
+ qualifiedName.toString().replace('.', '/')
+ NestingKind.MEMBER ->
+ enclosingElement.internalName + "$" + simpleName
+ NestingKind.LOCAL, NestingKind.ANONYMOUS ->
+ error("Unsupported nesting $nestingKind")
+ null ->
+ error("Unsupported, nestingKind == null")
+ }
+ }
+ is QualifiedNameable -> qualifiedName.toString().replace('.', '/')
+ else -> simpleName.toString()
+ }
+
+/**
+ * @return the "field descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+@Suppress("unused")
+internal val NoType.descriptor: String
+ get() = "V"
+
+/**
+ * @return the "field descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal val DeclaredType.descriptor: String
+ get() = "L" + asElement().internalName + ";"
+
+/**
+ * @return the "field descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal val PrimitiveType.descriptor: String
+ get() = when (this.kind) {
+ BYTE -> "B"
+ CHAR -> "C"
+ DOUBLE -> "D"
+ FLOAT -> "F"
+ INT -> "I"
+ LONG -> "J"
+ SHORT -> "S"
+ BOOLEAN -> "Z"
+ else -> error("Unknown primitive type $this")
+ }
+
+/**
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal fun TypeMirror.descriptor(types: Types): String =
+ accept(JvmDescriptorTypeVisitor, types)
+
+/**
+ * @return the "field descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal fun WildcardType.descriptor(types: Types): String =
+ types.erasure(this).descriptor(types)
+
+/**
+ * @return the "field descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal fun TypeVariable.descriptor(types: Types): String =
+ types.erasure(this).descriptor(types)
+
+/**
+ * @return the "field descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal fun ArrayType.descriptor(types: Types): String =
+ "[" + componentType.descriptor(types)
+
+/**
+ * @return the "method descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal fun ExecutableType.descriptor(types: Types): String {
+ val parameterDescriptors = parameterTypes.joinToString(separator = "") { it.descriptor(types) }
+ val returnDescriptor = returnType.descriptor(types)
+ return "($parameterDescriptors)$returnDescriptor"
+}
+
+/**
+ * Returns the JVM signature in the form "$Name$MethodDescriptor", for example: `equals(Ljava/lang/Object;)Z`.
+ *
+ * Useful for comparing with [JvmMethodSignature].
+ *
+ * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+ */
+internal fun ExecutableElement.jvmMethodSignature(types: Types): String {
+ return "$simpleName${asType().descriptor(types)}"
+}
+
+/**
+ * Returns the JVM signature in the form "$Name:$FieldDescriptor", for example: `"value:Ljava/lang/String;"`.
+ *
+ * Useful for comparing with [JvmFieldSignature].
+ *
+ * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+ */
+internal fun VariableElement.jvmFieldSignature(types: Types): String {
+ return "$simpleName:${asType().descriptor(types)}"
+}
+
+/**
+ * When applied over a type, it returns either:
+ * - a "field descriptor", for example: `Ljava/lang/Object;`
+ * - a "method descriptor", for example: `(Ljava/lang/Object;)Z`
+ *
+ * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+ */
+internal object JvmDescriptorTypeVisitor : AbstractTypeVisitor6<String, Types>() {
+ override fun visitNoType(t: NoType, types: Types): String = t.descriptor
+ override fun visitDeclared(t: DeclaredType, types: Types): String = t.descriptor
+ override fun visitPrimitive(t: PrimitiveType, types: Types): String = t.descriptor
+
+ override fun visitArray(t: ArrayType, types: Types): String = t.descriptor(types)
+ override fun visitWildcard(t: WildcardType, types: Types): String = t.descriptor(types)
+ override fun visitExecutable(t: ExecutableType, types: Types): String = t.descriptor(types)
+ override fun visitTypeVariable(t: TypeVariable, types: Types): String = t.descriptor(types)
+
+ override fun visitNull(t: NullType, types: Types): String = visitUnknown(
+ t,
+ types,
+ )
+ override fun visitError(t: ErrorType, types: Types): String = visitUnknown(
+ t,
+ types,
+ )
+
+ override fun visitUnknown(t: TypeMirror, types: Types): String = error("Unsupported type $t")
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/Optional.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/Optional.kt
new file mode 100644
index 00000000..41935e36
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/Optional.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.classinspectors
+
+/**
+ * Simple `Optional` implementation for use in collections that don't allow `null` values.
+ */
+internal data class Optional<out T : Any>(val nullableValue: T?)
+
+internal fun <T : Any> T?.toOptional(): Optional<T> = Optional(
+ this,
+)
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ReflectiveClassInspector.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ReflectiveClassInspector.kt
new file mode 100644
index 00000000..fc31ba09
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ReflectiveClassInspector.kt
@@ -0,0 +1,604 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.classinspectors
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.asTypeName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.JAVA_DEPRECATED
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.filterOutNullabilityAnnotations
+import com.squareup.kotlinpoet.metadata.hasAnnotations
+import com.squareup.kotlinpoet.metadata.hasConstant
+import com.squareup.kotlinpoet.metadata.isAnnotation
+import com.squareup.kotlinpoet.metadata.isCompanionObject
+import com.squareup.kotlinpoet.metadata.isConst
+import com.squareup.kotlinpoet.metadata.isDeclaration
+import com.squareup.kotlinpoet.metadata.isSynthesized
+import com.squareup.kotlinpoet.metadata.isValue
+import com.squareup.kotlinpoet.metadata.readKotlinClassMetadata
+import com.squareup.kotlinpoet.metadata.specs.ClassData
+import com.squareup.kotlinpoet.metadata.specs.ClassInspector
+import com.squareup.kotlinpoet.metadata.specs.ConstructorData
+import com.squareup.kotlinpoet.metadata.specs.ContainerData
+import com.squareup.kotlinpoet.metadata.specs.EnumEntryData
+import com.squareup.kotlinpoet.metadata.specs.FieldData
+import com.squareup.kotlinpoet.metadata.specs.FileData
+import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier
+import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.TRANSIENT
+import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.VOLATILE
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.DEFAULT
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.STATIC
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.SYNCHRONIZED
+import com.squareup.kotlinpoet.metadata.specs.KM_CONSTRUCTOR_COMPARATOR
+import com.squareup.kotlinpoet.metadata.specs.KM_FUNCTION_COMPARATOR
+import com.squareup.kotlinpoet.metadata.specs.KM_PROPERTY_COMPARATOR
+import com.squareup.kotlinpoet.metadata.specs.MethodData
+import com.squareup.kotlinpoet.metadata.specs.PropertyData
+import com.squareup.kotlinpoet.metadata.toKmClass
+import java.lang.reflect.Constructor
+import java.lang.reflect.Field
+import java.lang.reflect.Method
+import java.lang.reflect.Modifier
+import java.lang.reflect.Parameter
+import java.util.TreeMap
+import java.util.concurrent.ConcurrentHashMap
+import kotlin.LazyThreadSafetyMode.NONE
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmDeclarationContainer
+import kotlinx.metadata.KmPackage
+import kotlinx.metadata.jvm.JvmFieldSignature
+import kotlinx.metadata.jvm.JvmMethodSignature
+import kotlinx.metadata.jvm.KotlinClassMetadata
+import kotlinx.metadata.jvm.fieldSignature
+import kotlinx.metadata.jvm.getterSignature
+import kotlinx.metadata.jvm.setterSignature
+import kotlinx.metadata.jvm.signature
+import kotlinx.metadata.jvm.syntheticMethodForAnnotations
+
+@KotlinPoetMetadataPreview
+public class ReflectiveClassInspector private constructor(
+ private val classLoader: ClassLoader?,
+) : ClassInspector {
+
+ private val classCache = ConcurrentHashMap<ClassName, Optional<Class<*>>>()
+ private val methodCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Method>>()
+ private val constructorCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Constructor<*>>>()
+ private val fieldCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Field>>()
+ private val enumCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Enum<*>>>()
+
+ private fun lookupClass(className: ClassName): Class<*>? {
+ return classCache.getOrPut(className) {
+ try {
+ if (classLoader == null) {
+ Class.forName(className.reflectionName())
+ } else {
+ Class.forName(className.reflectionName(), true, classLoader)
+ }
+ } catch (e: ClassNotFoundException) {
+ null
+ }.toOptional()
+ }.nullableValue
+ }
+
+ override val supportsNonRuntimeRetainedAnnotations: Boolean = false
+
+ override fun declarationContainerFor(className: ClassName): KmDeclarationContainer {
+ val clazz = lookupClass(className)
+ ?: error("No type element found for: $className.")
+
+ val metadata = clazz.getAnnotation(Metadata::class.java)
+ return when (val kotlinClassMetadata = metadata.readKotlinClassMetadata()) {
+ is KotlinClassMetadata.Class -> kotlinClassMetadata.toKmClass()
+ is KotlinClassMetadata.FileFacade -> kotlinClassMetadata.toKmPackage()
+ else -> TODO("Not implemented yet: ${kotlinClassMetadata.javaClass.simpleName}")
+ }
+ }
+
+ override fun isInterface(className: ClassName): Boolean {
+ if (className in ClassInspectorUtil.KOTLIN_INTRINSIC_INTERFACES) {
+ return true
+ }
+ return lookupClass(className)?.isInterface ?: false
+ }
+
+ private fun Class<*>.lookupField(fieldSignature: JvmFieldSignature): Field? {
+ return try {
+ val signatureString = fieldSignature.asString()
+ fieldCache.getOrPut(this to signatureString) {
+ declaredFields
+ .asSequence()
+ .onEach { it.isAccessible = true }
+ .find { signatureString == it.jvmFieldSignature }.toOptional()
+ }.nullableValue
+ } catch (e: ClassNotFoundException) {
+ null
+ }
+ }
+
+ private fun Class<*>.lookupMethod(
+ methodSignature: JvmMethodSignature,
+ ): Method? {
+ val signatureString = methodSignature.asString()
+ return methodCache.getOrPut(this to signatureString) {
+ declaredMethods
+ .asSequence()
+ .onEach { it.isAccessible = true }
+ .find { signatureString == it.jvmMethodSignature }.toOptional()
+ }.nullableValue
+ }
+
+ private fun Class<*>.lookupConstructor(
+ constructorSignature: JvmMethodSignature,
+ ): Constructor<*>? {
+ val signatureString = constructorSignature.asString()
+ return constructorCache.getOrPut(this to signatureString) {
+ declaredConstructors
+ .asSequence()
+ .onEach { it.isAccessible = true }
+ .find { signatureString == it.jvmMethodSignature }.toOptional()
+ }.nullableValue
+ }
+
+ private fun Field.jvmModifiers(): Set<JvmFieldModifier> {
+ return mutableSetOf<JvmFieldModifier>().apply {
+ if (Modifier.isTransient(modifiers)) {
+ add(TRANSIENT)
+ }
+ if (Modifier.isVolatile(modifiers)) {
+ add(VOLATILE)
+ }
+ if (Modifier.isStatic(modifiers)) {
+ add(JvmFieldModifier.STATIC)
+ }
+ }
+ }
+
+ private fun Field.annotationSpecs(): List<AnnotationSpec> {
+ return filterOutNullabilityAnnotations(
+ declaredAnnotations.orEmpty().map { AnnotationSpec.get(it, includeDefaultValues = true) },
+ )
+ }
+
+ private fun Constructor<*>.annotationSpecs(): List<AnnotationSpec> {
+ return filterOutNullabilityAnnotations(
+ declaredAnnotations.orEmpty().map { AnnotationSpec.get(it, true) },
+ )
+ }
+
+ private fun Method.jvmModifiers(): Set<JvmMethodModifier> {
+ return methodJvmModifiers(modifiers, isDefault)
+ }
+
+ private fun Constructor<*>.jvmModifiers(): Set<JvmMethodModifier> {
+ return methodJvmModifiers(modifiers, false)
+ }
+
+ private fun methodJvmModifiers(modifiers: Int, isDefault: Boolean): Set<JvmMethodModifier> {
+ val jvmMethodModifiers = mutableSetOf<JvmMethodModifier>()
+ if (Modifier.isSynchronized(modifiers)) {
+ jvmMethodModifiers += SYNCHRONIZED
+ }
+ if (Modifier.isStatic(modifiers)) {
+ jvmMethodModifiers += STATIC
+ }
+ if (isDefault) {
+ jvmMethodModifiers += DEFAULT
+ }
+ return jvmMethodModifiers
+ }
+
+ private fun Method.annotationSpecs(): List<AnnotationSpec> {
+ return filterOutNullabilityAnnotations(
+ declaredAnnotations.orEmpty().map { AnnotationSpec.get(it, includeDefaultValues = true) },
+ )
+ }
+
+ private fun Parameter.annotationSpecs(): List<AnnotationSpec> {
+ return filterOutNullabilityAnnotations(
+ declaredAnnotations.map { AnnotationSpec.get(it, includeDefaultValues = true) },
+ )
+ }
+
+ private fun Method.exceptionTypeNames(): List<TypeName> {
+ return exceptionTypes.orEmpty().mapTo(mutableListOf()) { it.asTypeName() }
+ }
+
+ private fun Constructor<*>.exceptionTypeNames(): List<TypeName> {
+ return exceptionTypes.orEmpty().mapTo(mutableListOf()) { it.asTypeName() }
+ }
+
+ override fun enumEntry(enumClassName: ClassName, memberName: String): EnumEntryData {
+ val clazz = lookupClass(enumClassName)
+ ?: error("No class found for: $enumClassName.")
+ check(clazz.isEnum) {
+ "Class must be an enum but isn't: $clazz"
+ }
+ val enumEntry = enumCache.getOrPut(clazz to memberName) {
+ clazz.enumConstants
+ .asSequence()
+ .map { it as Enum<*> }
+ .find { it.name == memberName }
+ .toOptional()
+ }.nullableValue
+ checkNotNull(enumEntry) {
+ "Could not find $memberName on $enumClassName"
+ }
+ return EnumEntryData(
+ declarationContainer = if (enumEntry.javaClass == clazz) {
+ // For simple enums with no class bodies, the entry class will be the same as the original
+ // class.
+ null
+ } else {
+ enumEntry.javaClass.getAnnotation(Metadata::class.java)?.toKmClass()
+ },
+ annotations = clazz.getField(enumEntry.name).annotationSpecs(),
+ )
+ }
+
+ private fun Field.constantValue(): CodeBlock? {
+ if (!Modifier.isStatic(modifiers)) {
+ return null
+ }
+ return get(null) // Constant means we can do a static get on it.
+ .let(ClassInspectorUtil::codeLiteralOf)
+ }
+
+ private fun JvmMethodSignature.isOverriddenIn(clazz: Class<*>): Boolean {
+ val signatureString = asString()
+ val classPackage = clazz.`package`.name
+ val interfaceMethods = clazz.interfaces.asSequence()
+ .flatMap { it.methods.asSequence() }
+ val superClassMethods = clazz.superclass?.methods.orEmpty().asSequence()
+ return interfaceMethods.plus(superClassMethods)
+ .filterNot { Modifier.isFinal(it.modifiers) }
+ .filterNot { Modifier.isStatic(it.modifiers) }
+ .filterNot { Modifier.isPrivate(it.modifiers) }
+ .filter {
+ Modifier.isPublic(it.modifiers) ||
+ Modifier.isProtected(it.modifiers) ||
+ // Package private
+ it.declaringClass.`package`.name == classPackage
+ }
+ .map { it.jvmMethodSignature }
+ .any { it == signatureString }
+ }
+
+ override fun methodExists(className: ClassName, methodSignature: JvmMethodSignature): Boolean {
+ return lookupClass(className)?.lookupMethod(methodSignature) != null
+ }
+
+ override fun containerData(
+ declarationContainer: KmDeclarationContainer,
+ className: ClassName,
+ parentClassName: ClassName?,
+ ): ContainerData {
+ val targetClass = lookupClass(className) ?: error("No class found for: $className.")
+ val isCompanionObject: Boolean = when (declarationContainer) {
+ is KmClass -> {
+ declarationContainer.isCompanionObject
+ }
+ is KmPackage -> {
+ false
+ }
+ else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}")
+ }
+
+ // Should only be called if parentName has been null-checked
+ val classIfCompanion by lazy(NONE) {
+ if (isCompanionObject && parentClassName != null) {
+ lookupClass(parentClassName)
+ ?: error("No class found for: $parentClassName.")
+ } else {
+ targetClass
+ }
+ }
+
+ val propertyData = declarationContainer.properties
+ .asSequence()
+ .filter { it.isDeclaration }
+ .filterNot { it.isSynthesized }
+ .associateWithTo(TreeMap(KM_PROPERTY_COMPARATOR)) { property ->
+ val isJvmField = ClassInspectorUtil.computeIsJvmField(
+ property = property,
+ classInspector = this,
+ isCompanionObject = isCompanionObject,
+ hasGetter = property.getterSignature != null,
+ hasSetter = property.setterSignature != null,
+ hasField = property.fieldSignature != null,
+ )
+
+ val fieldData = property.fieldSignature?.let { fieldSignature ->
+ // Check the field in the parent first. For const/static/jvmField elements, these only
+ // exist in the parent and we want to check that if necessary to avoid looking up a
+ // non-existent field in the companion.
+ val parentModifiers = if (isCompanionObject && parentClassName != null) {
+ classIfCompanion.lookupField(fieldSignature)?.jvmModifiers().orEmpty()
+ } else {
+ emptySet()
+ }
+
+ val isStatic = JvmFieldModifier.STATIC in parentModifiers
+
+ // TODO we looked up field once, let's reuse it
+ val classForOriginalField = targetClass.takeUnless {
+ isCompanionObject &&
+ (property.isConst || isJvmField || isStatic)
+ } ?: classIfCompanion
+
+ val field = classForOriginalField.lookupField(fieldSignature)
+ ?: error("No field $fieldSignature found in $classForOriginalField.")
+ val constant = if (property.hasConstant) {
+ val fieldWithConstant = classIfCompanion.takeIf { it != targetClass }?.let {
+ if (it.isInterface) {
+ field
+ } else {
+ // const properties are relocated to the enclosing class
+ it.lookupField(fieldSignature)
+ ?: error("No field $fieldSignature found in $it.")
+ }
+ } ?: field
+ fieldWithConstant.constantValue()
+ } else {
+ null
+ }
+
+ val jvmModifiers = field.jvmModifiers() + parentModifiers
+
+ // For static, const, or JvmField fields in a companion object, the companion
+ // object's field is marked as synthetic to hide it from Java, but in this case
+ // it's a false positive for this check in kotlin.
+ val isSynthetic = field.isSynthetic &&
+ !(
+ isCompanionObject &&
+ (property.isConst || isJvmField || JvmFieldModifier.STATIC in jvmModifiers)
+ )
+
+ FieldData(
+ annotations = field.annotationSpecs(),
+ isSynthetic = isSynthetic,
+ jvmModifiers = jvmModifiers.filterNotTo(mutableSetOf()) {
+ // JvmField companion objects don't need JvmStatic, it's implicit
+ isCompanionObject && isJvmField && it == JvmFieldModifier.STATIC
+ },
+ constant = constant,
+ )
+ }
+
+ val getterData = property.getterSignature?.let { getterSignature ->
+ val method = classIfCompanion.lookupMethod(getterSignature)
+ method?.methodData(
+ clazz = targetClass,
+ signature = getterSignature,
+ hasAnnotations = property.getterFlags.hasAnnotations,
+ jvmInformationMethod = classIfCompanion.takeIf { it != targetClass }
+ ?.lookupMethod(getterSignature) ?: method,
+ )
+ ?: error("No getter method $getterSignature found in $classIfCompanion.")
+ }
+
+ val setterData = property.setterSignature?.let { setterSignature ->
+ val method = classIfCompanion.lookupMethod(setterSignature)
+ method?.methodData(
+ clazz = targetClass,
+ signature = setterSignature,
+ hasAnnotations = property.setterFlags.hasAnnotations,
+ jvmInformationMethod = classIfCompanion.takeIf { it != targetClass }
+ ?.lookupMethod(setterSignature) ?: method,
+ knownIsOverride = getterData?.isOverride,
+ )
+ ?: error("No setter method $setterSignature found in $classIfCompanion.")
+ }
+
+ val annotations = mutableListOf<AnnotationSpec>()
+ if (property.flags.hasAnnotations) {
+ property.syntheticMethodForAnnotations?.let { annotationsHolderSignature ->
+ targetClass.lookupMethod(annotationsHolderSignature)?.let { method ->
+ annotations += method.annotationSpecs()
+ // Cover for https://github.com/square/kotlinpoet/issues/1046
+ .filterNot { it.typeName == JAVA_DEPRECATED }
+ }
+ }
+ }
+
+ PropertyData(
+ annotations = annotations,
+ fieldData = fieldData,
+ getterData = getterData,
+ setterData = setterData,
+ isJvmField = isJvmField,
+ )
+ }
+
+ val methodData = declarationContainer.functions
+ .associateWithTo(TreeMap(KM_FUNCTION_COMPARATOR)) { kmFunction ->
+ val signature = kmFunction.signature
+ if (signature != null) {
+ val method = targetClass.lookupMethod(signature)
+ method?.methodData(
+ clazz = targetClass,
+ signature = signature,
+ hasAnnotations = kmFunction.flags.hasAnnotations,
+ jvmInformationMethod = classIfCompanion.takeIf { it != targetClass }?.lookupMethod(signature)
+ ?: method,
+ )
+ ?: error("No method $signature found in $targetClass.")
+ } else {
+ MethodData.EMPTY
+ }
+ }
+
+ when (declarationContainer) {
+ is KmClass -> {
+ val classAnnotations = if (declarationContainer.flags.hasAnnotations) {
+ ClassInspectorUtil.createAnnotations {
+ addAll(targetClass.annotations.map { AnnotationSpec.get(it, includeDefaultValues = true) })
+ }
+ } else {
+ emptyList()
+ }
+ val constructorData = declarationContainer.constructors
+ .associateWithTo(TreeMap(KM_CONSTRUCTOR_COMPARATOR)) { kmConstructor ->
+ if (declarationContainer.isAnnotation || declarationContainer.isValue) {
+ //
+ // Annotations are interfaces in reflection, but kotlin metadata will still report a
+ // constructor signature
+ //
+ // Inline classes have no constructors at runtime
+ //
+ return@associateWithTo ConstructorData.EMPTY
+ }
+ val signature = kmConstructor.signature
+ if (signature != null) {
+ val constructor = targetClass.lookupConstructor(signature)
+ ?: error("No constructor $signature found in $targetClass.")
+ ConstructorData(
+ annotations = if (kmConstructor.flags.hasAnnotations) {
+ constructor.annotationSpecs()
+ } else {
+ emptyList()
+ },
+ parameterAnnotations = constructor.parameters.indexedAnnotationSpecs(),
+ isSynthetic = constructor.isSynthetic,
+ jvmModifiers = constructor.jvmModifiers(),
+ exceptions = constructor.exceptionTypeNames(),
+ )
+ } else {
+ ConstructorData.EMPTY
+ }
+ }
+ return ClassData(
+ declarationContainer = declarationContainer,
+ className = className,
+ annotations = classAnnotations,
+ properties = propertyData,
+ constructors = constructorData,
+ methods = methodData,
+ )
+ }
+ is KmPackage -> {
+ // There's no flag for checking if there are annotations, so we just eagerly check in this
+ // case. All annotations on this class are file: site targets in source. This does not
+ // include @JvmName since it does not have RUNTIME retention. In practice this doesn't
+ // really matter, but it does mean we can't know for certain if the file should be called
+ // FooKt.kt or Foo.kt.
+ val fileAnnotations = ClassInspectorUtil.createAnnotations(FILE) {
+ addAll(targetClass.annotations.map { AnnotationSpec.get(it, includeDefaultValues = true) })
+ }
+ return FileData(
+ declarationContainer = declarationContainer,
+ annotations = fileAnnotations,
+ properties = propertyData,
+ methods = methodData,
+ className = className,
+ )
+ }
+ else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}")
+ }
+ }
+
+ private fun Array<Parameter>.indexedAnnotationSpecs(): Map<Int, Collection<AnnotationSpec>> {
+ return withIndex().associate { (index, parameter) ->
+ index to ClassInspectorUtil.createAnnotations { addAll(parameter.annotationSpecs()) }
+ }
+ }
+
+ private fun Method.methodData(
+ clazz: Class<*>,
+ signature: JvmMethodSignature,
+ hasAnnotations: Boolean,
+ jvmInformationMethod: Method = this,
+ knownIsOverride: Boolean? = null,
+ ): MethodData {
+ return MethodData(
+ annotations = if (hasAnnotations) annotationSpecs() else emptyList(),
+ parameterAnnotations = parameters.indexedAnnotationSpecs(),
+ isSynthetic = isSynthetic,
+ jvmModifiers = jvmInformationMethod.jvmModifiers(),
+ isOverride = knownIsOverride ?: signature.isOverriddenIn(clazz),
+ exceptions = exceptionTypeNames(),
+ )
+ }
+
+ public companion object {
+ @JvmStatic
+ @KotlinPoetMetadataPreview
+ public fun create(classLoader: ClassLoader? = null): ClassInspector {
+ return ReflectiveClassInspector(classLoader)
+ }
+
+ private val Class<*>.descriptor: String
+ get() {
+ return when {
+ isPrimitive -> when (kotlin) {
+ Byte::class -> "B"
+ Char::class -> "C"
+ Double::class -> "D"
+ Float::class -> "F"
+ Int::class -> "I"
+ Long::class -> "J"
+ Short::class -> "S"
+ Boolean::class -> "Z"
+ Void::class -> "V"
+ else -> throw RuntimeException("Unrecognized primitive $this")
+ }
+ isArray -> name.replace('.', '/')
+ else -> "L$name;".replace('.', '/')
+ }
+ }
+
+ private val Method.descriptor: String
+ get() = parameterTypes.joinToString(
+ separator = "",
+ prefix = "(",
+ postfix = ")${returnType.descriptor}",
+ ) { it.descriptor }
+
+ /**
+ * Returns the JVM signature in the form "$Name$MethodDescriptor", for example: `equals(Ljava/lang/Object;)Z`.
+ *
+ * Useful for comparing with [JvmMethodSignature].
+ *
+ * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+ */
+ private val Method.jvmMethodSignature: String get() = "$name$descriptor"
+
+ private val Constructor<*>.descriptor: String
+ get() = parameterTypes.joinToString(separator = "", prefix = "(", postfix = ")V") { it.descriptor }
+
+ /**
+ * Returns the JVM signature in the form "<init>$MethodDescriptor", for example: `"<init>(Ljava/lang/Object;)V")`.
+ *
+ * Useful for comparing with [JvmMethodSignature].
+ *
+ * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+ */
+ private val Constructor<*>.jvmMethodSignature: String get() = "<init>$descriptor"
+
+ /**
+ * Returns the JVM signature in the form "$Name:$FieldDescriptor", for example: `"value:Ljava/lang/String;"`.
+ *
+ * Useful for comparing with [JvmFieldSignature].
+ *
+ * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+ */
+ private val Field.jvmFieldSignature: String get() = "$name:${type.descriptor}"
+ }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ClassInspector.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ClassInspector.kt
new file mode 100644
index 00000000..14c019fd
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ClassInspector.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmDeclarationContainer
+import kotlinx.metadata.jvm.JvmMethodSignature
+
+/** A basic interface for looking up JVM information about a given Class. */
+@KotlinPoetMetadataPreview
+public interface ClassInspector {
+
+ /**
+ * Indicates if this [ClassInspector] supports [AnnotationRetention.RUNTIME]-retained annotations.
+ * This is used to indicate if manual inference of certain non-RUNTIME-retained annotations should
+ * be done, such as [JvmName].
+ */
+ public val supportsNonRuntimeRetainedAnnotations: Boolean
+
+ /**
+ * Creates a new [ContainerData] instance for a given [declarationContainer].
+ *
+ * @param declarationContainer the source [KmDeclarationContainer] to read from.
+ * @param className the [ClassName] of the target class to to read from.
+ * @param parentClassName the parent [ClassName] name if [declarationContainer] is nested, inner,
+ * or is a companion object.
+ */
+ public fun containerData(
+ declarationContainer: KmDeclarationContainer,
+ className: ClassName,
+ parentClassName: ClassName?,
+ ): ContainerData
+
+ /**
+ * Looks up other declaration containers, such as for nested members. Note that this class would
+ * always be Kotlin, so Metadata can be relied on for this.
+ *
+ * @param className The [ClassName] representation of the class.
+ * @return the read [KmDeclarationContainer] from its metadata. If no class or facade
+ * file was found, this should throw an exception.
+ */
+ public fun declarationContainerFor(className: ClassName): KmDeclarationContainer
+
+ /**
+ * Looks up a class and returns whether or not it is an interface. Note that this class can be
+ * Java or Kotlin, so Metadata should not be relied on for this.
+ *
+ * @param className The [ClassName] representation of the class.
+ * @return whether or not it is an interface.
+ */
+ public fun isInterface(className: ClassName): Boolean
+
+ /**
+ * Looks up the enum entry on a given enum given its member name.
+ *
+ * @param enumClassName The [ClassName] representation of the enum class.
+ * @param memberName The simple member name.
+ * @return the [EnumEntryData]
+ */
+ public fun enumEntry(enumClassName: ClassName, memberName: String): EnumEntryData
+
+ /**
+ * Looks up if a given [methodSignature] within [className] exists.
+ *
+ * @param className The [ClassName] representation of the class.
+ * @param methodSignature The method signature to check.
+ * @return whether or not the method exists.
+ */
+ public fun methodExists(className: ClassName, methodSignature: JvmMethodSignature): Boolean
+}
+
+/**
+ * Creates a new [ContainerData] instance for a given [className].
+ *
+ * @param className the [ClassName] of the target class to to read from.
+ * @param parentClassName the parent [ClassName] name if [className] is nested, inner, or is a
+ * companion object.
+ */
+@KotlinPoetMetadataPreview
+public fun ClassInspector.containerData(
+ className: ClassName,
+ parentClassName: ClassName?,
+): ContainerData {
+ return containerData(declarationContainerFor(className), className, parentClassName)
+}
+
+/**
+ * Looks up other classes, such as for nested members. Note that this class would always be
+ * Kotlin, so Metadata can be relied on for this.
+ *
+ * @param className The [ClassName] representation of the class.
+ * @return the read [KmClass] from its metadata. If no class was found, this should throw
+ * an exception.
+ */
+@KotlinPoetMetadataPreview
+public fun ClassInspector.classFor(className: ClassName): KmClass {
+ val container = declarationContainerFor(className)
+ check(container is KmClass) {
+ "Container is not a class! Was ${container.javaClass.simpleName}"
+ }
+ return container
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ConstructorData.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ConstructorData.kt
new file mode 100644
index 00000000..01c98390
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ConstructorData.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+
+/**
+ * Represents relevant information on a constructor used for [ClassInspector]. Should only be
+ * associated with constructors of a [ClassData].
+ *
+ * @param annotations declared annotations on this constructor.
+ * @property parameterAnnotations a mapping of parameter indices to annotations on them.
+ * @property isSynthetic indicates if this constructor is synthetic or not.
+ * @property jvmModifiers set of [JvmMethodModifiers][JvmMethodModifier] on this constructor.
+ * @property exceptions list of exceptions thrown by this constructor.
+ */
+@KotlinPoetMetadataPreview
+public data class ConstructorData(
+ private val annotations: List<AnnotationSpec>,
+ val parameterAnnotations: Map<Int, Collection<AnnotationSpec>>,
+ val isSynthetic: Boolean,
+ val jvmModifiers: Set<JvmMethodModifier>,
+ val exceptions: List<TypeName>,
+) {
+
+ /**
+ * A collection of all annotations on this constructor, including any derived from [jvmModifiers],
+ * [isSynthetic], and [exceptions].
+ */
+ val allAnnotations: Collection<AnnotationSpec> = ClassInspectorUtil.createAnnotations {
+ addAll(annotations)
+ if (isSynthetic) {
+ add(ClassInspectorUtil.JVM_SYNTHETIC_SPEC)
+ }
+ addAll(jvmModifiers.mapNotNull { it.annotationSpec() })
+ exceptions.takeIf { it.isNotEmpty() }
+ ?.let {
+ add(ClassInspectorUtil.createThrowsSpec(it))
+ }
+ }
+
+ public companion object {
+ public val EMPTY: ConstructorData = ConstructorData(
+ annotations = emptyList(),
+ parameterAnnotations = emptyMap(),
+ isSynthetic = false,
+ jvmModifiers = emptySet(),
+ exceptions = emptyList(),
+ )
+ }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ContainerData.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ContainerData.kt
new file mode 100644
index 00000000..f1006d01
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ContainerData.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmConstructor
+import kotlinx.metadata.KmDeclarationContainer
+import kotlinx.metadata.KmFunction
+import kotlinx.metadata.KmPackage
+import kotlinx.metadata.KmProperty
+
+/**
+ * Represents relevant information on a declaration container used for [ClassInspector]. Can only
+ * ever be applied on a Kotlin type (i.e. is annotated with [Metadata]).
+ *
+ * @property declarationContainer the [KmDeclarationContainer] as parsed from the class's
+ * [@Metadata][Metadata] annotation.
+ * @property annotations declared annotations on this class.
+ * @property properties the mapping of [declarationContainer]'s properties to parsed [PropertyData].
+ * @property methods the mapping of [declarationContainer]'s methods to parsed [MethodData].
+ */
+@KotlinPoetMetadataPreview
+public interface ContainerData {
+ public val declarationContainer: KmDeclarationContainer
+ public val annotations: Collection<AnnotationSpec>
+ public val properties: Map<KmProperty, PropertyData>
+ public val methods: Map<KmFunction, MethodData>
+}
+
+/**
+ * Represents relevant information on a Kotlin class used for [ClassInspector]. Can only ever be
+ * applied on a class and not file facades.
+ *
+ * @property declarationContainer the [KmClass] as parsed from the class's
+ * [@Metadata][Metadata] annotation.
+ * @property className the KotlinPoet [ClassName] of the class.
+ * @property constructors the mapping of [declarationContainer]'s constructors to parsed
+ * [ConstructorData].
+ */
+@KotlinPoetMetadataPreview
+public data class ClassData(
+ override val declarationContainer: KmClass,
+ val className: ClassName,
+ override val annotations: Collection<AnnotationSpec>,
+ override val properties: Map<KmProperty, PropertyData>,
+ val constructors: Map<KmConstructor, ConstructorData>,
+ override val methods: Map<KmFunction, MethodData>,
+) : ContainerData
+
+/**
+ * Represents relevant information on a file facade used for [ClassInspector].
+ *
+ * @property declarationContainer the [KmClass] as parsed from the class's
+ * [@Metadata][Metadata] annotation.
+ * @property className the KotlinPoet [ClassName] of the underlying facade class in JVM.
+ * @property jvmName the `@JvmName` of the class or null if it does not have a custom name.
+ * Default will try to infer from the [className].
+ */
+@KotlinPoetMetadataPreview
+public data class FileData(
+ override val declarationContainer: KmPackage,
+ override val annotations: Collection<AnnotationSpec>,
+ override val properties: Map<KmProperty, PropertyData>,
+ override val methods: Map<KmFunction, MethodData>,
+ val className: ClassName,
+ val jvmName: String? =
+ if (!className.simpleName.endsWith("Kt")) className.simpleName else null,
+) : ContainerData {
+
+ /**
+ * The file name of the container, defaults to [className]'s simple name + "Kt". If a [jvmName] is
+ * specified, it will always defer to that.
+ */
+ val fileName: String = jvmName ?: className.simpleName.removeSuffix("Kt")
+}
+
+/**
+ * Represents relevant information on a Kotlin enum entry.
+ *
+ * @property declarationContainer the [KmClass] as parsed from the entry's
+ * [@Metadata][Metadata] annotation.
+ * @property annotations the annotations for the entry
+ */
+@KotlinPoetMetadataPreview
+public data class EnumEntryData(
+ val declarationContainer: KmClass?,
+ val annotations: Collection<AnnotationSpec>,
+)
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/FieldData.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/FieldData.kt
new file mode 100644
index 00000000..a000ddef
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/FieldData.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FIELD
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+
+/**
+ * Represents relevant information on a field used for [ClassInspector]. Should only be
+ * associated with a [PropertyData].
+ *
+ * @param annotations declared annotations on this field.
+ * @property isSynthetic indicates if this field is synthetic or not.
+ * @property jvmModifiers set of [JvmMethodModifiers][JvmMethodModifier] on this field.
+ * @property constant the constant value of this field, if available. Note that this is does not
+ * strictly imply that the associated property is `const`.
+ */
+@KotlinPoetMetadataPreview
+public data class FieldData(
+ private val annotations: List<AnnotationSpec>,
+ val isSynthetic: Boolean,
+ val jvmModifiers: Set<JvmFieldModifier>,
+ val constant: CodeBlock?,
+) {
+
+ /**
+ * A collection of all annotations on this method, including any derived from [jvmModifiers]
+ * and [isSynthetic].
+ */
+ val allAnnotations: Collection<AnnotationSpec> = ClassInspectorUtil.createAnnotations(
+ FIELD,
+ ) {
+ addAll(annotations)
+ if (isSynthetic) {
+ add(ClassInspectorUtil.JVM_SYNTHETIC_SPEC)
+ }
+ addAll(jvmModifiers.mapNotNull(JvmFieldModifier::annotationSpec))
+ }
+
+ public companion object {
+ public val SYNTHETIC: FieldData = FieldData(
+ annotations = emptyList(),
+ isSynthetic = true,
+ jvmModifiers = emptySet(),
+ constant = null,
+ )
+ }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmFieldModifier.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmFieldModifier.kt
new file mode 100644
index 00000000..dc2a55bb
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmFieldModifier.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+
+/** Modifiers that are annotations in Kotlin but modifier keywords in bytecode. */
+@KotlinPoetMetadataPreview
+public enum class JvmFieldModifier : JvmModifier {
+ STATIC {
+ override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
+ JvmStatic::class.asClassName(),
+ ).build()
+ },
+ TRANSIENT {
+ override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
+ Transient::class.asClassName(),
+ ).build()
+ },
+ VOLATILE {
+ override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
+ Volatile::class.asClassName(),
+ ).build()
+ },
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmMethodModifier.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmMethodModifier.kt
new file mode 100644
index 00000000..5d63738b
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmMethodModifier.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+
+/** Modifiers that are annotations or implicit in Kotlin but modifier keywords in bytecode. */
+@KotlinPoetMetadataPreview
+public enum class JvmMethodModifier : JvmModifier {
+ STATIC {
+ override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
+ JvmStatic::class.asClassName(),
+ ).build()
+ },
+ SYNCHRONIZED {
+ override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
+ Synchronized::class.asClassName(),
+ ).build()
+ },
+ DEFAULT,
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmModifier.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmModifier.kt
new file mode 100644
index 00000000..f09e6add
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmModifier.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+
+/**
+ * Represents a JVM modifier that is represented as an annotation in Kotlin but as a modifier in
+ * bytecode. Examples include annotations such as [@JvmStatic][JvmStatic] or
+ * [@JvmSynthetic][JvmSynthetic].
+ *
+ * This API is considered read-only and should not be implemented outside of KotlinPoet.
+ */
+@KotlinPoetMetadataPreview
+public interface JvmModifier {
+ public fun annotationSpec(): AnnotationSpec? {
+ return null
+ }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KmTypes.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KmTypes.kt
new file mode 100644
index 00000000..cd12479c
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KmTypes.kt
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.LambdaTypeName
+import com.squareup.kotlinpoet.ParameterizedTypeName
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.STAR
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.TypeVariableName
+import com.squareup.kotlinpoet.WildcardTypeName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+import com.squareup.kotlinpoet.metadata.isNullable
+import com.squareup.kotlinpoet.metadata.isPrimary
+import com.squareup.kotlinpoet.metadata.isReified
+import com.squareup.kotlinpoet.metadata.isSuspend
+import com.squareup.kotlinpoet.tags.TypeAliasTag
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmClassifier
+import kotlinx.metadata.KmClassifier.Class
+import kotlinx.metadata.KmClassifier.TypeAlias
+import kotlinx.metadata.KmClassifier.TypeParameter
+import kotlinx.metadata.KmConstructor
+import kotlinx.metadata.KmFlexibleTypeUpperBound
+import kotlinx.metadata.KmFunction
+import kotlinx.metadata.KmProperty
+import kotlinx.metadata.KmType
+import kotlinx.metadata.KmTypeParameter
+import kotlinx.metadata.KmTypeProjection
+import kotlinx.metadata.KmVariance
+import kotlinx.metadata.KmVariance.IN
+import kotlinx.metadata.KmVariance.INVARIANT
+import kotlinx.metadata.KmVariance.OUT
+import kotlinx.metadata.jvm.annotations
+import kotlinx.metadata.jvm.signature
+
+/**
+ * `true` if this is an extension type (i.e. String.() -> Unit vs (String) -> Unit).
+ *
+ * See details: https://discuss.kotlinlang.org/t/announcing-kotlinx-metadata-jvm-library-for-reading-modifying-metadata-of-kotlin-jvm-class-files/7980/27
+ */
+public val KmType.isExtensionType: Boolean get() {
+ return annotations.any { it.className == "kotlin/ExtensionFunctionType" }
+}
+
+@KotlinPoetMetadataPreview
+internal val KmClass.primaryConstructor: KmConstructor?
+ get() = constructors.find { it.isPrimary }
+
+internal fun KmVariance.toKModifier(): KModifier? {
+ return when (this) {
+ IN -> KModifier.IN
+ OUT -> KModifier.OUT
+ INVARIANT -> null
+ }
+}
+
+@KotlinPoetMetadataPreview
+internal fun KmTypeProjection.toTypeName(
+ typeParamResolver: TypeParameterResolver,
+): TypeName {
+ val typename = type?.toTypeName(typeParamResolver) ?: STAR
+ return when (variance) {
+ IN -> WildcardTypeName.consumerOf(typename)
+ OUT -> WildcardTypeName.producerOf(typename)
+ INVARIANT -> typename
+ null -> STAR
+ }
+}
+
+/**
+ * Converts a given [KmType] into a KotlinPoet representation, attempting to give a correct
+ * "source" representation. This includes converting [functions][kotlin.Function] and `suspend`
+ * types to appropriate [lambda representations][LambdaTypeName].
+ */
+@KotlinPoetMetadataPreview
+internal fun KmType.toTypeName(
+ typeParamResolver: TypeParameterResolver,
+): TypeName {
+ val argumentList = arguments.map { it.toTypeName(typeParamResolver) }
+ val type: TypeName = when (val valClassifier = classifier) {
+ is TypeParameter -> {
+ typeParamResolver[valClassifier.id]
+ }
+ is KmClassifier.Class -> {
+ flexibleTypeUpperBound?.toTypeName(typeParamResolver)?.let { return it }
+ outerType?.toTypeName(typeParamResolver)?.let { return it }
+ var finalType: TypeName = ClassInspectorUtil.createClassName(valClassifier.name)
+ if (argumentList.isNotEmpty()) {
+ val finalTypeString = finalType.toString()
+ if (finalTypeString.startsWith("kotlin.Function")) {
+ // It's a lambda type!
+ finalType = if (finalTypeString == "kotlin.FunctionN") {
+ TODO("unclear how to express this one since it has arity")
+ } else {
+ val (parameters, returnType) = if (isSuspend) {
+ // Coroutines always adds an `Any?` return type, but we kind of just want the
+ // source representation, so we trick it here and ignore the last.
+ argumentList.dropLast(2).toTypedArray() to argumentList.dropLast(1).last().let {
+ // Coroutines makes these a `Continuation<T>` of the type, so we want the parameterized type
+ check(it is ParameterizedTypeName)
+ it.typeArguments[0]
+ }
+ } else {
+ argumentList.dropLast(1).toTypedArray() to argumentList.last()
+ }
+ val lambdaType = if (isExtensionType) {
+ // Extension function type! T.(). First parameter is actually the receiver.
+ LambdaTypeName.get(
+ receiver = parameters[0],
+ parameters = parameters.drop(1).toTypedArray(),
+ returnType = returnType,
+ )
+ } else {
+ LambdaTypeName.get(
+ receiver = null,
+ parameters = parameters,
+ returnType = returnType,
+ )
+ }
+ lambdaType.copy(suspending = isSuspend)
+ }
+ } else {
+ finalType = (finalType as ClassName).parameterizedBy(argumentList)
+ }
+ }
+ finalType
+ }
+ is TypeAlias -> {
+ ClassInspectorUtil.createClassName(valClassifier.name)
+ }
+ }
+
+ val annotations = ClassInspectorUtil.createAnnotations {
+ for (annotation in annotations) {
+ add(annotation.toAnnotationSpec())
+ }
+ }.toList()
+ val finalType = type.copy(nullable = isNullable, annotations = annotations)
+ return abbreviatedType?.let {
+ // This is actually an alias! The "abbreviated type" is the alias and how it's actually
+ // represented in source. So instead - we'll return the abbreviated type but store the "real"
+ // type in tags for reference.
+ val abbreviatedTypeName = it.toTypeName(typeParamResolver)
+ abbreviatedTypeName.copy(
+ tags = mapOf(TypeAliasTag::class to TypeAliasTag(finalType)),
+ )
+ } ?: finalType
+}
+
+@KotlinPoetMetadataPreview
+internal fun KmTypeParameter.toTypeVariableName(
+ typeParamResolver: TypeParameterResolver,
+): TypeVariableName {
+ val finalVariance = variance.toKModifier()
+ val typeVariableName = TypeVariableName(
+ name = name,
+ bounds = upperBounds.map { it.toTypeName(typeParamResolver) },
+ variance = finalVariance,
+ )
+ val annotations = ClassInspectorUtil.createAnnotations {
+ for (annotation in annotations) {
+ add(annotation.toAnnotationSpec())
+ }
+ }.toList()
+ return typeVariableName.copy(
+ reified = isReified,
+ tags = mapOf(KmTypeParameter::class to this),
+ annotations = annotations,
+ )
+}
+
+@KotlinPoetMetadataPreview
+private fun KmFlexibleTypeUpperBound.toTypeName(
+ typeParamResolver: TypeParameterResolver,
+): TypeName {
+ // TODO tag typeFlexibilityId somehow?
+ return WildcardTypeName.producerOf(type.toTypeName(typeParamResolver))
+}
+
+internal interface TypeParameterResolver {
+ val parametersMap: Map<Int, TypeVariableName>
+ operator fun get(index: Int): TypeVariableName
+
+ companion object {
+ val EMPTY = object : TypeParameterResolver {
+ override val parametersMap: Map<Int, TypeVariableName> = emptyMap()
+
+ override fun get(index: Int): TypeVariableName = throw NoSuchElementException("No type parameters!")
+ }
+ }
+}
+
+@KotlinPoetMetadataPreview
+internal fun List<KmTypeParameter>.toTypeParameterResolver(
+ fallback: TypeParameterResolver? = null,
+): TypeParameterResolver {
+ val parametersMap = LinkedHashMap<Int, TypeVariableName>()
+ val typeParamResolver = { id: Int ->
+ parametersMap[id]
+ ?: fallback?.get(id)
+ ?: throw IllegalStateException("No type argument found for $id!")
+ }
+
+ val resolver = object : TypeParameterResolver {
+ override val parametersMap: Map<Int, TypeVariableName> = parametersMap
+
+ override operator fun get(index: Int): TypeVariableName = typeParamResolver(index)
+ }
+
+ // Fill the parametersMap. Need to do sequentially and allow for referencing previously defined params
+ for (typeParam in this) {
+ // Put the simple typevar in first, then it can be referenced in the full toTypeVariable()
+ // replacement later that may add bounds referencing this.
+ parametersMap[typeParam.id] = TypeVariableName(typeParam.name)
+ }
+
+ for (typeParam in this) {
+ // Now replace it with the full version.
+ parametersMap[typeParam.id] = typeParam.toTypeVariableName(resolver)
+ }
+
+ return resolver
+}
+
+internal val KM_PROPERTY_COMPARATOR = Comparator<KmProperty> { o1, o2 ->
+ // No need to check fields, getters, etc as properties must have distinct names
+ o1.name.compareTo(o2.name)
+}
+
+internal val KM_FUNCTION_COMPARATOR = Comparator<KmFunction> { o1, o2 ->
+ var result = o1.name.compareTo(o2.name)
+ if (result != 0) return@Comparator result
+
+ val signature1 = o1.signature
+ val signature2 = o2.signature
+ if (signature1 != null && signature2 != null) {
+ result = signature1.asString().compareTo(signature2.asString())
+ if (result != 0) return@Comparator result
+ }
+
+ // Fallback - calculate signature
+ val manualSignature1 = o1.computeSignature()
+ val manualSignature2 = o2.computeSignature()
+ manualSignature1.compareTo(manualSignature2)
+}
+
+internal val KM_CONSTRUCTOR_COMPARATOR = Comparator<KmConstructor> { o1, o2 ->
+ val signature1 = o1.signature
+ val signature2 = o2.signature
+ if (signature1 != null && signature2 != null) {
+ val result = signature1.asString().compareTo(signature2.asString())
+ if (result != 0) return@Comparator result
+ }
+
+ // Fallback - calculate signature
+ val manualSignature1 = o1.computeSignature()
+ val manualSignature2 = o2.computeSignature()
+ manualSignature1.compareTo(manualSignature2)
+}
+
+// Computes a simple signature string good enough for hashing
+private fun KmFunction.computeSignature(): String {
+ return "$name(${valueParameters.joinToString(",") { it.type.simpleName }})${returnType.simpleName}"
+}
+
+private fun KmConstructor.computeSignature(): String {
+ return "$<init>(${valueParameters.joinToString(",") { it.type.simpleName }})"
+}
+
+private val KmType?.simpleName: String get() {
+ if (this == null) return "void"
+ return when (val c = classifier) {
+ is Class -> c.name
+ is TypeParameter -> "Object"
+ is TypeAlias -> c.name
+ }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecs.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecs.kt
new file mode 100644
index 00000000..6c0730a6
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecs.kt
@@ -0,0 +1,948 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * 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.
+ */
+@file:JvmName("KotlinPoetMetadataSpecs")
+
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.ANY
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.FunSpec.Builder
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.KModifier.ABSTRACT
+import com.squareup.kotlinpoet.KModifier.CONST
+import com.squareup.kotlinpoet.KModifier.CROSSINLINE
+import com.squareup.kotlinpoet.KModifier.DATA
+import com.squareup.kotlinpoet.KModifier.EXPECT
+import com.squareup.kotlinpoet.KModifier.EXTERNAL
+import com.squareup.kotlinpoet.KModifier.FINAL
+import com.squareup.kotlinpoet.KModifier.INFIX
+import com.squareup.kotlinpoet.KModifier.INLINE
+import com.squareup.kotlinpoet.KModifier.INNER
+import com.squareup.kotlinpoet.KModifier.INTERNAL
+import com.squareup.kotlinpoet.KModifier.LATEINIT
+import com.squareup.kotlinpoet.KModifier.NOINLINE
+import com.squareup.kotlinpoet.KModifier.OPEN
+import com.squareup.kotlinpoet.KModifier.OPERATOR
+import com.squareup.kotlinpoet.KModifier.PRIVATE
+import com.squareup.kotlinpoet.KModifier.PROTECTED
+import com.squareup.kotlinpoet.KModifier.PUBLIC
+import com.squareup.kotlinpoet.KModifier.SEALED
+import com.squareup.kotlinpoet.KModifier.SUSPEND
+import com.squareup.kotlinpoet.KModifier.TAILREC
+import com.squareup.kotlinpoet.KModifier.VALUE
+import com.squareup.kotlinpoet.KModifier.VARARG
+import com.squareup.kotlinpoet.MemberName
+import com.squareup.kotlinpoet.ParameterSpec
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.TypeAliasSpec
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.UNIT
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.PropertyAccessorFlag
+import com.squareup.kotlinpoet.metadata.PropertyAccessorFlag.IS_EXTERNAL
+import com.squareup.kotlinpoet.metadata.PropertyAccessorFlag.IS_INLINE
+import com.squareup.kotlinpoet.metadata.PropertyAccessorFlag.IS_NOT_DEFAULT
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.createAnnotations
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.createClassName
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.toTreeSet
+import com.squareup.kotlinpoet.metadata.declaresDefaultValue
+import com.squareup.kotlinpoet.metadata.hasAnnotations
+import com.squareup.kotlinpoet.metadata.hasGetter
+import com.squareup.kotlinpoet.metadata.hasSetter
+import com.squareup.kotlinpoet.metadata.isAbstract
+import com.squareup.kotlinpoet.metadata.isAnnotation
+import com.squareup.kotlinpoet.metadata.isClass
+import com.squareup.kotlinpoet.metadata.isCompanionObject
+import com.squareup.kotlinpoet.metadata.isConst
+import com.squareup.kotlinpoet.metadata.isCrossInline
+import com.squareup.kotlinpoet.metadata.isData
+import com.squareup.kotlinpoet.metadata.isDeclaration
+import com.squareup.kotlinpoet.metadata.isDelegated
+import com.squareup.kotlinpoet.metadata.isDelegation
+import com.squareup.kotlinpoet.metadata.isEnum
+import com.squareup.kotlinpoet.metadata.isEnumEntry
+import com.squareup.kotlinpoet.metadata.isExpect
+import com.squareup.kotlinpoet.metadata.isExternal
+import com.squareup.kotlinpoet.metadata.isFinal
+import com.squareup.kotlinpoet.metadata.isFun
+import com.squareup.kotlinpoet.metadata.isInfix
+import com.squareup.kotlinpoet.metadata.isInline
+import com.squareup.kotlinpoet.metadata.isInner
+import com.squareup.kotlinpoet.metadata.isInterface
+import com.squareup.kotlinpoet.metadata.isInternal
+import com.squareup.kotlinpoet.metadata.isLateinit
+import com.squareup.kotlinpoet.metadata.isNoInline
+import com.squareup.kotlinpoet.metadata.isObject
+import com.squareup.kotlinpoet.metadata.isOpen
+import com.squareup.kotlinpoet.metadata.isOperator
+import com.squareup.kotlinpoet.metadata.isPrimary
+import com.squareup.kotlinpoet.metadata.isPrivate
+import com.squareup.kotlinpoet.metadata.isProtected
+import com.squareup.kotlinpoet.metadata.isPublic
+import com.squareup.kotlinpoet.metadata.isReified
+import com.squareup.kotlinpoet.metadata.isSealed
+import com.squareup.kotlinpoet.metadata.isSuspend
+import com.squareup.kotlinpoet.metadata.isSynthesized
+import com.squareup.kotlinpoet.metadata.isTailRec
+import com.squareup.kotlinpoet.metadata.isVal
+import com.squareup.kotlinpoet.metadata.isValue
+import com.squareup.kotlinpoet.metadata.isVar
+import com.squareup.kotlinpoet.metadata.propertyAccessorFlags
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.DEFAULT
+import com.squareup.kotlinpoet.metadata.toKmClass
+import com.squareup.kotlinpoet.tag
+import java.util.Locale
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.PackageElement
+import javax.lang.model.element.TypeElement
+import kotlin.reflect.KClass
+import kotlinx.metadata.Flags
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmClassifier
+import kotlinx.metadata.KmConstructor
+import kotlinx.metadata.KmFunction
+import kotlinx.metadata.KmPackage
+import kotlinx.metadata.KmProperty
+import kotlinx.metadata.KmType
+import kotlinx.metadata.KmTypeAlias
+import kotlinx.metadata.KmValueParameter
+import kotlinx.metadata.jvm.JvmMethodSignature
+import kotlinx.metadata.jvm.getterSignature
+import kotlinx.metadata.jvm.jvmInternalName
+import kotlinx.metadata.jvm.setterSignature
+import kotlinx.metadata.jvm.signature
+
+/** @return a [TypeSpec] ABI representation of this [KClass]. */
+@KotlinPoetMetadataPreview
+public fun KClass<*>.toTypeSpec(
+ classInspector: ClassInspector? = null,
+): TypeSpec = java.toTypeSpec(classInspector)
+
+/** @return a [TypeSpec] ABI representation of this [KClass]. */
+@KotlinPoetMetadataPreview
+public fun Class<*>.toTypeSpec(
+ classInspector: ClassInspector? = null,
+): TypeSpec = toKmClass().toTypeSpec(classInspector, asClassName())
+
+/** @return a [TypeSpec] ABI representation of this [TypeElement]. */
+@Suppress("DEPRECATION")
+@KotlinPoetMetadataPreview
+public fun TypeElement.toTypeSpec(
+ classInspector: ClassInspector? = null,
+): TypeSpec = toKmClass().toTypeSpec(classInspector, asClassName())
+
+/** @return a [FileSpec] ABI representation of this [KClass]. */
+@KotlinPoetMetadataPreview
+public fun KClass<*>.toFileSpec(
+ classInspector: ClassInspector? = null,
+): FileSpec = java.toFileSpec(classInspector)
+
+/** @return a [FileSpec] ABI representation of this [KClass]. */
+@KotlinPoetMetadataPreview
+public fun Class<*>.toFileSpec(
+ classInspector: ClassInspector? = null,
+): FileSpec = FileSpec.get(`package`.name, toTypeSpec(classInspector))
+
+/** @return a [FileSpec] ABI representation of this [TypeElement]. */
+@KotlinPoetMetadataPreview
+public fun TypeElement.toFileSpec(
+ classInspector: ClassInspector? = null,
+): FileSpec = FileSpec.get(
+ packageName = packageName,
+ typeSpec = toTypeSpec(classInspector),
+)
+
+/** @return a [TypeSpec] ABI representation of this [KmClass]. */
+@KotlinPoetMetadataPreview
+public fun KmClass.toTypeSpec(
+ classInspector: ClassInspector?,
+ className: ClassName = createClassName(name),
+): TypeSpec {
+ return toTypeSpec(classInspector, className, null)
+}
+
+/** @return a [FileSpec] ABI representation of this [KmClass]. */
+@KotlinPoetMetadataPreview
+public fun KmClass.toFileSpec(
+ classInspector: ClassInspector?,
+ className: ClassName = createClassName(name),
+): FileSpec {
+ return FileSpec.get(
+ packageName = className.packageName,
+ typeSpec = toTypeSpec(classInspector, className),
+ )
+}
+
+/** @return a [FileSpec] ABI representation of this [KmPackage]. */
+@KotlinPoetMetadataPreview
+public fun KmPackage.toFileSpec(
+ classInspector: ClassInspector?,
+ className: ClassName,
+): FileSpec {
+ val fileData = classInspector?.containerData(className, null)
+ check(fileData is FileData?) {
+ "Unexpected container data type: ${fileData?.javaClass}"
+ }
+ val fileName = fileData?.fileName ?: className.simpleName
+ return FileSpec.builder(className.packageName, fileName)
+ .apply {
+ fileData?.let { data ->
+ data.jvmName?.let { name ->
+ addAnnotation(
+ AnnotationSpec.builder(ClassInspectorUtil.JVM_NAME)
+ .addMember("name = %S", name)
+ .build(),
+ )
+ }
+ val fileAnnotations = createAnnotations(FILE) {
+ addAll(data.annotations.filterNot { it.typeName == METADATA })
+ }
+ for (fileAnnotation in fileAnnotations) {
+ addAnnotation(fileAnnotation)
+ }
+ }
+ for (function in functions) {
+ val methodData = fileData?.methods?.get(function)
+ addFunction(
+ function.toFunSpec(
+ classInspector = classInspector,
+ containerData = fileData,
+ methodData = methodData,
+ isInInterface = false,
+ ),
+ )
+ }
+ for (property in properties) {
+ val propertyData = fileData?.properties?.get(property)
+ addProperty(
+ property.toPropertySpec(
+ classInspector = classInspector,
+ containerData = fileData,
+ propertyData = propertyData,
+ isInInterface = false,
+ ),
+ )
+ }
+ for (alias in typeAliases) {
+ addTypeAlias(alias.toTypeAliasSpec())
+ }
+ }
+ .build()
+}
+
+private const val NOT_IMPLEMENTED = "throw·NotImplementedError(\"Stub!\")"
+
+@KotlinPoetMetadataPreview
+private fun KmClass.toTypeSpec(
+ classInspector: ClassInspector?,
+ className: ClassName,
+ parentClassName: ClassName?,
+): TypeSpec {
+ val classTypeParamsResolver = typeParameters.toTypeParameterResolver()
+ val jvmInternalName = name.jvmInternalName
+ val simpleName = className.simpleName
+ val classData = classInspector?.containerData(className, parentClassName)
+ check(classData is ClassData?) {
+ "Unexpected container data type: ${classData?.javaClass}"
+ }
+
+ val builder = when {
+ isAnnotation -> TypeSpec.annotationBuilder(simpleName)
+ isCompanionObject -> TypeSpec.companionObjectBuilder(companionObjectName(simpleName))
+ isEnum -> TypeSpec.enumBuilder(simpleName)
+ isExpect -> TypeSpec.expectClassBuilder(simpleName)
+ isObject -> TypeSpec.objectBuilder(simpleName)
+ isInterface -> {
+ if (classData?.declarationContainer?.isFun == true) {
+ TypeSpec.funInterfaceBuilder(simpleName)
+ } else {
+ TypeSpec.interfaceBuilder(simpleName)
+ }
+ }
+ isEnumEntry -> TypeSpec.anonymousClassBuilder()
+ else -> TypeSpec.classBuilder(simpleName)
+ }
+
+ classData?.annotations
+ ?.filterNot {
+ it.typeName == METADATA || it.typeName in JAVA_ANNOTATION_ANNOTATIONS
+ }
+ ?.let(builder::addAnnotations)
+
+ if (isEnum) {
+ enumEntries.forEach { entryName ->
+ val typeSpec = if (classInspector != null) {
+ val entry = classInspector.enumEntry(className, entryName)
+ entry.declarationContainer
+ ?.let { enumEntryClass ->
+ val entryClassName = className.nestedClass(entryName)
+ enumEntryClass.toTypeSpec(classInspector, entryClassName, parentClassName = className)
+ }
+ ?: TypeSpec.anonymousClassBuilder()
+ .addAnnotations(entry.annotations)
+ .build()
+ } else {
+ TypeSpec.anonymousClassBuilder()
+ .addKdoc(
+ "No ClassInspector was available during metadata parsing, so this entry may not be reflected accurately if it has a class body.",
+ )
+ .build()
+ }
+ builder.addEnumConstant(entryName, typeSpec)
+ }
+ }
+
+ if (!isEnumEntry) {
+ visibilityFrom(flags) { builder.addModifiers(it) }
+ builder.addModifiers(
+ *flags.modalities
+ .filterNot { it == FINAL } // Default
+ .filterNot { isInterface && it == ABSTRACT } // Abstract is a default on interfaces
+ .toTypedArray(),
+ )
+ if (isData) {
+ builder.addModifiers(DATA)
+ }
+ if (isExternal) {
+ builder.addModifiers(EXTERNAL)
+ }
+ if (isValue) {
+ builder.addModifiers(VALUE)
+ }
+ if (isInner) {
+ builder.addModifiers(INNER)
+ }
+ builder.addTypeVariables(typeParameters.map { it.toTypeVariableName(classTypeParamsResolver) })
+ // If we have an inspector, we can check exactly which "supertype" is an interface vs
+ // class. Without a handler though, we have to best-effort guess. Usually, the flow is:
+ // - First element of a non-interface type is the superclass (can be `Any`)
+ // - First element of an interface type is the first superinterface
+ val superClassFilter = classInspector?.let { handler ->
+ { type: KmType ->
+ !handler.isInterface(createClassName((type.classifier as KmClassifier.Class).name))
+ }
+ } ?: { true }
+ val superClass = supertypes.asSequence()
+ .filter { it.classifier is KmClassifier.Class }
+ .find(superClassFilter)
+ if (superClass != null && !isEnum && !isInterface && !isAnnotation) {
+ superClass.toTypeName(classTypeParamsResolver).takeIf { it != ANY }
+ ?.let(builder::superclass)
+ }
+ builder.addSuperinterfaces(
+ supertypes.asSequence()
+ .filterNot { it == superClass }
+ .map { it.toTypeName(classTypeParamsResolver) }
+ .filterNot { it == ANY }
+ .asIterable(),
+ )
+ val primaryConstructorParams = mutableMapOf<String, ParameterSpec>()
+ if (isClass || isAnnotation || isEnum) {
+ primaryConstructor?.let {
+ it.toFunSpec(classTypeParamsResolver, classData?.constructors?.get(it) ?: return@let)
+ .also { spec ->
+ val finalSpec = if (isEnum && spec.annotations.isEmpty()) {
+ // Metadata specifies the constructor as private, but that's implicit so we can omit it
+ spec.toBuilder().apply { modifiers.remove(PRIVATE) }.build()
+ } else {
+ spec
+ }
+ builder.primaryConstructor(finalSpec)
+ primaryConstructorParams.putAll(spec.parameters.associateBy { it.name })
+ }
+ }
+ constructors.filter { !it.isPrimary }.takeIf { it.isNotEmpty() }?.let { secondaryConstructors ->
+ builder.addFunctions(
+ secondaryConstructors
+ .mapNotNull { kmConstructor ->
+ classData?.constructors?.get(kmConstructor)?.let { kmConstructor to it }
+ }
+ .map { (kmConstructor, constructorData) ->
+ kmConstructor.toFunSpec(classTypeParamsResolver, constructorData)
+ },
+ )
+ }
+ }
+ builder.addProperties(
+ properties
+ .asSequence()
+ .filter { it.isDeclaration }
+ .filterNot { it.isSynthesized }
+ .map { it to classData?.properties?.get(it) }
+ .map { (property, propertyData) ->
+ property.toPropertySpec(
+ typeParamResolver = classTypeParamsResolver,
+ isConstructorParam = property.name in primaryConstructorParams,
+ classInspector = classInspector,
+ containerData = classData,
+ propertyData = propertyData,
+ )
+ }
+ .asIterable(),
+ )
+ companionObject?.let { objectName ->
+ val companionType = if (classInspector != null) {
+ val companionClassName = className.nestedClass(objectName)
+ classInspector.classFor(companionClassName)
+ .toTypeSpec(classInspector, companionClassName, parentClassName = className)
+ } else {
+ TypeSpec.companionObjectBuilder(companionObjectName(objectName))
+ .addKdoc(
+ "No ClassInspector was available during metadata parsing, so this companion object's API/contents may not be reflected accurately.",
+ )
+ .build()
+ }
+ builder.addType(companionType)
+ }
+ }
+ builder.addFunctions(
+ functions
+ .asSequence()
+ .filter { it.isDeclaration }
+ .filterNot { it.isDelegation }
+ .filterNot { it.isSynthesized }
+ .map { it to classData?.methods?.get(it) }
+ .map { (func, methodData) ->
+ func.toFunSpec(classTypeParamsResolver, classInspector, classData, methodData)
+ .toBuilder()
+ .apply {
+ // For interface methods, remove any body and mark the methods as abstract
+ fun isKotlinDefaultInterfaceMethod(): Boolean {
+ classInspector?.let { handler ->
+ func.signature?.let { signature ->
+ val suffix = signature.desc.removePrefix("(")
+ return handler.methodExists(
+ className.nestedClass("DefaultImpls"),
+ signature.copy(
+ desc = "(L$jvmInternalName;$suffix",
+ ),
+ )
+ }
+ }
+ return false
+ }
+ // For interface methods, remove any body and mark the methods as abstract
+ // IFF it doesn't have a default interface body.
+ if (isInterface &&
+ annotations.none { it.typeName == JVM_DEFAULT } &&
+ (methodData?.jvmModifiers?.contains(DEFAULT) == false) &&
+ !isKotlinDefaultInterfaceMethod()
+ ) {
+ addModifiers(ABSTRACT)
+ clearBody()
+ } else if (ABSTRACT in modifiers) {
+ // Remove bodies for abstract functions
+ clearBody()
+ }
+ if (methodData?.isSynthetic == true) {
+ addKdoc(
+ "Note: Since this is a synthetic function, some JVM information " +
+ "(annotations, modifiers) may be missing.",
+ )
+ }
+ }
+ .build()
+ }
+ .asIterable(),
+ )
+
+ for (it in nestedClasses) {
+ val nestedClassName = className.nestedClass(it)
+ val nestedClass = classInspector?.classFor(nestedClassName)
+ val nestedType = if (nestedClass != null) {
+ if (nestedClass.isCompanionObject) {
+ // We handle these separately
+ continue
+ } else {
+ nestedClass.toTypeSpec(classInspector, nestedClassName, parentClassName = className)
+ }
+ } else {
+ TypeSpec.classBuilder(it)
+ .addKdoc(
+ "No ClassInspector was available during metadata parsing, so this nested class's API/contents may not be reflected accurately.",
+ )
+ .build()
+ }
+ builder.addType(nestedType)
+ }
+
+ return builder
+ .tag(this)
+ .build()
+}
+
+private fun companionObjectName(name: String): String? {
+ return if (name == "Companion") null else name
+}
+
+@KotlinPoetMetadataPreview
+private fun KmConstructor.toFunSpec(
+ typeParamResolver: TypeParameterResolver,
+ constructorData: ConstructorData?,
+): FunSpec {
+ return FunSpec.constructorBuilder()
+ .apply {
+ addAnnotations(constructorData?.allAnnotations.orEmpty())
+ visibilityFrom(flags) { addModifiers(it) }
+ addParameters(
+ this@toFunSpec.valueParameters.mapIndexed { index, param ->
+ param.toParameterSpec(
+ typeParamResolver,
+ constructorData?.takeIf { it != ConstructorData.EMPTY }
+ ?.parameterAnnotations
+ ?.get(index)
+ .orEmpty(),
+ )
+ },
+ )
+ if (!isPrimary) {
+ // TODO How do we know when to add callSuperConstructor()?
+ }
+ }
+ .tag(this)
+ .build()
+}
+
+@KotlinPoetMetadataPreview
+private val ContainerData.isInterface: Boolean get() {
+ return declarationContainer.let { container ->
+ container is KmClass && container.isInterface
+ }
+}
+
+@KotlinPoetMetadataPreview
+private fun KmFunction.toFunSpec(
+ classTypeParamsResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
+ classInspector: ClassInspector? = null,
+ containerData: ContainerData? = null,
+ methodData: MethodData? = null,
+ isInInterface: Boolean = containerData?.isInterface ?: false,
+): FunSpec {
+ val typeParamsResolver = typeParameters.toTypeParameterResolver(
+ fallback = classTypeParamsResolver,
+ )
+ val mutableAnnotations = mutableListOf<AnnotationSpec>()
+ if (classInspector != null && containerData != null) {
+ signature?.let { signature ->
+ if (!containerData.isInterface) {
+ // Infer if JvmName was used
+ // We skip interface types for this because they can't have @JvmName.
+ signature.jvmNameAnnotation(name)?.let { jvmNameAnnotation ->
+ mutableAnnotations += jvmNameAnnotation
+ }
+ }
+ }
+ }
+ val anyReified = typeParameters.any { it.isReified }
+ val isInFacade = containerData is FileData
+ val annotations = mutableAnnotations
+ .plus(methodData?.allAnnotations(containsReifiedTypeParameter = anyReified).orEmpty())
+ .filterNot { isInFacade && it.typeName == JVM_STATIC }
+ .toTreeSet()
+ return FunSpec.builder(name)
+ .apply {
+ addAnnotations(annotations)
+ visibilityFrom(flags) { addModifiers(it) }
+ val isOverride = methodData?.isOverride == true
+ addModifiers(
+ flags.modalities
+ .filterNot { it == FINAL && !isOverride } // Final is the default
+ .filterNot { it == OPEN && isOverride } // Overrides are implicitly open
+ .filterNot { it == OPEN && isInInterface }, // interface methods are implicitly open
+ )
+ if (valueParameters.isNotEmpty()) {
+ addParameters(
+ valueParameters.mapIndexed { index, param ->
+ param.toParameterSpec(
+ typeParamsResolver,
+ // This can be empty if the element is synthetic
+ methodData?.parameterAnnotations?.get(index).orEmpty(),
+ )
+ },
+ )
+ }
+ if (typeParameters.isNotEmpty()) {
+ addTypeVariables(typeParameters.map { it.toTypeVariableName(typeParamsResolver) })
+ }
+ if (methodData?.isOverride == true) {
+ addModifiers(KModifier.OVERRIDE)
+ }
+ if (isOperator) {
+ addModifiers(OPERATOR)
+ }
+ if (isInfix) {
+ addModifiers(INFIX)
+ }
+ if (isInline) {
+ addModifiers(INLINE)
+ }
+ if (isTailRec) {
+ addModifiers(TAILREC)
+ }
+ if (isExternal) {
+ addModifiers(EXTERNAL)
+ }
+ if (isExpect) {
+ addModifiers(EXPECT)
+ }
+ if (isSuspend) {
+ addModifiers(SUSPEND)
+ }
+ val returnTypeName = this@toFunSpec.returnType.toTypeName(typeParamsResolver)
+ if (returnTypeName != UNIT) {
+ returns(returnTypeName)
+ if (!flags.isAbstract) {
+ addStatement(NOT_IMPLEMENTED)
+ }
+ }
+ receiverParameterType?.toTypeName(typeParamsResolver)?.let { receiver(it) }
+ }
+ .tag(this)
+ .build()
+}
+
+@KotlinPoetMetadataPreview
+private fun KmValueParameter.toParameterSpec(
+ typeParamResolver: TypeParameterResolver,
+ annotations: Collection<AnnotationSpec>,
+): ParameterSpec {
+ val paramType = varargElementType ?: type ?: throw IllegalStateException("No argument type!")
+ return ParameterSpec.builder(name, paramType.toTypeName(typeParamResolver))
+ .apply {
+ addAnnotations(annotations)
+ if (varargElementType != null) {
+ addModifiers(VARARG)
+ }
+ if (isCrossInline) {
+ addModifiers(CROSSINLINE)
+ }
+ if (isNoInline) {
+ addModifiers(NOINLINE)
+ }
+ if (declaresDefaultValue) {
+ defaultValue(NOT_IMPLEMENTED)
+ }
+ }
+ .tag(this)
+ .build()
+}
+
+@KotlinPoetMetadataPreview
+private fun KmProperty.toPropertySpec(
+ typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
+ isConstructorParam: Boolean = false,
+ classInspector: ClassInspector? = null,
+ containerData: ContainerData? = null,
+ propertyData: PropertyData? = null,
+ isInInterface: Boolean = containerData?.isInterface ?: false,
+): PropertySpec {
+ val isOverride = propertyData?.isOverride ?: false
+ val returnTypeName = returnType.toTypeName(typeParamResolver)
+ val mutableAnnotations = mutableListOf<AnnotationSpec>()
+ if (containerData != null && propertyData != null) {
+ if (hasGetter) {
+ getterSignature?.let { getterSignature ->
+ if (!containerData.isInterface &&
+ !flags.isOpen &&
+ !flags.isAbstract
+ ) {
+ // Infer if JvmName was used
+ // We skip interface types or open/abstract properties because they can't have @JvmName.
+ // For annotation properties, kotlinc puts JvmName annotations by default in
+ // bytecode but they're implicit in source, so we expect the simple name for
+ // annotation types.
+ val expectedMetadataName = if (containerData is ClassData &&
+ containerData.declarationContainer.isAnnotation
+ ) {
+ name
+ } else {
+ "get${name.safeCapitalize(Locale.US)}"
+ }
+ getterSignature.jvmNameAnnotation(
+ metadataName = expectedMetadataName,
+ useSiteTarget = UseSiteTarget.GET,
+ )?.let { jvmNameAnnotation ->
+ mutableAnnotations += jvmNameAnnotation
+ }
+ }
+ }
+ }
+ if (hasSetter) {
+ setterSignature?.let { setterSignature ->
+ if (containerData is ClassData &&
+ !containerData.declarationContainer.isAnnotation &&
+ !containerData.declarationContainer.isInterface &&
+ classInspector?.supportsNonRuntimeRetainedAnnotations == false &&
+ !flags.isOpen &&
+ !flags.isAbstract
+ ) {
+ // Infer if JvmName was used
+ // We skip annotation types for this because they can't have vars.
+ // We skip interface types or open/abstract properties because they can't have @JvmName.
+ setterSignature.jvmNameAnnotation(
+ metadataName = "set${name.safeCapitalize(Locale.US)}",
+ useSiteTarget = UseSiteTarget.SET,
+ )?.let { jvmNameAnnotation ->
+ mutableAnnotations += jvmNameAnnotation
+ }
+ }
+ }
+ }
+ }
+ return PropertySpec.builder(name, returnTypeName)
+ .apply {
+ // If a property annotation doesn't have a custom site target and is used in a constructor
+ // we have to add the property: site target to it.
+
+ val isInFacade = containerData is FileData
+ val finalAnnotations = mutableAnnotations
+ .plus(propertyData?.allAnnotations.orEmpty())
+ .filterNot { (isConst || isInFacade) && it.typeName == JVM_STATIC }
+ .map {
+ if (isConstructorParam && it.useSiteTarget == null) {
+ // TODO Ideally don't do this if the annotation use site is only field?
+ // e.g. JvmField. It's technically fine, but redundant on parameters as it's
+ // automatically applied to the property for these annotation types.
+ // This is another thing ClassInspector *could* tell us
+ it.toBuilder().useSiteTarget(UseSiteTarget.PROPERTY).build()
+ } else {
+ it
+ }
+ }
+ .toTreeSet()
+ addAnnotations(finalAnnotations)
+ visibilityFrom(flags) { addModifiers(it) }
+ addModifiers(
+ flags.modalities
+ .filterNot { it == FINAL && !isOverride } // Final is the default
+ .filterNot { it == OPEN && isOverride } // Overrides are implicitly open
+ .filterNot { it == OPEN && isInInterface } // Interface properties implicitly open
+ .filterNot { it == ABSTRACT && isInInterface }, // Interface properties implicitly abstract
+ )
+ if (isOverride) {
+ addModifiers(KModifier.OVERRIDE)
+ }
+ if (isConst) {
+ addModifiers(CONST)
+ }
+ if (isVar) {
+ mutable(true)
+ } else if (isVal) {
+ mutable(false)
+ }
+ if (isDelegated) {
+ // Placeholders for these are tricky
+ addKdoc("Note: delegation is ABI stub only and not guaranteed to match source code.")
+ if (isVal) {
+ delegate("%M { %L }", MemberName("kotlin", "lazy"), NOT_IMPLEMENTED) // Placeholder
+ } else {
+ if (returnTypeName.isNullable) {
+ delegate(
+ "%T.observable(null) { _, _, _ -> }",
+ ClassName("kotlin.properties", "Delegates"),
+ )
+ } else {
+ delegate("%T.notNull()", ClassName("kotlin.properties", "Delegates")) // Placeholder
+ }
+ }
+ }
+ if (isExpect) {
+ addModifiers(EXPECT)
+ }
+ if (isExternal) {
+ addModifiers(EXTERNAL)
+ }
+ if (isLateinit) {
+ addModifiers(LATEINIT)
+ }
+ if (isConstructorParam || (!isDelegated && !isLateinit)) {
+ val constant = propertyData?.fieldData?.constant
+ when {
+ constant != null -> initializer(constant)
+ isConstructorParam -> initializer(name)
+ returnTypeName.isNullable -> initializer("null")
+ flags.isAbstract || isInInterface -> {
+ // No-op, don't emit an initializer for abstract or interface properties
+ }
+ else -> initializer(NOT_IMPLEMENTED)
+ }
+ }
+ // Delegated properties have setters/getters defined for some reason, ignore here
+ // since the delegate handles it
+ // vals with initialized constants have a getter in bytecode but not a body in kotlin source
+ val modifierSet = modifiers.toSet()
+ if (hasGetter && !isDelegated && !flags.isAbstract) {
+ propertyAccessor(
+ modifierSet,
+ getterFlags,
+ FunSpec.getterBuilder().addStatement(NOT_IMPLEMENTED),
+ isOverride,
+ )?.let(::getter)
+ }
+ if (hasSetter && !isDelegated && !flags.isAbstract) {
+ propertyAccessor(modifierSet, setterFlags, FunSpec.setterBuilder(), isOverride)?.let(::setter)
+ }
+ }
+ .tag(this)
+ .build()
+}
+
+@KotlinPoetMetadataPreview
+private fun propertyAccessor(
+ propertyModifiers: Set<KModifier>,
+ flags: Flags,
+ functionBuilder: Builder,
+ isOverride: Boolean,
+): FunSpec? {
+ val visibility = flags.visibility
+ if (visibility == PUBLIC || visibility !in propertyModifiers) {
+ // This is redundant and just a stub
+ // For annotations on this accessor, we declare them on the property with site target instead
+ return null
+ }
+ val modalities = flags.modalities
+ .filterNot { it == FINAL && !isOverride }
+ .filterNot { it == OPEN && isOverride }
+ val propertyAccessorFlags = flags.propertyAccessorFlags
+ return if (visibility != PUBLIC || modalities.isNotEmpty() || propertyAccessorFlags.isNotEmpty()) {
+ functionBuilder
+ .apply {
+ addModifiers(visibility)
+ addModifiers(modalities)
+ addModifiers(*propertyAccessorFlags.toKModifiersArray())
+ }
+ .build()
+ } else {
+ null
+ }
+}
+
+private fun Set<PropertyAccessorFlag>.toKModifiersArray(): Array<KModifier> {
+ return mapNotNull {
+ when (it) {
+ IS_EXTERNAL -> EXTERNAL
+ IS_INLINE -> INLINE
+ IS_NOT_DEFAULT -> null // Gracefully skip over these
+ }
+ }.toTypedArray()
+}
+
+@KotlinPoetMetadataPreview
+private fun KmTypeAlias.toTypeAliasSpec(): TypeAliasSpec {
+ val typeParamResolver = typeParameters.toTypeParameterResolver()
+ return TypeAliasSpec.builder(name, underlyingType.toTypeName(typeParamResolver))
+ .apply {
+ visibilityFrom(flags) {
+ addModifiers(it)
+ }
+ if (flags.hasAnnotations) {
+ val annotationSpecs = this@toTypeAliasSpec.annotations
+ .map { it.toAnnotationSpec() }
+ addAnnotations(annotationSpecs)
+ }
+ }
+ .addTypeVariables(typeParamResolver.parametersMap.values)
+ .build()
+}
+
+private fun JvmMethodSignature.jvmNameAnnotation(
+ metadataName: String,
+ useSiteTarget: UseSiteTarget? = null,
+): AnnotationSpec? {
+ return if (name == metadataName) {
+ null
+ } else {
+ return AnnotationSpec.builder(JvmName::class)
+ .addMember("name = %S", name)
+ .useSiteTarget(useSiteTarget)
+ .build()
+ }
+}
+
+private val JAVA_ANNOTATION_ANNOTATIONS = setOf(
+ java.lang.annotation.Retention::class.asClassName(),
+ java.lang.annotation.Target::class.asClassName(),
+)
+
+@KotlinPoetMetadataPreview
+private val Flags.visibility: KModifier
+ get() = when {
+ isInternal -> INTERNAL
+ isPrivate -> PRIVATE
+ isProtected -> PROTECTED
+ isPublic -> PUBLIC
+ else -> {
+ // IS_PRIVATE_TO_THIS or IS_LOCAL, so just default to public
+ PUBLIC
+ }
+ }
+
+@KotlinPoetMetadataPreview
+private fun visibilityFrom(flags: Flags, body: (KModifier) -> Unit) {
+ val modifierVisibility = flags.visibility
+ if (modifierVisibility != PUBLIC) {
+ body(modifierVisibility)
+ }
+}
+
+private fun String.safeCapitalize(locale: Locale): String {
+ return replaceFirstChar { if (it.isLowerCase()) it.titlecase(locale) else it.toString() }
+}
+
+@KotlinPoetMetadataPreview
+private val Flags.modalities: Set<KModifier>
+ get() = setOf {
+ if (isFinal) {
+ add(FINAL)
+ }
+ if (isOpen) {
+ add(OPEN)
+ }
+ if (isAbstract) {
+ add(ABSTRACT)
+ }
+ if (isSealed) {
+ add(SEALED)
+ }
+ }
+
+private inline fun <E> setOf(body: MutableSet<E>.() -> Unit): Set<E> {
+ return mutableSetOf<E>().apply(body).toSet()
+}
+
+private val METADATA = Metadata::class.asClassName()
+
+@Suppress("DEPRECATION")
+private val JVM_DEFAULT = JvmDefault::class.asClassName()
+private val JVM_STATIC = JvmStatic::class.asClassName()
+
+@PublishedApi
+internal val Element.packageName: String
+ get() {
+ var element = this
+ while (element.kind != ElementKind.PACKAGE) {
+ element = element.enclosingElement
+ }
+ return (element as PackageElement).toString()
+ }
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/MethodData.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/MethodData.kt
new file mode 100644
index 00000000..7319e9d8
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/MethodData.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+
+/**
+ * Represents relevant information on a method used for [ClassInspector]. Should only be
+ * associated with methods of a [ClassData] or [PropertyData].
+ *
+ * @param annotations declared annotations on this method.
+ * @property parameterAnnotations a mapping of parameter indices to annotations on them.
+ * @property isSynthetic indicates if this method is synthetic or not.
+ * @property jvmModifiers set of [JvmMethodModifiers][JvmMethodModifier] on this method.
+ * @property isOverride indicates if this method overrides one in a supertype.
+ * @property exceptions list of exceptions thrown by this method.
+ */
+@KotlinPoetMetadataPreview
+public data class MethodData(
+ private val annotations: List<AnnotationSpec>,
+ val parameterAnnotations: Map<Int, Collection<AnnotationSpec>>,
+ val isSynthetic: Boolean,
+ val jvmModifiers: Set<JvmMethodModifier>,
+ val isOverride: Boolean,
+ val exceptions: List<TypeName>,
+) {
+
+ /**
+ * A collection of all annotations on this method, including any derived from [jvmModifiers],
+ * [isSynthetic], and [exceptions].
+ *
+ * @param useSiteTarget an optional [UseSiteTarget] that all annotations on this method should
+ * use.
+ * @param containsReifiedTypeParameter an optional boolean indicating if any type parameters on
+ * this function are `reified`, which are implicitly synthetic.
+ */
+ public fun allAnnotations(
+ useSiteTarget: UseSiteTarget? = null,
+ containsReifiedTypeParameter: Boolean = false,
+ ): Collection<AnnotationSpec> {
+ return ClassInspectorUtil.createAnnotations(
+ useSiteTarget,
+ ) {
+ addAll(annotations)
+ if (isSynthetic && !containsReifiedTypeParameter) {
+ add(ClassInspectorUtil.JVM_SYNTHETIC_SPEC)
+ }
+ addAll(jvmModifiers.mapNotNull(JvmMethodModifier::annotationSpec))
+ exceptions.takeIf { it.isNotEmpty() }
+ ?.let {
+ add(ClassInspectorUtil.createThrowsSpec(it, useSiteTarget))
+ }
+ }
+ }
+
+ public companion object {
+ public val SYNTHETIC: MethodData = MethodData(
+ annotations = emptyList(),
+ parameterAnnotations = emptyMap(),
+ isSynthetic = true,
+ jvmModifiers = emptySet(),
+ isOverride = false,
+ exceptions = emptyList(),
+ )
+ public val EMPTY: MethodData = MethodData(
+ annotations = emptyList(),
+ parameterAnnotations = emptyMap(),
+ isSynthetic = false,
+ jvmModifiers = emptySet(),
+ isOverride = false,
+ exceptions = emptyList(),
+ )
+ }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/PropertyData.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/PropertyData.kt
new file mode 100644
index 00000000..9fc6d3e4
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/PropertyData.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.GET
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.SET
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+
+/**
+ * Represents relevant information on a property used for [ClassInspector]. Should only be
+ * associated with properties of a [ClassData].
+ *
+ * @param annotations declared annotations on this property.
+ * @property fieldData associated [FieldData] with this property, if any.
+ * @property getterData associated getter (as [MethodData]) with this property, if any.
+ * @property setterData associated setter (as [MethodData]) with this property, if any.
+ * @property isJvmField indicates if this property should be treated as a jvm field.
+ */
+@KotlinPoetMetadataPreview
+public data class PropertyData(
+ private val annotations: List<AnnotationSpec>,
+ val fieldData: FieldData?,
+ val getterData: MethodData?,
+ val setterData: MethodData?,
+ val isJvmField: Boolean,
+) {
+ /** Indicates if this property overrides another from a supertype. */
+ val isOverride: Boolean = (getterData?.isOverride ?: false) || (setterData?.isOverride ?: false)
+
+ /**
+ * A collection of all annotations on this property including declared ones and any derived from
+ * [fieldData], [getterData], [setterData], and [isJvmField].
+ */
+ val allAnnotations: Collection<AnnotationSpec> = ClassInspectorUtil.createAnnotations {
+ // Don't add annotations that are already defined on the parent
+ val higherScopedAnnotations = annotations.associateBy { it.typeName }
+ val fieldAnnotations = fieldData?.allAnnotations.orEmpty()
+ .filterNot { it.typeName in higherScopedAnnotations }
+ .associateByTo(LinkedHashMap()) { it.typeName }
+ val getterAnnotations = getterData?.allAnnotations(GET).orEmpty()
+ .filterNot { it.typeName in higherScopedAnnotations }
+ .associateByTo(LinkedHashMap()) { it.typeName }
+
+ val finalTopAnnotations = annotations.toMutableList()
+
+ // If this is a val, and annotation is on both getter and field, we can move it to just the
+ // regular annotations
+ if (setterData == null && !isJvmField) {
+ val sharedAnnotations = getterAnnotations.keys.intersect(fieldAnnotations.keys)
+ for (sharedAnnotation in sharedAnnotations) {
+ // Add it to the top-level annotations without a site-target
+ finalTopAnnotations += getterAnnotations.getValue(sharedAnnotation).toBuilder()
+ .useSiteTarget(null)
+ .build()
+
+ // Remove from field and getter
+ fieldAnnotations.remove(sharedAnnotation)
+ getterAnnotations.remove(sharedAnnotation)
+ }
+ }
+
+ addAll(finalTopAnnotations)
+ addAll(fieldAnnotations.values)
+ addAll(getterAnnotations.values)
+ addAll(
+ setterData?.allAnnotations(SET).orEmpty()
+ .filterNot { it.typeName in higherScopedAnnotations },
+ )
+ if (isJvmField) {
+ add(ClassInspectorUtil.JVM_FIELD_SPEC)
+ }
+ }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/kmAnnotations.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/kmAnnotations.kt
new file mode 100644
index 00000000..f6b44670
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/kmAnnotations.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.joinToCode
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.createClassName
+import com.squareup.kotlinpoet.tag
+import kotlinx.metadata.KmAnnotation
+import kotlinx.metadata.KmAnnotationArgument
+import kotlinx.metadata.KmAnnotationArgument.AnnotationValue
+import kotlinx.metadata.KmAnnotationArgument.ArrayValue
+import kotlinx.metadata.KmAnnotationArgument.BooleanValue
+import kotlinx.metadata.KmAnnotationArgument.ByteValue
+import kotlinx.metadata.KmAnnotationArgument.CharValue
+import kotlinx.metadata.KmAnnotationArgument.DoubleValue
+import kotlinx.metadata.KmAnnotationArgument.EnumValue
+import kotlinx.metadata.KmAnnotationArgument.FloatValue
+import kotlinx.metadata.KmAnnotationArgument.IntValue
+import kotlinx.metadata.KmAnnotationArgument.KClassValue
+import kotlinx.metadata.KmAnnotationArgument.LongValue
+import kotlinx.metadata.KmAnnotationArgument.ShortValue
+import kotlinx.metadata.KmAnnotationArgument.StringValue
+import kotlinx.metadata.KmAnnotationArgument.UByteValue
+import kotlinx.metadata.KmAnnotationArgument.UIntValue
+import kotlinx.metadata.KmAnnotationArgument.ULongValue
+import kotlinx.metadata.KmAnnotationArgument.UShortValue
+
+@KotlinPoetMetadataPreview
+internal fun KmAnnotation.toAnnotationSpec(): AnnotationSpec {
+ val cn = createClassName(className)
+ return AnnotationSpec.builder(cn)
+ .apply {
+ arguments.forEach { (name, arg) ->
+ addMember("%L = %L", name, arg.toCodeBlock())
+ }
+ }
+ .tag(this)
+ .build()
+}
+
+@OptIn(ExperimentalUnsignedTypes::class)
+@KotlinPoetMetadataPreview
+internal fun KmAnnotationArgument.toCodeBlock(): CodeBlock {
+ return when (this) {
+ is ByteValue -> CodeBlock.of("%L", value)
+ is CharValue -> CodeBlock.of("'%L'", value)
+ is ShortValue -> CodeBlock.of("%L", value)
+ is IntValue -> CodeBlock.of("%L", value)
+ is LongValue -> CodeBlock.of("%LL", value)
+ is FloatValue -> CodeBlock.of("%LF", value)
+ is DoubleValue -> CodeBlock.of("%L", value)
+ is BooleanValue -> CodeBlock.of("%L", value)
+ is UByteValue -> CodeBlock.of("%Lu", value)
+ is UShortValue -> CodeBlock.of("%Lu", value)
+ is UIntValue -> CodeBlock.of("%Lu", value)
+ is ULongValue -> CodeBlock.of("%Lu", value)
+ is StringValue -> CodeBlock.of("%S", value)
+ is KClassValue -> CodeBlock.of("%T::class", createClassName(className))
+ is EnumValue -> CodeBlock.of("%T.%L", createClassName(enumClassName), enumEntryName)
+ is AnnotationValue -> CodeBlock.of("%L", annotation.toAnnotationSpec())
+ is ArrayValue -> elements.map { it.toCodeBlock() }.joinToCode(", ", "[", "]")
+ }
+}
diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFile.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFile.kt
new file mode 100644
index 00000000..6eb40a88
--- /dev/null
+++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFile.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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.
+ */
+@file:JvmName("FacadeFile")
+@file:FileAnnotation("file annotations!")
+
+package com.squareup.kotlinpoet.metadata.specs
+
+import kotlin.annotation.AnnotationTarget.FILE
+
+@Target(FILE)
+annotation class FileAnnotation(val value: String)
+
+@JvmName("jvmStaticFunction")
+fun jvmNameFunction() {
+}
+
+fun regularFun() {
+}
+
+@Synchronized
+fun synchronizedFun() {
+}
+
+@JvmOverloads
+fun jvmOverloads(
+ param1: String,
+ optionalParam2: String = "",
+ nullableParam3: String? = null,
+) {
+}
+
+val BOOL_PROP = false
+val BINARY_PROP = 0b00001011
+val INT_PROP = 1
+val UNDERSCORES_PROP = 1_000_000
+val HEX_PROP = 0x0F
+val UNDERSCORES_HEX_PROP = 0xFF_EC_DE_5E
+val LONG_PROP = 1L
+val FLOAT_PROP = 1.0f
+val DOUBLE_PROP = 1.0
+val STRING_PROP = "prop"
+var VAR_BOOL_PROP = false
+var VAR_BINARY_PROP = 0b00001011
+var VAR_INT_PROP = 1
+var VAR_UNDERSCORES_PROP = 1_000_000
+var VAR_HEX_PROP = 0x0F
+var VAR_UNDERSCORES_HEX_PROP = 0xFF_EC_DE_5E
+var VAR_LONG_PROP = 1L
+var VAR_FLOAT_PROP = 1.0f
+var VAR_DOUBLE_PROP = 1.0
+var VAR_STRING_PROP = "prop"
+
+const val CONST_BOOL_PROP = false
+const val CONST_BINARY_PROP = 0b00001011
+const val CONST_INT_PROP = 1
+const val CONST_UNDERSCORES_PROP = 1_000_000
+const val CONST_HEX_PROP = 0x0F
+const val CONST_UNDERSCORES_HEX_PROP = 0xFF_EC_DE_5E
+const val CONST_LONG_PROP = 1L
+const val CONST_FLOAT_PROP = 1.0f
+const val CONST_DOUBLE_PROP = 1.0
+const val CONST_STRING_PROP = "prop"
+
+@JvmField
+@JvmSynthetic
+val syntheticFieldProperty: kotlin.String? = null
+
+@field:JvmSynthetic
+val syntheticProperty: kotlin.String? = null
+
+@get:JvmSynthetic
+val syntheticPropertyGet: kotlin.String? = null
+
+@get:JvmSynthetic
+@set:JvmSynthetic
+var syntheticPropertyGetAndSet: kotlin.String? = null
+
+@set:JvmSynthetic
+var syntheticPropertySet: kotlin.String? = null
+
+typealias FacadeTypeAliasName = String
+typealias FacadeGenericTypeAlias = List<String>
+typealias FacadeNestedTypeAlias = List<GenericTypeAlias>
diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFileTest.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFileTest.kt
new file mode 100644
index 00000000..adea853f
--- /dev/null
+++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFileTest.kt
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.specs
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.specs.MultiClassInspectorTest.ClassInspectorType.ELEMENTS
+import com.squareup.kotlinpoet.metadata.specs.MultiClassInspectorTest.ClassInspectorType.REFLECTIVE
+import org.junit.Test
+
+@KotlinPoetMetadataPreview
+class FacadeFileTest : MultiClassInspectorTest() {
+
+ @IgnoreForHandlerType(
+ handlerType = ELEMENTS,
+ reason = "Elements can detect JvmOverloads, JvmName not possible in reflection",
+ )
+ @Test
+ fun facadeFile_reflective() {
+ val fileSpec = Class.forName(
+ "com.squareup.kotlinpoet.metadata.specs.FacadeFile",
+ ).kotlin.toFileSpecWithTestHandler()
+ assertThat(fileSpec.name).isEqualTo("FacadeFile")
+ //language=kotlin
+ assertThat(fileSpec.trimmedToString()).isEqualTo(
+ """
+ @file:JvmName(name = "FacadeFile")
+ @file:FileAnnotation(value = "file annotations!")
+
+ package com.squareup.kotlinpoet.metadata.specs
+
+ import com.squareup.kotlinpoet.metadata.specs.FileAnnotation
+ import kotlin.Boolean
+ import kotlin.Double
+ import kotlin.Float
+ import kotlin.Int
+ import kotlin.Long
+ import kotlin.String
+ import kotlin.Unit
+ import kotlin.collections.List
+ import kotlin.jvm.JvmField
+ import kotlin.jvm.JvmName
+ import kotlin.jvm.JvmSynthetic
+ import kotlin.jvm.Synchronized
+
+ @JvmName(name = "jvmStaticFunction")
+ public fun jvmNameFunction(): Unit {
+ }
+
+ public fun jvmOverloads(
+ param1: String,
+ optionalParam2: String = throw NotImplementedError("Stub!"),
+ nullableParam3: String? = throw NotImplementedError("Stub!"),
+ ): Unit {
+ }
+
+ public fun regularFun(): Unit {
+ }
+
+ @Synchronized
+ public fun synchronizedFun(): Unit {
+ }
+
+ public val BINARY_PROP: Int = 11
+
+ public val BOOL_PROP: Boolean = false
+
+ public const val CONST_BINARY_PROP: Int = 11
+
+ public const val CONST_BOOL_PROP: Boolean = false
+
+ public const val CONST_DOUBLE_PROP: Double = 1.0
+
+ public const val CONST_FLOAT_PROP: Float = 1.0F
+
+ public const val CONST_HEX_PROP: Int = 15
+
+ public const val CONST_INT_PROP: Int = 1
+
+ public const val CONST_LONG_PROP: Long = 1L
+
+ public const val CONST_STRING_PROP: String = "prop"
+
+ public const val CONST_UNDERSCORES_HEX_PROP: Long = 4_293_713_502L
+
+ public const val CONST_UNDERSCORES_PROP: Int = 1_000_000
+
+ public val DOUBLE_PROP: Double = 1.0
+
+ public val FLOAT_PROP: Float = 1.0F
+
+ public val HEX_PROP: Int = 15
+
+ public val INT_PROP: Int = 1
+
+ public val LONG_PROP: Long = 1L
+
+ public val STRING_PROP: String = "prop"
+
+ public val UNDERSCORES_HEX_PROP: Long = 4_293_713_502L
+
+ public val UNDERSCORES_PROP: Int = 1_000_000
+
+ public var VAR_BINARY_PROP: Int = throw NotImplementedError("Stub!")
+
+ public var VAR_BOOL_PROP: Boolean = throw NotImplementedError("Stub!")
+
+ public var VAR_DOUBLE_PROP: Double = throw NotImplementedError("Stub!")
+
+ public var VAR_FLOAT_PROP: Float = throw NotImplementedError("Stub!")
+
+ public var VAR_HEX_PROP: Int = throw NotImplementedError("Stub!")
+
+ public var VAR_INT_PROP: Int = throw NotImplementedError("Stub!")
+
+ public var VAR_LONG_PROP: Long = throw NotImplementedError("Stub!")
+
+ public var VAR_STRING_PROP: String = throw NotImplementedError("Stub!")
+
+ public var VAR_UNDERSCORES_HEX_PROP: Long = throw NotImplementedError("Stub!")
+
+ public var VAR_UNDERSCORES_PROP: Int = throw NotImplementedError("Stub!")
+
+ @field:JvmSynthetic
+ @JvmField
+ public val syntheticFieldProperty: String? = null
+
+ @field:JvmSynthetic
+ public val syntheticProperty: String? = null
+
+ @get:JvmSynthetic
+ public val syntheticPropertyGet: String? = null
+
+ @get:JvmSynthetic
+ @set:JvmSynthetic
+ public var syntheticPropertyGetAndSet: String? = null
+
+ @set:JvmSynthetic
+ public var syntheticPropertySet: String? = null
+
+ public typealias FacadeGenericTypeAlias = List<String>
+
+ public typealias FacadeNestedTypeAlias = List<GenericTypeAlias>
+
+ public typealias FacadeTypeAliasName = String
+ """.trimIndent(),
+ )
+ }
+
+ @IgnoreForHandlerType(
+ handlerType = REFLECTIVE,
+ reason = "Elements can detect JvmOverloads, JvmName not possible in reflection",
+ )
+ @Test
+ fun facadeFile_elements() {
+ val fileSpec = Class.forName(
+ "com.squareup.kotlinpoet.metadata.specs.FacadeFile",
+ ).kotlin.toFileSpecWithTestHandler()
+ assertThat(fileSpec.name).isEqualTo("FacadeFile")
+ //language=kotlin
+ assertThat(fileSpec.trimmedToString()).isEqualTo(
+ """
+ @file:FileAnnotation(value = "file annotations!")
+ @file:JvmName(name = "FacadeFile")
+
+ package com.squareup.kotlinpoet.metadata.specs
+
+ import com.squareup.kotlinpoet.metadata.specs.FileAnnotation
+ import kotlin.Boolean
+ import kotlin.Double
+ import kotlin.Float
+ import kotlin.Int
+ import kotlin.Long
+ import kotlin.String
+ import kotlin.Unit
+ import kotlin.collections.List
+ import kotlin.jvm.JvmName
+ import kotlin.jvm.JvmOverloads
+ import kotlin.jvm.JvmSynthetic
+ import kotlin.jvm.Synchronized
+
+ @JvmName(name = "jvmStaticFunction")
+ public fun jvmNameFunction(): Unit {
+ }
+
+ @JvmOverloads
+ public fun jvmOverloads(
+ param1: String,
+ optionalParam2: String = throw NotImplementedError("Stub!"),
+ nullableParam3: String? = throw NotImplementedError("Stub!"),
+ ): Unit {
+ }
+
+ public fun regularFun(): Unit {
+ }
+
+ @Synchronized
+ public fun synchronizedFun(): Unit {
+ }
+
+ public val BINARY_PROP: Int = throw NotImplementedError("Stub!")
+
+ public val BOOL_PROP: Boolean = throw NotImplementedError("Stub!")
+
+ public const val CONST_BINARY_PROP: Int = 11
+
+ public const val CONST_BOOL_PROP: Boolean = false
+
+ public const val CONST_DOUBLE_PROP: Double = 1.0
+
+ public const val CONST_FLOAT_PROP: Float = 1.0F
+
+ public const val CONST_HEX_PROP: Int = 15
+
+ public const val CONST_INT_PROP: Int = 1
+
+ public const val CONST_LONG_PROP: Long = 1L
+
+ public const val CONST_STRING_PROP: String = "prop"
+
+ public const val CONST_UNDERSCORES_HEX_PROP: Long = 4_293_713_502L
+
+ public const val CONST_UNDERSCORES_PROP: Int = 1_000_000
+
+ public val DOUBLE_PROP: Double = throw NotImplementedError("Stub!")
+
+ public val FLOAT_PROP: Float = throw NotImplementedError("Stub!")
+
+ public val HEX_PROP: Int = throw NotImplementedError("Stub!")
+
+ public val INT_PROP: Int = throw NotImplementedError("Stub!")
+
+ public val LONG_PROP: Long = throw NotImplementedError("Stub!")
+
+ public val STRING_PROP: String = throw NotImplementedError("Stub!")
+
+ public val UNDERSCORES_HEX_PROP: Long = throw NotImplementedError("Stub!")
+
+ public val UNDERSCORES_PROP: Int = throw NotImplementedError("Stub!")
+
+ public var VAR_BINARY_PROP: Int = throw NotImplementedError("Stub!")
+
+ public var VAR_BOOL_PROP: Boolean = throw NotImplementedError("Stub!")
+
+ public var VAR_DOUBLE_PROP: Double = throw NotImplementedError("Stub!")
+
+ public var VAR_FLOAT_PROP: Float = throw NotImplementedError("Stub!")
+
+ public var VAR_HEX_PROP: Int = throw NotImplementedError("Stub!")
+
+ public var VAR_INT_PROP: Int = throw NotImplementedError("Stub!")
+
+ public var VAR_LONG_PROP: Long = throw NotImplementedError("Stub!")
+
+ public var VAR_STRING_PROP: String = throw NotImplementedError("Stub!")
+
+ public var VAR_UNDERSCORES_HEX_PROP: Long = throw NotImplementedError("Stub!")
+
+ public var VAR_UNDERSCORES_PROP: Int = throw NotImplementedError("Stub!")
+
+ @field:JvmSynthetic
+ public val syntheticFieldProperty: String? = null
+
+ @field:JvmSynthetic
+ public val syntheticProperty: String? = null
+
+ @get:JvmSynthetic
+ public val syntheticPropertyGet: String? = null
+
+ @get:JvmSynthetic
+ @set:JvmSynthetic
+ public var syntheticPropertyGetAndSet: String? = null
+
+ @set:JvmSynthetic
+ public var syntheticPropertySet: String? = null
+
+ public typealias FacadeGenericTypeAlias = List<String>
+
+ public typealias FacadeNestedTypeAlias = List<GenericTypeAlias>
+
+ public typealias FacadeTypeAliasName = String
+ """.trimIndent(),
+ )
+ }
+
+ @IgnoreForHandlerType(
+ handlerType = ELEMENTS,
+ reason = "JvmName not possible in reflection",
+ )
+ @Test
+ fun noJvmName_reflective() {
+ val fileSpec = Class.forName(
+ "com.squareup.kotlinpoet.metadata.specs.NoJvmNameFacadeFileKt",
+ ).kotlin.toFileSpecWithTestHandler()
+ assertThat(fileSpec.name).isEqualTo("NoJvmNameFacadeFile")
+ //language=kotlin
+ assertThat(fileSpec.trimmedToString()).isEqualTo(
+ """
+ package com.squareup.kotlinpoet.metadata.specs
+
+ import kotlin.String
+
+ public val prop: String = ""
+ """.trimIndent(),
+ )
+ }
+
+ @IgnoreForHandlerType(
+ handlerType = REFLECTIVE,
+ reason = "JvmName not possible in reflection",
+ )
+ @Test
+ fun noJvmName_elements() {
+ val fileSpec = Class.forName(
+ "com.squareup.kotlinpoet.metadata.specs.NoJvmNameFacadeFileKt",
+ ).kotlin.toFileSpecWithTestHandler()
+ assertThat(fileSpec.name).isEqualTo("NoJvmNameFacadeFile")
+ //language=kotlin
+ assertThat(fileSpec.trimmedToString()).isEqualTo(
+ """
+ package com.squareup.kotlinpoet.metadata.specs
+
+ import kotlin.String
+
+ public val prop: String = throw NotImplementedError("Stub!")
+ """.trimIndent(),
+ )
+ }
+
+ @IgnoreForHandlerType(
+ handlerType = ELEMENTS,
+ reason = "JvmName not possible in reflection",
+ )
+ @Test
+ fun jvmName_with_kt_reflective() {
+ val fileSpec = Class.forName(
+ "com.squareup.kotlinpoet.metadata.specs.JvmNameKt",
+ ).kotlin.toFileSpecWithTestHandler()
+ assertThat(fileSpec.name).isEqualTo("JvmName")
+ //language=kotlin
+ assertThat(fileSpec.trimmedToString()).isEqualTo(
+ """
+ package com.squareup.kotlinpoet.metadata.specs
+
+ import kotlin.String
+
+ public val prop2: String = ""
+ """.trimIndent(),
+ )
+ }
+
+ @IgnoreForHandlerType(
+ handlerType = REFLECTIVE,
+ reason = "JvmName not possible in reflection",
+ )
+ @Test
+ fun jvmName_with_kt_elements() {
+ val fileSpec = Class.forName(
+ "com.squareup.kotlinpoet.metadata.specs.JvmNameKt",
+ ).kotlin.toFileSpecWithTestHandler()
+ assertThat(fileSpec.name).isEqualTo("JvmName")
+ //language=kotlin
+ assertThat(fileSpec.trimmedToString()).isEqualTo(
+ """
+ @file:JvmName(name = "JvmNameKt")
+
+ package com.squareup.kotlinpoet.metadata.specs
+
+ import kotlin.String
+ import kotlin.jvm.JvmName
+
+ public val prop2: String = throw NotImplementedError("Stub!")
+ """.trimIndent(),
+ )
+ }
+}
+
+private fun FileSpec.trimmedToString(): String {
+ return buildString { writeTo(this) }.trim()
+}
diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmNameWithKtFacadeFile.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmNameWithKtFacadeFile.kt
new file mode 100644
index 00000000..008b36f1
--- /dev/null
+++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmNameWithKtFacadeFile.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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.
+ */
+@file:JvmName("JvmNameKt")
+
+package com.squareup.kotlinpoet.metadata.specs
+
+val prop2: String = ""
diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KmAnnotationsTest.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KmAnnotationsTest.kt
new file mode 100644
index 00000000..c7928b4e
--- /dev/null
+++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KmAnnotationsTest.kt
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.specs
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import kotlin.test.Test
+import kotlinx.metadata.KmAnnotation
+import kotlinx.metadata.KmAnnotationArgument.AnnotationValue
+import kotlinx.metadata.KmAnnotationArgument.ArrayValue
+import kotlinx.metadata.KmAnnotationArgument.BooleanValue
+import kotlinx.metadata.KmAnnotationArgument.ByteValue
+import kotlinx.metadata.KmAnnotationArgument.CharValue
+import kotlinx.metadata.KmAnnotationArgument.DoubleValue
+import kotlinx.metadata.KmAnnotationArgument.EnumValue
+import kotlinx.metadata.KmAnnotationArgument.FloatValue
+import kotlinx.metadata.KmAnnotationArgument.IntValue
+import kotlinx.metadata.KmAnnotationArgument.KClassValue
+import kotlinx.metadata.KmAnnotationArgument.LongValue
+import kotlinx.metadata.KmAnnotationArgument.ShortValue
+import kotlinx.metadata.KmAnnotationArgument.StringValue
+import kotlinx.metadata.KmAnnotationArgument.UByteValue
+import kotlinx.metadata.KmAnnotationArgument.UIntValue
+import kotlinx.metadata.KmAnnotationArgument.ULongValue
+import kotlinx.metadata.KmAnnotationArgument.UShortValue
+
+@OptIn(ExperimentalUnsignedTypes::class)
+@KotlinPoetMetadataPreview
+class KmAnnotationsTest {
+
+ @Test fun noMembers() {
+ val annotation = KmAnnotation("test/NoMembersAnnotation", emptyMap())
+ assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+ """
+ @test.NoMembersAnnotation
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun byteValue() {
+ val annotation = KmAnnotation(
+ "test/ByteValueAnnotation",
+ mapOf("value" to ByteValue(2)),
+ )
+ assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+ """
+ @test.ByteValueAnnotation(value = 2)
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun charValue() {
+ val annotation = KmAnnotation(
+ "test/CharValueAnnotation",
+ mapOf("value" to CharValue('2')),
+ )
+ assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+ """
+ @test.CharValueAnnotation(value = '2')
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun shortValue() {
+ val annotation = KmAnnotation(
+ "test/ShortValueAnnotation",
+ mapOf("value" to ShortValue(2)),
+ )
+ assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+ """
+ @test.ShortValueAnnotation(value = 2)
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun intValue() {
+ val annotation = KmAnnotation(
+ "test/IntValueAnnotation",
+ mapOf("value" to IntValue(2)),
+ )
+ assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+ """
+ @test.IntValueAnnotation(value = 2)
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun longValue() {
+ val annotation = KmAnnotation(
+ "test/LongValueAnnotation",
+ mapOf("value" to LongValue(2L)),
+ )
+ assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+ """
+ @test.LongValueAnnotation(value = 2L)
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun floatValue() {
+ val annotation = KmAnnotation(
+ "test/FloatValueAnnotation",
+ mapOf("value" to FloatValue(2.0F)),
+ )
+ assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+ """
+ @test.FloatValueAnnotation(value = 2.0F)
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun doubleValue() {
+ val annotation = KmAnnotation(
+ "test/DoubleValueAnnotation",
+ mapOf("value" to DoubleValue(2.0)),
+ )
+ assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+ """
+ @test.DoubleValueAnnotation(value = 2.0)
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun booleanValue() {
+ val annotation = KmAnnotation(
+ "test/BooleanValueAnnotation",
+ mapOf("value" to BooleanValue(true)),
+ )
+ assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+ """
+ @test.BooleanValueAnnotation(value = true)
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun uByteValue() {
+ val annotation = KmAnnotation(
+ "test/UByteValueAnnotation",
+ mapOf("value" to UByteValue(2u)),
+ )
+ assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+ """
+ @test.UByteValueAnnotation(value = 2u)
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun uShortValue() {
+ val annotation = KmAnnotation(
+ "test/UShortValueAnnotation",
+ mapOf("value" to UShortValue(2u)),
+ )
+ assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+ """
+ @test.UShortValueAnnotation(value = 2u)
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun uIntValue() {
+ val annotation = KmAnnotation(
+ "test/UIntValueAnnotation",
+ mapOf("value" to UIntValue(2u)),
+ )
+ assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+ """
+ @test.UIntValueAnnotation(value = 2u)
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun uLongValue() {
+ val annotation = KmAnnotation(
+ "test/ULongValueAnnotation",
+ mapOf("value" to ULongValue(2u)),
+ )
+ assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+ """
+ @test.ULongValueAnnotation(value = 2u)
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun stringValue() {
+ val annotation = KmAnnotation(
+ "test/StringValueAnnotation",
+ mapOf("value" to StringValue("taco")),
+ )
+ assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+ """
+ @test.StringValueAnnotation(value = "taco")
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun kClassValue() {
+ val annotation = KmAnnotation(
+ "test/KClassValueAnnotation",
+ mapOf("value" to KClassValue("test/OtherClass", 0)),
+ )
+ assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+ """
+ @test.KClassValueAnnotation(value = test.OtherClass::class)
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun enumValue() {
+ val annotation = KmAnnotation(
+ "test/EnumValueAnnotation",
+ mapOf("value" to EnumValue("test/OtherClass", "VALUE")),
+ )
+ assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+ """
+ @test.EnumValueAnnotation(value = test.OtherClass.VALUE)
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun annotationValue() {
+ val annotation = KmAnnotation(
+ "test/AnnotationValueAnnotation",
+ mapOf(
+ "value" to AnnotationValue(
+ KmAnnotation("test/OtherAnnotation", mapOf("value" to StringValue("Hello!"))),
+ ),
+ ),
+ )
+ assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+ """
+ @test.AnnotationValueAnnotation(value = test.OtherAnnotation(value = "Hello!"))
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun arrayValue() {
+ val annotation = KmAnnotation(
+ "test/ArrayValueAnnotation",
+ mapOf("value" to ArrayValue(listOf(IntValue(1), IntValue(2), IntValue(3)))),
+ )
+ assertThat(annotation.toAnnotationSpec().toString()).isEqualTo(
+ """
+ @test.ArrayValueAnnotation(value = [1, 2, 3])
+ """.trimIndent(),
+ )
+ }
+}
diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecsTest.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecsTest.kt
new file mode 100644
index 00000000..9198dbcb
--- /dev/null
+++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecsTest.kt
@@ -0,0 +1,2250 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * 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.
+ */
+@file:OptIn(KotlinPoetMetadataPreview::class)
+@file:Suppress(
+ "DEPRECATION",
+ "NOTHING_TO_INLINE",
+ "RedundantSuspendModifier",
+ "RedundantUnitReturnType",
+ "RedundantVisibilityModifier",
+ "RemoveEmptyPrimaryConstructor",
+ "RemoveRedundantQualifierName",
+ "UNUSED_PARAMETER",
+ "unused",
+)
+
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.LIST
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.STRING
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.specs.MultiClassInspectorTest.ClassInspectorType.ELEMENTS
+import com.squareup.kotlinpoet.metadata.specs.MultiClassInspectorTest.ClassInspectorType.REFLECTIVE
+import com.squareup.kotlinpoet.tag
+import com.squareup.kotlinpoet.tags.TypeAliasTag
+import kotlin.annotation.AnnotationRetention.RUNTIME
+import kotlin.annotation.AnnotationTarget.TYPE
+import kotlin.annotation.AnnotationTarget.TYPE_PARAMETER
+import kotlin.properties.Delegates
+import kotlin.test.fail
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmConstructor
+import kotlinx.metadata.KmFunction
+import kotlinx.metadata.KmProperty
+import kotlinx.metadata.KmTypeParameter
+import kotlinx.metadata.KmValueParameter
+import org.junit.Ignore
+import org.junit.Test
+
+class KotlinPoetMetadataSpecsTest : MultiClassInspectorTest() {
+
+ @Test
+ fun constructorData() {
+ val typeSpec = ConstructorClass::class.toTypeSpecWithTestHandler()
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class ConstructorClass(
+ public val foo: kotlin.String,
+ vararg bar: kotlin.Int,
+ ) {
+ public constructor(bar: kotlin.Int)
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class ConstructorClass(val foo: String, vararg bar: Int) {
+ // Secondary constructors are ignored, so we expect this constructor to not be the one picked
+ // up in the test.
+ constructor(bar: Int) : this("defaultFoo")
+ }
+
+ @Test
+ fun supertype() {
+ val typeSpec = Supertype::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class Supertype() : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.BaseType(), com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.BaseInterface
+ """.trimIndent(),
+ )
+ }
+
+ abstract class BaseType
+ interface BaseInterface
+ class Supertype : BaseType(), BaseInterface
+
+ @IgnoreForHandlerType(
+ reason = "Elements properly resolves the string constant",
+ handlerType = ELEMENTS,
+ )
+ @Test
+ fun propertiesReflective() {
+ val typeSpec = Properties::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class Properties() {
+ public var aList: kotlin.collections.List<kotlin.Int> = throw NotImplementedError("Stub!")
+
+ public val bar: kotlin.String? = null
+
+ public var baz: kotlin.Int = throw NotImplementedError("Stub!")
+
+ public val foo: kotlin.String = throw NotImplementedError("Stub!")
+ }
+ """.trimIndent(),
+ )
+ }
+
+ @IgnoreForHandlerType(
+ reason = "Elements properly resolves the string constant",
+ handlerType = REFLECTIVE,
+ )
+ @Test
+ fun propertiesElements() {
+ val typeSpec = Properties::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class Properties() {
+ public var aList: kotlin.collections.List<kotlin.Int> = throw NotImplementedError("Stub!")
+
+ public val bar: kotlin.String? = null
+
+ public var baz: kotlin.Int = throw NotImplementedError("Stub!")
+
+ public val foo: kotlin.String = throw NotImplementedError("Stub!")
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class Properties {
+ val foo: String = ""
+ val bar: String? = null
+ var baz: Int = 0
+ var aList: List<Int> = emptyList()
+ }
+
+ @Test
+ fun companionObject() {
+ val typeSpec = CompanionObject::class.toTypeSpecWithTestHandler()
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class CompanionObject() {
+ public companion object
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class CompanionObject {
+ companion object
+ }
+
+ @Test
+ fun namedCompanionObject() {
+ val typeSpec = NamedCompanionObject::class.toTypeSpecWithTestHandler()
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class NamedCompanionObject() {
+ public companion object Named
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class NamedCompanionObject {
+ companion object Named
+ }
+
+ @Test
+ fun generics() {
+ val typeSpec = Generics::class.toTypeSpecWithTestHandler()
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class Generics<out T, in R, V>(
+ public val genericInput: T,
+ )
+ """.trimIndent(),
+ )
+ }
+
+ class Generics<out T, in R, V>(val genericInput: T)
+
+ @Test
+ fun typeAliases() {
+ val typeSpec = TypeAliases::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class TypeAliases(
+ public val foo: com.squareup.kotlinpoet.metadata.specs.TypeAliasName,
+ public val bar: com.squareup.kotlinpoet.metadata.specs.GenericTypeAlias,
+ )
+ """.trimIndent(),
+ )
+
+ val fooPropertyType = typeSpec.propertySpecs.first { it.name == "foo" }.type
+ val fooAliasData = fooPropertyType.tag<TypeAliasTag>()
+ checkNotNull(fooAliasData)
+ assertThat(fooAliasData.abbreviatedType).isEqualTo(STRING)
+
+ val barPropertyType = typeSpec.propertySpecs.first { it.name == "bar" }.type
+ val barAliasData = barPropertyType.tag<TypeAliasTag>()
+ checkNotNull(barAliasData)
+ assertThat(barAliasData.abbreviatedType).isEqualTo(LIST.parameterizedBy(STRING))
+ }
+
+ class TypeAliases(val foo: TypeAliasName, val bar: GenericTypeAlias)
+
+ @Test
+ fun propertyMutability() {
+ val typeSpec = PropertyMutability::class.toTypeSpecWithTestHandler()
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class PropertyMutability(
+ public val foo: kotlin.String,
+ public var mutableFoo: kotlin.String,
+ )
+ """.trimIndent(),
+ )
+ }
+
+ class PropertyMutability(val foo: String, var mutableFoo: String)
+
+ @Test
+ fun collectionMutability() {
+ val typeSpec = CollectionMutability::class.toTypeSpecWithTestHandler()
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class CollectionMutability(
+ public val immutableList: kotlin.collections.List<kotlin.String>,
+ public val mutableList: kotlin.collections.MutableList<kotlin.String>,
+ )
+ """.trimIndent(),
+ )
+ }
+
+ class CollectionMutability(val immutableList: List<String>, val mutableList: MutableList<String>)
+
+ @Test
+ fun suspendTypes() {
+ val typeSpec = SuspendTypes::class.toTypeSpecWithTestHandler()
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class SuspendTypes() {
+ public val testProp: suspend (kotlin.Int, kotlin.Long) -> kotlin.String = throw NotImplementedError("Stub!")
+
+ public suspend fun testComplexSuspendFun(body: suspend (kotlin.Int, suspend (kotlin.Long) -> kotlin.String) -> kotlin.String): kotlin.Unit {
+ }
+
+ public fun testFun(body: suspend (kotlin.Int, kotlin.Long) -> kotlin.String): kotlin.Unit {
+ }
+
+ public suspend fun testSuspendFun(param1: kotlin.String): kotlin.Unit {
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class SuspendTypes {
+ val testProp: suspend (Int, Long) -> String = { _, _ -> "" }
+
+ fun testFun(body: suspend (Int, Long) -> String) {
+ }
+
+ suspend fun testSuspendFun(param1: String) {
+ }
+
+ suspend fun testComplexSuspendFun(body: suspend (Int, suspend (Long) -> String) -> String) {
+ }
+ }
+
+ @Test
+ fun parameters() {
+ val typeSpec = Parameters::class.toTypeSpecWithTestHandler()
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class Parameters() {
+ public inline fun hasDefault(param1: kotlin.String = throw NotImplementedError("Stub!")): kotlin.Unit {
+ }
+
+ public inline fun `inline`(crossinline param1: () -> kotlin.String): kotlin.Unit {
+ }
+
+ public inline fun `noinline`(noinline param1: () -> kotlin.String): kotlin.String = throw NotImplementedError("Stub!")
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class Parameters {
+ inline fun inline(crossinline param1: () -> String) {
+ }
+
+ inline fun noinline(noinline param1: () -> String): String {
+ return ""
+ }
+
+ inline fun hasDefault(param1: String = "Nope") {
+ }
+ }
+
+ @Test
+ fun lambdaReceiver() {
+ val typeSpec = LambdaReceiver::class.toTypeSpecWithTestHandler()
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class LambdaReceiver() {
+ public fun lambdaReceiver(block: kotlin.String.() -> kotlin.Unit): kotlin.Unit {
+ }
+
+ public fun lambdaReceiver2(block: kotlin.String.(kotlin.Int) -> kotlin.Unit): kotlin.Unit {
+ }
+
+ public fun lambdaReceiver3(block: kotlin.String.(kotlin.Int, kotlin.String) -> kotlin.Unit): kotlin.Unit {
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class LambdaReceiver {
+ fun lambdaReceiver(block: String.() -> Unit) {
+ }
+ fun lambdaReceiver2(block: String.(Int) -> Unit) {
+ }
+ fun lambdaReceiver3(block: String.(Int, String) -> Unit) {
+ }
+ }
+
+ @Test
+ fun nestedTypeAlias() {
+ val typeSpec = NestedTypeAliasTest::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class NestedTypeAliasTest() {
+ public val prop: com.squareup.kotlinpoet.metadata.specs.NestedTypeAlias = throw NotImplementedError("Stub!")
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class NestedTypeAliasTest {
+ val prop: NestedTypeAlias = listOf(listOf(""))
+ }
+
+ @Test
+ fun inlineClass() {
+ val typeSpec = InlineClass::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ @kotlin.jvm.JvmInline
+ public value class InlineClass(
+ public val `value`: kotlin.String,
+ )
+ """.trimIndent(),
+ )
+ }
+
+ @Test
+ fun valueClass() {
+ val typeSpec = ValueClass::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ @kotlin.jvm.JvmInline
+ public value class ValueClass(
+ public val `value`: kotlin.String,
+ )
+ """.trimIndent(),
+ )
+ }
+
+ @Test
+ fun functionReferencingTypeParam() {
+ val typeSpec = FunctionsReferencingTypeParameters::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class FunctionsReferencingTypeParameters<T>() {
+ public fun test(`param`: T): kotlin.Unit {
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class FunctionsReferencingTypeParameters<T> {
+ fun test(param: T) {
+ }
+ }
+
+ @Test
+ fun overriddenThings() {
+ val typeSpec = OverriddenThings::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public abstract class OverriddenThings() : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.OverriddenThingsBase(), com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.OverriddenThingsInterface {
+ public override var openProp: kotlin.String = throw NotImplementedError("Stub!")
+
+ public override var openPropInterface: kotlin.String = throw NotImplementedError("Stub!")
+
+ public override fun openFunction(): kotlin.Unit {
+ }
+
+ public override fun openFunctionInterface(): kotlin.Unit {
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ abstract class OverriddenThingsBase {
+ abstract var openProp: String
+
+ abstract fun openFunction()
+ }
+
+ interface OverriddenThingsInterface {
+ var openPropInterface: String
+
+ fun openFunctionInterface()
+ }
+
+ abstract class OverriddenThings : OverriddenThingsBase(), OverriddenThingsInterface {
+ override var openProp: String = ""
+ override var openPropInterface: String = ""
+
+ override fun openFunction() {
+ }
+
+ override fun openFunctionInterface() {
+ }
+ }
+
+ @Test
+ fun delegatedProperties() {
+ val typeSpec = DelegatedProperties::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class DelegatedProperties() {
+ /**
+ * Note: delegation is ABI stub only and not guaranteed to match source code.
+ */
+ public val immutable: kotlin.String by kotlin.lazy { throw NotImplementedError("Stub!") }
+
+ /**
+ * Note: delegation is ABI stub only and not guaranteed to match source code.
+ */
+ public val immutableNullable: kotlin.String? by kotlin.lazy { throw NotImplementedError("Stub!") }
+
+ /**
+ * Note: delegation is ABI stub only and not guaranteed to match source code.
+ */
+ public var mutable: kotlin.String by kotlin.properties.Delegates.notNull()
+
+ /**
+ * Note: delegation is ABI stub only and not guaranteed to match source code.
+ */
+ public var mutableNullable: kotlin.String? by kotlin.properties.Delegates.observable(null) { _, _, _ -> }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class DelegatedProperties {
+ val immutable: String by lazy { "" }
+ val immutableNullable: String? by lazy { "" }
+ var mutable: String by Delegates.notNull()
+ var mutableNullable: String? by Delegates.observable(null) { _, _, _ -> }
+ }
+
+ @Ignore("Need to be able to know about class delegation in metadata")
+ @Test
+ fun classDelegation() {
+ val typeSpec = ClassDelegation::class.toTypeSpecWithTestHandler()
+
+ // TODO Assert this also excludes functions handled by the delegate
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class ClassDelegation<T>(
+ delegate: List<T>
+ ): List<T> by delegate
+ """.trimIndent(),
+ )
+ }
+
+ class ClassDelegation<T>(delegate: List<T>) : List<T> by delegate
+
+ @Test
+ fun simpleEnum() {
+ val typeSpec = SimpleEnum::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public enum class SimpleEnum() {
+ FOO,
+ BAR,
+ BAZ,
+ }
+ """.trimIndent(),
+ )
+ }
+
+ enum class SimpleEnum {
+ FOO, BAR, BAZ
+ }
+
+ @Test
+ fun complexEnum() {
+ val typeSpec = ComplexEnum::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public enum class ComplexEnum(
+ public val `value`: kotlin.String,
+ ) {
+ FOO {
+ public override fun toString(): kotlin.String = throw NotImplementedError("Stub!")
+ },
+ BAR {
+ public override fun toString(): kotlin.String = throw NotImplementedError("Stub!")
+ },
+ BAZ {
+ public override fun toString(): kotlin.String = throw NotImplementedError("Stub!")
+ },
+ ;
+ }
+ """.trimIndent(),
+ )
+ }
+
+ enum class ComplexEnum(val value: String) {
+ FOO("foo") {
+ override fun toString(): String {
+ return "foo1"
+ }
+ },
+ BAR("bar") {
+ override fun toString(): String {
+ return "bar1"
+ }
+ },
+ BAZ("baz") {
+ override fun toString(): String {
+ return "baz1"
+ }
+ },
+ }
+
+ @Test
+ fun enumWithAnnotation() {
+ val typeSpec = EnumWithAnnotation::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public enum class EnumWithAnnotation() {
+ FOO,
+ @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.FieldAnnotation
+ BAR,
+ BAZ,
+ }
+ """.trimIndent(),
+ )
+ }
+
+ enum class EnumWithAnnotation {
+ FOO, @FieldAnnotation
+ BAR, BAZ
+ }
+
+ @Test
+ fun complexEnumWithAnnotation() {
+ val typeSpec = ComplexEnumWithAnnotation::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public enum class ComplexEnumWithAnnotation(
+ public val `value`: kotlin.String,
+ ) {
+ FOO {
+ public override fun toString(): kotlin.String = throw NotImplementedError("Stub!")
+ },
+ @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.FieldAnnotation
+ BAR {
+ public override fun toString(): kotlin.String = throw NotImplementedError("Stub!")
+ },
+ BAZ {
+ public override fun toString(): kotlin.String = throw NotImplementedError("Stub!")
+ },
+ ;
+ }
+ """.trimIndent(),
+ )
+ }
+
+ enum class ComplexEnumWithAnnotation(val value: String) {
+ FOO("foo") {
+ override fun toString(): String {
+ return "foo1"
+ }
+ },
+
+ @FieldAnnotation
+ BAR("bar") {
+ override fun toString(): String {
+ return "bar1"
+ }
+ },
+ BAZ("baz") {
+ override fun toString(): String {
+ return "baz1"
+ }
+ },
+ }
+
+ @Test
+ fun interfaces() {
+ val testInterfaceSpec = TestInterface::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(testInterfaceSpec.trimmedToString()).isEqualTo(
+ """
+ public interface TestInterface {
+ public fun complex(input: kotlin.String, input2: kotlin.String = throw NotImplementedError("Stub!")): kotlin.String = throw NotImplementedError("Stub!")
+
+ public fun hasDefault(): kotlin.Unit {
+ }
+
+ public fun hasDefaultMultiParam(input: kotlin.String, input2: kotlin.String): kotlin.String = throw NotImplementedError("Stub!")
+
+ public fun hasDefaultSingleParam(input: kotlin.String): kotlin.String = throw NotImplementedError("Stub!")
+
+ @kotlin.jvm.JvmDefault
+ public fun hasJvmDefault(): kotlin.Unit {
+ }
+
+ public fun noDefault(): kotlin.Unit
+
+ public fun noDefaultWithInput(input: kotlin.String): kotlin.Unit
+
+ public fun noDefaultWithInputDefault(input: kotlin.String = throw NotImplementedError("Stub!")): kotlin.Unit
+ }
+ """.trimIndent(),
+ )
+
+ val subInterfaceSpec = SubInterface::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(subInterfaceSpec.trimmedToString()).isEqualTo(
+ """
+ public interface SubInterface : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.TestInterface {
+ public override fun hasDefault(): kotlin.Unit {
+ }
+
+ @kotlin.jvm.JvmDefault
+ public override fun hasJvmDefault(): kotlin.Unit {
+ }
+
+ public fun subInterfaceFunction(): kotlin.Unit {
+ }
+ }
+ """.trimIndent(),
+ )
+
+ val implSpec = TestSubInterfaceImpl::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(implSpec.trimmedToString()).isEqualTo(
+ """
+ public class TestSubInterfaceImpl() : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.SubInterface {
+ public override fun noDefault(): kotlin.Unit {
+ }
+
+ public override fun noDefaultWithInput(input: kotlin.String): kotlin.Unit {
+ }
+
+ public override fun noDefaultWithInputDefault(input: kotlin.String): kotlin.Unit {
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ interface TestInterface {
+
+ fun noDefault()
+
+ fun noDefaultWithInput(input: String)
+
+ fun noDefaultWithInputDefault(input: String = "")
+
+ @JvmDefault
+ fun hasJvmDefault() {
+ }
+
+ fun hasDefault() {
+ }
+
+ fun hasDefaultSingleParam(input: String): String {
+ return "1234"
+ }
+
+ fun hasDefaultMultiParam(input: String, input2: String): String {
+ return "1234"
+ }
+
+ fun complex(input: String, input2: String = ""): String {
+ return "5678"
+ }
+ }
+
+ interface SubInterface : TestInterface {
+ fun subInterfaceFunction() {
+ }
+
+ @JvmDefault
+ override fun hasJvmDefault() {
+ super.hasJvmDefault()
+ }
+
+ override fun hasDefault() {
+ super.hasDefault()
+ }
+ }
+
+ class TestSubInterfaceImpl : SubInterface {
+ override fun noDefault() {
+ }
+
+ override fun noDefaultWithInput(input: String) {
+ }
+
+ override fun noDefaultWithInputDefault(input: String) {
+ }
+ }
+
+ @Test
+ fun backwardReferencingTypeVars() {
+ val typeSpec = BackwardReferencingTypeVars::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public interface BackwardReferencingTypeVars<T> : kotlin.collections.List<kotlin.collections.Set<T>>
+ """.trimIndent(),
+ )
+ }
+
+ interface BackwardReferencingTypeVars<T> : List<Set<T>>
+
+ @Test
+ fun taggedTypes() {
+ val typeSpec = TaggedTypes::class.toTypeSpecWithTestHandler()
+ assertThat(typeSpec.tag<KmClass>()).isNotNull()
+
+ val constructorSpec = typeSpec.primaryConstructor ?: fail("No constructor found!")
+ assertThat(constructorSpec.tag<KmConstructor>()).isNotNull()
+
+ val parameterSpec = constructorSpec.parameters[0]
+ assertThat(parameterSpec.tag<KmValueParameter>()).isNotNull()
+
+ val typeVar = typeSpec.typeVariables[0]
+ assertThat(typeVar.tag<KmTypeParameter>()).isNotNull()
+
+ val funSpec = typeSpec.funSpecs[0]
+ assertThat(funSpec.tag<KmFunction>()).isNotNull()
+
+ val propertySpec = typeSpec.propertySpecs[0]
+ assertThat(propertySpec.tag<KmProperty>()).isNotNull()
+ }
+
+ class TaggedTypes<T>(val param: T) {
+ val property: String = ""
+
+ fun function() {
+ }
+ }
+
+ @Test
+ fun annotations() {
+ val typeSpec = MyAnnotation::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public annotation class MyAnnotation(
+ public val `value`: kotlin.String,
+ )
+ """.trimIndent(),
+ )
+ }
+
+ annotation class MyAnnotation(val value: String)
+
+ @Test
+ fun functionTypeArgsSupersedeClass() {
+ val typeSpec = GenericClass::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class GenericClass<T>() {
+ public fun <T> functionAlsoWithT(`param`: T): kotlin.Unit {
+ }
+
+ public fun <R> functionWithADifferentType(`param`: R): kotlin.Unit {
+ }
+
+ public fun functionWithT(`param`: T): kotlin.Unit {
+ }
+
+ /**
+ * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing.
+ */
+ public inline fun <reified T> `reified`(`param`: T): kotlin.Unit {
+ }
+ }
+ """.trimIndent(),
+ )
+
+ val func1TypeVar = typeSpec.funSpecs.find { it.name == "functionAlsoWithT" }!!.typeVariables.first()
+ val classTypeVar = typeSpec.typeVariables.first()
+
+ assertThat(func1TypeVar).isNotSameInstanceAs(classTypeVar)
+ }
+
+ class GenericClass<T> {
+ fun functionWithT(param: T) {
+ }
+ fun <T> functionAlsoWithT(param: T) {
+ }
+ fun <R> functionWithADifferentType(param: R) {
+ }
+
+ // Regression for https://github.com/square/kotlinpoet/issues/829
+ inline fun <reified T> reified(param: T) {
+ }
+ }
+
+ @Test
+ fun complexCompanionObject() {
+ val typeSpec = ComplexCompanionObject::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class ComplexCompanionObject() {
+ public companion object ComplexObject : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CompanionBase(), com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CompanionInterface
+ }
+ """.trimIndent(),
+ )
+ }
+
+ interface CompanionInterface
+ open class CompanionBase
+
+ class ComplexCompanionObject {
+ companion object ComplexObject : CompanionBase(), CompanionInterface
+ }
+
+ @IgnoreForHandlerType(
+ reason = "TODO Synthetic methods that hold annotations aren't visible in these tests",
+ handlerType = ELEMENTS,
+ )
+ @Test
+ fun annotationsAreCopied() {
+ val typeSpec = AnnotationHolders::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class AnnotationHolders @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.ConstructorAnnotation constructor() {
+ @field:com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.FieldAnnotation
+ public var `field`: kotlin.String? = null
+
+ @get:com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.GetterAnnotation
+ public var getter: kotlin.String? = null
+
+ @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.HolderAnnotation
+ @kotlin.jvm.JvmField
+ public var holder: kotlin.String? = null
+
+ @set:com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.SetterAnnotation
+ public var setter: kotlin.String? = null
+
+ @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.ConstructorAnnotation
+ public constructor(`value`: kotlin.String)
+
+ @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.FunctionAnnotation
+ public fun function(): kotlin.Unit {
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class AnnotationHolders @ConstructorAnnotation constructor() {
+
+ @ConstructorAnnotation
+ constructor(value: String) : this()
+
+ @field:FieldAnnotation
+ var field: String? = null
+
+ @get:GetterAnnotation
+ var getter: String? = null
+
+ @set:SetterAnnotation
+ var setter: String? = null
+
+ @HolderAnnotation
+ @JvmField
+ var holder: String? = null
+
+ @FunctionAnnotation
+ fun function() {
+ }
+ }
+
+ @Retention(RUNTIME)
+ annotation class ConstructorAnnotation
+
+ @Retention(RUNTIME)
+ annotation class FieldAnnotation
+
+ @Retention(RUNTIME)
+ annotation class GetterAnnotation
+
+ @Retention(RUNTIME)
+ annotation class SetterAnnotation
+
+ @Retention(RUNTIME)
+ annotation class HolderAnnotation
+
+ @Retention(RUNTIME)
+ annotation class FunctionAnnotation
+
+ @IgnoreForHandlerType(
+ reason = "Elements properly resolves the regular properties + JvmStatic, but reflection will not",
+ handlerType = REFLECTIVE,
+ )
+ @Test
+ fun constantValuesElements() {
+ val typeSpec = Constants::class.toTypeSpecWithTestHandler()
+
+ // Note: formats like hex/binary/underscore are not available as formatted at runtime
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class Constants(
+ public val `param`: kotlin.String = throw NotImplementedError("Stub!"),
+ ) {
+ public val binaryProp: kotlin.Int = throw NotImplementedError("Stub!")
+
+ public val boolProp: kotlin.Boolean = throw NotImplementedError("Stub!")
+
+ public val doubleProp: kotlin.Double = throw NotImplementedError("Stub!")
+
+ public val floatProp: kotlin.Float = throw NotImplementedError("Stub!")
+
+ public val hexProp: kotlin.Int = throw NotImplementedError("Stub!")
+
+ public val intProp: kotlin.Int = throw NotImplementedError("Stub!")
+
+ public val longProp: kotlin.Long = throw NotImplementedError("Stub!")
+
+ public val stringProp: kotlin.String = throw NotImplementedError("Stub!")
+
+ public val underscoresHexProp: kotlin.Long = throw NotImplementedError("Stub!")
+
+ public val underscoresProp: kotlin.Int = throw NotImplementedError("Stub!")
+
+ public companion object {
+ public const val CONST_BINARY_PROP: kotlin.Int = 11
+
+ public const val CONST_BOOL_PROP: kotlin.Boolean = false
+
+ public const val CONST_DOUBLE_PROP: kotlin.Double = 1.0
+
+ public const val CONST_FLOAT_PROP: kotlin.Float = 1.0F
+
+ public const val CONST_HEX_PROP: kotlin.Int = 15
+
+ public const val CONST_INT_PROP: kotlin.Int = 1
+
+ public const val CONST_LONG_PROP: kotlin.Long = 1L
+
+ public const val CONST_STRING_PROP: kotlin.String = "prop"
+
+ public const val CONST_UNDERSCORES_HEX_PROP: kotlin.Long = 4_293_713_502L
+
+ public const val CONST_UNDERSCORES_PROP: kotlin.Int = 1_000_000
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_BINARY_PROP: kotlin.Int = throw NotImplementedError("Stub!")
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_BOOL_PROP: kotlin.Boolean = throw NotImplementedError("Stub!")
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_DOUBLE_PROP: kotlin.Double = throw NotImplementedError("Stub!")
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_FLOAT_PROP: kotlin.Float = throw NotImplementedError("Stub!")
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_HEX_PROP: kotlin.Int = throw NotImplementedError("Stub!")
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_INT_PROP: kotlin.Int = throw NotImplementedError("Stub!")
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_LONG_PROP: kotlin.Long = throw NotImplementedError("Stub!")
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_STRING_PROP: kotlin.String = throw NotImplementedError("Stub!")
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_UNDERSCORES_HEX_PROP: kotlin.Long = throw NotImplementedError("Stub!")
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_UNDERSCORES_PROP: kotlin.Int = throw NotImplementedError("Stub!")
+ }
+ }
+ """.trimIndent(),
+ )
+
+ // TODO check with objects
+ }
+
+ @IgnoreForHandlerType(
+ reason = "Elements properly resolves the regular properties + JvmStatic, but reflection will not",
+ handlerType = ELEMENTS,
+ )
+ @Test
+ fun constantValuesReflective() {
+ val typeSpec = Constants::class.toTypeSpecWithTestHandler()
+
+ // Note: formats like hex/binary/underscore are not available as formatted in elements
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class Constants(
+ public val `param`: kotlin.String = throw NotImplementedError("Stub!"),
+ ) {
+ public val binaryProp: kotlin.Int = throw NotImplementedError("Stub!")
+
+ public val boolProp: kotlin.Boolean = throw NotImplementedError("Stub!")
+
+ public val doubleProp: kotlin.Double = throw NotImplementedError("Stub!")
+
+ public val floatProp: kotlin.Float = throw NotImplementedError("Stub!")
+
+ public val hexProp: kotlin.Int = throw NotImplementedError("Stub!")
+
+ public val intProp: kotlin.Int = throw NotImplementedError("Stub!")
+
+ public val longProp: kotlin.Long = throw NotImplementedError("Stub!")
+
+ public val stringProp: kotlin.String = throw NotImplementedError("Stub!")
+
+ public val underscoresHexProp: kotlin.Long = throw NotImplementedError("Stub!")
+
+ public val underscoresProp: kotlin.Int = throw NotImplementedError("Stub!")
+
+ public companion object {
+ public const val CONST_BINARY_PROP: kotlin.Int = 11
+
+ public const val CONST_BOOL_PROP: kotlin.Boolean = false
+
+ public const val CONST_DOUBLE_PROP: kotlin.Double = 1.0
+
+ public const val CONST_FLOAT_PROP: kotlin.Float = 1.0F
+
+ public const val CONST_HEX_PROP: kotlin.Int = 15
+
+ public const val CONST_INT_PROP: kotlin.Int = 1
+
+ public const val CONST_LONG_PROP: kotlin.Long = 1L
+
+ public const val CONST_STRING_PROP: kotlin.String = "prop"
+
+ public const val CONST_UNDERSCORES_HEX_PROP: kotlin.Long = 4_293_713_502L
+
+ public const val CONST_UNDERSCORES_PROP: kotlin.Int = 1_000_000
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_BINARY_PROP: kotlin.Int = 11
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_BOOL_PROP: kotlin.Boolean = false
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_DOUBLE_PROP: kotlin.Double = 1.0
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_FLOAT_PROP: kotlin.Float = 1.0F
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_HEX_PROP: kotlin.Int = 15
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_INT_PROP: kotlin.Int = 1
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_LONG_PROP: kotlin.Long = 1L
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_STRING_PROP: kotlin.String = "prop"
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_UNDERSCORES_HEX_PROP: kotlin.Long = 4_293_713_502L
+
+ @kotlin.jvm.JvmStatic
+ public val STATIC_CONST_UNDERSCORES_PROP: kotlin.Int = 1_000_000
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class Constants(
+ val param: String = "param",
+ ) {
+ val boolProp = false
+ val binaryProp = 0b00001011
+ val intProp = 1
+ val underscoresProp = 1_000_000
+ val hexProp = 0x0F
+ val underscoresHexProp = 0xFF_EC_DE_5E
+ val longProp = 1L
+ val floatProp = 1.0F
+ val doubleProp = 1.0
+ val stringProp = "prop"
+
+ companion object {
+ @JvmStatic val STATIC_CONST_BOOL_PROP = false
+
+ @JvmStatic val STATIC_CONST_BINARY_PROP = 0b00001011
+
+ @JvmStatic val STATIC_CONST_INT_PROP = 1
+
+ @JvmStatic val STATIC_CONST_UNDERSCORES_PROP = 1_000_000
+
+ @JvmStatic val STATIC_CONST_HEX_PROP = 0x0F
+
+ @JvmStatic val STATIC_CONST_UNDERSCORES_HEX_PROP = 0xFF_EC_DE_5E
+
+ @JvmStatic val STATIC_CONST_LONG_PROP = 1L
+
+ @JvmStatic val STATIC_CONST_FLOAT_PROP = 1.0f
+
+ @JvmStatic val STATIC_CONST_DOUBLE_PROP = 1.0
+
+ @JvmStatic val STATIC_CONST_STRING_PROP = "prop"
+
+ const val CONST_BOOL_PROP = false
+ const val CONST_BINARY_PROP = 0b00001011
+ const val CONST_INT_PROP = 1
+ const val CONST_UNDERSCORES_PROP = 1_000_000
+ const val CONST_HEX_PROP = 0x0F
+ const val CONST_UNDERSCORES_HEX_PROP = 0xFF_EC_DE_5E
+ const val CONST_LONG_PROP = 1L
+ const val CONST_FLOAT_PROP = 1.0f
+ const val CONST_DOUBLE_PROP = 1.0
+ const val CONST_STRING_PROP = "prop"
+ }
+ }
+
+ @Test
+ fun jvmAnnotations() {
+ val typeSpec = JvmAnnotations::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class JvmAnnotations() {
+ @get:kotlin.jvm.Synchronized
+ public val synchronizedGetProp: kotlin.String? = null
+
+ @set:kotlin.jvm.Synchronized
+ public var synchronizedSetProp: kotlin.String? = null
+
+ @kotlin.jvm.Transient
+ public val transientProp: kotlin.String? = null
+
+ @kotlin.jvm.Volatile
+ public var volatileProp: kotlin.String? = null
+
+ @kotlin.jvm.Synchronized
+ public fun synchronizedFun(): kotlin.Unit {
+ }
+ }
+ """.trimIndent(),
+ )
+
+ val interfaceSpec = JvmAnnotationsInterface::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(interfaceSpec.trimmedToString()).isEqualTo(
+ """
+ public interface JvmAnnotationsInterface {
+ @kotlin.jvm.JvmDefault
+ public fun defaultMethod(): kotlin.Unit {
+ }
+
+ public fun notDefaultMethod(): kotlin.Unit
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class JvmAnnotations {
+ @Transient val transientProp: String? = null
+
+ @Volatile var volatileProp: String? = null
+
+ @get:Synchronized val synchronizedGetProp: String? = null
+
+ @set:Synchronized var synchronizedSetProp: String? = null
+
+ @Synchronized
+ fun synchronizedFun() {
+ }
+ }
+
+ interface JvmAnnotationsInterface {
+ @JvmDefault
+ fun defaultMethod() {
+ }
+ fun notDefaultMethod()
+ }
+
+ @Test
+ fun nestedClasses() {
+ val typeSpec = NestedClasses::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class NestedClasses() {
+ public abstract class NestedClass<T>() : kotlin.collections.List<T>
+
+ public inner class NestedInnerClass()
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class NestedClasses {
+ abstract class NestedClass<T> : List<T>
+ inner class NestedInnerClass
+ }
+
+ @IgnoreForHandlerType(
+ reason = "Reflection properly resolves companion properties + JvmStatic + JvmName, but " +
+ "elements will not",
+ handlerType = ELEMENTS,
+ )
+ @Test
+ fun jvmNamesReflective() {
+ val typeSpec = JvmNameData::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class JvmNameData(
+ @get:kotlin.jvm.JvmName(name = "jvmParam")
+ public val `param`: kotlin.String,
+ ) {
+ @get:kotlin.jvm.JvmName(name = "jvmPropertyGet")
+ public val propertyGet: kotlin.String? = null
+
+ @get:kotlin.jvm.JvmName(name = "jvmPropertyGetAndSet")
+ @set:kotlin.jvm.JvmName(name = "jvmPropertyGetAndSet")
+ public var propertyGetAndSet: kotlin.String? = null
+
+ @set:kotlin.jvm.JvmName(name = "jvmPropertySet")
+ public var propertySet: kotlin.String? = null
+
+ @kotlin.jvm.JvmName(name = "jvmFunction")
+ public fun function(): kotlin.Unit {
+ }
+
+ public interface InterfaceWithJvmName {
+ public companion object {
+ @get:kotlin.jvm.JvmName(name = "fooBoolJvm")
+ @kotlin.jvm.JvmStatic
+ public val FOO_BOOL: kotlin.Boolean = false
+
+ @kotlin.jvm.JvmName(name = "jvmStaticFunction")
+ @kotlin.jvm.JvmStatic
+ public fun staticFunction(): kotlin.Unit {
+ }
+ }
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ @IgnoreForHandlerType(
+ reason = "Reflection properly resolves companion properties + JvmStatic + JvmName, but " +
+ "elements will not",
+ handlerType = REFLECTIVE,
+ )
+ @Test
+ fun jvmNamesElements() {
+ val typeSpec = JvmNameData::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class JvmNameData(
+ @get:kotlin.jvm.JvmName(name = "jvmParam")
+ public val `param`: kotlin.String,
+ ) {
+ @get:kotlin.jvm.JvmName(name = "jvmPropertyGet")
+ public val propertyGet: kotlin.String? = null
+
+ @get:kotlin.jvm.JvmName(name = "jvmPropertyGetAndSet")
+ @set:kotlin.jvm.JvmName(name = "jvmPropertyGetAndSet")
+ public var propertyGetAndSet: kotlin.String? = null
+
+ @set:kotlin.jvm.JvmName(name = "jvmPropertySet")
+ public var propertySet: kotlin.String? = null
+
+ @kotlin.jvm.JvmName(name = "jvmFunction")
+ public fun function(): kotlin.Unit {
+ }
+
+ public interface InterfaceWithJvmName {
+ public companion object {
+ @get:kotlin.jvm.JvmName(name = "fooBoolJvm")
+ @kotlin.jvm.JvmStatic
+ public val FOO_BOOL: kotlin.Boolean = throw NotImplementedError("Stub!")
+
+ @kotlin.jvm.JvmName(name = "jvmStaticFunction")
+ @kotlin.jvm.JvmStatic
+ public fun staticFunction(): kotlin.Unit {
+ }
+ }
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class JvmNameData(
+ @get:JvmName("jvmParam") val param: String,
+ ) {
+
+ @get:JvmName("jvmPropertyGet")
+ val propertyGet: String? = null
+
+ @set:JvmName("jvmPropertySet")
+ var propertySet: String? = null
+
+ @set:JvmName("jvmPropertyGetAndSet")
+ @get:JvmName("jvmPropertyGetAndSet")
+ var propertyGetAndSet: String? = null
+
+ @JvmName("jvmFunction")
+ fun function() {
+ }
+
+ // Interfaces can't have JvmName, but covering a potential edge case of having a companion
+ // object with JvmName elements. Also covers an edge case where constants have getters
+ interface InterfaceWithJvmName {
+ companion object {
+ @JvmStatic
+ @get:JvmName("fooBoolJvm")
+ val FOO_BOOL = false
+
+ @JvmName("jvmStaticFunction")
+ @JvmStatic
+ fun staticFunction() {
+ }
+ }
+ }
+ }
+
+ @IgnoreForHandlerType(
+ reason = "JvmOverloads is not runtime retained and thus not visible to reflection.",
+ handlerType = REFLECTIVE,
+ )
+ @Test
+ fun overloads() {
+ val typeSpec = Overloads::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class Overloads @kotlin.jvm.JvmOverloads constructor(
+ public val param1: kotlin.String,
+ public val optionalParam2: kotlin.String = throw NotImplementedError("Stub!"),
+ public val nullableParam3: kotlin.String? = throw NotImplementedError("Stub!"),
+ ) {
+ @kotlin.jvm.JvmOverloads
+ public fun testFunction(
+ param1: kotlin.String,
+ optionalParam2: kotlin.String = throw NotImplementedError("Stub!"),
+ nullableParam3: kotlin.String? = throw NotImplementedError("Stub!"),
+ ): kotlin.Unit {
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class Overloads @JvmOverloads constructor(
+ val param1: String,
+ val optionalParam2: String = "",
+ val nullableParam3: String? = null,
+ ) {
+ @JvmOverloads
+ fun testFunction(
+ param1: String,
+ optionalParam2: String = "",
+ nullableParam3: String? = null,
+ ) {
+ }
+ }
+
+ @IgnoreForHandlerType(
+ reason = "Elements generates initializer values.",
+ handlerType = ELEMENTS,
+ )
+ @Test
+ fun jvmFields_reflective() {
+ val typeSpec = Fields::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class Fields(
+ @property:kotlin.jvm.JvmField
+ public val param1: kotlin.String,
+ ) {
+ @kotlin.jvm.JvmField
+ public val fieldProp: kotlin.String = throw NotImplementedError("Stub!")
+
+ public companion object {
+ @kotlin.jvm.JvmField
+ public val companionProp: kotlin.String = ""
+
+ public const val constCompanionProp: kotlin.String = ""
+
+ @kotlin.jvm.JvmStatic
+ public val staticCompanionProp: kotlin.String = ""
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ @IgnoreForHandlerType(
+ reason = "Elements generates initializer values.",
+ handlerType = REFLECTIVE,
+ )
+ @Test
+ fun jvmFields_elements() {
+ val typeSpec = Fields::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class Fields(
+ @property:kotlin.jvm.JvmField
+ public val param1: kotlin.String,
+ ) {
+ @kotlin.jvm.JvmField
+ public val fieldProp: kotlin.String = throw NotImplementedError("Stub!")
+
+ public companion object {
+ @kotlin.jvm.JvmField
+ public val companionProp: kotlin.String = throw NotImplementedError("Stub!")
+
+ public const val constCompanionProp: kotlin.String = ""
+
+ @kotlin.jvm.JvmStatic
+ public val staticCompanionProp: kotlin.String = throw NotImplementedError("Stub!")
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class Fields(
+ @JvmField val param1: String,
+ ) {
+ @JvmField val fieldProp: String = ""
+
+ companion object {
+ @JvmField val companionProp: String = ""
+
+ @JvmStatic val staticCompanionProp: String = ""
+ const val constCompanionProp: String = ""
+ }
+ }
+
+ @IgnoreForHandlerType(
+ reason = "Synthetic constructs aren't available in elements, so some information like " +
+ "JvmStatic can't be deduced.",
+ handlerType = ELEMENTS,
+ )
+ @Test
+ fun synthetics_reflective() {
+ val typeSpec = Synthetics::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class Synthetics(
+ @get:kotlin.jvm.JvmSynthetic
+ public val `param`: kotlin.String,
+ ) {
+ @field:kotlin.jvm.JvmSynthetic
+ public val fieldProperty: kotlin.String? = null
+
+ @field:kotlin.jvm.JvmSynthetic
+ public val `property`: kotlin.String? = null
+
+ @get:kotlin.jvm.JvmSynthetic
+ public val propertyGet: kotlin.String? = null
+
+ @get:kotlin.jvm.JvmSynthetic
+ @set:kotlin.jvm.JvmSynthetic
+ public var propertyGetAndSet: kotlin.String? = null
+
+ @set:kotlin.jvm.JvmSynthetic
+ public var propertySet: kotlin.String? = null
+
+ /**
+ * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing.
+ */
+ @kotlin.jvm.JvmSynthetic
+ public fun function(): kotlin.Unit {
+ }
+
+ public interface InterfaceWithJvmName {
+ /**
+ * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing.
+ */
+ @kotlin.jvm.JvmSynthetic
+ public fun interfaceFunction(): kotlin.Unit
+
+ public companion object {
+ @get:kotlin.jvm.JvmSynthetic
+ @kotlin.jvm.JvmStatic
+ public val FOO_BOOL: kotlin.Boolean = false
+
+ /**
+ * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing.
+ */
+ @kotlin.jvm.JvmStatic
+ @kotlin.jvm.JvmSynthetic
+ public fun staticFunction(): kotlin.Unit {
+ }
+ }
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ @IgnoreForHandlerType(
+ reason = "Synthetic constructs aren't available in elements, so some information like " +
+ "JvmStatic can't be deduced.",
+ handlerType = REFLECTIVE,
+ )
+ @Test
+ fun synthetics_elements() {
+ val typeSpec = Synthetics::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class Synthetics(
+ @get:kotlin.jvm.JvmSynthetic
+ public val `param`: kotlin.String,
+ ) {
+ @field:kotlin.jvm.JvmSynthetic
+ public val fieldProperty: kotlin.String? = null
+
+ @field:kotlin.jvm.JvmSynthetic
+ public val `property`: kotlin.String? = null
+
+ @get:kotlin.jvm.JvmSynthetic
+ public val propertyGet: kotlin.String? = null
+
+ @get:kotlin.jvm.JvmSynthetic
+ @set:kotlin.jvm.JvmSynthetic
+ public var propertyGetAndSet: kotlin.String? = null
+
+ @set:kotlin.jvm.JvmSynthetic
+ public var propertySet: kotlin.String? = null
+
+ /**
+ * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing.
+ */
+ @kotlin.jvm.JvmSynthetic
+ public fun function(): kotlin.Unit {
+ }
+
+ public interface InterfaceWithJvmName {
+ /**
+ * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing.
+ */
+ @kotlin.jvm.JvmSynthetic
+ public fun interfaceFunction(): kotlin.Unit
+
+ public companion object {
+ @get:kotlin.jvm.JvmSynthetic
+ @kotlin.jvm.JvmStatic
+ public val FOO_BOOL: kotlin.Boolean = throw NotImplementedError("Stub!")
+
+ /**
+ * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing.
+ */
+ @kotlin.jvm.JvmSynthetic
+ public fun staticFunction(): kotlin.Unit {
+ }
+ }
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class Synthetics(
+ @get:JvmSynthetic val param: String,
+ ) {
+
+ @JvmSynthetic
+ val property: String? = null
+
+ @field:JvmSynthetic
+ val fieldProperty: String? = null
+
+ @get:JvmSynthetic
+ val propertyGet: String? = null
+
+ @set:JvmSynthetic
+ var propertySet: String? = null
+
+ @set:JvmSynthetic
+ @get:JvmSynthetic
+ var propertyGetAndSet: String? = null
+
+ @JvmSynthetic
+ fun function() {
+ }
+
+ // Interfaces can have JvmSynthetic, so covering a potential edge case of having a companion
+ // object with JvmSynthetic elements. Also covers an edge case where constants have getters
+ interface InterfaceWithJvmName {
+ @JvmSynthetic
+ fun interfaceFunction()
+
+ companion object {
+ @JvmStatic
+ @get:JvmSynthetic
+ val FOO_BOOL = false
+
+ @JvmSynthetic
+ @JvmStatic
+ fun staticFunction() {
+ }
+ }
+ }
+ }
+
+ @Test
+ fun throws() {
+ val typeSpec = Throwing::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class Throwing @kotlin.jvm.Throws(exceptionClasses = [java.lang.IllegalStateException::class]) constructor() {
+ @get:kotlin.jvm.Throws(exceptionClasses = [java.lang.IllegalStateException::class])
+ @set:kotlin.jvm.Throws(exceptionClasses = [java.lang.IllegalStateException::class])
+ public var getterAndSetterThrows: kotlin.String? = null
+
+ @get:kotlin.jvm.Throws(exceptionClasses = [java.lang.IllegalStateException::class])
+ public val getterThrows: kotlin.String? = null
+
+ @set:kotlin.jvm.Throws(exceptionClasses = [java.lang.IllegalStateException::class])
+ public var setterThrows: kotlin.String? = null
+
+ @kotlin.jvm.Throws(exceptionClasses = [java.lang.IllegalStateException::class])
+ public fun testFunction(): kotlin.Unit {
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class Throwing
+ @Throws(IllegalStateException::class)
+ constructor() {
+
+ @get:Throws(IllegalStateException::class)
+ val getterThrows: String? = null
+
+ @set:Throws(IllegalStateException::class)
+ var setterThrows: String? = null
+
+ @get:Throws(IllegalStateException::class)
+ @set:Throws(IllegalStateException::class)
+ var getterAndSetterThrows: String? = null
+
+ @Throws(IllegalStateException::class)
+ fun testFunction() {
+ }
+ }
+
+ // The meta-ist of metadata meta-tests.
+ @IgnoreForHandlerType(
+ reason = "Reflection can't parse non-runtime retained annotations",
+ handlerType = REFLECTIVE,
+ )
+ @Test
+ fun metaTest_elements() {
+ val typeSpec = Metadata::class.toTypeSpecWithTestHandler()
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ @kotlin.SinceKotlin(version = "1.3")
+ @kotlin.`annotation`.Retention(value = kotlin.`annotation`.AnnotationRetention.RUNTIME)
+ @kotlin.`annotation`.Target(allowedTargets = arrayOf(kotlin.`annotation`.AnnotationTarget.CLASS))
+ public annotation class Metadata(
+ @get:kotlin.jvm.JvmName(name = "k")
+ public val kind: kotlin.Int = throw NotImplementedError("Stub!"),
+ @get:kotlin.jvm.JvmName(name = "mv")
+ public val metadataVersion: kotlin.IntArray = throw NotImplementedError("Stub!"),
+ @get:kotlin.jvm.JvmName(name = "bv")
+ public val bytecodeVersion: kotlin.IntArray = throw NotImplementedError("Stub!"),
+ @get:kotlin.jvm.JvmName(name = "d1")
+ public val data1: kotlin.Array<kotlin.String> = throw NotImplementedError("Stub!"),
+ @get:kotlin.jvm.JvmName(name = "d2")
+ public val data2: kotlin.Array<kotlin.String> = throw NotImplementedError("Stub!"),
+ @get:kotlin.jvm.JvmName(name = "xs")
+ public val extraString: kotlin.String = throw NotImplementedError("Stub!"),
+ @get:kotlin.jvm.JvmName(name = "pn")
+ public val packageName: kotlin.String = throw NotImplementedError("Stub!"),
+ @get:kotlin.jvm.JvmName(name = "xi")
+ public val extraInt: kotlin.Int = throw NotImplementedError("Stub!"),
+ )
+ """.trimIndent(),
+ )
+ }
+
+ // The meta-ist of metadata meta-tests.
+ @IgnoreForHandlerType(
+ reason = "Reflection can't parse non-runtime retained annotations",
+ handlerType = ELEMENTS,
+ )
+ @Test
+ fun metaTest_reflection() {
+ val typeSpec = Metadata::class.toTypeSpecWithTestHandler()
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ @kotlin.`annotation`.Retention(value = kotlin.`annotation`.AnnotationRetention.RUNTIME)
+ @kotlin.`annotation`.Target(allowedTargets = arrayOf(kotlin.`annotation`.AnnotationTarget.CLASS))
+ public annotation class Metadata(
+ @get:kotlin.jvm.JvmName(name = "k")
+ public val kind: kotlin.Int = throw NotImplementedError("Stub!"),
+ @get:kotlin.jvm.JvmName(name = "mv")
+ public val metadataVersion: kotlin.IntArray = throw NotImplementedError("Stub!"),
+ @get:kotlin.jvm.JvmName(name = "bv")
+ public val bytecodeVersion: kotlin.IntArray = throw NotImplementedError("Stub!"),
+ @get:kotlin.jvm.JvmName(name = "d1")
+ public val data1: kotlin.Array<kotlin.String> = throw NotImplementedError("Stub!"),
+ @get:kotlin.jvm.JvmName(name = "d2")
+ public val data2: kotlin.Array<kotlin.String> = throw NotImplementedError("Stub!"),
+ @get:kotlin.jvm.JvmName(name = "xs")
+ public val extraString: kotlin.String = throw NotImplementedError("Stub!"),
+ @get:kotlin.jvm.JvmName(name = "pn")
+ public val packageName: kotlin.String = throw NotImplementedError("Stub!"),
+ @get:kotlin.jvm.JvmName(name = "xi")
+ public val extraInt: kotlin.Int = throw NotImplementedError("Stub!"),
+ )
+ """.trimIndent(),
+ )
+ }
+
+ @Test
+ fun classNamesAndNesting() {
+ // Make sure we parse class names correctly at all levels
+ val typeSpec = ClassNesting::class.toTypeSpecWithTestHandler()
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class ClassNesting() {
+ public class NestedClass() {
+ public class SuperNestedClass() {
+ public inner class SuperDuperInnerClass()
+ }
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ @IgnoreForHandlerType(
+ reason = "compile-testing can't handle class names with dashes, will throw " +
+ "\"class file for com.squareup.kotlinpoet.metadata.specs.Fuzzy\$ClassNesting\$-Nested not found\"",
+ handlerType = ELEMENTS,
+ )
+ @Test
+ fun classNamesAndNesting_pathological() {
+ // Make sure we parse class names correctly at all levels
+ val typeSpec = `Fuzzy$ClassNesting`::class.toTypeSpecWithTestHandler()
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class `Fuzzy${'$'}ClassNesting`() {
+ public class `-Nested`() {
+ public class SuperNestedClass() {
+ public inner class `-${'$'}Fuzzy${'$'}Super${'$'}Weird-Nested${'$'}Name`()
+ }
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ @IgnoreForHandlerType(
+ reason = "Property site-target annotations are always stored on the synthetic annotations " +
+ "method, which is not accessible in the elements API",
+ handlerType = ELEMENTS,
+ )
+ @Test
+ fun parameterAnnotations_reflective() {
+ val typeSpec = ParameterAnnotations::class.toTypeSpecWithTestHandler()
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class ParameterAnnotations(
+ @property:com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "${'$'}{'${'$'}'}a")
+ @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "b")
+ public val param1: kotlin.String,
+ @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "2")
+ param2: kotlin.String,
+ ) {
+ public fun function(@com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "woo") param1: kotlin.String): kotlin.Unit {
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ @IgnoreForHandlerType(
+ reason = "Property site-target annotations are always stored on the synthetic annotations " +
+ "method, which is not accessible in the elements API",
+ handlerType = REFLECTIVE,
+ )
+ @Test
+ fun parameterAnnotations_elements() {
+ val typeSpec = ParameterAnnotations::class.toTypeSpecWithTestHandler()
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class ParameterAnnotations(
+ @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "b")
+ public val param1: kotlin.String,
+ @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "2")
+ param2: kotlin.String,
+ ) {
+ public fun function(@com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "woo") param1: kotlin.String): kotlin.Unit {
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ annotation class CustomAnnotation(val name: String)
+
+ class ParameterAnnotations(
+ @property:CustomAnnotation("\$a")
+ @param:CustomAnnotation("b")
+ val param1: String,
+ @CustomAnnotation("2") param2: String,
+ ) {
+ fun function(@CustomAnnotation("woo") param1: String) {
+ }
+ }
+
+ @IgnoreForHandlerType(
+ reason = "Non-runtime annotations are not present for reflection.",
+ handlerType = ELEMENTS,
+ )
+ @Test
+ fun classAnnotations_reflective() {
+ val typeSpec = ClassAnnotations::class.toTypeSpecWithTestHandler()
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.RuntimeCustomClassAnnotation(name = "Runtime")
+ public class ClassAnnotations()
+ """.trimIndent(),
+ )
+ }
+
+ @IgnoreForHandlerType(
+ reason = "Non-runtime annotations are not present for reflection.",
+ handlerType = REFLECTIVE,
+ )
+ @Test
+ fun classAnnotations_elements() {
+ val typeSpec = ClassAnnotations::class.toTypeSpecWithTestHandler()
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.BinaryCustomClassAnnotation(name = "Binary")
+ @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.RuntimeCustomClassAnnotation(name = "Runtime")
+ public class ClassAnnotations()
+ """.trimIndent(),
+ )
+ }
+
+ @Retention(AnnotationRetention.SOURCE)
+ annotation class SourceCustomClassAnnotation(val name: String)
+
+ @Retention(AnnotationRetention.BINARY)
+ annotation class BinaryCustomClassAnnotation(val name: String)
+
+ @Retention(AnnotationRetention.RUNTIME)
+ annotation class RuntimeCustomClassAnnotation(val name: String)
+
+ @SourceCustomClassAnnotation("Source")
+ @BinaryCustomClassAnnotation("Binary")
+ @RuntimeCustomClassAnnotation("Runtime")
+ class ClassAnnotations
+
+ @Test
+ fun typeAnnotations() {
+ val typeSpec = TypeAnnotations::class.toTypeSpecWithTestHandler()
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class TypeAnnotations() {
+ public val foo: kotlin.collections.List<@com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.TypeAnnotation kotlin.String> = throw NotImplementedError("Stub!")
+
+ public fun <T> bar(input: @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.TypeAnnotation kotlin.String, input2: @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.TypeAnnotation (@com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.TypeAnnotation kotlin.Int) -> @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.TypeAnnotation kotlin.String): kotlin.Unit {
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ @Target(TYPE, TYPE_PARAMETER)
+ annotation class TypeAnnotation
+
+ class TypeAnnotations {
+ val foo: List<@TypeAnnotation String> = emptyList()
+
+ fun <@TypeAnnotation T> bar(
+ input: @TypeAnnotation String,
+ input2: @TypeAnnotation (@TypeAnnotation Int) -> @TypeAnnotation String,
+ ) {
+ }
+ }
+
+ // Regression test for https://github.com/square/kotlinpoet/issues/812
+ @Test
+ fun backwardTypeVarReferences() {
+ val typeSpec = Asset::class.toTypeSpecWithTestHandler()
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public class Asset<A : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Asset<A>>() {
+ public fun <D : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Asset<D>, C : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Asset<A>> function(): kotlin.Unit {
+ }
+
+ public class AssetIn<in C : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Asset.AssetIn<C>>()
+
+ public class AssetOut<out B : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Asset.AssetOut<B>>()
+ }
+ """.trimIndent(),
+ )
+ }
+
+ class Asset<A : Asset<A>> {
+ fun <D : Asset<D>, C : Asset<A>> function() {
+ }
+
+ class AssetOut<out B : AssetOut<B>>
+ class AssetIn<in C : AssetIn<C>>
+ }
+
+ // Regression test for https://github.com/square/kotlinpoet/issues/821
+ @Test
+ fun abstractClass() {
+ val typeSpec = AbstractClass::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public abstract class AbstractClass() {
+ public val baz: kotlin.String? = null
+
+ public abstract val foo: kotlin.String
+
+ public abstract fun bar(): kotlin.Unit
+
+ public abstract fun barWithReturn(): kotlin.String
+
+ public fun fuz(): kotlin.Unit {
+ }
+
+ public fun fuzWithReturn(): kotlin.String = throw NotImplementedError("Stub!")
+ }
+ """.trimIndent(),
+ )
+ }
+
+ abstract class AbstractClass {
+ abstract val foo: String
+ abstract fun bar()
+ abstract fun barWithReturn(): String
+
+ val baz: String? = null
+ fun fuz() {}
+ fun fuzWithReturn(): String {
+ return ""
+ }
+ }
+
+ // Regression test for https://github.com/square/kotlinpoet/issues/820
+ @Test
+ fun internalAbstractProperty() {
+ val typeSpec = InternalAbstractPropertyHolder::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public abstract class InternalAbstractPropertyHolder() {
+ internal abstract val valProp: kotlin.String
+
+ internal abstract var varProp: kotlin.String
+ }
+ """.trimIndent(),
+ )
+ }
+
+ abstract class InternalAbstractPropertyHolder {
+ internal abstract val valProp: String
+ internal abstract var varProp: String
+ }
+
+ @Test
+ fun modalities() {
+ val abstractModalities = AbstractModalities::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(abstractModalities.trimmedToString()).isEqualTo(
+ """
+ public abstract class AbstractModalities() : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.ModalitiesInterface {
+ public val implicitFinalProp: kotlin.String? = null
+
+ public override val interfaceProp: kotlin.String? = null
+
+ public open val openProp: kotlin.String? = null
+
+ public fun implicitFinalFun(): kotlin.Unit {
+ }
+
+ public override fun interfaceFun(): kotlin.Unit {
+ }
+
+ public open fun openFun(): kotlin.Unit {
+ }
+ }
+ """.trimIndent(),
+ )
+
+ val finalAbstractModalities = FinalAbstractModalities::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(finalAbstractModalities.trimmedToString()).isEqualTo(
+ """
+ public abstract class FinalAbstractModalities() : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.ModalitiesInterface {
+ public final override val interfaceProp: kotlin.String? = null
+
+ public final override fun interfaceFun(): kotlin.Unit {
+ }
+ }
+ """.trimIndent(),
+ )
+
+ val modalities = Modalities::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(modalities.trimmedToString()).isEqualTo(
+ """
+ public class Modalities() : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.AbstractModalities() {
+ public override val interfaceProp: kotlin.String? = null
+
+ public override val openProp: kotlin.String? = null
+
+ public override fun interfaceFun(): kotlin.Unit {
+ }
+
+ public override fun openFun(): kotlin.Unit {
+ }
+ }
+ """.trimIndent(),
+ )
+ }
+
+ interface ModalitiesInterface {
+ val interfaceProp: String?
+ fun interfaceFun()
+ }
+ abstract class AbstractModalities : ModalitiesInterface {
+ override val interfaceProp: String? = null
+ override fun interfaceFun() {
+ }
+ val implicitFinalProp: String? = null
+ fun implicitFinalFun() {
+ }
+ open val openProp: String? = null
+ open fun openFun() {
+ }
+ }
+ abstract class FinalAbstractModalities : ModalitiesInterface {
+ final override val interfaceProp: String? = null
+ final override fun interfaceFun() {
+ }
+ }
+ class Modalities : AbstractModalities() {
+ override val interfaceProp: String? = null
+ override fun interfaceFun() {
+ }
+
+ override val openProp: String? = null
+ override fun openFun() {
+ }
+ }
+
+ @Test
+ fun funClass() {
+ val funInterface = FunInterface::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(funInterface.trimmedToString()).isEqualTo(
+ """
+ public fun interface FunInterface {
+ public fun example(): kotlin.Unit
+ }
+ """.trimIndent(),
+ )
+ }
+
+ fun interface FunInterface {
+ fun example()
+ }
+
+ @Test
+ fun selfReferencingTypeParams() {
+ val typeSpec = Node::class.toTypeSpecWithTestHandler()
+
+ //language=kotlin
+ assertThat(typeSpec.trimmedToString()).isEqualTo(
+ """
+ public open class Node<T : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Node<T, R>, R : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Node<R, T>>() {
+ public var r: R? = null
+
+ public var t: T? = null
+ }
+ """.trimIndent(),
+ )
+ }
+
+ open class Node<T : Node<T, R>, R : Node<R, T>> {
+ var t: T? = null
+ var r: R? = null
+ }
+}
+
+class ClassNesting {
+ class NestedClass {
+ class SuperNestedClass {
+ inner class SuperDuperInnerClass
+ }
+ }
+}
+
+class `Fuzzy$ClassNesting` {
+ class `-Nested` {
+ class SuperNestedClass {
+ inner class `-$Fuzzy$Super$Weird-Nested$Name`
+ }
+ }
+}
+
+private fun TypeSpec.trimmedToString(): String {
+ return toString().trim()
+}
+
+inline class InlineClass(val value: String)
+
+@JvmInline
+value class ValueClass(val value: String)
+
+typealias TypeAliasName = String
+typealias GenericTypeAlias = List<String>
+typealias NestedTypeAlias = List<GenericTypeAlias>
diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/MultiClassInspectorTest.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/MultiClassInspectorTest.kt
new file mode 100644
index 00000000..cb7c21d6
--- /dev/null
+++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/MultiClassInspectorTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.specs
+
+import com.google.testing.compile.CompilationRule
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ElementsClassInspector
+import com.squareup.kotlinpoet.metadata.classinspectors.ReflectiveClassInspector
+import com.squareup.kotlinpoet.metadata.specs.MultiClassInspectorTest.ClassInspectorType
+import com.squareup.kotlinpoet.metadata.toKotlinClassMetadata
+import java.lang.annotation.Inherited
+import kotlin.annotation.AnnotationRetention.RUNTIME
+import kotlin.reflect.KClass
+import kotlinx.metadata.jvm.KotlinClassMetadata.FileFacade
+import org.junit.Assume
+import org.junit.Rule
+import org.junit.rules.TestRule
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.model.Statement
+
+/** Base test class that runs all tests with multiple [ClassInspectorTypes][ClassInspectorType]. */
+@RunWith(Parameterized::class)
+@KotlinPoetMetadataPreview
+abstract class MultiClassInspectorTest {
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun data(): Collection<Array<ClassInspectorType>> {
+ return listOf(
+ arrayOf(ClassInspectorType.REFLECTIVE),
+ arrayOf(ClassInspectorType.ELEMENTS),
+ )
+ }
+ }
+
+ enum class ClassInspectorType {
+ NONE {
+ override fun create(testInstance: MultiClassInspectorTest): ClassInspector {
+ throw IllegalStateException("Should not be called, just here to default the jvmfield to something.")
+ }
+ },
+ REFLECTIVE {
+ override fun create(testInstance: MultiClassInspectorTest): ClassInspector {
+ return ReflectiveClassInspector.create()
+ }
+ },
+ ELEMENTS {
+ override fun create(testInstance: MultiClassInspectorTest): ClassInspector {
+ return ElementsClassInspector.create(testInstance.compilation.elements, testInstance.compilation.types)
+ }
+ },
+ ;
+
+ abstract fun create(testInstance: MultiClassInspectorTest): ClassInspector
+ }
+
+ @Retention(RUNTIME)
+ @Target(AnnotationTarget.FUNCTION)
+ @Inherited
+ annotation class IgnoreForHandlerType(
+ val reason: String,
+ val handlerType: ClassInspectorType,
+ )
+
+ @JvmField
+ @Parameter
+ var classInspectorType: ClassInspectorType = ClassInspectorType.NONE
+
+ @Rule
+ @JvmField
+ val compilation = CompilationRule()
+
+ @Rule
+ @JvmField
+ val ignoreForElementsRule = TestRule { base, description ->
+ object : Statement() {
+ override fun evaluate() {
+ val annotation = description.getAnnotation(
+ IgnoreForHandlerType::class.java,
+ )
+ val shouldIgnore = annotation?.handlerType == classInspectorType
+ Assume.assumeTrue(
+ "Ignoring ${description.methodName}: ${annotation?.reason}",
+ !shouldIgnore,
+ )
+ base.evaluate()
+ }
+ }
+ }
+
+ protected fun KClass<*>.toTypeSpecWithTestHandler(): TypeSpec {
+ return toTypeSpec(classInspectorType.create(this@MultiClassInspectorTest))
+ }
+
+ protected fun KClass<*>.toFileSpecWithTestHandler(): FileSpec {
+ val classInspector = classInspectorType.create(this@MultiClassInspectorTest)
+ return java.annotations.filterIsInstance<Metadata>().first().toKotlinClassMetadata<FileFacade>()
+ .toKmPackage()
+ .toFileSpec(classInspector, asClassName())
+ }
+}
diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/NoJvmNameFacadeFile.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/NoJvmNameFacadeFile.kt
new file mode 100644
index 00000000..6347108d
--- /dev/null
+++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/NoJvmNameFacadeFile.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.specs
+
+val prop: String = ""
diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/ReflectiveClassInspectorTest.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/ReflectiveClassInspectorTest.kt
new file mode 100644
index 00000000..a4ecc3de
--- /dev/null
+++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/ReflectiveClassInspectorTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ReflectiveClassInspector
+import com.tschuchort.compiletesting.KotlinCompilation
+import com.tschuchort.compiletesting.SourceFile
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import org.junit.Test
+
+/**
+ * Class to test the new functionality of Issue#1036.
+ * @see <a href="https://github.com/square/kotlinpoet/issues/1036">issue</a>
+ * @author oberstrike
+ */
+@KotlinPoetMetadataPreview
+class ReflectiveClassInspectorTest {
+
+ data class Person(val name: String)
+
+ /**
+ * Tests if the [ReflectiveClassInspector] can be created without a
+ * custom ClassLoader and still works.
+ */
+ @Test
+ fun standardClassLoaderTest() {
+ val classInspector = ReflectiveClassInspector.create()
+ val className = Person::class.asClassName()
+ val declarationContainer = classInspector.declarationContainerFor(className)
+ assertNotNull(declarationContainer)
+ }
+
+ /**
+ * Tests if the [ReflectiveClassInspector] can be created with a
+ * custom ClassLoader.
+ */
+ @Test
+ fun useACustomClassLoaderTest() {
+ val testClass = "Person"
+ val testPropertyName = "name"
+ val testPropertyType = "String"
+ val testPackageName = "com.test"
+ val testClassName = ClassName(testPackageName, testClass)
+ val testKtFileName = "KClass.kt"
+
+ val kotlinSource = SourceFile.kotlin(
+ testKtFileName,
+ """
+ package $testPackageName
+ data class $testClass(val $testPropertyName: $testPropertyType)
+ """.trimIndent(),
+ )
+
+ val result = KotlinCompilation().apply {
+ sources = listOf(kotlinSource)
+ }.compile()
+
+ assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode)
+ val classLoader = result.classLoader
+ val classInspector = ReflectiveClassInspector.create(classLoader)
+
+ val declarationContainer = classInspector.declarationContainerFor(testClassName)
+
+ val properties = declarationContainer.properties
+ assertEquals(1, properties.size)
+
+ val testProperty = properties.findLast { it.name == testPropertyName }
+ assertNotNull(testProperty)
+
+ val returnType = testProperty.returnType
+ assertNotNull(returnType)
+ }
+}
diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/classinspectors/ClassInspectorUtilTest.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/classinspectors/ClassInspectorUtilTest.kt
new file mode 100644
index 00000000..4e1c268b
--- /dev/null
+++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/classinspectors/ClassInspectorUtilTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.metadata.specs.classinspectors
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+import kotlin.test.Test
+
+@KotlinPoetMetadataPreview
+class ClassInspectorUtilTest {
+
+ @Test fun createClassName_simple() {
+ assertThat(ClassInspectorUtil.createClassName("some/path/Foo"))
+ .isEqualTo(ClassName("some.path", "Foo"))
+ }
+
+ @Test fun createClassName_nested() {
+ assertThat(ClassInspectorUtil.createClassName("some/path/Foo.Nested"))
+ .isEqualTo(ClassName("some.path", "Foo", "Nested"))
+ }
+
+ @Test fun createClassName_simple_dollarNameStart() {
+ assertThat(ClassInspectorUtil.createClassName("some/path/Foo$"))
+ .isEqualTo(ClassName("some.path", "Foo$"))
+ }
+
+ @Test fun createClassName_simple_dollarNameMiddle() {
+ assertThat(ClassInspectorUtil.createClassName("some/path/Fo${'$'}o"))
+ .isEqualTo(ClassName("some.path", "Fo${'$'}o"))
+ }
+
+ @Test fun createClassName_simple_dollarNameEnd() {
+ assertThat(ClassInspectorUtil.createClassName("some/path/Nested.${'$'}Foo"))
+ .isEqualTo(ClassName("some.path", "Nested", "${'$'}Foo"))
+ }
+
+ @Test fun createClassName_nested_dollarNameStart() {
+ assertThat(ClassInspectorUtil.createClassName("some/path/Nested.Foo$"))
+ .isEqualTo(ClassName("some.path", "Nested", "Foo$"))
+ }
+
+ @Test fun createClassName_nested_dollarNameMiddle() {
+ assertThat(ClassInspectorUtil.createClassName("some/path/Nested.Fo${'$'}o"))
+ .isEqualTo(ClassName("some.path", "Nested", "Fo${'$'}o"))
+ }
+
+ @Test fun createClassName_nested_dollarNameEnd() {
+ assertThat(ClassInspectorUtil.createClassName("some/path/Nested.${'$'}Foo"))
+ .isEqualTo(ClassName("some.path", "Nested", "${'$'}Foo"))
+ }
+
+ @Test fun createClassName_noPackageName() {
+ assertThat(ClassInspectorUtil.createClassName("ClassWithNoPackage"))
+ .isEqualTo(ClassName("", "ClassWithNoPackage"))
+ }
+
+ // Regression test for avoiding https://github.com/square/kotlinpoet/issues/795
+ @Test fun createClassName_noEmptyNames() {
+ val noPackage = ClassInspectorUtil.createClassName("ClassWithNoPackage")
+ assertThat(noPackage.simpleNames.any { it.isEmpty() }).isFalse()
+
+ val withPackage = ClassInspectorUtil.createClassName("path/to/ClassWithNoPackage")
+ assertThat(withPackage.simpleNames.any { it.isEmpty() }).isFalse()
+ }
+
+ @Test fun createClassName_packageWithCaps() {
+ assertThat(ClassInspectorUtil.createClassName("some/Path/Foo.Nested"))
+ .isEqualTo(ClassName("some.Path", "Foo", "Nested"))
+ }
+
+ @Test fun throwsSpec_normal() {
+ assertThat(ClassInspectorUtil.createThrowsSpec(listOf(Exception::class.asClassName())))
+ .isEqualTo(
+ AnnotationSpec.builder(Throws::class.asClassName())
+ .addMember("exceptionClasses = [%T::class]", Exception::class.asClassName())
+ .build(),
+ )
+ }
+}
diff --git a/interop/ksp/api/ksp.api b/interop/ksp/api/ksp.api
new file mode 100644
index 00000000..cc8cb2b8
--- /dev/null
+++ b/interop/ksp/api/ksp.api
@@ -0,0 +1,64 @@
+public final class com/squareup/kotlinpoet/ksp/AnnotationsKt {
+ public static final fun toAnnotationSpec (Lcom/google/devtools/ksp/symbol/KSAnnotation;)Lcom/squareup/kotlinpoet/AnnotationSpec;
+}
+
+public final class com/squareup/kotlinpoet/ksp/KsClassDeclarationsKt {
+ public static final fun toClassName (Lcom/google/devtools/ksp/symbol/KSClassDeclaration;)Lcom/squareup/kotlinpoet/ClassName;
+}
+
+public final class com/squareup/kotlinpoet/ksp/KsTypesKt {
+ public static final fun toClassName (Lcom/google/devtools/ksp/symbol/KSType;)Lcom/squareup/kotlinpoet/ClassName;
+ public static final fun toTypeName (Lcom/google/devtools/ksp/symbol/KSType;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;)Lcom/squareup/kotlinpoet/TypeName;
+ public static final fun toTypeName (Lcom/google/devtools/ksp/symbol/KSTypeArgument;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;)Lcom/squareup/kotlinpoet/TypeName;
+ public static final fun toTypeName (Lcom/google/devtools/ksp/symbol/KSTypeReference;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;)Lcom/squareup/kotlinpoet/TypeName;
+ public static synthetic fun toTypeName$default (Lcom/google/devtools/ksp/symbol/KSType;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeName;
+ public static synthetic fun toTypeName$default (Lcom/google/devtools/ksp/symbol/KSTypeArgument;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeName;
+ public static synthetic fun toTypeName$default (Lcom/google/devtools/ksp/symbol/KSTypeReference;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeName;
+ public static final fun toTypeVariableName (Lcom/google/devtools/ksp/symbol/KSTypeParameter;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static synthetic fun toTypeVariableName$default (Lcom/google/devtools/ksp/symbol/KSTypeParameter;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeVariableName;
+}
+
+public final class com/squareup/kotlinpoet/ksp/ModifiersKt {
+ public static final fun toKModifier (Lcom/google/devtools/ksp/symbol/Modifier;)Lcom/squareup/kotlinpoet/KModifier;
+}
+
+public abstract interface class com/squareup/kotlinpoet/ksp/OriginatingKSFiles {
+ public abstract fun getFiles ()Ljava/util/List;
+}
+
+public final class com/squareup/kotlinpoet/ksp/OriginatingKSFilesKt {
+ public static final fun addOriginatingKSFile (Lcom/squareup/kotlinpoet/FunSpec$Builder;Lcom/google/devtools/ksp/symbol/KSFile;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static final fun addOriginatingKSFile (Lcom/squareup/kotlinpoet/PropertySpec$Builder;Lcom/google/devtools/ksp/symbol/KSFile;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public static final fun addOriginatingKSFile (Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;Lcom/google/devtools/ksp/symbol/KSFile;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public static final fun addOriginatingKSFile (Lcom/squareup/kotlinpoet/TypeSpec$Builder;Lcom/google/devtools/ksp/symbol/KSFile;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static final fun kspDependencies (Lcom/squareup/kotlinpoet/FileSpec;ZLjava/lang/Iterable;)Lcom/google/devtools/ksp/processing/Dependencies;
+ public static synthetic fun kspDependencies$default (Lcom/squareup/kotlinpoet/FileSpec;ZLjava/lang/Iterable;ILjava/lang/Object;)Lcom/google/devtools/ksp/processing/Dependencies;
+ public static final fun originatingKSFiles (Lcom/squareup/kotlinpoet/FileSpec;)Ljava/util/List;
+ public static final fun originatingKSFiles (Lcom/squareup/kotlinpoet/FunSpec;)Ljava/util/List;
+ public static final fun originatingKSFiles (Lcom/squareup/kotlinpoet/PropertySpec;)Ljava/util/List;
+ public static final fun originatingKSFiles (Lcom/squareup/kotlinpoet/TypeAliasSpec;)Ljava/util/List;
+ public static final fun originatingKSFiles (Lcom/squareup/kotlinpoet/TypeSpec;)Ljava/util/List;
+ public static final fun writeTo (Lcom/squareup/kotlinpoet/FileSpec;Lcom/google/devtools/ksp/processing/CodeGenerator;Lcom/google/devtools/ksp/processing/Dependencies;)V
+ public static final fun writeTo (Lcom/squareup/kotlinpoet/FileSpec;Lcom/google/devtools/ksp/processing/CodeGenerator;ZLjava/lang/Iterable;)V
+ public static synthetic fun writeTo$default (Lcom/squareup/kotlinpoet/FileSpec;Lcom/google/devtools/ksp/processing/CodeGenerator;ZLjava/lang/Iterable;ILjava/lang/Object;)V
+}
+
+public abstract interface class com/squareup/kotlinpoet/ksp/TypeParameterResolver {
+ public static final field Companion Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver$Companion;
+ public abstract fun get (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public abstract fun getParametersMap ()Ljava/util/Map;
+}
+
+public final class com/squareup/kotlinpoet/ksp/TypeParameterResolver$Companion {
+ public final fun getEMPTY ()Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;
+}
+
+public final class com/squareup/kotlinpoet/ksp/TypeParameterResolverKt {
+ public static final fun toTypeParameterResolver (Ljava/util/List;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;Ljava/lang/String;)Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;
+ public static synthetic fun toTypeParameterResolver$default (Ljava/util/List;Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/ksp/TypeParameterResolver;
+}
+
+public final class com/squareup/kotlinpoet/ksp/VisibilitiesKt {
+ public static final fun toKModifier (Lcom/google/devtools/ksp/symbol/Visibility;)Lcom/squareup/kotlinpoet/KModifier;
+}
+
diff --git a/interop/ksp/build.gradle.kts b/interop/ksp/build.gradle.kts
new file mode 100644
index 00000000..c18fa7c9
--- /dev/null
+++ b/interop/ksp/build.gradle.kts
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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.
+ */
+
+tasks.jar {
+ manifest {
+ attributes("Automatic-Module-Name" to "com.squareup.kotlinpoet.ksp")
+ }
+}
+
+dependencies {
+ api(project(":kotlinpoet"))
+ compileOnly(libs.ksp.api)
+ testImplementation(libs.kotlin.junit)
+ testImplementation(libs.truth)
+}
diff --git a/interop/ksp/gradle.properties b/interop/ksp/gradle.properties
new file mode 100644
index 00000000..dda5a7df
--- /dev/null
+++ b/interop/ksp/gradle.properties
@@ -0,0 +1,4 @@
+POM_ARTIFACT_ID=kotlinpoet-ksp
+POM_NAME=KotlinPoet (KSP Interop)
+POM_DESCRIPTION=Extensions for interop with KSP (Kotlin Symbol Processing).
+POM_PACKAGING=jar
diff --git a/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Annotations.kt b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Annotations.kt
new file mode 100644
index 00000000..7964de2e
--- /dev/null
+++ b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Annotations.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.ksp
+
+import com.google.devtools.ksp.symbol.AnnotationUseSiteTarget
+import com.google.devtools.ksp.symbol.ClassKind
+import com.google.devtools.ksp.symbol.KSAnnotation
+import com.google.devtools.ksp.symbol.KSClassDeclaration
+import com.google.devtools.ksp.symbol.KSName
+import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.KSTypeAlias
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.ParameterizedTypeName
+
+/** Returns an [AnnotationSpec] representation of this [KSAnnotation] instance. */
+public fun KSAnnotation.toAnnotationSpec(): AnnotationSpec {
+ val builder = when (val type = annotationType.resolve().unwrapTypeAlias().toTypeName()) {
+ is ClassName -> AnnotationSpec.builder(type)
+ is ParameterizedTypeName -> AnnotationSpec.builder(type)
+ else -> error("This is never possible.")
+ }
+ useSiteTarget?.let { builder.useSiteTarget(it.kpAnalog) }
+ // TODO support type params once they're exposed https://github.com/google/ksp/issues/753
+ for (argument in arguments) {
+ val member = CodeBlock.builder()
+ val name = argument.name!!.getShortName()
+ member.add("%N = ", name)
+ addValueToBlock(argument.value!!, member)
+ builder.addMember(member.build())
+ }
+ return builder.build()
+}
+
+private val AnnotationUseSiteTarget.kpAnalog: UseSiteTarget get() = when (this) {
+ AnnotationUseSiteTarget.FILE -> UseSiteTarget.FILE
+ AnnotationUseSiteTarget.PROPERTY -> UseSiteTarget.PROPERTY
+ AnnotationUseSiteTarget.FIELD -> UseSiteTarget.FIELD
+ AnnotationUseSiteTarget.GET -> UseSiteTarget.GET
+ AnnotationUseSiteTarget.SET -> UseSiteTarget.SET
+ AnnotationUseSiteTarget.RECEIVER -> UseSiteTarget.RECEIVER
+ AnnotationUseSiteTarget.PARAM -> UseSiteTarget.PARAM
+ AnnotationUseSiteTarget.SETPARAM -> UseSiteTarget.SETPARAM
+ AnnotationUseSiteTarget.DELEGATE -> UseSiteTarget.DELEGATE
+}
+
+internal fun KSType.unwrapTypeAlias(): KSType {
+ return if (this.declaration is KSTypeAlias) {
+ (this.declaration as KSTypeAlias).type.resolve()
+ } else {
+ this
+ }
+}
+
+private fun addValueToBlock(value: Any, member: CodeBlock.Builder) {
+ when (value) {
+ is List<*> -> {
+ // Array type
+ val arrayType = when (value.firstOrNull()) {
+ is Boolean -> "booleanArrayOf"
+ is Byte -> "byteArrayOf"
+ is Char -> "charArrayOf"
+ is Short -> "shortArrayOf"
+ is Int -> "intArrayOf"
+ is Long -> "longArrayOf"
+ is Float -> "floatArrayOf"
+ is Double -> "doubleArrayOf"
+ else -> "arrayOf"
+ }
+ member.add("$arrayType(⇥⇥")
+ value.forEachIndexed { index, innerValue ->
+ if (index > 0) member.add(", ")
+ addValueToBlock(innerValue!!, member)
+ }
+ member.add("⇤⇤)")
+ }
+ is KSType -> {
+ val unwrapped = value.unwrapTypeAlias()
+ val isEnum = (unwrapped.declaration as KSClassDeclaration).classKind == ClassKind.ENUM_ENTRY
+ if (isEnum) {
+ val parent = unwrapped.declaration.parentDeclaration as KSClassDeclaration
+ val entry = unwrapped.declaration.simpleName.getShortName()
+ member.add("%T.%L", parent.toClassName(), entry)
+ } else {
+ member.add("%T::class", unwrapped.toClassName())
+ }
+ }
+ is KSName ->
+ member.add(
+ "%T.%L",
+ ClassName.bestGuess(value.getQualifier()),
+ value.getShortName(),
+ )
+ is KSAnnotation -> member.add("%L", value.toAnnotationSpec())
+ else -> member.add(memberForValue(value))
+ }
+}
+
+/**
+ * Creates a [CodeBlock] with parameter `format` depending on the given `value` object.
+ * Handles a number of special cases, such as appending "f" to `Float` values, and uses
+ * `%L` for other types.
+ */
+internal fun memberForValue(value: Any) = when (value) {
+ is Class<*> -> CodeBlock.of("%T::class", value)
+ is Enum<*> -> CodeBlock.of("%T.%L", value.javaClass, value.name)
+ is String -> CodeBlock.of("%S", value)
+ is Float -> CodeBlock.of("%Lf", value)
+ is Double -> CodeBlock.of("%L", value)
+ is Char -> CodeBlock.of("'%L'", value)
+ is Byte -> CodeBlock.of("$value.toByte()")
+ is Short -> CodeBlock.of("$value.toShort()")
+ // Int or Boolean
+ else -> CodeBlock.of("%L", value)
+}
diff --git a/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/KsClassDeclarations.kt b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/KsClassDeclarations.kt
new file mode 100644
index 00000000..443be1b3
--- /dev/null
+++ b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/KsClassDeclarations.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.ksp
+
+import com.google.devtools.ksp.symbol.KSClassDeclaration
+import com.squareup.kotlinpoet.ClassName
+
+/** Returns the [ClassName] representation of this [KSClassDeclaration]. */
+public fun KSClassDeclaration.toClassName(): ClassName {
+ return toClassNameInternal()
+}
diff --git a/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/KsTypes.kt b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/KsTypes.kt
new file mode 100644
index 00000000..0572b739
--- /dev/null
+++ b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/KsTypes.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.ksp
+
+import com.google.devtools.ksp.symbol.ClassKind
+import com.google.devtools.ksp.symbol.KSClassDeclaration
+import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.KSTypeAlias
+import com.google.devtools.ksp.symbol.KSTypeArgument
+import com.google.devtools.ksp.symbol.KSTypeParameter
+import com.google.devtools.ksp.symbol.KSTypeReference
+import com.google.devtools.ksp.symbol.Variance
+import com.google.devtools.ksp.symbol.Variance.CONTRAVARIANT
+import com.google.devtools.ksp.symbol.Variance.COVARIANT
+import com.google.devtools.ksp.symbol.Variance.INVARIANT
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.STAR
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.TypeVariableName
+import com.squareup.kotlinpoet.WildcardTypeName
+import com.squareup.kotlinpoet.tags.TypeAliasTag
+
+/** Returns the [ClassName] representation of this [KSType] IFF it's a [KSClassDeclaration]. */
+public fun KSType.toClassName(): ClassName {
+ val decl = declaration
+ check(decl is KSClassDeclaration) {
+ "Declaration was not a KSClassDeclaration: $this"
+ }
+ return decl.toClassName()
+}
+
+/**
+ * Returns the [TypeName] representation of this [KSType].
+ *
+ * @see toTypeParameterResolver
+ * @param typeParamResolver an optional resolver for enclosing declarations' type parameters. Parent
+ * declarations can be anything with generics that child nodes declare as
+ * defined by [KSType.arguments].
+ */
+public fun KSType.toTypeName(
+ typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
+): TypeName = toTypeName(typeParamResolver, emptyList())
+
+internal fun KSType.toTypeName(
+ typeParamResolver: TypeParameterResolver,
+ typeArguments: List<KSTypeArgument>,
+): TypeName {
+ require(!isError) {
+ "Error type '$this' is not resolvable in the current round of processing."
+ }
+ val type = when (val decl = declaration) {
+ is KSClassDeclaration -> {
+ val arguments = if (decl.classKind == ClassKind.ANNOTATION_CLASS) {
+ arguments
+ } else {
+ typeArguments
+ }
+
+ decl.toClassName().withTypeArguments(arguments.map { it.toTypeName(typeParamResolver) })
+ }
+ is KSTypeParameter -> typeParamResolver[decl.name.getShortName()]
+ is KSTypeAlias -> {
+ var typeAlias: KSTypeAlias = decl
+ var arguments = arguments
+
+ var resolvedType: KSType
+ var mappedArgs: List<KSTypeArgument>
+ var extraResolver: TypeParameterResolver = typeParamResolver
+ while (true) {
+ resolvedType = typeAlias.type.resolve()
+ mappedArgs = mapTypeArgumentsFromTypeAliasToAbbreviatedType(
+ typeAlias = typeAlias,
+ typeAliasTypeArguments = arguments,
+ abbreviatedType = resolvedType,
+ )
+ extraResolver = if (typeAlias.typeParameters.isEmpty()) {
+ extraResolver
+ } else {
+ typeAlias.typeParameters.toTypeParameterResolver(extraResolver)
+ }
+
+ typeAlias = resolvedType.declaration as? KSTypeAlias ?: break
+ arguments = mappedArgs
+ }
+
+ val abbreviatedType = resolvedType
+ .toTypeName(extraResolver)
+ .copy(nullable = isMarkedNullable)
+ .rawType()
+ .withTypeArguments(mappedArgs.map { it.toTypeName(extraResolver) })
+
+ val aliasArgs = typeArguments.map { it.toTypeName(typeParamResolver) }
+
+ decl.toClassNameInternal()
+ .withTypeArguments(aliasArgs)
+ .copy(tags = mapOf(TypeAliasTag::class to TypeAliasTag(abbreviatedType)))
+ }
+ else -> error("Unsupported type: $declaration")
+ }
+
+ return type.copy(nullable = isMarkedNullable)
+}
+
+private fun mapTypeArgumentsFromTypeAliasToAbbreviatedType(
+ typeAlias: KSTypeAlias,
+ typeAliasTypeArguments: List<KSTypeArgument>,
+ abbreviatedType: KSType,
+): List<KSTypeArgument> {
+ return abbreviatedType.arguments
+ .map { typeArgument ->
+ // Check if type argument is a reference to a typealias type parameter, and not an actual type.
+ val typeAliasTypeParameterIndex = typeAlias.typeParameters.indexOfFirst { typeAliasTypeParameter ->
+ typeAliasTypeParameter.name.asString() == typeArgument.type.toString()
+ }
+ if (typeAliasTypeParameterIndex >= 0) {
+ typeAliasTypeArguments[typeAliasTypeParameterIndex]
+ } else {
+ typeArgument
+ }
+ }
+}
+
+/**
+ * Returns a [TypeVariableName] representation of this [KSTypeParameter].
+ *
+ * @see toTypeParameterResolver
+ * @param typeParamResolver an optional resolver for enclosing declarations' type parameters. Parent
+ * declarations can be anything with generics that child nodes declare as
+ * defined by [KSType.arguments].
+ */
+public fun KSTypeParameter.toTypeVariableName(
+ typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
+): TypeVariableName {
+ val typeVarName = name.getShortName()
+ val typeVarBounds = bounds.map { it.toTypeName(typeParamResolver) }.toList()
+ val typeVarVariance = when (variance) {
+ COVARIANT -> KModifier.OUT
+ CONTRAVARIANT -> KModifier.IN
+ else -> null
+ }
+ return TypeVariableName(typeVarName, bounds = typeVarBounds, variance = typeVarVariance)
+}
+
+/**
+ * Returns a [TypeName] representation of this [KSTypeArgument].
+ *
+ * @see toTypeParameterResolver
+ * @param typeParamResolver an optional resolver for enclosing declarations' type parameters. Parent
+ * declarations can be anything with generics that child nodes declare as
+ * defined by [KSType.arguments].
+ */
+public fun KSTypeArgument.toTypeName(
+ typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
+): TypeName {
+ val type = this.type ?: return STAR
+ return when (variance) {
+ COVARIANT -> WildcardTypeName.producerOf(type.toTypeName(typeParamResolver))
+ CONTRAVARIANT -> WildcardTypeName.consumerOf(type.toTypeName(typeParamResolver))
+ Variance.STAR -> STAR
+ INVARIANT -> type.toTypeName(typeParamResolver)
+ }
+}
+
+/**
+ * Returns a [TypeName] representation of this [KSTypeReference].
+ *
+ * @see toTypeParameterResolver
+ * @param typeParamResolver an optional resolver for enclosing declarations' type parameters. Parent
+ * declarations can be anything with generics that child nodes declare as
+ * defined by [KSType.arguments].
+ */
+public fun KSTypeReference.toTypeName(
+ typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
+): TypeName {
+ return resolve().toTypeName(typeParamResolver, element?.typeArguments.orEmpty())
+}
diff --git a/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Modifiers.kt b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Modifiers.kt
new file mode 100644
index 00000000..d4d8aa56
--- /dev/null
+++ b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Modifiers.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.ksp
+
+import com.google.devtools.ksp.symbol.Modifier
+import com.google.devtools.ksp.symbol.Modifier.ABSTRACT
+import com.google.devtools.ksp.symbol.Modifier.ACTUAL
+import com.google.devtools.ksp.symbol.Modifier.ANNOTATION
+import com.google.devtools.ksp.symbol.Modifier.CROSSINLINE
+import com.google.devtools.ksp.symbol.Modifier.DATA
+import com.google.devtools.ksp.symbol.Modifier.ENUM
+import com.google.devtools.ksp.symbol.Modifier.EXPECT
+import com.google.devtools.ksp.symbol.Modifier.EXTERNAL
+import com.google.devtools.ksp.symbol.Modifier.FINAL
+import com.google.devtools.ksp.symbol.Modifier.FUN
+import com.google.devtools.ksp.symbol.Modifier.IN
+import com.google.devtools.ksp.symbol.Modifier.INFIX
+import com.google.devtools.ksp.symbol.Modifier.INLINE
+import com.google.devtools.ksp.symbol.Modifier.INNER
+import com.google.devtools.ksp.symbol.Modifier.INTERNAL
+import com.google.devtools.ksp.symbol.Modifier.LATEINIT
+import com.google.devtools.ksp.symbol.Modifier.NOINLINE
+import com.google.devtools.ksp.symbol.Modifier.OPEN
+import com.google.devtools.ksp.symbol.Modifier.OPERATOR
+import com.google.devtools.ksp.symbol.Modifier.OUT
+import com.google.devtools.ksp.symbol.Modifier.OVERRIDE
+import com.google.devtools.ksp.symbol.Modifier.PRIVATE
+import com.google.devtools.ksp.symbol.Modifier.PROTECTED
+import com.google.devtools.ksp.symbol.Modifier.PUBLIC
+import com.google.devtools.ksp.symbol.Modifier.REIFIED
+import com.google.devtools.ksp.symbol.Modifier.SEALED
+import com.google.devtools.ksp.symbol.Modifier.SUSPEND
+import com.google.devtools.ksp.symbol.Modifier.TAILREC
+import com.google.devtools.ksp.symbol.Modifier.VALUE
+import com.google.devtools.ksp.symbol.Modifier.VARARG
+import com.squareup.kotlinpoet.KModifier
+
+/**
+ * Returns the [KModifier] representation of this [Modifier] or null if this is a Java-only
+ * modifier (i.e. prefixed with `JAVA_`), which do not have obvious [KModifier] analogues.
+ */
+public fun Modifier.toKModifier(): KModifier? {
+ return when (this) {
+ PUBLIC -> KModifier.PUBLIC
+ PRIVATE -> KModifier.PRIVATE
+ INTERNAL -> KModifier.INTERNAL
+ PROTECTED -> KModifier.PROTECTED
+ IN -> KModifier.IN
+ OUT -> KModifier.OUT
+ OVERRIDE -> KModifier.OVERRIDE
+ LATEINIT -> KModifier.LATEINIT
+ ENUM -> KModifier.ENUM
+ SEALED -> KModifier.SEALED
+ ANNOTATION -> KModifier.ANNOTATION
+ DATA -> KModifier.DATA
+ INNER -> KModifier.INNER
+ FUN -> KModifier.FUN
+ VALUE -> KModifier.VALUE
+ SUSPEND -> KModifier.SUSPEND
+ TAILREC -> KModifier.TAILREC
+ OPERATOR -> KModifier.OPERATOR
+ INFIX -> KModifier.INFIX
+ INLINE -> KModifier.INLINE
+ EXTERNAL -> KModifier.EXTERNAL
+ ABSTRACT -> KModifier.ABSTRACT
+ FINAL -> KModifier.FINAL
+ OPEN -> KModifier.OPEN
+ VARARG -> KModifier.VARARG
+ NOINLINE -> KModifier.NOINLINE
+ CROSSINLINE -> KModifier.CROSSINLINE
+ REIFIED -> KModifier.REIFIED
+ EXPECT -> KModifier.EXPECT
+ ACTUAL -> KModifier.ACTUAL
+ else -> null
+ }
+}
diff --git a/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/OriginatingKSFiles.kt b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/OriginatingKSFiles.kt
new file mode 100644
index 00000000..f7c40b3d
--- /dev/null
+++ b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/OriginatingKSFiles.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.ksp
+
+import com.google.devtools.ksp.processing.CodeGenerator
+import com.google.devtools.ksp.processing.Dependencies
+import com.google.devtools.ksp.symbol.KSFile
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.Taggable
+import com.squareup.kotlinpoet.TypeAliasSpec
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.tag
+import java.io.OutputStreamWriter
+import java.nio.charset.StandardCharsets
+
+/**
+ * A simple holder class for containing originating [KSFiles][KSFile], which are used by KSP to
+ * inform its incremental processing.
+ *
+ * See [the docs](https://github.com/google/ksp/blob/main/docs/incremental.md) for more information.
+ */
+public interface OriginatingKSFiles {
+ public val files: List<KSFile>
+}
+
+/** Returns this spec's originating [KSFiles][KSFile] for use with incremental processing. */
+public fun TypeSpec.originatingKSFiles(): List<KSFile> = getKSFilesTag()
+
+/** Returns this spec's originating [KSFiles][KSFile] for use with incremental processing. */
+public fun FunSpec.originatingKSFiles(): List<KSFile> = getKSFilesTag()
+
+/** Returns this spec's originating [KSFiles][KSFile] for use with incremental processing. */
+public fun PropertySpec.originatingKSFiles(): List<KSFile> = getKSFilesTag()
+
+/** Returns this spec's originating [KSFiles][KSFile] for use with incremental processing. */
+public fun TypeAliasSpec.originatingKSFiles(): List<KSFile> = getKSFilesTag()
+
+/**
+ * Returns the list of all files added to the contained
+ * [TypeSpecs][TypeSpec], [PropertySpecs][PropertySpec], [FunSpecs][FunSpec], or
+ * [TypeAliasSpecs][TypeAliasSpec] contained in this spec.
+ */
+public fun FileSpec.originatingKSFiles(): List<KSFile> {
+ return members
+ .flatMap {
+ when (it) {
+ is FunSpec -> it.originatingKSFiles()
+ is PropertySpec -> it.originatingKSFiles()
+ is TypeSpec -> it.originatingKSFiles()
+ is TypeAliasSpec -> it.originatingKSFiles()
+ else -> emptyList()
+ }
+ }
+ .distinct()
+}
+
+/** Adds the given [ksFile] to this builder's tags for use with [originatingKSFiles]. */
+public fun TypeAliasSpec.Builder.addOriginatingKSFile(ksFile: KSFile): TypeAliasSpec.Builder = apply {
+ getOrCreateKSFilesTag().add(ksFile)
+}
+
+/** Adds the given [ksFile] to this builder's tags for use with [originatingKSFiles]. */
+public fun PropertySpec.Builder.addOriginatingKSFile(ksFile: KSFile): PropertySpec.Builder = apply {
+ getOrCreateKSFilesTag().add(ksFile)
+}
+
+/** Adds the given [ksFile] to this builder's tags for use with [originatingKSFiles]. */
+public fun FunSpec.Builder.addOriginatingKSFile(ksFile: KSFile): FunSpec.Builder = apply {
+ getOrCreateKSFilesTag().add(ksFile)
+}
+
+/** Adds the given [ksFile] to this builder's tags for use with [originatingKSFiles]. */
+public fun TypeSpec.Builder.addOriginatingKSFile(ksFile: KSFile): TypeSpec.Builder = apply {
+ getOrCreateKSFilesTag().add(ksFile)
+}
+
+/**
+ * Writes this [FileSpec] to a given [codeGenerator] with the given [originatingKSFiles].
+ *
+ * Note that if none are specified, the [originatingKSFiles] argument defaults to using
+ * [FileSpec.originatingKSFiles], which will automatically resolve any files added to the
+ * contained declarations.
+ *
+ * See [the docs](https://github.com/google/ksp/blob/main/docs/incremental.md) for more information.
+ *
+ * @see FileSpec.originatingKSFiles
+ * @param codeGenerator the [CodeGenerator] to write to.
+ * @param aggregating flag indicating if this is an aggregating symbol processor.
+ */
+public fun FileSpec.writeTo(
+ codeGenerator: CodeGenerator,
+ aggregating: Boolean,
+ originatingKSFiles: Iterable<KSFile> = originatingKSFiles(),
+) {
+ val dependencies = kspDependencies(aggregating, originatingKSFiles)
+ writeTo(codeGenerator, dependencies)
+}
+
+/**
+ * Writes this [FileSpec] to a given [codeGenerator] with the given [dependencies].
+ *
+ * See [the docs](https://github.com/google/ksp/blob/main/docs/incremental.md) for more information.
+ *
+ * @see FileSpec.originatingKSFiles
+ * @see kspDependencies
+ * @param codeGenerator the [CodeGenerator] to write to.
+ * @param dependencies the [Dependencies] to create a new file with.
+ */
+public fun FileSpec.writeTo(
+ codeGenerator: CodeGenerator,
+ dependencies: Dependencies,
+) {
+ val file = codeGenerator.createNewFile(dependencies, packageName, name)
+ // Don't use writeTo(file) because that tries to handle directories under the hood
+ OutputStreamWriter(file, StandardCharsets.UTF_8)
+ .use(::writeTo)
+}
+
+/**
+ * Returns a KSP [Dependencies] component of this [FileSpec] with the given [originatingKSFiles],
+ * intended to be used in tandem with [writeTo].
+ *
+ * Note that if no [originatingKSFiles] are specified, the [originatingKSFiles] argument defaults
+ * to using [FileSpec.originatingKSFiles], which will automatically resolve any files added to the
+ * contained declarations.
+ *
+ * See [the docs](https://github.com/google/ksp/blob/main/docs/incremental.md) for more information.
+ *
+ * @see FileSpec.originatingKSFiles
+ * @see FileSpec.writeTo
+ * @param aggregating flag indicating if this is an aggregating symbol processor.
+ */
+public fun FileSpec.kspDependencies(
+ aggregating: Boolean,
+ originatingKSFiles: Iterable<KSFile> = originatingKSFiles(),
+): Dependencies = Dependencies(aggregating, *originatingKSFiles.toList().toTypedArray())
+
+/**
+ * A mutable [OriginatingKSFiles] instance for use with KotlinPoet Builders via [Taggable.Builder].
+ */
+private interface MutableOriginatingKSFiles : OriginatingKSFiles {
+ override val files: MutableList<KSFile>
+}
+
+private data class MutableOriginatingKSFilesImpl(override val files: MutableList<KSFile> = mutableListOf()) : MutableOriginatingKSFiles
+
+private fun Taggable.getKSFilesTag(): List<KSFile> {
+ return tag<OriginatingKSFiles>()?.files.orEmpty()
+}
+
+private fun Taggable.Builder<*>.getOrCreateKSFilesTag(): MutableList<KSFile> {
+ val holder = tags.getOrPut(
+ OriginatingKSFiles::class,
+ ::MutableOriginatingKSFilesImpl,
+ ) as MutableOriginatingKSFiles
+ return holder.files
+}
diff --git a/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/TypeParameterResolver.kt b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/TypeParameterResolver.kt
new file mode 100644
index 00000000..1304b2a4
--- /dev/null
+++ b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/TypeParameterResolver.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.ksp
+
+import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.KSTypeParameter
+import com.squareup.kotlinpoet.TypeVariableName
+
+/**
+ * A resolver for enclosing declarations' type parameters. Parent declarations can be anything with
+ * generics that child nodes declare as defined by [KSType.arguments].
+ *
+ * This is important for resolving inherited generics on child declarations, as KSP interop
+ * otherwise can't resolve them.
+ *
+ * In general, you want to retrieve an instance of this via [toTypeParameterResolver].
+ *
+ * @see toTypeParameterResolver
+ */
+public interface TypeParameterResolver {
+ public val parametersMap: Map<String, TypeVariableName>
+ public operator fun get(index: String): TypeVariableName
+
+ public companion object {
+ /**
+ * An empty instance of [TypeParameterResolver], only should be used if enclosing declarations
+ * are known to not have arguments, such as top-level classes.
+ */
+ public val EMPTY: TypeParameterResolver = object : TypeParameterResolver {
+ override val parametersMap: Map<String, TypeVariableName> = emptyMap()
+
+ override fun get(index: String): TypeVariableName = throw NoSuchElementException("No TypeParameter found for index $index")
+ }
+ }
+}
+
+/**
+ * Returns a [TypeParameterResolver] for this list of [KSTypeParameters][KSTypeParameter] for use
+ * with enclosed declarations.
+ *
+ * @param parent the optional parent resolver, if any. An example of this is cases where you might
+ * create a resolver for a [KSFunction] and supply a parent resolved from the
+ * enclosing [KSClassDeclaration].
+ * @param sourceTypeHint an optional hint for error messages. Unresolvable parameter IDs will
+ * include this hint in the thrown error's message.
+ */
+public fun List<KSTypeParameter>.toTypeParameterResolver(
+ parent: TypeParameterResolver? = null,
+ sourceTypeHint: String = "<unknown>",
+): TypeParameterResolver {
+ val parametersMap = LinkedHashMap<String, TypeVariableName>()
+ val typeParamResolver = { id: String ->
+ parametersMap[id]
+ ?: parent?.get(id)
+ ?: throw IllegalStateException(
+ "No type argument found for $id! Analyzed $sourceTypeHint with known parameters " +
+ "${parametersMap.keys}",
+ )
+ }
+
+ val resolver = object : TypeParameterResolver {
+ override val parametersMap: Map<String, TypeVariableName> = parametersMap
+
+ override operator fun get(index: String): TypeVariableName = typeParamResolver(index)
+ }
+
+ // Fill the parametersMap. Need to do sequentially and allow for referencing previously defined params
+ for (typeVar in this) {
+ // Put the simple typevar in first, then it can be referenced in the full toTypeVariable()
+ // replacement later that may add bounds referencing this.
+ val id = typeVar.name.getShortName()
+ parametersMap[id] = TypeVariableName(id)
+ }
+
+ for (typeVar in this) {
+ val id = typeVar.name.getShortName()
+ // Now replace it with the full version.
+ parametersMap[id] = typeVar.toTypeVariableName(resolver)
+ }
+
+ return resolver
+}
diff --git a/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Visibilities.kt b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Visibilities.kt
new file mode 100644
index 00000000..dcca6d2e
--- /dev/null
+++ b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/Visibilities.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.ksp
+
+import com.google.devtools.ksp.symbol.Visibility
+import com.google.devtools.ksp.symbol.Visibility.JAVA_PACKAGE
+import com.google.devtools.ksp.symbol.Visibility.LOCAL
+import com.squareup.kotlinpoet.KModifier
+
+/**
+ * Returns the [KModifier] representation of this visibility or null if this is [JAVA_PACKAGE]
+ * or [LOCAL] (which do not have obvious [KModifier] alternatives).
+ */
+public fun Visibility.toKModifier(): KModifier? {
+ return when (this) {
+ Visibility.PUBLIC -> KModifier.PUBLIC
+ Visibility.PRIVATE -> KModifier.PRIVATE
+ Visibility.PROTECTED -> KModifier.PROTECTED
+ Visibility.INTERNAL -> KModifier.INTERNAL
+ else -> null
+ }
+}
diff --git a/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/utils.kt b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/utils.kt
new file mode 100644
index 00000000..7bc8feda
--- /dev/null
+++ b/interop/ksp/src/main/kotlin/com/squareup/kotlinpoet/ksp/utils.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.ksp
+
+import com.google.devtools.ksp.isLocal
+import com.google.devtools.ksp.symbol.KSDeclaration
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.LambdaTypeName
+import com.squareup.kotlinpoet.ParameterizedTypeName
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.TypeName
+
+internal fun TypeName.rawType(): ClassName {
+ return findRawType() ?: throw IllegalArgumentException("Cannot get raw type from $this")
+}
+
+internal fun TypeName.findRawType(): ClassName? {
+ return when (this) {
+ is ClassName -> this
+ is ParameterizedTypeName -> rawType
+ is LambdaTypeName -> {
+ var count = parameters.size
+ if (receiver != null) {
+ count++
+ }
+ val functionSimpleName = if (count >= 23) {
+ "FunctionN"
+ } else {
+ "Function$count"
+ }
+ ClassName("kotlin.jvm.functions", functionSimpleName)
+ }
+ else -> null
+ }
+}
+
+internal fun ClassName.withTypeArguments(arguments: List<TypeName>): TypeName {
+ return if (arguments.isEmpty()) {
+ this
+ } else {
+ this.parameterizedBy(arguments)
+ }
+}
+
+internal fun KSDeclaration.toClassNameInternal(): ClassName {
+ require(!isLocal()) {
+ "Local/anonymous classes are not supported!"
+ }
+ val pkgName = packageName.asString()
+ val typesString = checkNotNull(qualifiedName).asString().removePrefix("$pkgName.")
+
+ val simpleNames = typesString
+ .split(".")
+ return ClassName(pkgName, simpleNames)
+}
diff --git a/interop/ksp/test-processor/build.gradle.kts b/interop/ksp/test-processor/build.gradle.kts
new file mode 100644
index 00000000..9fc212e7
--- /dev/null
+++ b/interop/ksp/test-processor/build.gradle.kts
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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.
+ */
+
+plugins {
+ id("com.google.devtools.ksp")
+}
+
+dependencies {
+ implementation(project(":kotlinpoet"))
+ implementation(project(":interop:ksp"))
+ implementation(libs.autoService)
+ compileOnly(libs.ksp.api)
+ ksp(libs.autoService.ksp)
+ // Always force the latest version of the KSP/kotlin impl in tests to match what we're building against
+ testImplementation(libs.ksp.api)
+ testImplementation(libs.kotlin.compilerEmbeddable)
+ testImplementation(libs.kotlin.annotationProcessingEmbeddable)
+ testImplementation(libs.ksp)
+ testImplementation(libs.kotlinCompileTesting)
+ testImplementation(libs.kotlinCompileTesting.ksp)
+ testImplementation(libs.kotlin.junit)
+ testImplementation(libs.truth)
+}
diff --git a/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessor.kt b/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessor.kt
new file mode 100644
index 00000000..b1d41d40
--- /dev/null
+++ b/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessor.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.ksp.test.processor
+
+import com.google.devtools.ksp.getDeclaredFunctions
+import com.google.devtools.ksp.getDeclaredProperties
+import com.google.devtools.ksp.getVisibility
+import com.google.devtools.ksp.isConstructor
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.processing.SymbolProcessor
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
+import com.google.devtools.ksp.symbol.KSAnnotated
+import com.google.devtools.ksp.symbol.KSClassDeclaration
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.ParameterSpec
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.ksp.addOriginatingKSFile
+import com.squareup.kotlinpoet.ksp.kspDependencies
+import com.squareup.kotlinpoet.ksp.originatingKSFiles
+import com.squareup.kotlinpoet.ksp.toAnnotationSpec
+import com.squareup.kotlinpoet.ksp.toKModifier
+import com.squareup.kotlinpoet.ksp.toTypeName
+import com.squareup.kotlinpoet.ksp.toTypeParameterResolver
+import com.squareup.kotlinpoet.ksp.toTypeVariableName
+import com.squareup.kotlinpoet.ksp.writeTo
+
+/**
+ * A simple processor that generates a skeleton API of classes annotated with [ExampleAnnotation]
+ * for test and verification purposes.
+ */
+class TestProcessor(private val env: SymbolProcessorEnvironment) : SymbolProcessor {
+
+ private val unwrapTypeAliases = env.options["unwrapTypeAliases"]?.toBooleanStrictOrNull() ?: false
+
+ override fun process(resolver: Resolver): List<KSAnnotated> {
+ resolver.getSymbolsWithAnnotation(ExampleAnnotation::class.java.canonicalName)
+ .forEach(::process)
+ return emptyList()
+ }
+
+ private fun process(decl: KSAnnotated) {
+ check(decl is KSClassDeclaration)
+
+ val classBuilder = TypeSpec.classBuilder(decl.simpleName.getShortName())
+ .addOriginatingKSFile(decl.containingFile!!)
+ .apply {
+ decl.getVisibility().toKModifier()?.let { addModifiers(it) }
+ addModifiers(decl.modifiers.mapNotNull { it.toKModifier() })
+ addAnnotations(
+ decl.annotations
+ .filterNot { it.shortName.getShortName() == "ExampleAnnotation" }
+ .map { it.toAnnotationSpec() }.asIterable(),
+ )
+ }
+ val classTypeParams = decl.typeParameters.toTypeParameterResolver()
+ classBuilder.addTypeVariables(
+ decl.typeParameters.map { typeParam ->
+ typeParam.toTypeVariableName(classTypeParams).let {
+ if (unwrapTypeAliases) {
+ it.unwrapTypeAlias()
+ } else {
+ it
+ }
+ }
+ },
+ )
+
+ // Add properties
+ for (property in decl.getDeclaredProperties()) {
+ classBuilder.addProperty(
+ PropertySpec.builder(
+ property.simpleName.getShortName(),
+ property.type.toTypeName(classTypeParams).let {
+ if (unwrapTypeAliases) {
+ it.unwrapTypeAlias()
+ } else {
+ it
+ }
+ },
+ )
+ .addOriginatingKSFile(decl.containingFile!!)
+ .mutable(property.isMutable)
+ .apply {
+ property.getVisibility().toKModifier()?.let { addModifiers(it) }
+ addModifiers(property.modifiers.mapNotNull { it.toKModifier() })
+ addAnnotations(
+ property.annotations
+ .map { it.toAnnotationSpec() }.asIterable(),
+ )
+ }
+ .build(),
+ )
+ }
+
+ // Add functions
+ for (function in decl.getDeclaredFunctions().filterNot { it.isConstructor() }) {
+ val functionTypeParams = function.typeParameters.toTypeParameterResolver(classTypeParams)
+ classBuilder.addFunction(
+ FunSpec.builder(function.simpleName.getShortName())
+ .addOriginatingKSFile(decl.containingFile!!)
+ .apply {
+ function.getVisibility().toKModifier()?.let { addModifiers(it) }
+ addModifiers(function.modifiers.mapNotNull { it.toKModifier() })
+ }
+ .addTypeVariables(
+ function.typeParameters.map { typeParam ->
+ typeParam.toTypeVariableName(functionTypeParams).let {
+ if (unwrapTypeAliases) {
+ it.unwrapTypeAlias()
+ } else {
+ it
+ }
+ }
+ },
+ )
+ .addParameters(
+ function.parameters.map { parameter ->
+ val parameterType = parameter.type.toTypeName(functionTypeParams).let {
+ if (unwrapTypeAliases) {
+ it.unwrapTypeAlias()
+ } else {
+ it
+ }
+ }
+ parameter.name?.let {
+ ParameterSpec.builder(it.getShortName(), parameterType).build()
+ } ?: ParameterSpec.unnamed(parameterType)
+ },
+ )
+ .returns(
+ function.returnType!!.toTypeName(functionTypeParams).let {
+ if (unwrapTypeAliases) {
+ it.unwrapTypeAlias()
+ } else {
+ it
+ }
+ },
+ )
+ .build(),
+ )
+ }
+
+ val typeSpec = classBuilder.build()
+ val fileSpec = FileSpec.builder(decl.packageName.asString(), "Test${typeSpec.name}")
+ .addType(typeSpec)
+ .build()
+
+ // Ensure that we're properly de-duping these under the hood.
+ check(fileSpec.originatingKSFiles().size == 1)
+
+ val dependencies = fileSpec.kspDependencies(aggregating = true)
+ check(dependencies.originatingFiles.size == 1)
+ check(dependencies.originatingFiles[0] == decl.containingFile)
+
+ fileSpec.writeTo(env.codeGenerator, dependencies)
+ }
+}
diff --git a/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessorProvider.kt b/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessorProvider.kt
new file mode 100644
index 00000000..35bf5ce9
--- /dev/null
+++ b/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessorProvider.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.ksp.test.processor
+
+import com.google.auto.service.AutoService
+import com.google.devtools.ksp.processing.SymbolProcessor
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
+import com.google.devtools.ksp.processing.SymbolProcessorProvider
+
+@AutoService(SymbolProcessorProvider::class)
+class TestProcessorProvider : SymbolProcessorProvider {
+ override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
+ return TestProcessor(environment)
+ }
+}
diff --git a/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TypeAliasUnwrapping.kt b/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TypeAliasUnwrapping.kt
new file mode 100644
index 00000000..599f8d70
--- /dev/null
+++ b/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TypeAliasUnwrapping.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.ksp.test.processor
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.LambdaTypeName
+import com.squareup.kotlinpoet.ParameterizedTypeName
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.TypeVariableName
+import com.squareup.kotlinpoet.WildcardTypeName
+import com.squareup.kotlinpoet.tag
+import com.squareup.kotlinpoet.tags.TypeAliasTag
+import java.util.TreeSet
+
+/*
+ * Example implementation of how to unwrap a typealias from TypeNameAliasTag
+ */
+
+internal fun TypeName.unwrapTypeAliasReal(): TypeName {
+ return tag<TypeAliasTag>()?.abbreviatedType?.let { unwrappedType ->
+ // If any type is nullable, then the whole thing is nullable
+ var isAnyNullable = isNullable
+ // Keep track of all annotations across type levels. Sort them too for consistency.
+ val runningAnnotations = TreeSet<AnnotationSpec>(compareBy { it.toString() }).apply {
+ addAll(annotations)
+ }
+ val nestedUnwrappedType = unwrappedType.unwrapTypeAlias()
+ runningAnnotations.addAll(nestedUnwrappedType.annotations)
+ isAnyNullable = isAnyNullable || nestedUnwrappedType.isNullable
+ nestedUnwrappedType.copy(nullable = isAnyNullable, annotations = runningAnnotations.toList())
+ } ?: this
+}
+
+// TypeVariableName gets a special overload because these usually need to be kept in a type-safe
+// manner.
+internal fun TypeVariableName.unwrapTypeAlias(): TypeVariableName {
+ return TypeVariableName(
+ name = name,
+ bounds = bounds.map { it.unwrapTypeAlias() },
+ variance = variance,
+ )
+ .copy(nullable = isNullable, annotations = annotations, tags = tags)
+}
+
+internal fun TypeName.unwrapTypeAlias(): TypeName {
+ return when (this) {
+ is ClassName -> unwrapTypeAliasReal()
+ is ParameterizedTypeName -> unwrapTypeAliasReal()
+ is TypeVariableName -> unwrapTypeAlias()
+ is WildcardTypeName -> unwrapTypeAliasReal()
+ is LambdaTypeName -> unwrapTypeAliasReal()
+ else -> throw UnsupportedOperationException("Type '${javaClass.simpleName}' is illegal. Only classes, parameterized types, wildcard types, or type variables are allowed.")
+ }
+}
diff --git a/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/exampleAnnotations.kt b/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/exampleAnnotations.kt
new file mode 100644
index 00000000..993222e2
--- /dev/null
+++ b/interop/ksp/test-processor/src/main/kotlin/com/squareup/kotlinpoet/ksp/test/processor/exampleAnnotations.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.ksp.test.processor
+
+import kotlin.reflect.KClass
+
+annotation class ExampleAnnotation
+
+annotation class ComprehensiveAnnotation<T : CharSequence>(
+ val boolean: Boolean,
+ val booleanArray: BooleanArray,
+ val byte: Byte,
+ val byteArray: ByteArray,
+ val char: Char,
+ val charArray: CharArray,
+ val short: Short,
+ val shortArray: ShortArray,
+ val int: Int,
+ val intArray: IntArray,
+ val long: Long,
+ val longArray: LongArray,
+ val float: Float,
+ val floatArray: FloatArray,
+ val double: Double,
+ val doubleArray: DoubleArray,
+ val string: String,
+ val stringArray: Array<String>,
+ val someClass: KClass<*>,
+ val someClasses: Array<KClass<*>>,
+ val enumValue: AnnotationEnumValue,
+ val enumValueArray: Array<AnnotationEnumValue>,
+ val anotherAnnotation: AnotherAnnotation,
+ val anotherAnnotationArray: Array<AnotherAnnotation>,
+ // This is still included even when the argument is omitted until https://github.com/google/ksp/issues/674
+ val defaultingString: String = "defaultValue",
+)
+
+annotation class AnotherAnnotation(val input: String)
+
+enum class AnnotationEnumValue {
+ ONE, TWO, THREE
+}
diff --git a/interop/ksp/test-processor/src/test/kotlin/com/squareup/kotlinpoet/ksp/test/processor/KsTypesTest.kt b/interop/ksp/test-processor/src/test/kotlin/com/squareup/kotlinpoet/ksp/test/processor/KsTypesTest.kt
new file mode 100644
index 00000000..b392f374
--- /dev/null
+++ b/interop/ksp/test-processor/src/test/kotlin/com/squareup/kotlinpoet/ksp/test/processor/KsTypesTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.ksp.test.processor
+
+import com.google.common.truth.Truth.assertThat
+import com.google.devtools.ksp.symbol.KSAnnotation
+import com.google.devtools.ksp.symbol.KSDeclaration
+import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.KSTypeArgument
+import com.google.devtools.ksp.symbol.Nullability
+import com.squareup.kotlinpoet.ksp.toTypeName
+import kotlin.test.assertFailsWith
+import org.junit.Test
+
+class KsTypesTest {
+ // Regression test for https://github.com/square/kotlinpoet/issues/1178
+ @Test
+ fun errorTypesShouldFail() {
+ val type = object : KSType {
+ override val isError: Boolean = true
+
+ // Boilerplate
+ override val annotations: Sequence<KSAnnotation>
+ get() = throw NotImplementedError()
+ override val arguments: List<KSTypeArgument>
+ get() = throw NotImplementedError()
+ override val declaration: KSDeclaration
+ get() = throw NotImplementedError()
+ override val isFunctionType: Boolean
+ get() = throw NotImplementedError()
+ override val isMarkedNullable: Boolean
+ get() = throw NotImplementedError()
+ override val isSuspendFunctionType: Boolean
+ get() = throw NotImplementedError()
+ override val nullability: Nullability
+ get() = throw NotImplementedError()
+
+ override fun isAssignableFrom(that: KSType): Boolean {
+ throw NotImplementedError()
+ }
+
+ override fun isCovarianceFlexible(): Boolean {
+ throw NotImplementedError()
+ }
+
+ override fun isMutabilityFlexible(): Boolean {
+ throw NotImplementedError()
+ }
+
+ override fun makeNotNullable(): KSType {
+ throw NotImplementedError()
+ }
+
+ override fun makeNullable(): KSType {
+ throw NotImplementedError()
+ }
+
+ override fun replace(arguments: List<KSTypeArgument>): KSType {
+ throw NotImplementedError()
+ }
+
+ override fun starProjection(): KSType {
+ throw NotImplementedError()
+ }
+ }
+
+ val exception = assertFailsWith<IllegalArgumentException> {
+ type.toTypeName()
+ }
+ assertThat(exception).hasMessageThat()
+ .contains("is not resolvable in the current round of processing")
+ }
+}
diff --git a/interop/ksp/test-processor/src/test/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessorTest.kt b/interop/ksp/test-processor/src/test/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessorTest.kt
new file mode 100644
index 00000000..06518eff
--- /dev/null
+++ b/interop/ksp/test-processor/src/test/kotlin/com/squareup/kotlinpoet/ksp/test/processor/TestProcessorTest.kt
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.ksp.test.processor
+
+import com.google.common.truth.Truth.assertThat
+import com.tschuchort.compiletesting.KotlinCompilation
+import com.tschuchort.compiletesting.SourceFile
+import com.tschuchort.compiletesting.SourceFile.Companion.kotlin
+import com.tschuchort.compiletesting.kspArgs
+import com.tschuchort.compiletesting.kspIncremental
+import com.tschuchort.compiletesting.kspSourcesDir
+import com.tschuchort.compiletesting.symbolProcessorProviders
+import java.io.File
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+
+class TestProcessorTest {
+
+ @Rule
+ @JvmField
+ val temporaryFolder: TemporaryFolder = TemporaryFolder()
+
+ @Test
+ fun smokeTest() {
+ val compilation = prepareCompilation(
+ kotlin(
+ "Example.kt",
+ """
+ package test
+
+ import com.squareup.kotlinpoet.ksp.test.processor.AnnotationEnumValue
+ import com.squareup.kotlinpoet.ksp.test.processor.AnotherAnnotation
+ import com.squareup.kotlinpoet.ksp.test.processor.ComprehensiveAnnotation
+ import com.squareup.kotlinpoet.ksp.test.processor.ExampleAnnotation
+
+ typealias TypeAliasName = String
+ typealias GenericTypeAlias = List<String>
+ typealias ParameterizedTypeAlias<T> = List<T>
+
+ @ComprehensiveAnnotation<String>(
+ true, // Omit the name intentionally here to test names are still picked up
+ booleanArray = [true],
+ byte = 0.toByte(),
+ byteArray = [0.toByte()],
+ char = 'a',
+ charArray = ['a', 'b', 'c'],
+ short = 0.toShort(),
+ shortArray = [0.toShort()],
+ int = 0,
+ intArray = [0],
+ long = 0L,
+ longArray = [0L],
+ float = 0f,
+ floatArray = [0f],
+ double = 0.0,
+ doubleArray = [0.0],
+ string = "Hello",
+ stringArray = ["Hello"],
+ someClass = String::class,
+ someClasses = [String::class, Int::class],
+ enumValue = AnnotationEnumValue.ONE,
+ enumValueArray = [AnnotationEnumValue.ONE, AnnotationEnumValue.TWO],
+ anotherAnnotation = AnotherAnnotation("Hello"),
+ anotherAnnotationArray = [AnotherAnnotation("Hello")]
+ )
+ @ExampleAnnotation
+ class SmokeTestClass<T, R : Any, E : Enum<E>> {
+ @field:AnotherAnnotation("siteTargeting")
+ private val propA: String = ""
+ internal val propB: String = ""
+ val propC: Int = 0
+ val propD: Int? = null
+ lateinit var propE: String
+ var propF: T? = null
+
+ fun functionA(): String {
+ error()
+ }
+
+ fun functionB(): R {
+ error()
+ }
+
+ fun <F> functionC(param1: String, param2: T, param3: F, param4: F?): R {
+ error()
+ }
+
+ suspend fun functionD(
+ param1: () -> String,
+ param2: (String) -> String,
+ param3: String.() -> String
+ ) {
+ }
+
+ // A whole bunch of wild types from Moshi's codegen smoke tests
+ fun wildTypes(
+ age: Int,
+ nationalities: List<String>,
+ weight: Float,
+ tattoos: Boolean = false,
+ race: String?,
+ hasChildren: Boolean = false,
+ favoriteFood: String? = null,
+ favoriteDrink: String? = "Water",
+ wildcardOut: MutableList<out String>,
+ nullableWildcardOut: MutableList<out String?>,
+ wildcardIn: Array<in String>,
+ any: List<*>,
+ anyTwo: List<Any>,
+ anyOut: MutableList<out Any>,
+ nullableAnyOut: MutableList<out Any?>,
+ favoriteThreeNumbers: IntArray,
+ favoriteArrayValues: Array<String>,
+ favoriteNullableArrayValues: Array<String?>,
+ nullableSetListMapArrayNullableIntWithDefault: Set<List<Map<String, Array<IntArray?>>>>?,
+ // These are actually currently rendered incorrectly and always unwrapped
+ aliasedName: TypeAliasName,
+ genericAlias: GenericTypeAlias,
+ parameterizedTypeAlias: ParameterizedTypeAlias<String>,
+ nestedArray: Array<Map<String, Any>>?
+ ) {
+
+ }
+ }
+ """,
+ ),
+ )
+ val result = compilation.compile()
+ assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
+ val generatedFileText = File(compilation.kspSourcesDir, "kotlin/test/TestSmokeTestClass.kt")
+ .readText()
+ assertThat(generatedFileText).isEqualTo(
+ """
+ package test
+
+ import com.squareup.kotlinpoet.ksp.test.processor.AnnotationEnumValue
+ import com.squareup.kotlinpoet.ksp.test.processor.AnotherAnnotation
+ import com.squareup.kotlinpoet.ksp.test.processor.ComprehensiveAnnotation
+ import kotlin.Any
+ import kotlin.Array
+ import kotlin.Boolean
+ import kotlin.Enum
+ import kotlin.Float
+ import kotlin.Function0
+ import kotlin.Function1
+ import kotlin.Int
+ import kotlin.IntArray
+ import kotlin.String
+ import kotlin.Unit
+ import kotlin.collections.List
+ import kotlin.collections.Map
+ import kotlin.collections.MutableList
+ import kotlin.collections.Set
+
+ @ComprehensiveAnnotation<String>(
+ boolean = true,
+ booleanArray = booleanArrayOf(true),
+ byte = 0.toByte(),
+ byteArray = byteArrayOf(0.toByte()),
+ char = 'a',
+ charArray = charArrayOf('a', 'b', 'c'),
+ short = 0.toShort(),
+ shortArray = shortArrayOf(0.toShort()),
+ int = 0,
+ intArray = intArrayOf(0),
+ long = 0,
+ longArray = longArrayOf(0),
+ float = 0.0f,
+ floatArray = floatArrayOf(0.0f),
+ double = 0.0,
+ doubleArray = doubleArrayOf(0.0),
+ string = "Hello",
+ stringArray = arrayOf("Hello"),
+ someClass = String::class,
+ someClasses = arrayOf(String::class, Int::class),
+ enumValue = AnnotationEnumValue.ONE,
+ enumValueArray = arrayOf(AnnotationEnumValue.ONE, AnnotationEnumValue.TWO),
+ anotherAnnotation = AnotherAnnotation(input = "Hello"),
+ anotherAnnotationArray = arrayOf(AnotherAnnotation(input = "Hello")),
+ defaultingString = "defaultValue",
+ )
+ public class SmokeTestClass<T, R : Any, E : Enum<E>> {
+ @field:AnotherAnnotation(input = "siteTargeting")
+ private val propA: String
+
+ internal val propB: String
+
+ public val propC: Int
+
+ public val propD: Int?
+
+ public lateinit var propE: String
+
+ public var propF: T?
+
+ public fun functionA(): String {
+ }
+
+ public fun functionB(): R {
+ }
+
+ public fun <F> functionC(
+ param1: String,
+ param2: T,
+ param3: F,
+ param4: F?,
+ ): R {
+ }
+
+ public suspend fun functionD(
+ param1: Function0<String>,
+ param2: Function1<String, String>,
+ param3: Function1<String, String>,
+ ): Unit {
+ }
+
+ public fun wildTypes(
+ age: Int,
+ nationalities: List<String>,
+ weight: Float,
+ tattoos: Boolean,
+ race: String?,
+ hasChildren: Boolean,
+ favoriteFood: String?,
+ favoriteDrink: String?,
+ wildcardOut: MutableList<out String>,
+ nullableWildcardOut: MutableList<out String?>,
+ wildcardIn: Array<in String>,
+ any: List<*>,
+ anyTwo: List<Any>,
+ anyOut: MutableList<out Any>,
+ nullableAnyOut: MutableList<*>,
+ favoriteThreeNumbers: IntArray,
+ favoriteArrayValues: Array<String>,
+ favoriteNullableArrayValues: Array<String?>,
+ nullableSetListMapArrayNullableIntWithDefault: Set<List<Map<String, Array<IntArray?>>>>?,
+ aliasedName: TypeAliasName,
+ genericAlias: GenericTypeAlias,
+ parameterizedTypeAlias: ParameterizedTypeAlias<String>,
+ nestedArray: Array<Map<String, Any>>?,
+ ): Unit {
+ }
+ }
+
+ """.trimIndent(),
+ )
+ }
+
+ @Test
+ fun unwrapTypeAliases() {
+ val compilation = prepareCompilation(
+ kotlin(
+ "Example.kt",
+ """
+ package test
+
+ import com.squareup.kotlinpoet.ksp.test.processor.ExampleAnnotation
+
+ typealias TypeAliasName = String
+ typealias GenericTypeAlias = List<String>
+ typealias GenericMapTypeAlias<V, K> = Map<K, V>
+ typealias T1Unused<T1, T2> = Map<T2, String>
+ typealias A1<T1, T2> = A2<T2, T1>
+ typealias A2<T2, T3> = Map<T3, T2>
+
+ @ExampleAnnotation
+ class Example {
+ fun aliases(
+ aliasedName: TypeAliasName,
+ genericAlias: GenericTypeAlias,
+ genericMapAlias: GenericMapTypeAlias<String, Int>,
+ t1Unused: T1Unused<String, Int>,
+ a1: A1<String, Int>,
+ ) {
+ }
+ }
+ """,
+ ),
+ )
+ compilation.kspArgs["unwrapTypeAliases"] = "true"
+ val result = compilation.compile()
+ assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
+ val generatedFileText = File(compilation.kspSourcesDir, "kotlin/test/TestExample.kt")
+ .readText()
+ assertThat(generatedFileText).isEqualTo(
+ """
+ package test
+
+ import kotlin.Int
+ import kotlin.String
+ import kotlin.Unit
+ import kotlin.collections.List
+ import kotlin.collections.Map
+
+ public class Example {
+ public fun aliases(
+ aliasedName: String,
+ genericAlias: List<String>,
+ genericMapAlias: Map<Int, String>,
+ t1Unused: Map<Int, String>,
+ a1: Map<String, Int>,
+ ): Unit {
+ }
+ }
+
+ """.trimIndent(),
+ )
+ }
+
+ @Test
+ fun complexSelfReferencingTypeArgs() {
+ val compilation = prepareCompilation(
+ kotlin(
+ "Example.kt",
+ """
+ package test
+
+ import com.squareup.kotlinpoet.ksp.test.processor.ExampleAnnotation
+
+ @ExampleAnnotation
+ open class Node<T : Node<T, R>, R : Node<R, T>> {
+ var t: T? = null
+ var r: R? = null
+ }
+ """,
+ ),
+ )
+
+ val result = compilation.compile()
+ assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
+ val generatedFileText = File(compilation.kspSourcesDir, "kotlin/test/TestNode.kt")
+ .readText()
+ assertThat(generatedFileText).isEqualTo(
+ """
+ package test
+
+ public open class Node<T : Node<T, R>, R : Node<R, T>> {
+ public var t: T?
+
+ public var r: R?
+ }
+
+ """.trimIndent(),
+ )
+ }
+
+ @Test
+ fun wildcardParameterForRecursiveTypeBound() {
+ // Enum is an example of a recursive type bound - Enum<E: Enum<E>>
+ val compilation = prepareCompilation(
+ kotlin(
+ "Example.kt",
+ """
+ package test
+
+ import com.squareup.kotlinpoet.ksp.test.processor.ExampleAnnotation
+
+ @ExampleAnnotation
+ class EnumWrapper {
+ val enumValue: Enum<*>
+ }
+ """,
+ ),
+ )
+
+ val result = compilation.compile()
+ assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
+ val generatedFileText = File(compilation.kspSourcesDir, "kotlin/test/TestEnumWrapper.kt")
+ .readText()
+ assertThat(generatedFileText).isEqualTo(
+ """
+ package test
+
+ import kotlin.Enum
+
+ public class EnumWrapper {
+ public val enumValue: Enum<*>
+ }
+
+ """.trimIndent(),
+ )
+ }
+
+ @Test
+ fun transitiveAliases() {
+ val compilation = prepareCompilation(
+ kotlin(
+ "Example.kt",
+ """
+ package test
+
+ import com.squareup.kotlinpoet.ksp.test.processor.ExampleAnnotation
+
+ typealias Alias23 = (Any) -> Any
+ typealias Alias77<Q> = List<Q>
+ typealias Alias73<Q> = Map<String, Q>
+ typealias Alias55<Q> = Alias73<Q>
+ typealias Alias99<Q> = Alias55<Q>
+ typealias Alias43<Q> = Alias77<Q>
+ typealias Alias47<Q> = Alias43<Q>
+ typealias Alias41<Z, Q> = (Alias43<Z>) -> Alias47<Q>
+
+ @ExampleAnnotation
+ interface TransitiveAliases {
+ fun <T : Alias41<Alias23, out Alias77<Alias73<Int>>>> bar(vararg arg1: T)
+ }
+ """,
+ ),
+ )
+
+ val result = compilation.compile()
+ assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
+ val generatedFileText = File(compilation.kspSourcesDir, "kotlin/test/TestTransitiveAliases.kt")
+ .readText()
+ assertThat(generatedFileText).isEqualTo(
+ """
+ package test
+
+ import kotlin.Int
+ import kotlin.Unit
+
+ public class TransitiveAliases {
+ public fun <T : Alias41<Alias23, out Alias77<Alias73<Int>>>> bar(arg1: T): Unit {
+ }
+ }
+
+ """.trimIndent(),
+ )
+ }
+
+ @Test
+ fun aliasAsTypeArgument() {
+ val compilation = prepareCompilation(
+ kotlin(
+ "Example.kt",
+ """
+ package test
+
+ import com.squareup.kotlinpoet.ksp.test.processor.ExampleAnnotation
+
+ typealias Alias997 = Map<String, Int>
+
+ @ExampleAnnotation
+ interface AliasAsTypeArgument {
+ fun bar(arg1: List<Alias997>)
+ }
+ """,
+ ),
+ )
+
+ val result = compilation.compile()
+ assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
+ val generatedFileText = File(compilation.kspSourcesDir, "kotlin/test/TestAliasAsTypeArgument.kt")
+ .readText()
+
+ assertThat(generatedFileText).isEqualTo(
+ """
+ package test
+
+ import kotlin.Unit
+ import kotlin.collections.List
+
+ public class AliasAsTypeArgument {
+ public fun bar(arg1: List<Alias997>): Unit {
+ }
+ }
+
+ """.trimIndent(),
+ )
+ }
+
+ private fun prepareCompilation(vararg sourceFiles: SourceFile): KotlinCompilation {
+ return KotlinCompilation()
+ .apply {
+ workingDir = temporaryFolder.root
+ inheritClassPath = true
+ symbolProcessorProviders = listOf(TestProcessorProvider())
+ sources = sourceFiles.asList()
+ verbose = false
+ kspIncremental = true // The default now
+ }
+ }
+}
diff --git a/kotlinpoet/api/kotlinpoet.api b/kotlinpoet/api/kotlinpoet.api
new file mode 100644
index 00000000..59f04dca
--- /dev/null
+++ b/kotlinpoet/api/kotlinpoet.api
@@ -0,0 +1,1164 @@
+public final class com/squareup/kotlinpoet/AnnotationSpec : com/squareup/kotlinpoet/Taggable {
+ public static final field Companion Lcom/squareup/kotlinpoet/AnnotationSpec$Companion;
+ public static final fun builder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+ public static final fun builder (Lcom/squareup/kotlinpoet/ParameterizedTypeName;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+ public static final fun builder (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+ public static final fun builder (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+ public fun equals (Ljava/lang/Object;)Z
+ public static final fun get (Ljava/lang/annotation/Annotation;)Lcom/squareup/kotlinpoet/AnnotationSpec;
+ public static final fun get (Ljava/lang/annotation/Annotation;Z)Lcom/squareup/kotlinpoet/AnnotationSpec;
+ public static final fun get (Ljavax/lang/model/element/AnnotationMirror;)Lcom/squareup/kotlinpoet/AnnotationSpec;
+ public final fun getClassName ()Lcom/squareup/kotlinpoet/ClassName;
+ public final fun getMembers ()Ljava/util/List;
+ public fun getTags ()Ljava/util/Map;
+ public final fun getTypeName ()Lcom/squareup/kotlinpoet/TypeName;
+ public final fun getUseSiteTarget ()Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+ public fun hashCode ()I
+ public fun tag (Ljava/lang/Class;)Ljava/lang/Object;
+ public fun tag (Lkotlin/reflect/KClass;)Ljava/lang/Object;
+ public final fun toBuilder ()Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/AnnotationSpec$Builder : com/squareup/kotlinpoet/Taggable$Builder {
+ public static final field Companion Lcom/squareup/kotlinpoet/AnnotationSpec$Builder$Companion;
+ public final fun addMember (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+ public final fun addMember (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+ public final fun build ()Lcom/squareup/kotlinpoet/AnnotationSpec;
+ public final fun getMembers ()Ljava/util/List;
+ public fun getTags ()Ljava/util/Map;
+ public fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+ public synthetic fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+ public fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+ public synthetic fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+ public final fun useSiteTarget (Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+}
+
+public final class com/squareup/kotlinpoet/AnnotationSpec$Builder$Companion {
+}
+
+public final class com/squareup/kotlinpoet/AnnotationSpec$Companion {
+ public final fun builder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+ public final fun builder (Lcom/squareup/kotlinpoet/ParameterizedTypeName;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+ public final fun builder (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+ public final fun builder (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/AnnotationSpec$Builder;
+ public final fun get (Ljava/lang/annotation/Annotation;)Lcom/squareup/kotlinpoet/AnnotationSpec;
+ public final fun get (Ljava/lang/annotation/Annotation;Z)Lcom/squareup/kotlinpoet/AnnotationSpec;
+ public final fun get (Ljavax/lang/model/element/AnnotationMirror;)Lcom/squareup/kotlinpoet/AnnotationSpec;
+ public static synthetic fun get$default (Lcom/squareup/kotlinpoet/AnnotationSpec$Companion;Ljava/lang/annotation/Annotation;ZILjava/lang/Object;)Lcom/squareup/kotlinpoet/AnnotationSpec;
+}
+
+public final class com/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget : java/lang/Enum {
+ public static final field DELEGATE Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+ public static final field FIELD Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+ public static final field FILE Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+ public static final field GET Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+ public static final field PARAM Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+ public static final field PROPERTY Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+ public static final field RECEIVER Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+ public static final field SET Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+ public static final field SETPARAM Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+ public static fun valueOf (Ljava/lang/String;)Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+ public static fun values ()[Lcom/squareup/kotlinpoet/AnnotationSpec$UseSiteTarget;
+}
+
+public final class com/squareup/kotlinpoet/ClassName : com/squareup/kotlinpoet/TypeName, java/lang/Comparable {
+ public static final field Companion Lcom/squareup/kotlinpoet/ClassName$Companion;
+ public fun <init> (Ljava/lang/String;)V
+ public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V
+ public fun <init> (Ljava/lang/String;Ljava/util/List;)V
+ public fun <init> (Ljava/lang/String;[Ljava/lang/String;)V
+ public static final fun bestGuess (Ljava/lang/String;)Lcom/squareup/kotlinpoet/ClassName;
+ public fun compareTo (Lcom/squareup/kotlinpoet/ClassName;)I
+ public synthetic fun compareTo (Ljava/lang/Object;)I
+ public final fun constructorReference ()Lcom/squareup/kotlinpoet/CodeBlock;
+ public fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/ClassName;
+ public synthetic fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/TypeName;
+ public final fun enclosingClassName ()Lcom/squareup/kotlinpoet/ClassName;
+ public final fun getCanonicalName ()Ljava/lang/String;
+ public final fun getPackageName ()Ljava/lang/String;
+ public final fun getSimpleName ()Ljava/lang/String;
+ public final fun getSimpleNames ()Ljava/util/List;
+ public final fun nestedClass (Ljava/lang/String;)Lcom/squareup/kotlinpoet/ClassName;
+ public final fun peerClass (Ljava/lang/String;)Lcom/squareup/kotlinpoet/ClassName;
+ public final fun reflectionName ()Ljava/lang/String;
+ public final fun topLevelClassName ()Lcom/squareup/kotlinpoet/ClassName;
+}
+
+public final class com/squareup/kotlinpoet/ClassName$Companion {
+ public final fun bestGuess (Ljava/lang/String;)Lcom/squareup/kotlinpoet/ClassName;
+}
+
+public final class com/squareup/kotlinpoet/ClassNames {
+ public static final fun get (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/ClassName;
+ public static final fun get (Ljavax/lang/model/element/TypeElement;)Lcom/squareup/kotlinpoet/ClassName;
+ public static final fun get (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ClassName;
+}
+
+public final class com/squareup/kotlinpoet/CodeBlock {
+ public static final field Companion Lcom/squareup/kotlinpoet/CodeBlock$Companion;
+ public synthetic fun <init> (Ljava/util/List;Ljava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public static final fun builder ()Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun hashCode ()I
+ public final fun isEmpty ()Z
+ public final fun isNotEmpty ()Z
+ public static final fun of (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/CodeBlock;
+ public final fun toBuilder ()Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/CodeBlock$Builder {
+ public fun <init> ()V
+ public final fun add (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+ public final fun add (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+ public final fun addNamed (Ljava/lang/String;Ljava/util/Map;)Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+ public final fun addStatement (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+ public final fun beginControlFlow (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+ public final fun build ()Lcom/squareup/kotlinpoet/CodeBlock;
+ public final fun clear ()Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+ public final fun endControlFlow ()Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+ public final fun indent ()Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+ public final fun isEmpty ()Z
+ public final fun isNotEmpty ()Z
+ public final fun nextControlFlow (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+ public final fun unindent ()Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+}
+
+public final class com/squareup/kotlinpoet/CodeBlock$Companion {
+ public final fun builder ()Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+ public final fun of (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/CodeBlock;
+}
+
+public final class com/squareup/kotlinpoet/CodeBlocks {
+ public static final fun buildCodeBlock (Lkotlin/jvm/functions/Function1;)Lcom/squareup/kotlinpoet/CodeBlock;
+ public static final fun joinToCode (Ljava/util/Collection;)Lcom/squareup/kotlinpoet/CodeBlock;
+ public static final fun joinToCode (Ljava/util/Collection;Ljava/lang/CharSequence;)Lcom/squareup/kotlinpoet/CodeBlock;
+ public static final fun joinToCode (Ljava/util/Collection;Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Lcom/squareup/kotlinpoet/CodeBlock;
+ public static final fun joinToCode (Ljava/util/Collection;Ljava/lang/CharSequence;Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Lcom/squareup/kotlinpoet/CodeBlock;
+ public static synthetic fun joinToCode$default (Ljava/util/Collection;Ljava/lang/CharSequence;Ljava/lang/CharSequence;Ljava/lang/CharSequence;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/CodeBlock;
+ public static final fun withIndent (Lcom/squareup/kotlinpoet/CodeBlock$Builder;Lkotlin/jvm/functions/Function1;)Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+}
+
+public abstract interface class com/squareup/kotlinpoet/ContextReceivable {
+ public abstract fun getContextReceiverTypes ()Ljava/util/List;
+}
+
+public abstract interface class com/squareup/kotlinpoet/ContextReceivable$Builder {
+ public abstract fun getContextReceiverTypes ()Ljava/util/List;
+}
+
+public final class com/squareup/kotlinpoet/ContextReceivable$Builder$DefaultImpls {
+}
+
+public abstract interface annotation class com/squareup/kotlinpoet/DelicateKotlinPoetApi : java/lang/annotation/Annotation {
+ public abstract fun message ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/Dynamic : com/squareup/kotlinpoet/TypeName {
+ public static final field INSTANCE Lcom/squareup/kotlinpoet/Dynamic;
+ public synthetic fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/TypeName;
+ public fun copy (ZLjava/util/List;Ljava/util/Map;)Ljava/lang/Void;
+}
+
+public abstract interface annotation class com/squareup/kotlinpoet/ExperimentalKotlinPoetApi : java/lang/annotation/Annotation {
+}
+
+public final class com/squareup/kotlinpoet/FileSpec : com/squareup/kotlinpoet/Taggable {
+ public static final field Companion Lcom/squareup/kotlinpoet/FileSpec$Companion;
+ public static final fun builder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public static final fun builder (Ljava/lang/String;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public fun equals (Ljava/lang/Object;)Z
+ public static final fun get (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeSpec;)Lcom/squareup/kotlinpoet/FileSpec;
+ public final fun getAnnotations ()Ljava/util/List;
+ public final fun getBody ()Lcom/squareup/kotlinpoet/CodeBlock;
+ public final fun getComment ()Lcom/squareup/kotlinpoet/CodeBlock;
+ public final fun getDefaultImports ()Ljava/util/Set;
+ public final fun getMembers ()Ljava/util/List;
+ public final fun getName ()Ljava/lang/String;
+ public final fun getPackageName ()Ljava/lang/String;
+ public fun getTags ()Ljava/util/Map;
+ public fun hashCode ()I
+ public final fun isScript ()Z
+ public static final fun scriptBuilder (Ljava/lang/String;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public fun tag (Ljava/lang/Class;)Ljava/lang/Object;
+ public fun tag (Lkotlin/reflect/KClass;)Ljava/lang/Object;
+ public final fun toBuilder ()Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun toBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun toBuilder (Ljava/lang/String;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public static synthetic fun toBuilder$default (Lcom/squareup/kotlinpoet/FileSpec;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun toJavaFileObject ()Ljavax/tools/JavaFileObject;
+ public fun toString ()Ljava/lang/String;
+ public final fun writeTo (Ljava/io/File;)V
+ public final fun writeTo (Ljava/lang/Appendable;)V
+ public final fun writeTo (Ljava/nio/file/Path;)V
+ public final fun writeTo (Ljavax/annotation/processing/Filer;)V
+}
+
+public final class com/squareup/kotlinpoet/FileSpec$Builder : com/squareup/kotlinpoet/Taggable$Builder {
+ public final fun addAliasedImport (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addAliasedImport (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addAliasedImport (Lcom/squareup/kotlinpoet/MemberName;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addAliasedImport (Ljava/lang/Class;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addAliasedImport (Lkotlin/reflect/KClass;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addAnnotation (Lcom/squareup/kotlinpoet/AnnotationSpec;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addAnnotation (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addAnnotation (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addAnnotation (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addBodyComment (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addCode (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addCode (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addComment (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addDefaultPackageImport (Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addFileComment (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addFunction (Lcom/squareup/kotlinpoet/FunSpec;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addImport (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addImport (Lcom/squareup/kotlinpoet/ClassName;[Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addImport (Lcom/squareup/kotlinpoet/Import;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addImport (Ljava/lang/Class;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addImport (Ljava/lang/Class;[Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addImport (Ljava/lang/Enum;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addImport (Ljava/lang/String;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addImport (Ljava/lang/String;[Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addImport (Lkotlin/reflect/KClass;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addImport (Lkotlin/reflect/KClass;[Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addKotlinDefaultImports (ZZ)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public static synthetic fun addKotlinDefaultImports$default (Lcom/squareup/kotlinpoet/FileSpec$Builder;ZZILjava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addNamedCode (Ljava/lang/String;Ljava/util/Map;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addProperty (Lcom/squareup/kotlinpoet/PropertySpec;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addStatement (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addType (Lcom/squareup/kotlinpoet/TypeSpec;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun addTypeAlias (Lcom/squareup/kotlinpoet/TypeAliasSpec;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun beginControlFlow (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun build ()Lcom/squareup/kotlinpoet/FileSpec;
+ public final fun clearBody ()Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun clearComment ()Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun clearImports ()Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun endControlFlow ()Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun getAnnotations ()Ljava/util/List;
+ public final fun getDefaultImports ()Ljava/util/Set;
+ public final fun getImports ()Ljava/util/List;
+ public final fun getMembers ()Ljava/util/List;
+ public final fun getName ()Ljava/lang/String;
+ public final fun getPackageName ()Ljava/lang/String;
+ public fun getTags ()Ljava/util/Map;
+ public final fun indent (Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun isScript ()Z
+ public final fun nextControlFlow (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public synthetic fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+ public fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public synthetic fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+}
+
+public final class com/squareup/kotlinpoet/FileSpec$Companion {
+ public final fun builder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun builder (Ljava/lang/String;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public final fun get (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeSpec;)Lcom/squareup/kotlinpoet/FileSpec;
+ public final fun scriptBuilder (Ljava/lang/String;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public static synthetic fun scriptBuilder$default (Lcom/squareup/kotlinpoet/FileSpec$Companion;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+}
+
+public final class com/squareup/kotlinpoet/FunSpec : com/squareup/kotlinpoet/ContextReceivable, com/squareup/kotlinpoet/OriginatingElementsHolder, com/squareup/kotlinpoet/Taggable {
+ public static final field Companion Lcom/squareup/kotlinpoet/FunSpec$Companion;
+ public static final fun builder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static final fun constructorBuilder ()Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getAnnotations ()Ljava/util/List;
+ public final fun getBody ()Lcom/squareup/kotlinpoet/CodeBlock;
+ public fun getContextReceiverTypes ()Ljava/util/List;
+ public final fun getDelegateConstructor ()Ljava/lang/String;
+ public final fun getDelegateConstructorArguments ()Ljava/util/List;
+ public final fun getKdoc ()Lcom/squareup/kotlinpoet/CodeBlock;
+ public final fun getModifiers ()Ljava/util/Set;
+ public final fun getName ()Ljava/lang/String;
+ public fun getOriginatingElements ()Ljava/util/List;
+ public final fun getParameters ()Ljava/util/List;
+ public final fun getReceiverKdoc ()Lcom/squareup/kotlinpoet/CodeBlock;
+ public final fun getReceiverType ()Lcom/squareup/kotlinpoet/TypeName;
+ public final fun getReturnKdoc ()Lcom/squareup/kotlinpoet/CodeBlock;
+ public final fun getReturnType ()Lcom/squareup/kotlinpoet/TypeName;
+ public fun getTags ()Ljava/util/Map;
+ public final fun getTypeVariables ()Ljava/util/List;
+ public static final fun getterBuilder ()Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public fun hashCode ()I
+ public final fun isAccessor ()Z
+ public final fun isConstructor ()Z
+ public static final fun overriding (Ljavax/lang/model/element/ExecutableElement;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static final fun overriding (Ljavax/lang/model/element/ExecutableElement;Ljavax/lang/model/type/DeclaredType;Ljavax/lang/model/util/Types;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static final fun setterBuilder ()Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public fun tag (Ljava/lang/Class;)Ljava/lang/Object;
+ public fun tag (Lkotlin/reflect/KClass;)Ljava/lang/Object;
+ public final fun toBuilder ()Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun toBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static synthetic fun toBuilder$default (Lcom/squareup/kotlinpoet/FunSpec;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/FunSpec$Builder : com/squareup/kotlinpoet/ContextReceivable$Builder, com/squareup/kotlinpoet/OriginatingElementsHolder$Builder, com/squareup/kotlinpoet/Taggable$Builder {
+ public final fun addAnnotation (Lcom/squareup/kotlinpoet/AnnotationSpec;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addAnnotation (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addAnnotation (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addAnnotation (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addAnnotations (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addCode (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addCode (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addComment (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addKdoc (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addKdoc (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addModifiers (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addModifiers ([Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addNamedCode (Ljava/lang/String;Ljava/util/Map;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public fun addOriginatingElement (Ljavax/lang/model/element/Element;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public synthetic fun addOriginatingElement (Ljavax/lang/model/element/Element;)Lcom/squareup/kotlinpoet/OriginatingElementsHolder$Builder;
+ public final fun addParameter (Lcom/squareup/kotlinpoet/ParameterSpec;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addParameter (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addParameter (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addParameter (Ljava/lang/String;Ljava/lang/reflect/Type;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addParameter (Ljava/lang/String;Ljava/lang/reflect/Type;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addParameter (Ljava/lang/String;Lkotlin/reflect/KClass;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addParameter (Ljava/lang/String;Lkotlin/reflect/KClass;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addParameters (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addStatement (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addTypeVariable (Lcom/squareup/kotlinpoet/TypeVariableName;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun addTypeVariables (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun beginControlFlow (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun build ()Lcom/squareup/kotlinpoet/FunSpec;
+ public final fun callSuperConstructor (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun callSuperConstructor (Ljava/util/List;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun callSuperConstructor ([Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun callSuperConstructor ([Ljava/lang/String;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static synthetic fun callSuperConstructor$default (Lcom/squareup/kotlinpoet/FunSpec$Builder;[Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun callThisConstructor (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun callThisConstructor (Ljava/util/List;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun callThisConstructor ([Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun callThisConstructor ([Ljava/lang/String;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static synthetic fun callThisConstructor$default (Lcom/squareup/kotlinpoet/FunSpec$Builder;[Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun clearBody ()Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public synthetic fun contextReceivers (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ContextReceivable$Builder;
+ public synthetic fun contextReceivers ([Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ContextReceivable$Builder;
+ public final fun endControlFlow ()Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun getAnnotations ()Ljava/util/List;
+ public fun getContextReceiverTypes ()Ljava/util/List;
+ public final fun getModifiers ()Ljava/util/List;
+ public fun getOriginatingElements ()Ljava/util/List;
+ public final fun getParameters ()Ljava/util/List;
+ public fun getTags ()Ljava/util/Map;
+ public final fun getTypeVariables ()Ljava/util/List;
+ public final fun jvmModifiers (Ljava/lang/Iterable;)V
+ public final fun nextControlFlow (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun receiver (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun receiver (Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun receiver (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun receiver (Ljava/lang/reflect/Type;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun receiver (Ljava/lang/reflect/Type;Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun receiver (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun receiver (Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun receiver (Lkotlin/reflect/KClass;Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static synthetic fun receiver$default (Lcom/squareup/kotlinpoet/FunSpec$Builder;Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static synthetic fun receiver$default (Lcom/squareup/kotlinpoet/FunSpec$Builder;Ljava/lang/reflect/Type;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static synthetic fun receiver$default (Lcom/squareup/kotlinpoet/FunSpec$Builder;Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun returns (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun returns (Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun returns (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun returns (Ljava/lang/reflect/Type;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun returns (Ljava/lang/reflect/Type;Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun returns (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun returns (Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun returns (Lkotlin/reflect/KClass;Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static synthetic fun returns$default (Lcom/squareup/kotlinpoet/FunSpec$Builder;Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static synthetic fun returns$default (Lcom/squareup/kotlinpoet/FunSpec$Builder;Ljava/lang/reflect/Type;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static synthetic fun returns$default (Lcom/squareup/kotlinpoet/FunSpec$Builder;Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public synthetic fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+ public fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public synthetic fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+}
+
+public final class com/squareup/kotlinpoet/FunSpec$Companion {
+ public final fun builder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun constructorBuilder ()Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun getterBuilder ()Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun overriding (Ljavax/lang/model/element/ExecutableElement;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun overriding (Ljavax/lang/model/element/ExecutableElement;Ljavax/lang/model/type/DeclaredType;Ljavax/lang/model/util/Types;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public final fun setterBuilder ()Lcom/squareup/kotlinpoet/FunSpec$Builder;
+}
+
+public final class com/squareup/kotlinpoet/Import : java/lang/Comparable {
+ public fun compareTo (Lcom/squareup/kotlinpoet/Import;)I
+ public synthetic fun compareTo (Ljava/lang/Object;)I
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/lang/String;
+ public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/squareup/kotlinpoet/Import;
+ public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/Import;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/Import;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getAlias ()Ljava/lang/String;
+ public final fun getQualifiedName ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/KModifier : java/lang/Enum {
+ public static final field ABSTRACT Lcom/squareup/kotlinpoet/KModifier;
+ public static final field ACTUAL Lcom/squareup/kotlinpoet/KModifier;
+ public static final field ANNOTATION Lcom/squareup/kotlinpoet/KModifier;
+ public static final field COMPANION Lcom/squareup/kotlinpoet/KModifier;
+ public static final field CONST Lcom/squareup/kotlinpoet/KModifier;
+ public static final field CROSSINLINE Lcom/squareup/kotlinpoet/KModifier;
+ public static final field DATA Lcom/squareup/kotlinpoet/KModifier;
+ public static final field ENUM Lcom/squareup/kotlinpoet/KModifier;
+ public static final field EXPECT Lcom/squareup/kotlinpoet/KModifier;
+ public static final field EXTERNAL Lcom/squareup/kotlinpoet/KModifier;
+ public static final field FINAL Lcom/squareup/kotlinpoet/KModifier;
+ public static final field FUN Lcom/squareup/kotlinpoet/KModifier;
+ public static final field IN Lcom/squareup/kotlinpoet/KModifier;
+ public static final field INFIX Lcom/squareup/kotlinpoet/KModifier;
+ public static final field INLINE Lcom/squareup/kotlinpoet/KModifier;
+ public static final field INNER Lcom/squareup/kotlinpoet/KModifier;
+ public static final field INTERNAL Lcom/squareup/kotlinpoet/KModifier;
+ public static final field LATEINIT Lcom/squareup/kotlinpoet/KModifier;
+ public static final field NOINLINE Lcom/squareup/kotlinpoet/KModifier;
+ public static final field OPEN Lcom/squareup/kotlinpoet/KModifier;
+ public static final field OPERATOR Lcom/squareup/kotlinpoet/KModifier;
+ public static final field OUT Lcom/squareup/kotlinpoet/KModifier;
+ public static final field OVERRIDE Lcom/squareup/kotlinpoet/KModifier;
+ public static final field PRIVATE Lcom/squareup/kotlinpoet/KModifier;
+ public static final field PROTECTED Lcom/squareup/kotlinpoet/KModifier;
+ public static final field PUBLIC Lcom/squareup/kotlinpoet/KModifier;
+ public static final field REIFIED Lcom/squareup/kotlinpoet/KModifier;
+ public static final field SEALED Lcom/squareup/kotlinpoet/KModifier;
+ public static final field SUSPEND Lcom/squareup/kotlinpoet/KModifier;
+ public static final field TAILREC Lcom/squareup/kotlinpoet/KModifier;
+ public static final field VALUE Lcom/squareup/kotlinpoet/KModifier;
+ public static final field VARARG Lcom/squareup/kotlinpoet/KModifier;
+ public static fun valueOf (Ljava/lang/String;)Lcom/squareup/kotlinpoet/KModifier;
+ public static fun values ()[Lcom/squareup/kotlinpoet/KModifier;
+}
+
+public final class com/squareup/kotlinpoet/KOperator : java/lang/Enum {
+ public static final field CONTAINS Lcom/squareup/kotlinpoet/KOperator;
+ public static final field DEC Lcom/squareup/kotlinpoet/KOperator;
+ public static final field DIV Lcom/squareup/kotlinpoet/KOperator;
+ public static final field DIV_ASSIGN Lcom/squareup/kotlinpoet/KOperator;
+ public static final field EQUALS Lcom/squareup/kotlinpoet/KOperator;
+ public static final field GE Lcom/squareup/kotlinpoet/KOperator;
+ public static final field GT Lcom/squareup/kotlinpoet/KOperator;
+ public static final field INC Lcom/squareup/kotlinpoet/KOperator;
+ public static final field ITERATOR Lcom/squareup/kotlinpoet/KOperator;
+ public static final field LE Lcom/squareup/kotlinpoet/KOperator;
+ public static final field LT Lcom/squareup/kotlinpoet/KOperator;
+ public static final field MINUS Lcom/squareup/kotlinpoet/KOperator;
+ public static final field MINUS_ASSIGN Lcom/squareup/kotlinpoet/KOperator;
+ public static final field NOT Lcom/squareup/kotlinpoet/KOperator;
+ public static final field NOT_CONTAINS Lcom/squareup/kotlinpoet/KOperator;
+ public static final field NOT_EQUALS Lcom/squareup/kotlinpoet/KOperator;
+ public static final field PLUS Lcom/squareup/kotlinpoet/KOperator;
+ public static final field PLUS_ASSIGN Lcom/squareup/kotlinpoet/KOperator;
+ public static final field RANGE_TO Lcom/squareup/kotlinpoet/KOperator;
+ public static final field REM Lcom/squareup/kotlinpoet/KOperator;
+ public static final field REM_ASSIGN Lcom/squareup/kotlinpoet/KOperator;
+ public static final field TIMES Lcom/squareup/kotlinpoet/KOperator;
+ public static final field TIMES_ASSIGN Lcom/squareup/kotlinpoet/KOperator;
+ public static final field UNARY_MINUS Lcom/squareup/kotlinpoet/KOperator;
+ public static final field UNARY_PLUS Lcom/squareup/kotlinpoet/KOperator;
+ public static fun valueOf (Ljava/lang/String;)Lcom/squareup/kotlinpoet/KOperator;
+ public static fun values ()[Lcom/squareup/kotlinpoet/KOperator;
+}
+
+public final class com/squareup/kotlinpoet/LambdaTypeName : com/squareup/kotlinpoet/TypeName {
+ public static final field Companion Lcom/squareup/kotlinpoet/LambdaTypeName$Companion;
+ public fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+ public synthetic fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/TypeName;
+ public final fun copy (ZLjava/util/List;ZLjava/util/Map;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+ public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/LambdaTypeName;ZLjava/util/List;ZLjava/util/Map;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+ public static final fun get (Lcom/squareup/kotlinpoet/TypeName;Ljava/util/List;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+ public static final fun get (Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/ParameterSpec;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+ public static final fun get (Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+ public final fun getParameters ()Ljava/util/List;
+ public final fun getReceiver ()Lcom/squareup/kotlinpoet/TypeName;
+ public final fun getReturnType ()Lcom/squareup/kotlinpoet/TypeName;
+ public final fun isSuspending ()Z
+}
+
+public final class com/squareup/kotlinpoet/LambdaTypeName$Companion {
+ public final fun get (Lcom/squareup/kotlinpoet/TypeName;Ljava/util/List;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+ public final fun get (Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/ParameterSpec;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+ public final fun get (Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+ public static synthetic fun get$default (Lcom/squareup/kotlinpoet/LambdaTypeName$Companion;Lcom/squareup/kotlinpoet/TypeName;Ljava/util/List;Lcom/squareup/kotlinpoet/TypeName;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+ public static synthetic fun get$default (Lcom/squareup/kotlinpoet/LambdaTypeName$Companion;Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/ParameterSpec;Lcom/squareup/kotlinpoet/TypeName;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+ public static synthetic fun get$default (Lcom/squareup/kotlinpoet/LambdaTypeName$Companion;Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/TypeName;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/LambdaTypeName;
+}
+
+public final class com/squareup/kotlinpoet/MemberName {
+ public static final field Companion Lcom/squareup/kotlinpoet/MemberName$Companion;
+ public fun <init> (Lcom/squareup/kotlinpoet/ClassName;Lcom/squareup/kotlinpoet/KOperator;)V
+ public fun <init> (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;)V
+ public fun <init> (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;Z)V
+ public fun <init> (Ljava/lang/String;Lcom/squareup/kotlinpoet/KOperator;)V
+ public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
+ public fun <init> (Ljava/lang/String;Ljava/lang/String;Z)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Lcom/squareup/kotlinpoet/ClassName;
+ public final fun component3 ()Ljava/lang/String;
+ public final fun component4 ()Lcom/squareup/kotlinpoet/KOperator;
+ public final fun component5 ()Z
+ public final fun copy (Ljava/lang/String;Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;Lcom/squareup/kotlinpoet/KOperator;Z)Lcom/squareup/kotlinpoet/MemberName;
+ public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/MemberName;Ljava/lang/String;Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;Lcom/squareup/kotlinpoet/KOperator;ZILjava/lang/Object;)Lcom/squareup/kotlinpoet/MemberName;
+ public fun equals (Ljava/lang/Object;)Z
+ public static final fun get (Ljava/lang/Class;Ljava/lang/String;)Lcom/squareup/kotlinpoet/MemberName;
+ public static final fun get (Lkotlin/reflect/KClass;Ljava/lang/String;)Lcom/squareup/kotlinpoet/MemberName;
+ public final fun getCanonicalName ()Ljava/lang/String;
+ public final fun getEnclosingClassName ()Lcom/squareup/kotlinpoet/ClassName;
+ public final fun getOperator ()Lcom/squareup/kotlinpoet/KOperator;
+ public final fun getPackageName ()Ljava/lang/String;
+ public final fun getSimpleName ()Ljava/lang/String;
+ public fun hashCode ()I
+ public final fun isExtension ()Z
+ public static final synthetic fun member (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;)Lcom/squareup/kotlinpoet/MemberName;
+ public final fun reference ()Lcom/squareup/kotlinpoet/CodeBlock;
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/MemberName$Companion {
+ public final fun get (Ljava/lang/Class;Ljava/lang/String;)Lcom/squareup/kotlinpoet/MemberName;
+ public final fun get (Lkotlin/reflect/KClass;Ljava/lang/String;)Lcom/squareup/kotlinpoet/MemberName;
+ public final synthetic fun member (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;)Lcom/squareup/kotlinpoet/MemberName;
+}
+
+public final class com/squareup/kotlinpoet/NameAllocator {
+ public fun <init> ()V
+ public final fun copy ()Lcom/squareup/kotlinpoet/NameAllocator;
+ public final fun get (Ljava/lang/Object;)Ljava/lang/String;
+ public final fun newName (Ljava/lang/String;)Ljava/lang/String;
+ public final fun newName (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;
+ public static synthetic fun newName$default (Lcom/squareup/kotlinpoet/NameAllocator;Ljava/lang/String;Ljava/lang/Object;ILjava/lang/Object;)Ljava/lang/String;
+}
+
+public abstract interface class com/squareup/kotlinpoet/OriginatingElementsHolder {
+ public abstract fun getOriginatingElements ()Ljava/util/List;
+}
+
+public abstract interface class com/squareup/kotlinpoet/OriginatingElementsHolder$Builder {
+ public abstract fun addOriginatingElement (Ljavax/lang/model/element/Element;)Lcom/squareup/kotlinpoet/OriginatingElementsHolder$Builder;
+ public abstract fun getOriginatingElements ()Ljava/util/List;
+}
+
+public final class com/squareup/kotlinpoet/OriginatingElementsHolder$Builder$DefaultImpls {
+ public static fun addOriginatingElement (Lcom/squareup/kotlinpoet/OriginatingElementsHolder$Builder;Ljavax/lang/model/element/Element;)Lcom/squareup/kotlinpoet/OriginatingElementsHolder$Builder;
+}
+
+public final class com/squareup/kotlinpoet/ParameterSpec : com/squareup/kotlinpoet/Taggable {
+ public static final field Companion Lcom/squareup/kotlinpoet/ParameterSpec$Companion;
+ public fun <init> (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;Ljava/lang/Iterable;)V
+ public fun <init> (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/KModifier;)V
+ public static final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public static final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public static final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public static final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public static final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public static final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public fun equals (Ljava/lang/Object;)Z
+ public static final fun get (Ljavax/lang/model/element/VariableElement;)Lcom/squareup/kotlinpoet/ParameterSpec;
+ public final fun getAnnotations ()Ljava/util/List;
+ public final fun getDefaultValue ()Lcom/squareup/kotlinpoet/CodeBlock;
+ public final fun getKdoc ()Lcom/squareup/kotlinpoet/CodeBlock;
+ public final fun getModifiers ()Ljava/util/Set;
+ public final fun getName ()Ljava/lang/String;
+ public fun getTags ()Ljava/util/Map;
+ public final fun getType ()Lcom/squareup/kotlinpoet/TypeName;
+ public fun hashCode ()I
+ public static final fun parametersOf (Ljavax/lang/model/element/ExecutableElement;)Ljava/util/List;
+ public fun tag (Ljava/lang/Class;)Ljava/lang/Object;
+ public fun tag (Lkotlin/reflect/KClass;)Ljava/lang/Object;
+ public final fun toBuilder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public static synthetic fun toBuilder$default (Lcom/squareup/kotlinpoet/ParameterSpec;Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public fun toString ()Ljava/lang/String;
+ public static final fun unnamed (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ParameterSpec;
+ public static final fun unnamed (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/ParameterSpec;
+ public static final fun unnamed (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ParameterSpec;
+}
+
+public final class com/squareup/kotlinpoet/ParameterSpec$Builder : com/squareup/kotlinpoet/Taggable$Builder {
+ public final fun addAnnotation (Lcom/squareup/kotlinpoet/AnnotationSpec;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public final fun addAnnotation (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public final fun addAnnotation (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public final fun addAnnotation (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public final fun addAnnotations (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public final fun addKdoc (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public final fun addKdoc (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public final fun addModifiers (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public final fun addModifiers ([Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public final fun build ()Lcom/squareup/kotlinpoet/ParameterSpec;
+ public final fun defaultValue (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public final fun defaultValue (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public final fun getAnnotations ()Ljava/util/List;
+ public final fun getKdoc ()Lcom/squareup/kotlinpoet/CodeBlock$Builder;
+ public final fun getModifiers ()Ljava/util/List;
+ public fun getTags ()Ljava/util/Map;
+ public final fun jvmModifiers (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public synthetic fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+ public fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public synthetic fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+}
+
+public final class com/squareup/kotlinpoet/ParameterSpec$Companion {
+ public final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/ParameterSpec$Builder;
+ public final fun get (Ljavax/lang/model/element/VariableElement;)Lcom/squareup/kotlinpoet/ParameterSpec;
+ public final fun parametersOf (Ljavax/lang/model/element/ExecutableElement;)Ljava/util/List;
+ public final fun unnamed (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ParameterSpec;
+ public final fun unnamed (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/ParameterSpec;
+ public final fun unnamed (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ParameterSpec;
+}
+
+public final class com/squareup/kotlinpoet/ParameterizedTypeName : com/squareup/kotlinpoet/TypeName {
+ public static final field Companion Lcom/squareup/kotlinpoet/ParameterizedTypeName$Companion;
+ public fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public synthetic fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/TypeName;
+ public final fun copy (ZLjava/util/List;Ljava/util/Map;Ljava/util/List;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/ParameterizedTypeName;ZLjava/util/List;Ljava/util/Map;Ljava/util/List;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public static final fun get (Lcom/squareup/kotlinpoet/ClassName;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public static final fun get (Lcom/squareup/kotlinpoet/ClassName;Ljava/util/List;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public static final fun get (Lcom/squareup/kotlinpoet/ClassName;[Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public static final fun get (Ljava/lang/Class;Ljava/lang/Class;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public static final fun get (Ljava/lang/Class;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public static final fun get (Ljava/lang/Class;[Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public static final fun get (Lkotlin/reflect/KClass;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public static final fun get (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public static final fun get (Lkotlin/reflect/KClass;[Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public final fun getRawType ()Lcom/squareup/kotlinpoet/ClassName;
+ public final fun getTypeArguments ()Ljava/util/List;
+ public final fun nestedClass (Ljava/lang/String;Ljava/util/List;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public final fun plusParameter (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public final fun plusParameter (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public final fun plusParameter (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+}
+
+public final class com/squareup/kotlinpoet/ParameterizedTypeName$Companion {
+ public final fun get (Lcom/squareup/kotlinpoet/ClassName;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public final fun get (Lcom/squareup/kotlinpoet/ClassName;Ljava/util/List;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public final fun get (Lcom/squareup/kotlinpoet/ClassName;[Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public final fun get (Ljava/lang/Class;Ljava/lang/Class;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public final fun get (Ljava/lang/Class;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public final fun get (Ljava/lang/Class;[Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public final fun get (Lkotlin/reflect/KClass;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public final fun get (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+ public final fun get (Lkotlin/reflect/KClass;[Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+}
+
+public final class com/squareup/kotlinpoet/ParameterizedTypeNames {
+ public static final fun asTypeName (Lkotlin/reflect/KType;)Lcom/squareup/kotlinpoet/TypeName;
+ public static final fun get (Ljava/lang/reflect/ParameterizedType;)Lcom/squareup/kotlinpoet/ParameterizedTypeName;
+}
+
+public final class com/squareup/kotlinpoet/PropertySpec : com/squareup/kotlinpoet/ContextReceivable, com/squareup/kotlinpoet/OriginatingElementsHolder, com/squareup/kotlinpoet/Taggable {
+ public static final field Companion Lcom/squareup/kotlinpoet/PropertySpec$Companion;
+ public static final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public static final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public static final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public static final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public static final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public static final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getAnnotations ()Ljava/util/List;
+ public fun getContextReceiverTypes ()Ljava/util/List;
+ public final fun getDelegated ()Z
+ public final fun getGetter ()Lcom/squareup/kotlinpoet/FunSpec;
+ public final fun getInitializer ()Lcom/squareup/kotlinpoet/CodeBlock;
+ public final fun getKdoc ()Lcom/squareup/kotlinpoet/CodeBlock;
+ public final fun getModifiers ()Ljava/util/Set;
+ public final fun getMutable ()Z
+ public final fun getName ()Ljava/lang/String;
+ public fun getOriginatingElements ()Ljava/util/List;
+ public final fun getReceiverType ()Lcom/squareup/kotlinpoet/TypeName;
+ public final fun getSetter ()Lcom/squareup/kotlinpoet/FunSpec;
+ public fun getTags ()Ljava/util/Map;
+ public final fun getType ()Lcom/squareup/kotlinpoet/TypeName;
+ public final fun getTypeVariables ()Ljava/util/List;
+ public fun hashCode ()I
+ public fun tag (Ljava/lang/Class;)Ljava/lang/Object;
+ public fun tag (Lkotlin/reflect/KClass;)Ljava/lang/Object;
+ public final fun toBuilder ()Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun toBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun toBuilder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public static synthetic fun toBuilder$default (Lcom/squareup/kotlinpoet/PropertySpec;Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/PropertySpec$Builder : com/squareup/kotlinpoet/ContextReceivable$Builder, com/squareup/kotlinpoet/OriginatingElementsHolder$Builder, com/squareup/kotlinpoet/Taggable$Builder {
+ public final fun addAnnotation (Lcom/squareup/kotlinpoet/AnnotationSpec;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun addAnnotation (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun addAnnotation (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun addAnnotation (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun addAnnotations (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun addKdoc (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun addKdoc (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun addModifiers (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun addModifiers ([Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public synthetic fun addOriginatingElement (Ljavax/lang/model/element/Element;)Lcom/squareup/kotlinpoet/OriginatingElementsHolder$Builder;
+ public fun addOriginatingElement (Ljavax/lang/model/element/Element;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun addTypeVariable (Lcom/squareup/kotlinpoet/TypeVariableName;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun addTypeVariables (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun build ()Lcom/squareup/kotlinpoet/PropertySpec;
+ public synthetic fun contextReceivers (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ContextReceivable$Builder;
+ public synthetic fun contextReceivers ([Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ContextReceivable$Builder;
+ public final fun delegate (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun delegate (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun getAnnotations ()Ljava/util/List;
+ public fun getContextReceiverTypes ()Ljava/util/List;
+ public final fun getModifiers ()Ljava/util/List;
+ public fun getOriginatingElements ()Ljava/util/List;
+ public fun getTags ()Ljava/util/Map;
+ public final fun getTypeVariables ()Ljava/util/List;
+ public final fun getter (Lcom/squareup/kotlinpoet/FunSpec;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun initializer (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun initializer (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun mutable (Z)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public static synthetic fun mutable$default (Lcom/squareup/kotlinpoet/PropertySpec$Builder;ZILjava/lang/Object;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun receiver (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun receiver (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun receiver (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun setter (Lcom/squareup/kotlinpoet/FunSpec;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public synthetic fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+ public fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public synthetic fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+}
+
+public final class com/squareup/kotlinpoet/PropertySpec$Companion {
+ public final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+}
+
+public abstract interface class com/squareup/kotlinpoet/Taggable {
+ public abstract fun getTags ()Ljava/util/Map;
+ public abstract fun tag (Ljava/lang/Class;)Ljava/lang/Object;
+ public abstract fun tag (Lkotlin/reflect/KClass;)Ljava/lang/Object;
+}
+
+public abstract interface class com/squareup/kotlinpoet/Taggable$Builder {
+ public abstract fun getTags ()Ljava/util/Map;
+ public abstract fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+ public abstract fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+}
+
+public final class com/squareup/kotlinpoet/Taggable$Builder$DefaultImpls {
+ public static fun tag (Lcom/squareup/kotlinpoet/Taggable$Builder;Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+ public static fun tag (Lcom/squareup/kotlinpoet/Taggable$Builder;Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+}
+
+public final class com/squareup/kotlinpoet/Taggable$DefaultImpls {
+ public static fun getTags (Lcom/squareup/kotlinpoet/Taggable;)Ljava/util/Map;
+ public static fun tag (Lcom/squareup/kotlinpoet/Taggable;Ljava/lang/Class;)Ljava/lang/Object;
+ public static fun tag (Lcom/squareup/kotlinpoet/Taggable;Lkotlin/reflect/KClass;)Ljava/lang/Object;
+}
+
+public final class com/squareup/kotlinpoet/TypeAliasSpec : com/squareup/kotlinpoet/Taggable {
+ public static final field Companion Lcom/squareup/kotlinpoet/TypeAliasSpec$Companion;
+ public static final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public static final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public static final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getAnnotations ()Ljava/util/List;
+ public final fun getKdoc ()Lcom/squareup/kotlinpoet/CodeBlock;
+ public final fun getModifiers ()Ljava/util/Set;
+ public final fun getName ()Ljava/lang/String;
+ public fun getTags ()Ljava/util/Map;
+ public final fun getType ()Lcom/squareup/kotlinpoet/TypeName;
+ public final fun getTypeVariables ()Ljava/util/List;
+ public fun hashCode ()I
+ public fun tag (Ljava/lang/Class;)Ljava/lang/Object;
+ public fun tag (Lkotlin/reflect/KClass;)Ljava/lang/Object;
+ public final fun toBuilder ()Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public final fun toBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public final fun toBuilder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public static synthetic fun toBuilder$default (Lcom/squareup/kotlinpoet/TypeAliasSpec;Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/TypeAliasSpec$Builder : com/squareup/kotlinpoet/Taggable$Builder {
+ public final fun addAnnotation (Lcom/squareup/kotlinpoet/AnnotationSpec;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public final fun addAnnotation (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public final fun addAnnotation (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public final fun addAnnotation (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public final fun addAnnotations (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public final fun addKdoc (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public final fun addKdoc (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public final fun addModifiers (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public final fun addModifiers ([Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public final fun addTypeVariable (Lcom/squareup/kotlinpoet/TypeVariableName;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public final fun addTypeVariables (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public final fun build ()Lcom/squareup/kotlinpoet/TypeAliasSpec;
+ public final fun getAnnotations ()Ljava/util/List;
+ public final fun getModifiers ()Ljava/util/Set;
+ public fun getTags ()Ljava/util/Map;
+ public final fun getTypeVariables ()Ljava/util/Set;
+ public synthetic fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+ public fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public synthetic fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+ public fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+}
+
+public final class com/squareup/kotlinpoet/TypeAliasSpec$Companion {
+ public final fun builder (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public final fun builder (Ljava/lang/String;Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+ public final fun builder (Ljava/lang/String;Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/TypeAliasSpec$Builder;
+}
+
+public abstract class com/squareup/kotlinpoet/TypeName : com/squareup/kotlinpoet/Taggable {
+ public static final field Companion Lcom/squareup/kotlinpoet/TypeName$Companion;
+ public synthetic fun <init> (ZLjava/util/List;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun copy (ZLjava/util/List;)Lcom/squareup/kotlinpoet/TypeName;
+ public abstract fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/TypeName;
+ public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/TypeName;ZLjava/util/List;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeName;
+ public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/TypeName;ZLjava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeName;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getAnnotations ()Ljava/util/List;
+ public fun getTags ()Ljava/util/Map;
+ public fun hashCode ()I
+ public final fun isAnnotated ()Z
+ public final fun isNullable ()Z
+ public fun tag (Ljava/lang/Class;)Ljava/lang/Object;
+ public fun tag (Lkotlin/reflect/KClass;)Ljava/lang/Object;
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class com/squareup/kotlinpoet/TypeName$Companion {
+}
+
+public final class com/squareup/kotlinpoet/TypeNames {
+ public static final field ANNOTATION Lcom/squareup/kotlinpoet/ClassName;
+ public static final field ANY Lcom/squareup/kotlinpoet/ClassName;
+ public static final field ARRAY Lcom/squareup/kotlinpoet/ClassName;
+ public static final field BOOLEAN Lcom/squareup/kotlinpoet/ClassName;
+ public static final field BOOLEAN_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+ public static final field BYTE Lcom/squareup/kotlinpoet/ClassName;
+ public static final field BYTE_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+ public static final field CHAR Lcom/squareup/kotlinpoet/ClassName;
+ public static final field CHAR_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+ public static final field CHAR_SEQUENCE Lcom/squareup/kotlinpoet/ClassName;
+ public static final field COLLECTION Lcom/squareup/kotlinpoet/ClassName;
+ public static final field COMPARABLE Lcom/squareup/kotlinpoet/ClassName;
+ public static final field DOUBLE Lcom/squareup/kotlinpoet/ClassName;
+ public static final field DOUBLE_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+ public static final field DYNAMIC Lcom/squareup/kotlinpoet/Dynamic;
+ public static final field ENUM Lcom/squareup/kotlinpoet/ClassName;
+ public static final field FLOAT Lcom/squareup/kotlinpoet/ClassName;
+ public static final field FLOAT_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+ public static final field INT Lcom/squareup/kotlinpoet/ClassName;
+ public static final field INT_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+ public static final field ITERABLE Lcom/squareup/kotlinpoet/ClassName;
+ public static final field LIST Lcom/squareup/kotlinpoet/ClassName;
+ public static final field LONG Lcom/squareup/kotlinpoet/ClassName;
+ public static final field LONG_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+ public static final field MAP Lcom/squareup/kotlinpoet/ClassName;
+ public static final field MAP_ENTRY Lcom/squareup/kotlinpoet/ClassName;
+ public static final field MUTABLE_COLLECTION Lcom/squareup/kotlinpoet/ClassName;
+ public static final field MUTABLE_ITERABLE Lcom/squareup/kotlinpoet/ClassName;
+ public static final field MUTABLE_LIST Lcom/squareup/kotlinpoet/ClassName;
+ public static final field MUTABLE_MAP Lcom/squareup/kotlinpoet/ClassName;
+ public static final field MUTABLE_MAP_ENTRY Lcom/squareup/kotlinpoet/ClassName;
+ public static final field MUTABLE_SET Lcom/squareup/kotlinpoet/ClassName;
+ public static final field NOTHING Lcom/squareup/kotlinpoet/ClassName;
+ public static final field NUMBER Lcom/squareup/kotlinpoet/ClassName;
+ public static final field SET Lcom/squareup/kotlinpoet/ClassName;
+ public static final field SHORT Lcom/squareup/kotlinpoet/ClassName;
+ public static final field SHORT_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+ public static final field STAR Lcom/squareup/kotlinpoet/WildcardTypeName;
+ public static final field STRING Lcom/squareup/kotlinpoet/ClassName;
+ public static final field THROWABLE Lcom/squareup/kotlinpoet/ClassName;
+ public static final field UNIT Lcom/squareup/kotlinpoet/ClassName;
+ public static final field U_BYTE Lcom/squareup/kotlinpoet/ClassName;
+ public static final field U_BYTE_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+ public static final field U_INT Lcom/squareup/kotlinpoet/ClassName;
+ public static final field U_INT_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+ public static final field U_LONG Lcom/squareup/kotlinpoet/ClassName;
+ public static final field U_LONG_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+ public static final field U_SHORT Lcom/squareup/kotlinpoet/ClassName;
+ public static final field U_SHORT_ARRAY Lcom/squareup/kotlinpoet/ClassName;
+ public static final fun get (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/TypeName;
+ public static final fun get (Ljavax/lang/model/type/TypeMirror;)Lcom/squareup/kotlinpoet/TypeName;
+ public static final fun get (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/ClassName;
+}
+
+public final class com/squareup/kotlinpoet/TypeSpec : com/squareup/kotlinpoet/ContextReceivable, com/squareup/kotlinpoet/OriginatingElementsHolder, com/squareup/kotlinpoet/Taggable {
+ public static final field Companion Lcom/squareup/kotlinpoet/TypeSpec$Companion;
+ public static final fun annotationBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static final fun annotationBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static final fun anonymousClassBuilder ()Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static final fun classBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static final fun classBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static final fun companionObjectBuilder ()Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static final fun companionObjectBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static final fun enumBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static final fun enumBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public fun equals (Ljava/lang/Object;)Z
+ public static final fun expectClassBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static final fun expectClassBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static final fun funInterfaceBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static final fun funInterfaceBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun getAnnotationSpecs ()Ljava/util/List;
+ public fun getContextReceiverTypes ()Ljava/util/List;
+ public final fun getEnumConstants ()Ljava/util/Map;
+ public final fun getFunSpecs ()Ljava/util/List;
+ public final fun getInitializerBlock ()Lcom/squareup/kotlinpoet/CodeBlock;
+ public final fun getInitializerIndex ()I
+ public final fun getKdoc ()Lcom/squareup/kotlinpoet/CodeBlock;
+ public final fun getKind ()Lcom/squareup/kotlinpoet/TypeSpec$Kind;
+ public final fun getModifiers ()Ljava/util/Set;
+ public final fun getName ()Ljava/lang/String;
+ public fun getOriginatingElements ()Ljava/util/List;
+ public final fun getPrimaryConstructor ()Lcom/squareup/kotlinpoet/FunSpec;
+ public final fun getPropertySpecs ()Ljava/util/List;
+ public final fun getSuperclass ()Lcom/squareup/kotlinpoet/TypeName;
+ public final fun getSuperclassConstructorParameters ()Ljava/util/List;
+ public final fun getSuperinterfaces ()Ljava/util/Map;
+ public fun getTags ()Ljava/util/Map;
+ public final fun getTypeSpecs ()Ljava/util/List;
+ public final fun getTypeVariables ()Ljava/util/List;
+ public fun hashCode ()I
+ public static final fun interfaceBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static final fun interfaceBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun isAnnotation ()Z
+ public final fun isAnonymousClass ()Z
+ public final fun isCompanion ()Z
+ public final fun isEnum ()Z
+ public final fun isFunctionalInterface ()Z
+ public static final fun objectBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static final fun objectBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public fun tag (Ljava/lang/Class;)Ljava/lang/Object;
+ public fun tag (Lkotlin/reflect/KClass;)Ljava/lang/Object;
+ public final fun toBuilder ()Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun toBuilder (Lcom/squareup/kotlinpoet/TypeSpec$Kind;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun toBuilder (Lcom/squareup/kotlinpoet/TypeSpec$Kind;Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static synthetic fun toBuilder$default (Lcom/squareup/kotlinpoet/TypeSpec;Lcom/squareup/kotlinpoet/TypeSpec$Kind;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public fun toString ()Ljava/lang/String;
+ public static final fun valueClassBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+}
+
+public final class com/squareup/kotlinpoet/TypeSpec$Builder : com/squareup/kotlinpoet/ContextReceivable$Builder, com/squareup/kotlinpoet/OriginatingElementsHolder$Builder, com/squareup/kotlinpoet/Taggable$Builder {
+ public final fun addAnnotation (Lcom/squareup/kotlinpoet/AnnotationSpec;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addAnnotation (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addAnnotation (Ljava/lang/Class;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addAnnotation (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addAnnotations (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addEnumConstant (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addEnumConstant (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeSpec;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static synthetic fun addEnumConstant$default (Lcom/squareup/kotlinpoet/TypeSpec$Builder;Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeSpec;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addFunction (Lcom/squareup/kotlinpoet/FunSpec;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addFunctions (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addInitializerBlock (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addKdoc (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addKdoc (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addModifiers (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addModifiers ([Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public synthetic fun addOriginatingElement (Ljavax/lang/model/element/Element;)Lcom/squareup/kotlinpoet/OriginatingElementsHolder$Builder;
+ public fun addOriginatingElement (Ljavax/lang/model/element/Element;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addProperties (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addProperty (Lcom/squareup/kotlinpoet/PropertySpec;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addProperty (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addProperty (Ljava/lang/String;Lcom/squareup/kotlinpoet/TypeName;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addProperty (Ljava/lang/String;Ljava/lang/reflect/Type;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addProperty (Ljava/lang/String;Ljava/lang/reflect/Type;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addProperty (Ljava/lang/String;Lkotlin/reflect/KClass;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addProperty (Ljava/lang/String;Lkotlin/reflect/KClass;[Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addSuperclassConstructorParameter (Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addSuperclassConstructorParameter (Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addSuperinterface (Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addSuperinterface (Lcom/squareup/kotlinpoet/TypeName;Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addSuperinterface (Ljava/lang/reflect/Type;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addSuperinterface (Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/CodeBlock;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addSuperinterface (Lkotlin/reflect/KClass;Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static synthetic fun addSuperinterface$default (Lcom/squareup/kotlinpoet/TypeSpec$Builder;Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static synthetic fun addSuperinterface$default (Lcom/squareup/kotlinpoet/TypeSpec$Builder;Ljava/lang/reflect/Type;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static synthetic fun addSuperinterface$default (Lcom/squareup/kotlinpoet/TypeSpec$Builder;Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/CodeBlock;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addSuperinterfaces (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addType (Lcom/squareup/kotlinpoet/TypeSpec;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addTypeVariable (Lcom/squareup/kotlinpoet/TypeVariableName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addTypeVariables (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun addTypes (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun build ()Lcom/squareup/kotlinpoet/TypeSpec;
+ public synthetic fun contextReceivers (Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/ContextReceivable$Builder;
+ public synthetic fun contextReceivers ([Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/ContextReceivable$Builder;
+ public final fun getAnnotationSpecs ()Ljava/util/List;
+ public final fun getEnumConstants ()Ljava/util/Map;
+ public final fun getFunSpecs ()Ljava/util/List;
+ public final fun getInitializerIndex ()I
+ public final fun getModifiers ()Ljava/util/Set;
+ public fun getOriginatingElements ()Ljava/util/List;
+ public final fun getPropertySpecs ()Ljava/util/List;
+ public final fun getSuperclassConstructorParameters ()Ljava/util/List;
+ public final fun getSuperinterfaces ()Ljava/util/Map;
+ public fun getTags ()Ljava/util/Map;
+ public final fun getTypeSpecs ()Ljava/util/List;
+ public final fun getTypeVariables ()Ljava/util/List;
+ public final fun primaryConstructor (Lcom/squareup/kotlinpoet/FunSpec;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun setInitializerIndex (I)V
+ public final fun superclass (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun superclass (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun superclass (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public synthetic fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+ public fun tag (Ljava/lang/Class;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public synthetic fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/Taggable$Builder;
+ public fun tag (Lkotlin/reflect/KClass;Ljava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+}
+
+public final class com/squareup/kotlinpoet/TypeSpec$Companion {
+ public final fun annotationBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun annotationBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun anonymousClassBuilder ()Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun classBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun classBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun companionObjectBuilder ()Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun companionObjectBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static synthetic fun companionObjectBuilder$default (Lcom/squareup/kotlinpoet/TypeSpec$Companion;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun enumBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun enumBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun expectClassBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun expectClassBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun funInterfaceBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun funInterfaceBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun interfaceBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun interfaceBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun objectBuilder (Lcom/squareup/kotlinpoet/ClassName;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun objectBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public final fun valueClassBuilder (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+}
+
+public final class com/squareup/kotlinpoet/TypeSpec$Kind : java/lang/Enum {
+ public static final field CLASS Lcom/squareup/kotlinpoet/TypeSpec$Kind;
+ public static final field INTERFACE Lcom/squareup/kotlinpoet/TypeSpec$Kind;
+ public static final field OBJECT Lcom/squareup/kotlinpoet/TypeSpec$Kind;
+ public static fun valueOf (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeSpec$Kind;
+ public static fun values ()[Lcom/squareup/kotlinpoet/TypeSpec$Kind;
+}
+
+public final class com/squareup/kotlinpoet/TypeVariableName : com/squareup/kotlinpoet/TypeName {
+ public static final field Companion Lcom/squareup/kotlinpoet/TypeVariableName$Companion;
+ public final fun copy (ZLjava/util/List;Ljava/util/List;ZLjava/util/Map;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public synthetic fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/TypeName;
+ public fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static synthetic fun copy$default (Lcom/squareup/kotlinpoet/TypeVariableName;ZLjava/util/List;Ljava/util/List;ZLjava/util/Map;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static final fun get (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static final fun get (Ljava/lang/String;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static final fun get (Ljava/lang/String;Ljava/util/List;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static final fun get (Ljava/lang/String;Ljava/util/List;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static final fun get (Ljava/lang/String;[Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static final fun get (Ljava/lang/String;[Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static final fun get (Ljava/lang/String;[Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static final fun get (Ljava/lang/String;[Ljava/lang/reflect/Type;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static final fun get (Ljava/lang/String;[Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static final fun get (Ljava/lang/String;[Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public final fun getBounds ()Ljava/util/List;
+ public final fun getName ()Ljava/lang/String;
+ public final fun getVariance ()Lcom/squareup/kotlinpoet/KModifier;
+ public static final fun getWithClasses (Ljava/lang/String;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static final fun getWithClasses (Ljava/lang/String;Ljava/lang/Iterable;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static final fun getWithTypes (Ljava/lang/String;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static final fun getWithTypes (Ljava/lang/String;Ljava/lang/Iterable;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public final fun isReified ()Z
+}
+
+public final class com/squareup/kotlinpoet/TypeVariableName$Companion {
+ public final fun get (Ljava/lang/String;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public final fun get (Ljava/lang/String;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public final fun get (Ljava/lang/String;Ljava/util/List;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public final fun get (Ljava/lang/String;Ljava/util/List;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public final fun get (Ljava/lang/String;[Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public final fun get (Ljava/lang/String;[Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public final fun get (Ljava/lang/String;[Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public final fun get (Ljava/lang/String;[Ljava/lang/reflect/Type;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public final fun get (Ljava/lang/String;[Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public final fun get (Ljava/lang/String;[Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static synthetic fun get$default (Lcom/squareup/kotlinpoet/TypeVariableName$Companion;Ljava/lang/String;Lcom/squareup/kotlinpoet/KModifier;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static synthetic fun get$default (Lcom/squareup/kotlinpoet/TypeVariableName$Companion;Ljava/lang/String;Ljava/util/List;Lcom/squareup/kotlinpoet/KModifier;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static synthetic fun get$default (Lcom/squareup/kotlinpoet/TypeVariableName$Companion;Ljava/lang/String;[Lcom/squareup/kotlinpoet/TypeName;Lcom/squareup/kotlinpoet/KModifier;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static synthetic fun get$default (Lcom/squareup/kotlinpoet/TypeVariableName$Companion;Ljava/lang/String;[Ljava/lang/reflect/Type;Lcom/squareup/kotlinpoet/KModifier;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static synthetic fun get$default (Lcom/squareup/kotlinpoet/TypeVariableName$Companion;Ljava/lang/String;[Lkotlin/reflect/KClass;Lcom/squareup/kotlinpoet/KModifier;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public final fun getWithClasses (Ljava/lang/String;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public final fun getWithClasses (Ljava/lang/String;Ljava/lang/Iterable;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static synthetic fun getWithClasses$default (Lcom/squareup/kotlinpoet/TypeVariableName$Companion;Ljava/lang/String;Ljava/lang/Iterable;Lcom/squareup/kotlinpoet/KModifier;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public final fun getWithTypes (Ljava/lang/String;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public final fun getWithTypes (Ljava/lang/String;Ljava/lang/Iterable;Lcom/squareup/kotlinpoet/KModifier;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static synthetic fun getWithTypes$default (Lcom/squareup/kotlinpoet/TypeVariableName$Companion;Ljava/lang/String;Ljava/lang/Iterable;Lcom/squareup/kotlinpoet/KModifier;ILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeVariableName;
+}
+
+public final class com/squareup/kotlinpoet/TypeVariableNames {
+ public static final fun asTypeVariableName (Lkotlin/reflect/KTypeParameter;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static final fun get (Ljavax/lang/model/element/TypeParameterElement;)Lcom/squareup/kotlinpoet/TypeVariableName;
+ public static final fun get (Ljavax/lang/model/type/TypeVariable;)Lcom/squareup/kotlinpoet/TypeVariableName;
+}
+
+public final class com/squareup/kotlinpoet/WildcardTypeName : com/squareup/kotlinpoet/TypeName {
+ public static final field Companion Lcom/squareup/kotlinpoet/WildcardTypeName$Companion;
+ public static final fun consumerOf (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+ public static final fun consumerOf (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+ public static final fun consumerOf (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+ public synthetic fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/TypeName;
+ public fun copy (ZLjava/util/List;Ljava/util/Map;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+ public final fun getInTypes ()Ljava/util/List;
+ public final fun getOutTypes ()Ljava/util/List;
+ public static final fun producerOf (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+ public static final fun producerOf (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+ public static final fun producerOf (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+}
+
+public final class com/squareup/kotlinpoet/WildcardTypeName$Companion {
+ public final fun consumerOf (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+ public final fun consumerOf (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+ public final fun consumerOf (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+ public final fun producerOf (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+ public final fun producerOf (Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+ public final fun producerOf (Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/WildcardTypeName;
+}
+
+public final class com/squareup/kotlinpoet/WildcardTypeNames {
+ public static final fun get (Ljava/lang/reflect/WildcardType;)Lcom/squareup/kotlinpoet/TypeName;
+ public static final fun get (Ljavax/lang/model/type/WildcardType;)Lcom/squareup/kotlinpoet/TypeName;
+}
+
+public final class com/squareup/kotlinpoet/jvm/JvmAnnotations {
+ public static final fun jvmDefault (Lcom/squareup/kotlinpoet/FunSpec$Builder;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static final fun jvmDefault (Lcom/squareup/kotlinpoet/PropertySpec$Builder;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public static final fun jvmField (Lcom/squareup/kotlinpoet/PropertySpec$Builder;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public static final fun jvmInline (Lcom/squareup/kotlinpoet/TypeSpec$Builder;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static final fun jvmMultifileClass (Lcom/squareup/kotlinpoet/FileSpec$Builder;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public static final fun jvmName (Lcom/squareup/kotlinpoet/FileSpec$Builder;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
+ public static final fun jvmName (Lcom/squareup/kotlinpoet/FunSpec$Builder;Ljava/lang/String;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static final fun jvmOverloads (Lcom/squareup/kotlinpoet/FunSpec$Builder;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static final fun jvmRecord (Lcom/squareup/kotlinpoet/TypeSpec$Builder;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static final fun jvmStatic (Lcom/squareup/kotlinpoet/FunSpec$Builder;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static final fun jvmStatic (Lcom/squareup/kotlinpoet/PropertySpec$Builder;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public static final fun jvmSuppressWildcards (Lcom/squareup/kotlinpoet/FunSpec$Builder;Z)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static final fun jvmSuppressWildcards (Lcom/squareup/kotlinpoet/PropertySpec$Builder;Z)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public static final fun jvmSuppressWildcards (Lcom/squareup/kotlinpoet/TypeName;Z)Lcom/squareup/kotlinpoet/TypeName;
+ public static final fun jvmSuppressWildcards (Lcom/squareup/kotlinpoet/TypeSpec$Builder;Z)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static synthetic fun jvmSuppressWildcards$default (Lcom/squareup/kotlinpoet/FunSpec$Builder;ZILjava/lang/Object;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static synthetic fun jvmSuppressWildcards$default (Lcom/squareup/kotlinpoet/PropertySpec$Builder;ZILjava/lang/Object;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public static synthetic fun jvmSuppressWildcards$default (Lcom/squareup/kotlinpoet/TypeName;ZILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeName;
+ public static synthetic fun jvmSuppressWildcards$default (Lcom/squareup/kotlinpoet/TypeSpec$Builder;ZILjava/lang/Object;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
+ public static final fun jvmWildcard (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/TypeName;
+ public static final fun strictfp (Lcom/squareup/kotlinpoet/FunSpec$Builder;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static final fun synchronized (Lcom/squareup/kotlinpoet/FunSpec$Builder;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static final fun throws (Lcom/squareup/kotlinpoet/FunSpec$Builder;Ljava/lang/Iterable;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static final fun throws (Lcom/squareup/kotlinpoet/FunSpec$Builder;[Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static final fun throws (Lcom/squareup/kotlinpoet/FunSpec$Builder;[Ljava/lang/reflect/Type;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static final fun throws (Lcom/squareup/kotlinpoet/FunSpec$Builder;[Lkotlin/reflect/KClass;)Lcom/squareup/kotlinpoet/FunSpec$Builder;
+ public static final fun transient (Lcom/squareup/kotlinpoet/PropertySpec$Builder;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+ public static final fun volatile (Lcom/squareup/kotlinpoet/PropertySpec$Builder;)Lcom/squareup/kotlinpoet/PropertySpec$Builder;
+}
+
+public final class com/squareup/kotlinpoet/tags/TypeAliasTag {
+ public fun <init> (Lcom/squareup/kotlinpoet/TypeName;)V
+ public final fun getAbbreviatedType ()Lcom/squareup/kotlinpoet/TypeName;
+}
+
diff --git a/kotlinpoet/build.gradle.kts b/kotlinpoet/build.gradle.kts
new file mode 100644
index 00000000..830f870f
--- /dev/null
+++ b/kotlinpoet/build.gradle.kts
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * 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.
+ */
+
+tasks.jar {
+ manifest {
+ attributes("Automatic-Module-Name" to "com.squareup.kotlinpoet")
+ }
+}
+
+tasks.compileTestKotlin {
+ kotlinOptions {
+ freeCompilerArgs = listOf("-opt-in=com.squareup.kotlinpoet.DelicateKotlinPoetApi")
+ }
+}
+
+spotless {
+ kotlin {
+ targetExclude(
+ // Non-Square licensed files
+ "src/main/java/com/squareup/kotlinpoet/ClassName.kt",
+ "src/test/java/com/squareup/kotlinpoet/AbstractTypesTest.kt",
+ "src/test/java/com/squareup/kotlinpoet/ClassNameTest.kt",
+ "src/test/java/com/squareup/kotlinpoet/TypesEclipseTest.kt",
+ "src/test/java/com/squareup/kotlinpoet/TypesTest.kt",
+ )
+ }
+}
+
+dependencies {
+ implementation(libs.kotlin.reflect)
+ testImplementation(libs.kotlin.junit)
+ testImplementation(libs.truth)
+ testImplementation(libs.compileTesting)
+ testImplementation(libs.jimfs)
+ testImplementation(libs.ecj)
+ testImplementation(libs.kotlinCompileTesting)
+ testImplementation(libs.kotlin.annotationProcessingEmbeddable)
+ testImplementation(libs.kotlin.compilerEmbeddable)
+}
diff --git a/kotlinpoet/gradle.properties b/kotlinpoet/gradle.properties
new file mode 100644
index 00000000..7258dd19
--- /dev/null
+++ b/kotlinpoet/gradle.properties
@@ -0,0 +1,4 @@
+POM_ARTIFACT_ID=kotlinpoet
+POM_NAME=KotlinPoet
+POM_DESCRIPTION=Use beautiful Kotlin code to generate beautiful Kotlin code.
+POM_PACKAGING=jar
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/AnnotationSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/AnnotationSpec.kt
new file mode 100644
index 00000000..d44264b6
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/AnnotationSpec.kt
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import java.lang.reflect.Array
+import java.util.Objects
+import javax.lang.model.element.AnnotationMirror
+import javax.lang.model.element.AnnotationValue
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.SimpleAnnotationValueVisitor7
+import kotlin.reflect.KClass
+
+/** A generated annotation on a declaration. */
+public class AnnotationSpec private constructor(
+ builder: Builder,
+ private val tagMap: TagMap = builder.buildTagMap(),
+) : Taggable by tagMap {
+ @Deprecated(
+ message = "Use typeName instead. This property will be removed in KotlinPoet 2.0.",
+ replaceWith = ReplaceWith("typeName"),
+ )
+ public val className: ClassName
+ get() = typeName as? ClassName ?: error("ClassName is not available. Call typeName instead.")
+ public val typeName: TypeName = builder.typeName
+ public val members: List<CodeBlock> = builder.members.toImmutableList()
+ public val useSiteTarget: UseSiteTarget? = builder.useSiteTarget
+
+ internal fun emit(codeWriter: CodeWriter, inline: Boolean, asParameter: Boolean = false) {
+ if (!asParameter) {
+ codeWriter.emit("@")
+ }
+ if (useSiteTarget != null) {
+ codeWriter.emit(useSiteTarget.keyword + ":")
+ }
+ codeWriter.emitCode("%T", typeName)
+
+ if (members.isEmpty() && !asParameter) {
+ // @Singleton
+ return
+ }
+
+ val whitespace = if (inline) "" else "\n"
+ val memberSeparator = if (inline) ", " else ",\n"
+ val memberSuffix = if (!inline && members.size > 1) "," else ""
+
+ // Inline:
+ // @Column(name = "updated_at", nullable = false)
+ //
+ // Not inline:
+ // @Column(
+ // name = "updated_at",
+ // nullable = false,
+ // )
+
+ codeWriter.emit("(")
+ if (members.size > 1) codeWriter.emit(whitespace).indent(1)
+ codeWriter.emitCode(
+ codeBlock = members
+ .map { if (inline) it.replaceAll("[⇥|⇤]", "") else it }
+ .joinToCode(separator = memberSeparator, suffix = memberSuffix),
+ isConstantContext = true,
+ )
+ if (members.size > 1) codeWriter.unindent(1).emit(whitespace)
+ codeWriter.emit(")")
+ }
+
+ public fun toBuilder(): Builder {
+ val builder = Builder(typeName)
+ builder.members += members
+ builder.useSiteTarget = useSiteTarget
+ builder.tags += tagMap.tags
+ return builder
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null) return false
+ if (javaClass != other.javaClass) return false
+ return toString() == other.toString()
+ }
+
+ override fun hashCode(): Int = toString().hashCode()
+
+ override fun toString(): String = buildCodeString {
+ emit(this, inline = true, asParameter = false)
+ }
+
+ public enum class UseSiteTarget(internal val keyword: String) {
+ FILE("file"),
+ PROPERTY("property"),
+ FIELD("field"),
+ GET("get"),
+ SET("set"),
+ RECEIVER("receiver"),
+ PARAM("param"),
+ SETPARAM("setparam"),
+ DELEGATE("delegate"),
+ }
+
+ public class Builder internal constructor(
+ internal val typeName: TypeName,
+ ) : Taggable.Builder<Builder> {
+ internal var useSiteTarget: UseSiteTarget? = null
+
+ public val members: MutableList<CodeBlock> = mutableListOf()
+ override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
+
+ public fun addMember(format: String, vararg args: Any): Builder =
+ addMember(CodeBlock.of(format, *args))
+
+ public fun addMember(codeBlock: CodeBlock): Builder = apply {
+ members += codeBlock
+ }
+
+ public fun useSiteTarget(useSiteTarget: UseSiteTarget?): Builder = apply {
+ this.useSiteTarget = useSiteTarget
+ }
+
+ public fun build(): AnnotationSpec = AnnotationSpec(this)
+
+ public companion object {
+ /**
+ * Creates a [CodeBlock] with parameter `format` depending on the given `value` object.
+ * Handles a number of special cases, such as appending "f" to `Float` values, and uses
+ * `%L` for other types.
+ */
+ internal fun memberForValue(value: Any) = when (value) {
+ is Class<*> -> CodeBlock.of("%T::class", value)
+ is Enum<*> -> CodeBlock.of("%T.%L", value.javaClass, value.name)
+ is String -> CodeBlock.of("%S", value)
+ is Float -> CodeBlock.of("%Lf", value)
+ is Char -> CodeBlock.of("'%L'", characterLiteralWithoutSingleQuotes(value))
+ else -> CodeBlock.of("%L", value)
+ }
+ }
+ }
+
+ /**
+ * Annotation value visitor adding members to the given builder instance.
+ */
+ @OptIn(DelicateKotlinPoetApi::class)
+ private class Visitor(
+ val builder: CodeBlock.Builder,
+ ) : SimpleAnnotationValueVisitor7<CodeBlock.Builder, String>(builder) {
+
+ override fun defaultAction(o: Any, name: String) =
+ builder.add(Builder.memberForValue(o))
+
+ override fun visitAnnotation(a: AnnotationMirror, name: String) =
+ builder.add("%L", get(a))
+
+ override fun visitEnumConstant(c: VariableElement, name: String) =
+ builder.add("%T.%L", c.asType().asTypeName(), c.simpleName)
+
+ override fun visitType(t: TypeMirror, name: String) =
+ builder.add("%T::class", t.asTypeName())
+
+ override fun visitArray(values: List<AnnotationValue>, name: String): CodeBlock.Builder {
+ builder.add("arrayOf(⇥⇥")
+ values.forEachIndexed { index, value ->
+ if (index > 0) builder.add(", ")
+ value.accept(this, name)
+ }
+ builder.add("⇤⇤)")
+ return builder
+ }
+ }
+
+ public companion object {
+ @DelicateKotlinPoetApi(
+ message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+ "using the kotlinpoet-metadata APIs instead.",
+ )
+ @JvmStatic
+ @JvmOverloads
+ public fun get(
+ annotation: Annotation,
+ includeDefaultValues: Boolean = false,
+ ): AnnotationSpec {
+ try {
+ @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+ val javaAnnotation = annotation as java.lang.annotation.Annotation
+ val builder = builder(javaAnnotation.annotationType())
+ .tag(annotation)
+ val methods = annotation.annotationType().declaredMethods.sortedBy { it.name }
+ for (method in methods) {
+ val value = method.invoke(annotation)
+ if (!includeDefaultValues) {
+ if (Objects.deepEquals(value, method.defaultValue)) {
+ continue
+ }
+ }
+ val member = CodeBlock.builder()
+ member.add("%L = ", method.name)
+ if (value.javaClass.isArray) {
+ member.add("arrayOf(⇥⇥")
+ for (i in 0 until Array.getLength(value)) {
+ if (i > 0) member.add(", ")
+ member.add(Builder.memberForValue(Array.get(value, i)))
+ }
+ member.add("⇤⇤)")
+ builder.addMember(member.build())
+ continue
+ }
+ if (value is Annotation) {
+ member.add("%L", get(value))
+ builder.addMember(member.build())
+ continue
+ }
+ member.add("%L", Builder.memberForValue(value))
+ builder.addMember(member.build())
+ }
+ return builder.build()
+ } catch (e: Exception) {
+ throw RuntimeException("Reflecting $annotation failed!", e)
+ }
+ }
+
+ @DelicateKotlinPoetApi(
+ message = "Mirror APIs don't give complete information on Kotlin types. Consider using" +
+ " the kotlinpoet-metadata APIs instead.",
+ )
+ @JvmStatic
+ public fun get(annotation: AnnotationMirror): AnnotationSpec {
+ val element = annotation.annotationType.asElement() as TypeElement
+ val builder = builder(element.asClassName()).tag(annotation)
+ for (executableElement in annotation.elementValues.keys) {
+ val member = CodeBlock.builder()
+ val visitor = Visitor(member)
+ val name = executableElement.simpleName.toString()
+ member.add("%L = ", name)
+ val value = annotation.elementValues[executableElement]!!
+ value.accept(visitor, name)
+ builder.addMember(member.build())
+ }
+ return builder.build()
+ }
+
+ @JvmStatic public fun builder(type: ClassName): Builder = Builder(type)
+
+ @JvmStatic public fun builder(type: ParameterizedTypeName): Builder = Builder(type)
+
+ @DelicateKotlinPoetApi(
+ message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+ "using the kotlinpoet-metadata APIs instead.",
+ )
+ @JvmStatic
+ public fun builder(type: Class<out Annotation>): Builder =
+ builder(type.asClassName())
+
+ @JvmStatic public fun builder(type: KClass<out Annotation>): Builder =
+ builder(type.asClassName())
+ }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ClassName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ClassName.kt
new file mode 100644
index 00000000..119d05af
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ClassName.kt
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * 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.
+ */
+@file:JvmName("ClassNames")
+
+package com.squareup.kotlinpoet
+
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.NestingKind.MEMBER
+import javax.lang.model.element.NestingKind.TOP_LEVEL
+import javax.lang.model.element.PackageElement
+import javax.lang.model.element.TypeElement
+import kotlin.reflect.KClass
+
+/** A fully-qualified class name for top-level and member classes. */
+public class ClassName internal constructor(
+ names: List<String>,
+ nullable: Boolean = false,
+ annotations: List<AnnotationSpec> = emptyList(),
+ tags: Map<KClass<*>, Any> = emptyMap()
+) : TypeName(nullable, annotations, TagMap(tags)), Comparable<ClassName> {
+ /**
+ * Returns a class name created from the given parts. For example, calling this with package name
+ * `"java.util"` and simple names `"Map"`, `"Entry"` yields `Map.Entry`.
+ */
+ @Deprecated("", level = DeprecationLevel.HIDDEN)
+ public constructor(packageName: String, simpleName: String, vararg simpleNames: String) :
+ this(listOf(packageName, simpleName, *simpleNames))
+
+ @Deprecated(
+ "Simple names must not be empty. Did you forget an argument?",
+ level = DeprecationLevel.ERROR,
+ replaceWith = ReplaceWith("ClassName(packageName, TODO())"),
+ )
+ public constructor(packageName: String) : this(packageName, listOf())
+
+ /**
+ * Returns a class name created from the given parts. For example, calling this with package name
+ * `"java.util"` and simple names `"Map"`, `"Entry"` yields `Map.Entry`.
+ */
+ public constructor(packageName: String, vararg simpleNames: String) :
+ this(listOf(packageName, *simpleNames)) {
+ require(simpleNames.isNotEmpty()) { "simpleNames must not be empty" }
+ require(simpleNames.none { it.isEmpty() }) {
+ "simpleNames must not contain empty items: ${simpleNames.contentToString()}"
+ }
+ }
+
+ /**
+ * Returns a class name created from the given parts. For example, calling this with package name
+ * `"java.util"` and simple names `"Map"`, `"Entry"` yields `Map.Entry`.
+ */
+ public constructor(packageName: String, simpleNames: List<String>) :
+ this(mutableListOf(packageName).apply { addAll(simpleNames) }) {
+ require(simpleNames.isNotEmpty()) { "simpleNames must not be empty" }
+ require(simpleNames.none { it.isEmpty() }) {
+ "simpleNames must not contain empty items: $simpleNames"
+ }
+ }
+
+ /** From top to bottom. This will be `["java.util", "Map", "Entry"]` for `Map.Entry`. */
+ private val names = names.toImmutableList()
+
+ /** Fully qualified name using `.` as a separator, like `kotlin.collections.Map.Entry`. */
+ public val canonicalName: String = if (names[0].isEmpty())
+ names.subList(1, names.size).joinToString(".") else
+ names.joinToString(".")
+
+ /** Package name, like `"kotlin.collections"` for `Map.Entry`. */
+ public val packageName: String get() = names[0]
+
+ /** Simple name of this class, like `"Entry"` for `Map.Entry`. */
+ public val simpleName: String get() = names[names.size - 1]
+
+ /**
+ * The enclosing classes, outermost first, followed by the simple name. This is `["Map", "Entry"]`
+ * for `Map.Entry`.
+ */
+ public val simpleNames: List<String> get() = names.subList(1, names.size)
+
+ override fun copy(
+ nullable: Boolean,
+ annotations: List<AnnotationSpec>,
+ tags: Map<KClass<*>, Any>
+ ): ClassName {
+ return ClassName(names, nullable, annotations, tags)
+ }
+
+ /**
+ * Returns the enclosing class, like `Map` for `Map.Entry`. Returns null if this class is not
+ * nested in another class.
+ */
+ public fun enclosingClassName(): ClassName? {
+ return if (names.size != 2)
+ ClassName(names.subList(0, names.size - 1)) else
+ null
+ }
+
+ /**
+ * Returns the top class in this nesting group. Equivalent to chained calls to
+ * [ClassName.enclosingClassName] until the result's enclosing class is null.
+ */
+ public fun topLevelClassName(): ClassName = ClassName(names.subList(0, 2))
+
+ /**
+ * Fully qualified name using `.` to separate package from the top level class name, and `$` to
+ * separate nested classes, like `kotlin.collections.Map$Entry`.
+ */
+ public fun reflectionName(): String {
+ // trivial case: no nested names
+ if (names.size == 2) {
+ return if (packageName.isEmpty())
+ names[1] else
+ packageName + "." + names[1]
+ }
+ // concat top level class name and nested names
+ return buildString {
+ append(topLevelClassName().canonicalName)
+ for (name in simpleNames.subList(1, simpleNames.size)) {
+ append('$').append(name)
+ }
+ }
+ }
+
+ /**
+ * Callable reference to the constructor of this class. Emits the enclosing class if one exists,
+ * followed by the reference operator `::`, followed by either [simpleName] or the
+ * fully-qualified name if this is a top-level class.
+ *
+ * Note: As `::$packageName.$simpleName` is not valid syntax, an aliased import may be required
+ * for a top-level class with a conflicting name.
+ */
+ public fun constructorReference(): CodeBlock {
+ val enclosing = enclosingClassName()
+ return if (enclosing != null) {
+ CodeBlock.of("%T::%N", enclosing, simpleName)
+ } else {
+ CodeBlock.of("::%T", this)
+ }
+ }
+
+ /** Returns a new [ClassName] instance for the specified `name` as nested inside this class. */
+ public fun nestedClass(name: String): ClassName = ClassName(names + name)
+
+ /**
+ * Returns a class that shares the same enclosing package or class. If this class is enclosed by
+ * another class, this is equivalent to `enclosingClassName().nestedClass(name)`. Otherwise
+ * it is equivalent to `get(packageName(), name)`.
+ */
+ public fun peerClass(name: String): ClassName {
+ val result = names.toMutableList()
+ result[result.size - 1] = name
+ return ClassName(result)
+ }
+
+ /**
+ * Orders by the fully-qualified name. Nested types are ordered immediately after their
+ * enclosing type. For example, the following types are ordered by this method:
+ *
+ * ```
+ * com.example.Robot
+ * com.example.Robot.Motor
+ * com.example.RoboticVacuum
+ * ```
+ */
+ override fun compareTo(other: ClassName): Int = canonicalName.compareTo(other.canonicalName)
+
+ override fun emit(out: CodeWriter) =
+ out.emit(out.lookupName(this).escapeSegmentsIfNecessary())
+
+ public companion object {
+ /**
+ * Returns a new [ClassName] instance for the given fully-qualified class name string. This
+ * method assumes that the input is ASCII and follows typical Java style (lowercase package
+ * names, UpperCamelCase class names) and may produce incorrect results or throw
+ * [IllegalArgumentException] otherwise. For that reason, the constructor should be preferred as
+ * it can create [ClassName] instances without such restrictions.
+ */
+ @JvmStatic public fun bestGuess(classNameString: String): ClassName {
+ val names = mutableListOf<String>()
+
+ // Add the package name, like "java.util.concurrent", or "" for no package.
+ var p = 0
+ while (p < classNameString.length && Character.isLowerCase(classNameString.codePointAt(p))) {
+ p = classNameString.indexOf('.', p) + 1
+ require(p != 0) { "couldn't make a guess for $classNameString" }
+ }
+ names += if (p != 0) classNameString.substring(0, p - 1) else ""
+
+ // Add the class names, like "Map" and "Entry".
+ for (part in classNameString.substring(p).split('.')) {
+ require(part.isNotEmpty() && Character.isUpperCase(part.codePointAt(0))) {
+ "couldn't make a guess for $classNameString"
+ }
+
+ names += part
+ }
+
+ require(names.size >= 2) { "couldn't make a guess for $classNameString" }
+ return ClassName(names)
+ }
+ }
+}
+
+@DelicateKotlinPoetApi(
+ message = "Java reflection APIs don't give complete information on Kotlin types. Consider using" +
+ " the kotlinpoet-metadata APIs instead."
+)
+@JvmName("get")
+public fun Class<*>.asClassName(): ClassName {
+ require(!isPrimitive) { "primitive types cannot be represented as a ClassName" }
+ require(Void.TYPE != this) { "'void' type cannot be represented as a ClassName" }
+ require(!isArray) { "array types cannot be represented as a ClassName" }
+ val names = mutableListOf<String>()
+ var c = this
+ while (true) {
+ names += c.simpleName
+ val enclosing = c.enclosingClass ?: break
+ c = enclosing
+ }
+ // Avoid unreliable Class.getPackage(). https://github.com/square/javapoet/issues/295
+ val lastDot = c.name.lastIndexOf('.')
+ if (lastDot != -1) names += c.name.substring(0, lastDot)
+ names.reverse()
+ return ClassName(names)
+}
+
+@JvmName("get")
+public fun KClass<*>.asClassName(): ClassName {
+ qualifiedName?.let { return ClassName.bestGuess(it) }
+ throw IllegalArgumentException("$this cannot be represented as a ClassName")
+}
+
+/** Returns the class name for `element`. */
+@DelicateKotlinPoetApi(
+ message = "Element APIs don't give complete information on Kotlin types. Consider using" +
+ " the kotlinpoet-metadata APIs instead."
+)
+@JvmName("get")
+public fun TypeElement.asClassName(): ClassName {
+ fun isClassOrInterface(e: Element) = e.kind.isClass || e.kind.isInterface
+
+ fun getPackage(type: Element): PackageElement {
+ var t = type
+ while (t.kind != ElementKind.PACKAGE) {
+ t = t.enclosingElement
+ }
+ return t as PackageElement
+ }
+
+ val names = mutableListOf<String>()
+ var e: Element = this
+ while (isClassOrInterface(e)) {
+ val eType = e as TypeElement
+ require(eType.nestingKind.isOneOf(TOP_LEVEL, MEMBER)) {
+ "unexpected type testing"
+ }
+ names += eType.simpleName.toString()
+ e = eType.enclosingElement
+ }
+ names += getPackage(this).qualifiedName.toString()
+ names.reverse()
+ return ClassName(names)
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeBlock.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeBlock.kt
new file mode 100644
index 00000000..61b4b202
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeBlock.kt
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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.
+ */
+@file:JvmName("CodeBlocks")
+
+package com.squareup.kotlinpoet
+
+import java.lang.reflect.Type
+import java.text.DecimalFormat
+import java.text.DecimalFormatSymbols
+import javax.lang.model.element.Element
+import javax.lang.model.type.TypeMirror
+import kotlin.reflect.KClass
+
+/**
+ * A fragment of a .kt file, potentially containing declarations, statements, and documentation.
+ * Code blocks are not necessarily well-formed Kotlin code, and are not validated. This class
+ * assumes kotlinc will check correctness later!
+ *
+ * Code blocks support placeholders like [java.text.Format]. This class primarily uses a percent
+ * sign `%` but has its own set of permitted placeholders:
+ *
+ * * `%L` emits a *literal* value with no escaping. Arguments for literals may be strings,
+ * primitives, [type declarations][TypeSpec], [annotations][AnnotationSpec] and even other code
+ * blocks.
+ * * `%N` emits a *name*, using name collision avoidance where necessary. Arguments for names may
+ * be strings (actually any [character sequence][CharSequence]), [parameters][ParameterSpec],
+ * [properties][PropertySpec], [functions][FunSpec], and [types][TypeSpec].
+ * * `%S` escapes the value as a *string*, wraps it with double quotes, and emits that. For
+ * example, `6" sandwich` is emitted `"6\" sandwich"`. `%S` will also escape all dollar signs
+ * (`$`), use `%P` for string templates.
+ * * `%P` - Similar to `%S`, but doesn't escape dollar signs (`$`) to allow creation of string
+ * templates. If the string contains dollar signs that should be escaped - use `%S`.
+ * * `%T` emits a *type* reference. Types will be imported if possible. Arguments for types may be
+ * [classes][Class].
+ * * `%M` emits a *member* reference. A member is either a function or a property. If the member is
+ * importable, e.g. it's a top-level function or a property declared inside an object, the import
+ * will be resolved if possible. Arguments for members must be of type [MemberName].
+ * * `%%` emits a percent sign.
+ * * `·` emits a space that never wraps. KotlinPoet prefers to wrap lines longer than 100 columns.
+ * It does this by replacing normal spaces with a newline and indent. Note that spaces in strings
+ * are never wrapped.
+ * * `⇥` increases the indentation level.
+ * * `⇤` decreases the indentation level.
+ * * `«` begins a statement. For multiline statements, every line after the first line is
+ * double-indented.
+ * * `»` ends a statement.
+ */
+public class CodeBlock private constructor(
+ internal val formatParts: List<String>,
+ internal val args: List<Any?>,
+) {
+ /** A heterogeneous list containing string literals and value placeholders. */
+
+ public fun isEmpty(): Boolean = formatParts.isEmpty()
+
+ public fun isNotEmpty(): Boolean = !isEmpty()
+
+ /**
+ * Returns a code block with `prefix` stripped off, or null if this code block doesn't start with
+ * `prefix`.
+ *
+ * This is a pretty basic implementation that might not cover cases like mismatched whitespace. We
+ * could offer something more lenient if necessary.
+ */
+ internal fun withoutPrefix(prefix: CodeBlock): CodeBlock? {
+ if (formatParts.size < prefix.formatParts.size) return null
+ if (args.size < prefix.args.size) return null
+
+ var prefixArgCount = 0
+ var firstFormatPart: String? = null
+
+ // Walk through the formatParts of prefix to confirm that it's a of this.
+ prefix.formatParts.forEachIndexed { index, formatPart ->
+ if (formatParts[index] != formatPart) {
+ // We've found a format part that doesn't match. If this is the very last format part check
+ // for a string prefix match. If that doesn't match, we're done.
+ if (index == prefix.formatParts.size - 1 && formatParts[index].startsWith(formatPart)) {
+ firstFormatPart = formatParts[index].substring(formatPart.length)
+ } else {
+ return null
+ }
+ }
+
+ // If the matching format part has an argument, check that too.
+ if (formatPart.startsWith("%") && !formatPart[1].isMultiCharNoArgPlaceholder) {
+ if (args[prefixArgCount] != prefix.args[prefixArgCount]) {
+ return null // Argument doesn't match.
+ }
+ prefixArgCount++
+ }
+ }
+
+ // We found a prefix. Prepare the suffix as a result.
+ val resultFormatParts = ArrayList<String>()
+ firstFormatPart?.let {
+ resultFormatParts.add(it)
+ }
+ for (i in prefix.formatParts.size until formatParts.size) {
+ resultFormatParts.add(formatParts[i])
+ }
+
+ val resultArgs = ArrayList<Any?>()
+ for (i in prefix.args.size until args.size) {
+ resultArgs.add(args[i])
+ }
+
+ return CodeBlock(resultFormatParts, resultArgs)
+ }
+
+ /**
+ * Returns a copy of the code block without leading and trailing no-arg placeholders
+ * (`⇥`, `⇤`, `«`, `»`).
+ */
+ internal fun trim(): CodeBlock {
+ var start = 0
+ var end = formatParts.size
+ while (start < end && formatParts[start] in NO_ARG_PLACEHOLDERS) {
+ start++
+ }
+ while (start < end && formatParts[end - 1] in NO_ARG_PLACEHOLDERS) {
+ end--
+ }
+ return when {
+ start > 0 || end < formatParts.size -> CodeBlock(formatParts.subList(start, end), args)
+ else -> this
+ }
+ }
+
+ /**
+ * Returns a copy of the code block with selected format parts replaced, similar to
+ * [java.lang.String.replaceAll].
+ *
+ * **Warning!** This method leaves the arguments list unchanged. Take care when replacing
+ * placeholders with arguments, such as `%L`, as it can result in a code block, where
+ * placeholders don't match their arguments.
+ */
+ internal fun replaceAll(oldValue: String, newValue: String) =
+ CodeBlock(formatParts.map { it.replace(oldValue, newValue) }, args)
+
+ internal fun hasStatements() = formatParts.any { "«" in it }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null) return false
+ if (javaClass != other.javaClass) return false
+ return toString() == other.toString()
+ }
+
+ override fun hashCode(): Int = toString().hashCode()
+
+ override fun toString(): String = buildCodeString { emitCode(this@CodeBlock) }
+
+ internal fun toString(codeWriter: CodeWriter): String = buildCodeString(codeWriter) {
+ emitCode(this@CodeBlock)
+ }
+
+ public fun toBuilder(): Builder {
+ val builder = Builder()
+ builder.formatParts += formatParts
+ builder.args.addAll(args)
+ return builder
+ }
+
+ public class Builder {
+ internal val formatParts = mutableListOf<String>()
+ internal val args = mutableListOf<Any?>()
+
+ public fun isEmpty(): Boolean = formatParts.isEmpty()
+
+ public fun isNotEmpty(): Boolean = !isEmpty()
+
+ /**
+ * Adds code using named arguments.
+ *
+ * Named arguments specify their name after the '%' followed by : and the corresponding type
+ * character. Argument names consist of characters in `a-z, A-Z, 0-9, and _` and must start
+ * with a lowercase character.
+ *
+ * For example, to refer to the type [java.lang.Integer] with the argument name `clazz` use a
+ * format string containing `%clazz:T` and include the key `clazz` with value
+ * `java.lang.Integer.class` in the argument map.
+ */
+ public fun addNamed(format: String, arguments: Map<String, *>): Builder = apply {
+ var p = 0
+
+ for (argument in arguments.keys) {
+ require(LOWERCASE matches argument) {
+ "argument '$argument' must start with a lowercase character"
+ }
+ }
+
+ while (p < format.length) {
+ val nextP = format.nextPotentialPlaceholderPosition(startIndex = p)
+ if (nextP == -1) {
+ formatParts += format.substring(p, format.length)
+ break
+ }
+
+ if (p != nextP) {
+ formatParts += format.substring(p, nextP)
+ p = nextP
+ }
+
+ var matchResult: MatchResult? = null
+ val colon = format.indexOf(':', p)
+ if (colon != -1) {
+ val endIndex = (colon + 2).coerceAtMost(format.length)
+ matchResult = NAMED_ARGUMENT.matchEntire(format.substring(p, endIndex))
+ }
+ if (matchResult != null) {
+ val argumentName = matchResult.groupValues[ARG_NAME]
+ require(arguments.containsKey(argumentName)) {
+ "Missing named argument for %$argumentName"
+ }
+ val formatChar = matchResult.groupValues[TYPE_NAME].first()
+ addArgument(format, formatChar, arguments[argumentName])
+ formatParts += "%$formatChar"
+ p += matchResult.range.last + 1
+ } else if (format[p].isSingleCharNoArgPlaceholder) {
+ formatParts += format.substring(p, p + 1)
+ p++
+ } else {
+ require(p < format.length - 1) { "dangling % at end" }
+ require(format[p + 1].isMultiCharNoArgPlaceholder) {
+ "unknown format %${format[p + 1]} at ${p + 1} in '$format'"
+ }
+ formatParts += format.substring(p, p + 2)
+ p += 2
+ }
+ }
+ }
+
+ /**
+ * Add code with positional or relative arguments.
+ *
+ * Relative arguments map 1:1 with the placeholders in the format string.
+ *
+ * Positional arguments use an index after the placeholder to identify which argument index
+ * to use. For example, for a literal to reference the 3rd argument: "%3L" (1 based index)
+ *
+ * Mixing relative and positional arguments in a call to add is invalid and will result in an
+ * error.
+ */
+ public fun add(format: String, vararg args: Any?): Builder = apply {
+ var hasRelative = false
+ var hasIndexed = false
+
+ var relativeParameterCount = 0
+ val indexedParameterCount = IntArray(args.size)
+
+ var p = 0
+ while (p < format.length) {
+ if (format[p].isSingleCharNoArgPlaceholder) {
+ formatParts += format[p].toString()
+ p++
+ continue
+ }
+
+ if (format[p] != '%') {
+ var nextP = format.nextPotentialPlaceholderPosition(startIndex = p + 1)
+ if (nextP == -1) nextP = format.length
+ formatParts += format.substring(p, nextP)
+ p = nextP
+ continue
+ }
+
+ p++ // '%'.
+
+ // Consume zero or more digits, leaving 'c' as the first non-digit char after the '%'.
+ val indexStart = p
+ var c: Char
+ do {
+ require(p < format.length) { "dangling format characters in '$format'" }
+ c = format[p++]
+ } while (c in '0'..'9')
+ val indexEnd = p - 1
+
+ // If 'c' doesn't take an argument, we're done.
+ if (c.isMultiCharNoArgPlaceholder) {
+ require(indexStart == indexEnd) { "%% may not have an index" }
+ formatParts += "%$c"
+ continue
+ }
+
+ // Find either the indexed argument, or the relative argument. (0-based).
+ val index: Int
+ if (indexStart < indexEnd) {
+ index = Integer.parseInt(format.substring(indexStart, indexEnd)) - 1
+ hasIndexed = true
+ if (args.isNotEmpty()) {
+ indexedParameterCount[index % args.size]++ // modulo is needed, checked below anyway
+ }
+ } else {
+ index = relativeParameterCount
+ hasRelative = true
+ relativeParameterCount++
+ }
+
+ require(index >= 0 && index < args.size) {
+ "index ${index + 1} for '${format.substring(
+ indexStart - 1,
+ indexEnd + 1,
+ )}' not in range (received ${args.size} arguments)"
+ }
+ require(!hasIndexed || !hasRelative) { "cannot mix indexed and positional parameters" }
+
+ addArgument(format, c, args[index])
+
+ formatParts += "%$c"
+ }
+
+ if (hasRelative) {
+ require(relativeParameterCount >= args.size) {
+ "unused arguments: expected $relativeParameterCount, received ${args.size}"
+ }
+ }
+ if (hasIndexed) {
+ val unused = mutableListOf<String>()
+ for (i in args.indices) {
+ if (indexedParameterCount[i] == 0) {
+ unused += "%" + (i + 1)
+ }
+ }
+ val s = if (unused.size == 1) "" else "s"
+ require(unused.isEmpty()) { "unused argument$s: ${unused.joinToString(", ")}" }
+ }
+ }
+
+ private fun addArgument(format: String, c: Char, arg: Any?) {
+ when (c) {
+ 'N' -> this.args += argToName(arg).escapeIfNecessary()
+ 'L' -> this.args += argToLiteral(arg)
+ 'S' -> this.args += argToString(arg)
+ 'P' -> this.args += if (arg is CodeBlock) arg else argToString(arg)
+ 'T' -> this.args += argToType(arg)
+ 'M' -> this.args += arg
+ else -> throw IllegalArgumentException(
+ String.format("invalid format string: '%s'", format),
+ )
+ }
+ }
+
+ private fun argToName(o: Any?) = when (o) {
+ is CharSequence -> o.toString()
+ is ParameterSpec -> o.name
+ is PropertySpec -> o.name
+ is FunSpec -> o.name
+ is TypeSpec -> o.name!!
+ is MemberName -> o.simpleName
+ else -> throw IllegalArgumentException("expected name but was $o")
+ }
+
+ private fun argToLiteral(o: Any?) = if (o is Number) formatNumericValue(o) else o
+
+ private fun argToString(o: Any?) = o?.toString()
+
+ private fun formatNumericValue(o: Number): Any? {
+ val format = DecimalFormatSymbols().apply {
+ decimalSeparator = '.'
+ groupingSeparator = '_'
+ }
+
+ val precision = if (o is Float || o is Double) o.toString().split(".").last().length else 0
+
+ val pattern = when (o) {
+ is Float, is Double -> "###,##0.0" + "#".repeat(precision - 1)
+ else -> "###,##0"
+ }
+
+ return DecimalFormat(pattern, format).format(o)
+ }
+
+ private fun logDeprecationWarning(o: Any) {
+ println(
+ "Deprecation warning: converting $o to TypeName. Conversion of TypeMirror and" +
+ " TypeElement is deprecated in KotlinPoet, use kotlin-metadata APIs instead.",
+ )
+ }
+
+ private fun argToType(o: Any?) = when (o) {
+ is TypeName -> o
+ is TypeMirror -> {
+ logDeprecationWarning(o)
+ o.asTypeName()
+ }
+ is Element -> {
+ logDeprecationWarning(o)
+ o.asType().asTypeName()
+ }
+ is Type -> o.asTypeName()
+ is KClass<*> -> o.asTypeName()
+ else -> throw IllegalArgumentException("expected type but was $o")
+ }
+
+ /**
+ * @param controlFlow the control flow construct and its code, such as `if (foo == 5)`.
+ * Shouldn't contain newline characters. Can contain opening braces, e.g.
+ * `beginControlFlow("list.forEach { element ->")`. If there's no opening brace at the end
+ * of the string, it will be added.
+ */
+ public fun beginControlFlow(controlFlow: String, vararg args: Any?): Builder = apply {
+ add(controlFlow.withOpeningBrace(), *args)
+ indent()
+ }
+
+ private fun String.withOpeningBrace(): String {
+ for (i in length - 1 downTo 0) {
+ if (this[i] == '{') {
+ return "$this\n"
+ } else if (this[i] == '}') {
+ break
+ }
+ }
+ return "$this·{\n"
+ }
+
+ /**
+ * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
+ * Shouldn't contain braces or newline characters.
+ */
+ public fun nextControlFlow(controlFlow: String, vararg args: Any?): Builder = apply {
+ unindent()
+ add("}·$controlFlow·{\n", *args)
+ indent()
+ }
+
+ public fun endControlFlow(): Builder = apply {
+ unindent()
+ add("}\n")
+ }
+
+ public fun addStatement(format: String, vararg args: Any?): Builder = apply {
+ add("«")
+ add(format, *args)
+ add("\n»")
+ }
+
+ public fun add(codeBlock: CodeBlock): Builder = apply {
+ formatParts += codeBlock.formatParts
+ args.addAll(codeBlock.args)
+ }
+
+ public fun indent(): Builder = apply {
+ formatParts += "⇥"
+ }
+
+ public fun unindent(): Builder = apply {
+ formatParts += "⇤"
+ }
+
+ public fun clear(): Builder = apply {
+ formatParts.clear()
+ args.clear()
+ }
+
+ public fun build(): CodeBlock = CodeBlock(formatParts.toImmutableList(), args.toImmutableList())
+ }
+
+ public companion object {
+ private val NAMED_ARGUMENT = Regex("%([\\w_]+):([\\w]).*")
+ private val LOWERCASE = Regex("[a-z]+[\\w_]*")
+ private const val ARG_NAME = 1
+ private const val TYPE_NAME = 2
+ private val NO_ARG_PLACEHOLDERS = setOf("⇥", "⇤", "«", "»")
+ internal val EMPTY = CodeBlock(emptyList(), emptyList())
+
+ @JvmStatic public fun of(format: String, vararg args: Any?): CodeBlock =
+ Builder().add(format, *args).build()
+
+ @JvmStatic public fun builder(): Builder = Builder()
+
+ internal val Char.isMultiCharNoArgPlaceholder get() = this == '%'
+ internal val Char.isSingleCharNoArgPlaceholder get() = isOneOf('⇥', '⇤', '«', '»')
+ internal val String.isPlaceholder
+ get() = (length == 1 && first().isSingleCharNoArgPlaceholder) ||
+ (length == 2 && first().isMultiCharNoArgPlaceholder)
+
+ internal fun String.nextPotentialPlaceholderPosition(startIndex: Int) =
+ indexOfAny(charArrayOf('%', '«', '»', '⇥', '⇤'), startIndex)
+ }
+}
+
+@JvmOverloads
+public fun Collection<CodeBlock>.joinToCode(
+ separator: CharSequence = ", ",
+ prefix: CharSequence = "",
+ suffix: CharSequence = "",
+): CodeBlock {
+ val blocks = toTypedArray()
+ val placeholders = Array(blocks.size) { "%L" }
+ return CodeBlock.of(placeholders.joinToString(separator, prefix, suffix), *blocks)
+}
+
+/**
+ * Builds new [CodeBlock] by populating newly created [CodeBlock.Builder] using provided
+ * [builderAction] and then converting it to [CodeBlock].
+ */
+public inline fun buildCodeBlock(builderAction: CodeBlock.Builder.() -> Unit): CodeBlock {
+ return CodeBlock.builder().apply(builderAction).build()
+}
+
+/**
+ * Calls [CodeBlock.Builder.indent] then executes the provided [builderAction] on the
+ * [CodeBlock.Builder] and then executes [CodeBlock.Builder.unindent] before returning the
+ * original [CodeBlock.Builder].
+ */
+public inline fun CodeBlock.Builder.withIndent(builderAction: CodeBlock.Builder.() -> Unit): CodeBlock.Builder {
+ return indent().also(builderAction).unindent()
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt
new file mode 100644
index 00000000..2884c65b
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt
@@ -0,0 +1,778 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import java.io.Closeable
+import kotlin.math.min
+
+/** Sentinel value that indicates that no user-provided package has been set. */
+private val NO_PACKAGE = String()
+
+internal val NULLABLE_ANY = ANY.copy(nullable = true)
+
+private fun extractMemberName(part: String): String {
+ require(Character.isJavaIdentifierStart(part[0])) { "not an identifier: $part" }
+ for (i in 1..part.length) {
+ if (!part.substring(0, i).isIdentifier) {
+ return part.substring(0, i - 1)
+ }
+ }
+ return part
+}
+
+internal inline fun buildCodeString(builderAction: CodeWriter.() -> Unit): String {
+ val stringBuilder = StringBuilder()
+ CodeWriter(stringBuilder, columnLimit = Integer.MAX_VALUE).use {
+ it.builderAction()
+ }
+ return stringBuilder.toString()
+}
+
+internal fun buildCodeString(
+ codeWriter: CodeWriter,
+ builderAction: CodeWriter.() -> Unit,
+): String {
+ val stringBuilder = StringBuilder()
+ codeWriter.emitInto(stringBuilder, builderAction)
+ return stringBuilder.toString()
+}
+
+/**
+ * Converts a [FileSpec] to a string suitable to both human- and kotlinc-consumption. This honors
+ * imports, indentation, and deferred variable names.
+ */
+internal class CodeWriter constructor(
+ out: Appendable,
+ private val indent: String = DEFAULT_INDENT,
+ imports: Map<String, Import> = emptyMap(),
+ private val importedTypes: Map<String, ClassName> = emptyMap(),
+ private val importedMembers: Map<String, MemberName> = emptyMap(),
+ columnLimit: Int = 100,
+) : Closeable {
+ private var out = LineWrapper(out, indent, columnLimit)
+ private var indentLevel = 0
+
+ private var kdoc = false
+ private var comment = false
+ private var packageName = NO_PACKAGE
+ private val typeSpecStack = mutableListOf<TypeSpec>()
+ private val memberImportNames = mutableSetOf<String>()
+ private val importableTypes = mutableMapOf<String, List<ClassName>>().withDefault { emptyList() }
+ private val importableMembers = mutableMapOf<String, List<MemberName>>().withDefault { emptyList() }
+ private val referencedNames = mutableSetOf<String>()
+ private var trailingNewline = false
+
+ val imports = imports.also {
+ for ((memberName, _) in imports) {
+ val lastDotIndex = memberName.lastIndexOf('.')
+ if (lastDotIndex >= 0) {
+ memberImportNames.add(memberName.substring(0, lastDotIndex))
+ }
+ }
+ }
+
+ /**
+ * When emitting a statement, this is the line of the statement currently being written. The first
+ * line of a statement is indented normally and subsequent wrapped lines are double-indented. This
+ * is -1 when the currently-written line isn't part of a statement.
+ */
+ var statementLine = -1
+
+ fun indent(levels: Int = 1) = apply {
+ indentLevel += levels
+ }
+
+ fun unindent(levels: Int = 1) = apply {
+ require(indentLevel - levels >= 0) { "cannot unindent $levels from $indentLevel" }
+ indentLevel -= levels
+ }
+
+ fun pushPackage(packageName: String) = apply {
+ check(this.packageName === NO_PACKAGE) { "package already set: ${this.packageName}" }
+ this.packageName = packageName
+ }
+
+ fun popPackage() = apply {
+ check(packageName !== NO_PACKAGE) { "package already set: $packageName" }
+ packageName = NO_PACKAGE
+ }
+
+ fun pushType(type: TypeSpec) = apply {
+ this.typeSpecStack.add(type)
+ }
+
+ fun popType() = apply {
+ this.typeSpecStack.removeAt(typeSpecStack.size - 1)
+ }
+
+ fun emitComment(codeBlock: CodeBlock) {
+ trailingNewline = true // Force the '//' prefix for the comment.
+ comment = true
+ try {
+ emitCode(codeBlock)
+ emit("\n")
+ } finally {
+ comment = false
+ }
+ }
+
+ fun emitKdoc(kdocCodeBlock: CodeBlock) {
+ if (kdocCodeBlock.isEmpty()) return
+
+ emit("/**\n")
+ kdoc = true
+ try {
+ emitCode(kdocCodeBlock, ensureTrailingNewline = true)
+ } finally {
+ kdoc = false
+ }
+ emit(" */\n")
+ }
+
+ fun emitAnnotations(annotations: List<AnnotationSpec>, inline: Boolean) {
+ for (annotationSpec in annotations) {
+ annotationSpec.emit(this, inline)
+ emit(if (inline) " " else "\n")
+ }
+ }
+
+ /**
+ * Emits `modifiers` in the standard order. Modifiers in `implicitModifiers` will not
+ * be emitted except for [KModifier.PUBLIC]
+ */
+ fun emitModifiers(
+ modifiers: Set<KModifier>,
+ implicitModifiers: Set<KModifier> = emptySet(),
+ ) {
+ if (shouldEmitPublicModifier(modifiers, implicitModifiers)) {
+ emit(KModifier.PUBLIC.keyword)
+ emit(" ")
+ }
+ val uniqueNonPublicExplicitOnlyModifiers =
+ modifiers
+ .filterNot { it == KModifier.PUBLIC }
+ .filterNot { implicitModifiers.contains(it) }
+ .toEnumSet()
+ for (modifier in uniqueNonPublicExplicitOnlyModifiers) {
+ emit(modifier.keyword)
+ emit(" ")
+ }
+ }
+
+ /**
+ * Emits the `context` block for [contextReceivers].
+ */
+ fun emitContextReceivers(contextReceivers: List<TypeName>, suffix: String = "") {
+ if (contextReceivers.isNotEmpty()) {
+ val receivers = contextReceivers
+ .map { CodeBlock.of("%T", it) }
+ .joinToCode(prefix = "context(", suffix = ")")
+ emitCode(receivers)
+ emit(suffix)
+ }
+ }
+
+ /**
+ * Emit type variables with their bounds. If a type variable has more than a single bound - call
+ * [emitWhereBlock] with same input to produce an additional `where` block.
+ *
+ * This should only be used when declaring type variables; everywhere else bounds are omitted.
+ */
+ fun emitTypeVariables(typeVariables: List<TypeVariableName>) {
+ if (typeVariables.isEmpty()) return
+
+ emit("<")
+ typeVariables.forEachIndexed { index, typeVariable ->
+ if (index > 0) emit(", ")
+ if (typeVariable.variance != null) {
+ emit("${typeVariable.variance.keyword} ")
+ }
+ if (typeVariable.isReified) {
+ emit("reified ")
+ }
+ emitCode("%L", typeVariable.name)
+ if (typeVariable.bounds.size == 1 && typeVariable.bounds[0] != NULLABLE_ANY) {
+ emitCode(" : %T", typeVariable.bounds[0])
+ }
+ }
+ emit(">")
+ }
+
+ /**
+ * Emit a `where` block containing type bounds for each type variable that has at least two
+ * bounds.
+ */
+ fun emitWhereBlock(typeVariables: List<TypeVariableName>) {
+ if (typeVariables.isEmpty()) return
+
+ var firstBound = true
+ for (typeVariable in typeVariables) {
+ if (typeVariable.bounds.size > 1) {
+ for (bound in typeVariable.bounds) {
+ if (!firstBound) emitCode(", ") else emitCode(" where ")
+ emitCode("%L : %T", typeVariable.name, bound)
+ firstBound = false
+ }
+ }
+ }
+ }
+
+ fun emitCode(s: String) = emitCode(CodeBlock.of(s))
+
+ fun emitCode(format: String, vararg args: Any?) = emitCode(CodeBlock.of(format, *args))
+
+ fun emitCode(
+ codeBlock: CodeBlock,
+ isConstantContext: Boolean = false,
+ ensureTrailingNewline: Boolean = false,
+ ) = apply {
+ var a = 0
+ var deferredTypeName: ClassName? = null // used by "import static" logic
+ val partIterator = codeBlock.formatParts.listIterator()
+ while (partIterator.hasNext()) {
+ when (val part = partIterator.next()) {
+ "%L" -> emitLiteral(codeBlock.args[a++], isConstantContext)
+
+ "%N" -> emit(codeBlock.args[a++] as String)
+
+ "%S" -> {
+ val string = codeBlock.args[a++] as String?
+ // Emit null as a literal null: no quotes.
+ val literal = if (string != null) {
+ stringLiteralWithQuotes(
+ string,
+ isInsideRawString = false,
+ isConstantContext = isConstantContext,
+ )
+ } else {
+ "null"
+ }
+ emit(literal, nonWrapping = true)
+ }
+
+ "%P" -> {
+ val string = codeBlock.args[a++]?.let { arg ->
+ if (arg is CodeBlock) {
+ arg.toString(this@CodeWriter)
+ } else {
+ arg as String?
+ }
+ }
+ // Emit null as a literal null: no quotes.
+ val literal = if (string != null) {
+ stringLiteralWithQuotes(
+ string,
+ isInsideRawString = true,
+ isConstantContext = isConstantContext,
+ )
+ } else {
+ "null"
+ }
+ emit(literal, nonWrapping = true)
+ }
+
+ "%T" -> {
+ var typeName = codeBlock.args[a++] as TypeName
+ if (typeName.isAnnotated) {
+ typeName.emitAnnotations(this)
+ typeName = typeName.copy(annotations = emptyList())
+ }
+ // defer "typeName.emit(this)" if next format part will be handled by the default case
+ var defer = false
+ if (typeName is ClassName && partIterator.hasNext()) {
+ if (!codeBlock.formatParts[partIterator.nextIndex()].startsWith("%")) {
+ val candidate = typeName
+ if (candidate.canonicalName in memberImportNames) {
+ check(deferredTypeName == null) { "pending type for static import?!" }
+ deferredTypeName = candidate
+ defer = true
+ }
+ }
+ }
+ if (!defer) typeName.emit(this)
+ typeName.emitNullable(this)
+ }
+
+ "%M" -> {
+ val memberName = codeBlock.args[a++] as MemberName
+ memberName.emit(this)
+ }
+
+ "%%" -> emit("%")
+
+ "⇥" -> indent()
+
+ "⇤" -> unindent()
+
+ "«" -> {
+ check(statementLine == -1) {
+ """
+ |Can't open a new statement until the current statement is closed (opening « followed
+ |by another « without a closing »).
+ |Current code block:
+ |- Format parts: ${codeBlock.formatParts.map(::escapeCharacterLiterals)}
+ |- Arguments: ${codeBlock.args}
+ |
+ """.trimMargin()
+ }
+ statementLine = 0
+ }
+
+ "»" -> {
+ check(statementLine != -1) {
+ """
+ |Can't close a statement that hasn't been opened (closing » is not preceded by an
+ |opening «).
+ |Current code block:
+ |- Format parts: ${codeBlock.formatParts.map(::escapeCharacterLiterals)}
+ |- Arguments: ${codeBlock.args}
+ |
+ """.trimMargin()
+ }
+ if (statementLine > 0) {
+ unindent(2) // End a multi-line statement. Decrease the indentation level.
+ }
+ statementLine = -1
+ }
+
+ else -> {
+ // Handle deferred type.
+ var doBreak = false
+ if (deferredTypeName != null) {
+ if (part.startsWith(".")) {
+ if (emitStaticImportMember(deferredTypeName.canonicalName, part)) {
+ // Okay, static import hit and all was emitted, so clean-up and jump to next part.
+ deferredTypeName = null
+ doBreak = true
+ }
+ }
+ if (!doBreak) {
+ deferredTypeName!!.emit(this)
+ deferredTypeName = null
+ }
+ }
+ if (!doBreak) {
+ emit(part)
+ }
+ }
+ }
+ }
+ if (ensureTrailingNewline && out.hasPendingSegments) {
+ emit("\n")
+ }
+ }
+
+ private fun emitStaticImportMember(canonical: String, part: String): Boolean {
+ val partWithoutLeadingDot = part.substring(1)
+ if (partWithoutLeadingDot.isEmpty()) return false
+ val first = partWithoutLeadingDot[0]
+ if (!Character.isJavaIdentifierStart(first)) return false
+ val explicit = imports[canonical + "." + extractMemberName(partWithoutLeadingDot)]
+ if (explicit != null) {
+ if (explicit.alias != null) {
+ val memberName = extractMemberName(partWithoutLeadingDot)
+ emit(partWithoutLeadingDot.replaceFirst(memberName, explicit.alias))
+ } else {
+ emit(partWithoutLeadingDot)
+ }
+ return true
+ }
+ return false
+ }
+
+ private fun emitLiteral(o: Any?, isConstantContext: Boolean) {
+ when (o) {
+ is TypeSpec -> o.emit(this, null)
+ is AnnotationSpec -> o.emit(this, inline = true, asParameter = isConstantContext)
+ is PropertySpec -> o.emit(this, emptySet())
+ is FunSpec -> o.emit(
+ codeWriter = this,
+ enclosingName = null,
+ implicitModifiers = setOf(KModifier.PUBLIC),
+ includeKdocTags = true,
+ )
+ is TypeAliasSpec -> o.emit(this)
+ is CodeBlock -> emitCode(o, isConstantContext = isConstantContext)
+ else -> emit(o.toString())
+ }
+ }
+
+ /**
+ * Returns the best name to identify `className` with in the current context. This uses the
+ * available imports and the current scope to find the shortest name available. It does not honor
+ * names visible due to inheritance.
+ */
+ fun lookupName(className: ClassName): String {
+ // Find the shortest suffix of className that resolves to className. This uses both local type
+ // names (so `Entry` in `Map` refers to `Map.Entry`). Also uses imports.
+ var nameResolved = false
+ var c: ClassName? = className
+ while (c != null) {
+ val alias = imports[c.canonicalName]?.alias
+ val simpleName = alias ?: c.simpleName
+ val resolved = resolve(simpleName)
+ nameResolved = resolved != null
+
+ // We don't care about nullability and type annotations here, as it's irrelevant for imports.
+ if (resolved == c.copy(nullable = false, annotations = emptyList())) {
+ if (alias != null) return alias
+ val suffixOffset = c.simpleNames.size - 1
+ referencedNames.add(className.topLevelClassName().simpleName)
+ return className.simpleNames.subList(
+ suffixOffset,
+ className.simpleNames.size,
+ ).joinToString(".")
+ }
+ c = c.enclosingClassName()
+ }
+
+ // If the name resolved but wasn't a match, we're stuck with the fully qualified name.
+ if (nameResolved) {
+ return className.canonicalName
+ }
+
+ // If the class is in the same package, we're done.
+ if (packageName == className.packageName) {
+ referencedNames.add(className.topLevelClassName().simpleName)
+ return className.simpleNames.joinToString(".")
+ }
+
+ // We'll have to use the fully-qualified name. Mark the type as importable for a future pass.
+ if (!kdoc) {
+ importableType(className)
+ }
+
+ return className.canonicalName
+ }
+
+ fun lookupName(memberName: MemberName): String {
+ val simpleName = imports[memberName.canonicalName]?.alias ?: memberName.simpleName
+ // Match an imported member.
+ val importedMember = importedMembers[simpleName]
+ if (importedMember == memberName) {
+ return simpleName
+ } else if (importedMember != null && memberName.enclosingClassName != null) {
+ val enclosingClassName = lookupName(memberName.enclosingClassName)
+ return "$enclosingClassName.$simpleName"
+ }
+
+ // If the member is in the same package, we're done.
+ if (packageName == memberName.packageName && memberName.enclosingClassName == null) {
+ referencedNames.add(memberName.simpleName)
+ return memberName.simpleName
+ }
+
+ // We'll have to use the fully-qualified name.
+ // Mark the member as importable for a future pass unless the name clashes with
+ // a method in the current context
+ if (!kdoc && (
+ memberName.isExtension ||
+ !isMethodNameUsedInCurrentContext(memberName.simpleName)
+ )
+ ) {
+ importableMember(memberName)
+ }
+
+ return memberName.canonicalName
+ }
+
+ // TODO(luqasn): also honor superclass members when resolving names.
+ private fun isMethodNameUsedInCurrentContext(simpleName: String): Boolean {
+ for (it in typeSpecStack.reversed()) {
+ if (it.funSpecs.any { it.name == simpleName }) {
+ return true
+ }
+ if (!it.modifiers.contains(KModifier.INNER)) {
+ break
+ }
+ }
+ return false
+ }
+
+ private fun importableType(className: ClassName) {
+ val topLevelClassName = className.topLevelClassName()
+ val simpleName = imports[className.canonicalName]?.alias ?: topLevelClassName.simpleName
+ // Check for name clashes with members.
+ if (simpleName !in importableMembers) {
+ importableTypes[simpleName] = importableTypes.getValue(simpleName) + topLevelClassName
+ }
+ }
+
+ private fun importableMember(memberName: MemberName) {
+ if (memberName.packageName.isNotEmpty()) {
+ val simpleName = imports[memberName.canonicalName]?.alias ?: memberName.simpleName
+ // Check for name clashes with types.
+ if (simpleName !in importableTypes) {
+ importableMembers[simpleName] = importableMembers.getValue(simpleName) + memberName
+ }
+ }
+ }
+
+ /**
+ * Returns the class or enum value referenced by `simpleName`, using the current nesting context and
+ * imports.
+ */
+ // TODO(jwilson): also honor superclass members when resolving names.
+ private fun resolve(simpleName: String): ClassName? {
+ // Match a child of the current (potentially nested) class.
+ for (i in typeSpecStack.indices.reversed()) {
+ val typeSpec = typeSpecStack[i]
+ if (simpleName in typeSpec.nestedTypesSimpleNames) {
+ return stackClassName(i, simpleName)
+ }
+ }
+
+ if (typeSpecStack.size > 0) {
+ val typeSpec = typeSpecStack[0]
+ if (typeSpec.name == simpleName) {
+ // Match the top-level class.
+ return ClassName(packageName, simpleName)
+ }
+ if (typeSpec.isEnum && typeSpec.enumConstants.keys.contains(simpleName)) {
+ // Match a top level enum value.
+ // Enum values are not proper classes but can still be modeled using ClassName.
+ return ClassName(packageName, typeSpec.name!!).nestedClass(simpleName)
+ }
+ }
+
+ // Match an imported type.
+ val importedType = importedTypes[simpleName]
+ if (importedType != null) return importedType
+
+ // No match.
+ return null
+ }
+
+ /** Returns the class named `simpleName` when nested in the class at `stackDepth`. */
+ private fun stackClassName(stackDepth: Int, simpleName: String): ClassName {
+ var className = ClassName(packageName, typeSpecStack[0].name!!)
+ for (i in 1..stackDepth) {
+ className = className.nestedClass(typeSpecStack[i].name!!)
+ }
+ return className.nestedClass(simpleName)
+ }
+
+ /**
+ * Emits `s` with indentation as required. It's important that all code that writes to
+ * [CodeWriter.out] does it through here, since we emit indentation lazily in order to avoid
+ * unnecessary trailing whitespace.
+ */
+ fun emit(s: String, nonWrapping: Boolean = false) = apply {
+ var first = true
+ for (line in s.split('\n')) {
+ // Emit a newline character. Make sure blank lines in KDoc & comments look good.
+ if (!first) {
+ if ((kdoc || comment) && trailingNewline) {
+ emitIndentation()
+ out.appendNonWrapping(if (kdoc) " *" else "//")
+ }
+ out.newline()
+ trailingNewline = true
+ if (statementLine != -1) {
+ if (statementLine == 0) {
+ indent(2) // Begin multiple-line statement. Increase the indentation level.
+ }
+ statementLine++
+ }
+ }
+
+ first = false
+ if (line.isEmpty()) continue // Don't indent empty lines.
+
+ // Emit indentation and comment prefix if necessary.
+ if (trailingNewline) {
+ emitIndentation()
+ if (kdoc) {
+ out.appendNonWrapping(" * ")
+ } else if (comment) {
+ out.appendNonWrapping("// ")
+ }
+ }
+
+ if (nonWrapping) {
+ out.appendNonWrapping(line)
+ } else {
+ out.append(
+ line,
+ indentLevel = if (kdoc) indentLevel else indentLevel + 2,
+ linePrefix = if (kdoc) " * " else "",
+ )
+ }
+ trailingNewline = false
+ }
+ }
+
+ private fun emitIndentation() {
+ for (j in 0 until indentLevel) {
+ out.appendNonWrapping(indent)
+ }
+ }
+
+ /**
+ * Returns whether a [KModifier.PUBLIC] should be emitted.
+ *
+ * If [modifiers] contains [KModifier.PUBLIC], this method always returns `true`.
+ *
+ * Otherwise, this will return `true` when [KModifier.PUBLIC] is one of the [implicitModifiers]
+ * and there are no other opposing modifiers (like [KModifier.PROTECTED] etc.) supplied by the
+ * consumer in [modifiers].
+ */
+ private fun shouldEmitPublicModifier(
+ modifiers: Set<KModifier>,
+ implicitModifiers: Set<KModifier>,
+ ): Boolean {
+ if (modifiers.contains(KModifier.PUBLIC)) {
+ return true
+ }
+
+ if (!implicitModifiers.contains(KModifier.PUBLIC)) {
+ return false
+ }
+
+ val hasOtherConsumerSpecifiedVisibility =
+ modifiers.containsAnyOf(KModifier.PRIVATE, KModifier.INTERNAL, KModifier.PROTECTED)
+
+ return !hasOtherConsumerSpecifiedVisibility
+ }
+
+ /**
+ * Returns the types that should have been imported for this code. If there were any simple name
+ * collisions, import aliases will be generated.
+ */
+ private fun suggestedTypeImports(): Map<String, Set<ClassName>> {
+ return importableTypes.filterKeys { it !in referencedNames }.mapValues { it.value.toSet() }
+ }
+
+ /**
+ * Returns the members that should have been imported for this code. If there were any simple name
+ * collisions, import aliases will be generated.
+ */
+ private fun suggestedMemberImports(): Map<String, Set<MemberName>> {
+ return importableMembers.filterKeys { it !in referencedNames }.mapValues { it.value.toSet() }
+ }
+
+ /**
+ * Perform emitting actions on the current [CodeWriter] using a custom [Appendable]. The
+ * [CodeWriter] will continue using the old [Appendable] after this method returns.
+ */
+ inline fun emitInto(out: Appendable, action: CodeWriter.() -> Unit) {
+ val codeWrapper = this
+ LineWrapper(out, indent = DEFAULT_INDENT, columnLimit = Int.MAX_VALUE).use { newOut ->
+ val oldOut = codeWrapper.out
+ codeWrapper.out = newOut
+ action()
+ codeWrapper.out = oldOut
+ }
+ }
+
+ override fun close() {
+ out.close()
+ }
+
+ companion object {
+ /**
+ * Makes a pass to collect imports by executing [emitStep], and returns an instance of
+ * [CodeWriter] pre-initialized with collected imports.
+ */
+ fun withCollectedImports(
+ out: Appendable,
+ indent: String,
+ memberImports: Map<String, Import>,
+ emitStep: (importsCollector: CodeWriter) -> Unit,
+ ): CodeWriter {
+ // First pass: emit the entire class, just to collect the types we'll need to import.
+ val importsCollector = CodeWriter(
+ NullAppendable,
+ indent,
+ memberImports,
+ columnLimit = Integer.MAX_VALUE,
+ )
+ emitStep(importsCollector)
+ val generatedImports = mutableMapOf<String, Import>()
+ val suggestedTypeImports = importsCollector.suggestedTypeImports()
+ .generateImports(
+ generatedImports,
+ canonicalName = ClassName::canonicalName,
+ packageName = ClassName::packageName,
+ capitalizeAliases = true,
+ )
+ val suggestedMemberImports = importsCollector.suggestedMemberImports()
+ .generateImports(
+ generatedImports,
+ canonicalName = MemberName::canonicalName,
+ packageName = MemberName::packageName,
+ capitalizeAliases = false,
+ )
+ importsCollector.close()
+
+ return CodeWriter(
+ out,
+ indent,
+ memberImports + generatedImports.filterKeys { it !in memberImports },
+ suggestedTypeImports,
+ suggestedMemberImports,
+ )
+ }
+
+ private fun <T> Map<String, Set<T>>.generateImports(
+ generatedImports: MutableMap<String, Import>,
+ canonicalName: T.() -> String,
+ packageName: T.() -> String,
+ capitalizeAliases: Boolean,
+ ): Map<String, T> {
+ return flatMap { (simpleName, qualifiedNames) ->
+ if (qualifiedNames.size == 1) {
+ listOf(simpleName to qualifiedNames.first()).also {
+ val canonicalName = qualifiedNames.first().canonicalName()
+ generatedImports[canonicalName] = Import(canonicalName)
+ }
+ } else {
+ generateImportAliases(simpleName, qualifiedNames, packageName, capitalizeAliases)
+ .onEach { (alias, qualifiedName) ->
+ val canonicalName = qualifiedName.canonicalName()
+ generatedImports[canonicalName] = Import(canonicalName, alias)
+ }
+ }
+ }.toMap()
+ }
+
+ private fun <T> generateImportAliases(
+ simpleName: String,
+ qualifiedNames: Set<T>,
+ packageName: T.() -> String,
+ capitalizeAliases: Boolean,
+ ): List<Pair<String, T>> {
+ val packageNameSegments = qualifiedNames.associateWith { qualifiedName ->
+ qualifiedName.packageName().split('.').map { it.replaceFirstChar(Char::uppercaseChar) }
+ }
+ val aliasNames = mutableMapOf<String, T>()
+ var segmentsToUse = 0
+ // Iterate until we have unique aliases for all names.
+ while (aliasNames.size != qualifiedNames.size) {
+ segmentsToUse += 1
+ aliasNames.clear()
+ for ((qualifiedName, segments) in packageNameSegments) {
+ val aliasPrefix = segments.takeLast(min(segmentsToUse, segments.size))
+ .joinToString(separator = "")
+ .replaceFirstChar { if (!capitalizeAliases) it.lowercaseChar() else it }
+ val aliasName = aliasPrefix + simpleName.replaceFirstChar(Char::uppercaseChar)
+ aliasNames[aliasName] = qualifiedName
+ }
+ }
+ return aliasNames.toList()
+ }
+ }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ContextReceivable.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ContextReceivable.kt
new file mode 100644
index 00000000..929096bc
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ContextReceivable.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+/** A KotlinPoet spec type that can have a context receiver. */
+public interface ContextReceivable {
+
+ /** The originating elements of this type. */
+ @ExperimentalKotlinPoetApi
+ public val contextReceiverTypes: List<TypeName>
+
+ /** The builder analogue to [ContextReceivable] types. */
+ public interface Builder<out T : Builder<T>> {
+
+ /** Mutable map of the current originating elements this builder contains. */
+ @ExperimentalKotlinPoetApi
+ public val contextReceiverTypes: MutableList<TypeName>
+
+ /** Adds the given [receiverTypes] to this type's list of originating elements. */
+ @Suppress("UNCHECKED_CAST")
+ @ExperimentalKotlinPoetApi
+ public fun contextReceivers(receiverTypes: Iterable<TypeName>): T = apply {
+ contextReceiverTypes += receiverTypes
+ } as T
+
+ /** Adds the given [receiverTypes] to this type's list of originating elements. */
+ @ExperimentalKotlinPoetApi
+ public fun contextReceivers(vararg receiverTypes: TypeName): T =
+ contextReceivers(receiverTypes.toList())
+ }
+}
+
+@ExperimentalKotlinPoetApi
+internal fun ContextReceivable.Builder<*>.buildContextReceivers() =
+ ContextReceivers(contextReceiverTypes.toImmutableList())
+
+@JvmInline
+@ExperimentalKotlinPoetApi
+internal value class ContextReceivers(
+ override val contextReceiverTypes: List<TypeName>,
+) : ContextReceivable
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/DelicateKotlinPoetApi.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/DelicateKotlinPoetApi.kt
new file mode 100644
index 00000000..11369334
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/DelicateKotlinPoetApi.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+/**
+ * Marks declarations in the KotlinPoet API that are **delicate** &mdash;
+ * they have limited use-case and shall be used with care in general code.
+ * Any use of a delicate declaration has to be carefully reviewed to make sure it is
+ * properly used and does not create problems like lossy Java -> Kotlin type parsing.
+ * Carefully read documentation and [message] of any declaration marked as `DelicateKotlinPoetApi`.
+ */
+@MustBeDocumented
+@Retention(value = AnnotationRetention.BINARY)
+@RequiresOptIn(
+ level = RequiresOptIn.Level.WARNING,
+ message = "This is a delicate API and its use requires care." +
+ " Make sure you fully read and understand documentation of the declaration that is marked as a delicate API.",
+)
+public annotation class DelicateKotlinPoetApi(val message: String)
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Dynamic.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Dynamic.kt
new file mode 100644
index 00000000..413b7b7a
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Dynamic.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import kotlin.reflect.KClass
+
+public object Dynamic : TypeName(false, emptyList(), TagMap(emptyMap())) {
+
+ override fun copy(
+ nullable: Boolean,
+ annotations: List<AnnotationSpec>,
+ tags: Map<KClass<*>, Any>,
+ ): Nothing = throw UnsupportedOperationException("dynamic doesn't support copying")
+
+ override fun emit(out: CodeWriter) = out.apply {
+ emit("dynamic")
+ }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt
new file mode 100644
index 00000000..2e58556d
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ExperimentalKotlinPoetApi.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import kotlin.annotation.AnnotationTarget.CLASS
+import kotlin.annotation.AnnotationTarget.FUNCTION
+import kotlin.annotation.AnnotationTarget.PROPERTY
+import kotlin.annotation.AnnotationTarget.TYPEALIAS
+
+/**
+ * Indicates that a given API is experimental and subject to change.
+ */
+@RequiresOptIn
+@Retention(AnnotationRetention.BINARY)
+@Target(CLASS, FUNCTION, PROPERTY, TYPEALIAS)
+public annotation class ExperimentalKotlinPoetApi
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FileSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FileSpec.kt
new file mode 100644
index 00000000..b2f2549d
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FileSpec.kt
@@ -0,0 +1,551 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
+import java.io.ByteArrayInputStream
+import java.io.File
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStreamWriter
+import java.net.URI
+import java.nio.charset.StandardCharsets.UTF_8
+import java.nio.file.Files
+import java.nio.file.Path
+import javax.annotation.processing.Filer
+import javax.tools.JavaFileObject
+import javax.tools.JavaFileObject.Kind
+import javax.tools.SimpleJavaFileObject
+import javax.tools.StandardLocation
+import kotlin.reflect.KClass
+
+/**
+ * A Kotlin file containing top level objects like classes, objects, functions, properties, and type
+ * aliases.
+ *
+ * Items are output in the following order:
+ * - Comment
+ * - Annotations
+ * - Package
+ * - Imports
+ * - Members
+ */
+public class FileSpec private constructor(
+ builder: Builder,
+ private val tagMap: TagMap = builder.buildTagMap(),
+) : Taggable by tagMap {
+ public val annotations: List<AnnotationSpec> = builder.annotations.toImmutableList()
+ public val comment: CodeBlock = builder.comment.build()
+ public val packageName: String = builder.packageName
+ public val name: String = builder.name
+ public val members: List<Any> = builder.members.toList()
+ public val defaultImports: Set<String> = builder.defaultImports.toSet()
+ public val body: CodeBlock = builder.body.build()
+ public val isScript: Boolean = builder.isScript
+ private val memberImports = builder.memberImports.associateBy(Import::qualifiedName)
+ private val indent = builder.indent
+ private val extension = if (isScript) "kts" else "kt"
+
+ @Throws(IOException::class)
+ public fun writeTo(out: Appendable) {
+ val codeWriter = CodeWriter.withCollectedImports(
+ out = out,
+ indent = indent,
+ memberImports = memberImports,
+ emitStep = { importsCollector -> emit(importsCollector, collectingImports = true) },
+ )
+ emit(codeWriter, collectingImports = false)
+ codeWriter.close()
+ }
+
+ /** Writes this to `directory` as UTF-8 using the standard directory structure. */
+ @Throws(IOException::class)
+ public fun writeTo(directory: Path) {
+ require(Files.notExists(directory) || Files.isDirectory(directory)) {
+ "path $directory exists but is not a directory."
+ }
+ var outputDirectory = directory
+ if (packageName.isNotEmpty()) {
+ for (packageComponent in packageName.split('.').dropLastWhile { it.isEmpty() }) {
+ outputDirectory = outputDirectory.resolve(packageComponent)
+ }
+ }
+
+ Files.createDirectories(outputDirectory)
+
+ val outputPath = outputDirectory.resolve("$name.$extension")
+ OutputStreamWriter(Files.newOutputStream(outputPath), UTF_8).use { writer -> writeTo(writer) }
+ }
+
+ /** Writes this to `directory` as UTF-8 using the standard directory structure. */
+ @Throws(IOException::class)
+ public fun writeTo(directory: File): Unit = writeTo(directory.toPath())
+
+ /** Writes this to `filer`. */
+ @Throws(IOException::class)
+ public fun writeTo(filer: Filer) {
+ val originatingElements = members.asSequence()
+ .filterIsInstance<OriginatingElementsHolder>()
+ .flatMap { it.originatingElements.asSequence() }
+ .toSet()
+ val filerSourceFile = filer.createResource(
+ StandardLocation.SOURCE_OUTPUT,
+ packageName,
+ "$name.$extension",
+ *originatingElements.toTypedArray(),
+ )
+ try {
+ filerSourceFile.openWriter().use { writer -> writeTo(writer) }
+ } catch (e: Exception) {
+ try {
+ filerSourceFile.delete()
+ } catch (ignored: Exception) {
+ }
+ throw e
+ }
+ }
+
+ private fun emit(codeWriter: CodeWriter, collectingImports: Boolean) {
+ if (comment.isNotEmpty()) {
+ codeWriter.emitComment(comment)
+ }
+
+ if (annotations.isNotEmpty()) {
+ codeWriter.emitAnnotations(annotations, inline = false)
+ codeWriter.emit("\n")
+ }
+
+ codeWriter.pushPackage(packageName)
+
+ val escapedPackageName = packageName.escapeSegmentsIfNecessary()
+
+ if (escapedPackageName.isNotEmpty()) {
+ codeWriter.emitCode("package·%L\n", escapedPackageName)
+ codeWriter.emit("\n")
+ }
+
+ // If we don't have default imports or are collecting them, we don't need to filter
+ var isDefaultImport: (String) -> Boolean = { false }
+ if (!collectingImports && defaultImports.isNotEmpty()) {
+ val defaultImports = defaultImports.map(String::escapeSegmentsIfNecessary)
+ isDefaultImport = { importName ->
+ importName.substringBeforeLast(".") in defaultImports
+ }
+ }
+ // Aliased imports should always appear at the bottom of the imports list.
+ val (aliasedImports, nonAliasedImports) = codeWriter.imports.values
+ .partition { it.alias != null }
+ val imports = nonAliasedImports.asSequence().map { it.toString() }
+ .filterNot(isDefaultImport)
+ .toSortedSet()
+ .plus(aliasedImports.map { it.toString() }.toSortedSet())
+
+ if (imports.isNotEmpty()) {
+ for (import in imports) {
+ codeWriter.emitCode("import·%L", import)
+ codeWriter.emit("\n")
+ }
+ codeWriter.emit("\n")
+ }
+
+ if (isScript) {
+ codeWriter.emitCode(body)
+ } else {
+ members.forEachIndexed { index, member ->
+ if (index > 0) codeWriter.emit("\n")
+ when (member) {
+ is TypeSpec -> member.emit(codeWriter, null)
+ is FunSpec -> member.emit(codeWriter, null, setOf(KModifier.PUBLIC), true)
+ is PropertySpec -> member.emit(codeWriter, setOf(KModifier.PUBLIC))
+ is TypeAliasSpec -> member.emit(codeWriter)
+ else -> throw AssertionError()
+ }
+ }
+ }
+
+ codeWriter.popPackage()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null) return false
+ if (javaClass != other.javaClass) return false
+ return toString() == other.toString()
+ }
+
+ override fun hashCode(): Int = toString().hashCode()
+
+ override fun toString(): String = buildString { writeTo(this) }
+
+ public fun toJavaFileObject(): JavaFileObject {
+ val uri = URI.create(
+ if (packageName.isEmpty()) {
+ name
+ } else {
+ packageName.replace('.', '/') + '/' + name
+ } + ".$extension",
+ )
+ return object : SimpleJavaFileObject(uri, Kind.SOURCE) {
+ private val lastModified = System.currentTimeMillis()
+ override fun getCharContent(ignoreEncodingErrors: Boolean): String {
+ return this@FileSpec.toString()
+ }
+
+ override fun openInputStream(): InputStream {
+ return ByteArrayInputStream(getCharContent(true).toByteArray(UTF_8))
+ }
+
+ override fun getLastModified() = lastModified
+ }
+ }
+
+ @JvmOverloads
+ public fun toBuilder(packageName: String = this.packageName, name: String = this.name): Builder {
+ val builder = Builder(packageName, name, isScript)
+ builder.annotations.addAll(annotations)
+ builder.comment.add(comment)
+ builder.members.addAll(this.members)
+ builder.indent = indent
+ builder.memberImports.addAll(memberImports.values)
+ builder.defaultImports.addAll(defaultImports)
+ builder.tags += tagMap.tags
+ builder.body.add(body)
+ return builder
+ }
+
+ public class Builder internal constructor(
+ public val packageName: String,
+ public val name: String,
+ public val isScript: Boolean,
+ ) : Taggable.Builder<Builder> {
+ internal val comment = CodeBlock.builder()
+ internal val memberImports = sortedSetOf<Import>()
+ internal var indent = DEFAULT_INDENT
+ override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
+
+ public val defaultImports: MutableSet<String> = mutableSetOf()
+ public val imports: List<Import> get() = memberImports.toList()
+ public val members: MutableList<Any> = mutableListOf()
+ public val annotations: MutableList<AnnotationSpec> = mutableListOf()
+ internal val body = CodeBlock.builder()
+
+ /**
+ * Add an annotation to the file.
+ *
+ * The annotation must either have a [`file` use-site target][AnnotationSpec.UseSiteTarget.FILE]
+ * or not have a use-site target specified (in which case it will be changed to `file`).
+ */
+ public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
+ val spec = when (annotationSpec.useSiteTarget) {
+ FILE -> annotationSpec
+ null -> annotationSpec.toBuilder().useSiteTarget(FILE).build()
+ else -> error(
+ "Use-site target ${annotationSpec.useSiteTarget} not supported for file annotations.",
+ )
+ }
+ annotations += spec
+ }
+
+ public fun addAnnotation(annotation: ClassName): Builder =
+ addAnnotation(AnnotationSpec.builder(annotation).build())
+
+ public fun addAnnotation(annotation: Class<*>): Builder =
+ addAnnotation(annotation.asClassName())
+
+ public fun addAnnotation(annotation: KClass<*>): Builder =
+ addAnnotation(annotation.asClassName())
+
+ /** Adds a file-site comment. This is prefixed to the start of the file and different from [addBodyComment]. */
+ public fun addFileComment(format: String, vararg args: Any): Builder = apply {
+ comment.add(format.replace(' ', '·'), *args)
+ }
+
+ @Deprecated(
+ "Use addFileComment() instead.",
+ ReplaceWith("addFileComment(format, args)"),
+ DeprecationLevel.ERROR,
+ )
+ public fun addComment(format: String, vararg args: Any): Builder = addFileComment(format, *args)
+
+ public fun clearComment(): Builder = apply {
+ comment.clear()
+ }
+
+ public fun addType(typeSpec: TypeSpec): Builder = apply {
+ if (isScript) {
+ body.add("%L", typeSpec)
+ } else {
+ members += typeSpec
+ }
+ }
+
+ public fun addFunction(funSpec: FunSpec): Builder = apply {
+ require(!funSpec.isConstructor && !funSpec.isAccessor) {
+ "cannot add ${funSpec.name} to file $name"
+ }
+ if (isScript) {
+ body.add("%L", funSpec)
+ } else {
+ members += funSpec
+ }
+ }
+
+ public fun addProperty(propertySpec: PropertySpec): Builder = apply {
+ if (isScript) {
+ body.add("%L", propertySpec)
+ } else {
+ members += propertySpec
+ }
+ }
+
+ public fun addTypeAlias(typeAliasSpec: TypeAliasSpec): Builder = apply {
+ if (isScript) {
+ body.add("%L", typeAliasSpec)
+ } else {
+ members += typeAliasSpec
+ }
+ }
+
+ public fun addImport(constant: Enum<*>): Builder = addImport(
+ (constant as java.lang.Enum<*>).declaringClass.asClassName(),
+ constant.name,
+ )
+
+ public fun addImport(`class`: Class<*>, vararg names: String): Builder = apply {
+ require(names.isNotEmpty()) { "names array is empty" }
+ addImport(`class`.asClassName(), names.toList())
+ }
+
+ public fun addImport(`class`: KClass<*>, vararg names: String): Builder = apply {
+ require(names.isNotEmpty()) { "names array is empty" }
+ addImport(`class`.asClassName(), names.toList())
+ }
+
+ public fun addImport(className: ClassName, vararg names: String): Builder = apply {
+ require(names.isNotEmpty()) { "names array is empty" }
+ addImport(className, names.toList())
+ }
+
+ public fun addImport(`class`: Class<*>, names: Iterable<String>): Builder =
+ addImport(`class`.asClassName(), names)
+
+ public fun addImport(`class`: KClass<*>, names: Iterable<String>): Builder =
+ addImport(`class`.asClassName(), names)
+
+ public fun addImport(className: ClassName, names: Iterable<String>): Builder = apply {
+ require("*" !in names) { "Wildcard imports are not allowed" }
+ for (name in names) {
+ memberImports += Import(className.canonicalName + "." + name)
+ }
+ }
+
+ public fun addImport(packageName: String, vararg names: String): Builder = apply {
+ require(names.isNotEmpty()) { "names array is empty" }
+ addImport(packageName, names.toList())
+ }
+
+ public fun addImport(packageName: String, names: Iterable<String>): Builder = apply {
+ require("*" !in names) { "Wildcard imports are not allowed" }
+ for (name in names) {
+ memberImports += if (packageName.isNotEmpty()) {
+ Import("$packageName.$name")
+ } else {
+ Import(name)
+ }
+ }
+ }
+
+ public fun addImport(import: Import): Builder = apply {
+ memberImports += import
+ }
+
+ public fun clearImports(): Builder = apply {
+ memberImports.clear()
+ }
+
+ public fun addAliasedImport(`class`: Class<*>, `as`: String): Builder =
+ addAliasedImport(`class`.asClassName(), `as`)
+
+ public fun addAliasedImport(`class`: KClass<*>, `as`: String): Builder =
+ addAliasedImport(`class`.asClassName(), `as`)
+
+ public fun addAliasedImport(className: ClassName, `as`: String): Builder = apply {
+ memberImports += Import(className.canonicalName, `as`)
+ }
+
+ public fun addAliasedImport(
+ className: ClassName,
+ memberName: String,
+ `as`: String,
+ ): Builder = apply {
+ memberImports += Import("${className.canonicalName}.$memberName", `as`)
+ }
+
+ public fun addAliasedImport(memberName: MemberName, `as`: String): Builder = apply {
+ memberImports += Import(memberName.canonicalName, `as`)
+ }
+
+ /**
+ * Adds a default import for the given [packageName].
+ *
+ * The format of this should be the qualified name of the package, e.g. `kotlin`, `java.lang`,
+ * `org.gradle.api`, etc.
+ */
+ public fun addDefaultPackageImport(packageName: String): Builder = apply {
+ defaultImports += packageName
+ }
+
+ /**
+ * Adds Kotlin's standard default package imports as described
+ * [here](https://kotlinlang.org/docs/packages.html#default-imports).
+ */
+ public fun addKotlinDefaultImports(
+ includeJvm: Boolean = true,
+ includeJs: Boolean = true,
+ ): Builder = apply {
+ defaultImports += KOTLIN_DEFAULT_IMPORTS
+ if (includeJvm) {
+ defaultImports += KOTLIN_DEFAULT_JVM_IMPORTS
+ }
+ if (includeJs) {
+ defaultImports += KOTLIN_DEFAULT_JS_IMPORTS
+ }
+ }
+
+ public fun indent(indent: String): Builder = apply {
+ this.indent = indent
+ }
+
+ public fun addCode(format: String, vararg args: Any?): Builder = apply {
+ check(isScript) {
+ "addCode() is only allowed in script files"
+ }
+ body.add(format, *args)
+ }
+
+ public fun addNamedCode(format: String, args: Map<String, *>): Builder = apply {
+ check(isScript) {
+ "addNamedCode() is only allowed in script files"
+ }
+ body.addNamed(format, args)
+ }
+
+ public fun addCode(codeBlock: CodeBlock): Builder = apply {
+ check(isScript) {
+ "addCode() is only allowed in script files"
+ }
+ body.add(codeBlock)
+ }
+
+ /** Adds a comment to the body of this script file in the order that it was added. */
+ public fun addBodyComment(format: String, vararg args: Any): Builder = apply {
+ check(isScript) {
+ "addBodyComment() is only allowed in script files"
+ }
+ body.add("//·${format.replace(' ', '·')}\n", *args)
+ }
+
+ /**
+ * @param controlFlow the control flow construct and its code, such as "if (foo == 5)".
+ * Shouldn't contain braces or newline characters.
+ */
+ public fun beginControlFlow(controlFlow: String, vararg args: Any): Builder = apply {
+ check(isScript) {
+ "beginControlFlow() is only allowed in script files"
+ }
+ body.beginControlFlow(controlFlow, *args)
+ }
+
+ /**
+ * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
+ * Shouldn't contain braces or newline characters.
+ */
+ public fun nextControlFlow(controlFlow: String, vararg args: Any): Builder = apply {
+ check(isScript) {
+ "nextControlFlow() is only allowed in script files"
+ }
+ body.nextControlFlow(controlFlow, *args)
+ }
+
+ public fun endControlFlow(): Builder = apply {
+ check(isScript) {
+ "endControlFlow() is only allowed in script files"
+ }
+ body.endControlFlow()
+ }
+
+ public fun addStatement(format: String, vararg args: Any): Builder = apply {
+ check(isScript) {
+ "addStatement() is only allowed in script files"
+ }
+ body.addStatement(format, *args)
+ }
+
+ public fun clearBody(): Builder = apply {
+ check(isScript) {
+ "clearBody() is only allowed in script files"
+ }
+ body.clear()
+ }
+
+ public fun build(): FileSpec {
+ for (annotationSpec in annotations) {
+ if (annotationSpec.useSiteTarget != FILE) {
+ error(
+ "Use-site target ${annotationSpec.useSiteTarget} not supported for file annotations.",
+ )
+ }
+ }
+ return FileSpec(this)
+ }
+ }
+
+ public companion object {
+ @JvmStatic public fun get(packageName: String, typeSpec: TypeSpec): FileSpec {
+ val fileName = typeSpec.name
+ ?: throw IllegalArgumentException("file name required but type has no name")
+ return builder(packageName, fileName).addType(typeSpec).build()
+ }
+
+ @JvmStatic public fun builder(className: ClassName): Builder {
+ require(className.simpleNames.size == 1) {
+ "nested types can't be used to name a file: ${className.simpleNames.joinToString(".")}"
+ }
+ return builder(className.packageName, className.simpleName)
+ }
+
+ @JvmStatic public fun builder(packageName: String, fileName: String): Builder =
+ Builder(packageName, fileName, isScript = false)
+
+ @JvmStatic public fun scriptBuilder(fileName: String, packageName: String = ""): Builder =
+ Builder(packageName, fileName, isScript = true)
+ }
+}
+
+internal const val DEFAULT_INDENT = " "
+
+private val KOTLIN_DEFAULT_IMPORTS = setOf(
+ "kotlin",
+ "kotlin.annotation",
+ "kotlin.collections",
+ "kotlin.comparisons",
+ "kotlin.io",
+ "kotlin.ranges",
+ "kotlin.sequences",
+ "kotlin.text",
+)
+private val KOTLIN_DEFAULT_JVM_IMPORTS = setOf("java.lang")
+private val KOTLIN_DEFAULT_JS_IMPORTS = setOf("kotlin.js")
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt
new file mode 100644
index 00000000..2aa75ffc
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/FunSpec.kt
@@ -0,0 +1,669 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.KModifier.ABSTRACT
+import com.squareup.kotlinpoet.KModifier.EXPECT
+import com.squareup.kotlinpoet.KModifier.EXTERNAL
+import com.squareup.kotlinpoet.KModifier.INLINE
+import com.squareup.kotlinpoet.KModifier.VARARG
+import java.lang.reflect.Type
+import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.ExecutableType
+import javax.lang.model.type.TypeVariable
+import javax.lang.model.util.Types
+import kotlin.DeprecationLevel.WARNING
+import kotlin.reflect.KClass
+
+/** A generated function declaration. */
+@OptIn(ExperimentalKotlinPoetApi::class)
+public class FunSpec private constructor(
+ builder: Builder,
+ private val tagMap: TagMap = builder.buildTagMap(),
+ private val delegateOriginatingElementsHolder: OriginatingElementsHolder = builder.buildOriginatingElements(),
+ private val contextReceivers: ContextReceivers = builder.buildContextReceivers(),
+) : Taggable by tagMap, OriginatingElementsHolder by delegateOriginatingElementsHolder, ContextReceivable by contextReceivers {
+ public val name: String = builder.name
+ public val kdoc: CodeBlock = builder.kdoc.build()
+ public val returnKdoc: CodeBlock = builder.returnKdoc
+ public val receiverKdoc: CodeBlock = builder.receiverKdoc
+ public val annotations: List<AnnotationSpec> = builder.annotations.toImmutableList()
+ public val modifiers: Set<KModifier> = builder.modifiers.toImmutableSet()
+ public val typeVariables: List<TypeVariableName> = builder.typeVariables.toImmutableList()
+ public val receiverType: TypeName? = builder.receiverType
+
+ public val returnType: TypeName? = builder.returnType
+ public val parameters: List<ParameterSpec> = builder.parameters.toImmutableList()
+ public val delegateConstructor: String? = builder.delegateConstructor
+ public val delegateConstructorArguments: List<CodeBlock> =
+ builder.delegateConstructorArguments.toImmutableList()
+ public val body: CodeBlock = builder.body.build()
+ private val isExternalGetter = name == GETTER && builder.modifiers.contains(EXTERNAL)
+ private val isEmptySetter = name == SETTER && parameters.isEmpty()
+
+ init {
+ require(body.isEmpty() || !builder.modifiers.containsAnyOf(ABSTRACT, EXPECT)) {
+ "abstract or expect function ${builder.name} cannot have code"
+ }
+ if (name == GETTER) {
+ require(!isExternalGetter || body.isEmpty()) {
+ "external getter cannot have code"
+ }
+ } else if (name == SETTER) {
+ require(parameters.size <= 1) {
+ "$name can have at most one parameter"
+ }
+ require(parameters.isNotEmpty() || body.isEmpty()) {
+ "parameterless setter cannot have code"
+ }
+ }
+ require(INLINE in modifiers || typeVariables.none { it.isReified }) {
+ "only type parameters of inline functions can be reified!"
+ }
+ }
+
+ internal fun parameter(name: String) = parameters.firstOrNull { it.name == name }
+
+ internal fun emit(
+ codeWriter: CodeWriter,
+ enclosingName: String?,
+ implicitModifiers: Set<KModifier>,
+ includeKdocTags: Boolean = false,
+ ) {
+ if (includeKdocTags) {
+ codeWriter.emitKdoc(kdocWithTags())
+ } else {
+ codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine())
+ }
+ codeWriter.emitContextReceivers(contextReceiverTypes, suffix = "\n")
+ codeWriter.emitAnnotations(annotations, false)
+ codeWriter.emitModifiers(modifiers, implicitModifiers)
+
+ if (!isConstructor && !name.isAccessor) {
+ codeWriter.emitCode("fun·")
+ }
+
+ if (typeVariables.isNotEmpty()) {
+ codeWriter.emitTypeVariables(typeVariables)
+ codeWriter.emit(" ")
+ }
+ emitSignature(codeWriter, enclosingName)
+ codeWriter.emitWhereBlock(typeVariables)
+
+ if (shouldOmitBody(implicitModifiers)) {
+ codeWriter.emit("\n")
+ return
+ }
+
+ val asExpressionBody = body.asExpressionBody()
+
+ if (asExpressionBody != null) {
+ codeWriter.emitCode(CodeBlock.of(" = %L", asExpressionBody), ensureTrailingNewline = true)
+ } else if (!isEmptySetter) {
+ codeWriter.emitCode("·{\n")
+ codeWriter.indent()
+ codeWriter.emitCode(body.returnsWithoutLinebreak(), ensureTrailingNewline = true)
+ codeWriter.unindent()
+ codeWriter.emit("}\n")
+ } else {
+ codeWriter.emit("\n")
+ }
+ }
+
+ private fun shouldOmitBody(implicitModifiers: Set<KModifier>): Boolean {
+ if (canNotHaveBody(implicitModifiers)) {
+ check(body.isEmpty()) { "function $name cannot have code" }
+ return true
+ }
+ return canBodyBeOmitted(implicitModifiers) && body.isEmpty()
+ }
+
+ private fun canNotHaveBody(implicitModifiers: Set<KModifier>) =
+ ABSTRACT in modifiers || EXPECT in modifiers + implicitModifiers
+
+ private fun canBodyBeOmitted(implicitModifiers: Set<KModifier>) = isConstructor ||
+ EXTERNAL in (modifiers + implicitModifiers) ||
+ ABSTRACT in modifiers
+
+ private fun emitSignature(codeWriter: CodeWriter, enclosingName: String?) {
+ if (isConstructor) {
+ codeWriter.emitCode("constructor", enclosingName)
+ } else if (name == GETTER) {
+ codeWriter.emitCode("get")
+ } else if (name == SETTER) {
+ codeWriter.emitCode("set")
+ } else {
+ if (receiverType != null) {
+ if (receiverType is LambdaTypeName) {
+ codeWriter.emitCode("(%T).", receiverType)
+ } else {
+ codeWriter.emitCode("%T.", receiverType)
+ }
+ }
+ codeWriter.emitCode("%N", this)
+ }
+
+ if (!isEmptySetter && !isExternalGetter) {
+ parameters.emit(codeWriter) { param ->
+ param.emit(codeWriter, includeType = name != SETTER)
+ }
+ }
+
+ if (returnType != null) {
+ codeWriter.emitCode(": %T", returnType)
+ } else if (emitUnitReturnType()) {
+ codeWriter.emitCode(": %T", UNIT)
+ }
+
+ if (delegateConstructor != null) {
+ codeWriter.emitCode(
+ delegateConstructorArguments
+ .joinToCode(prefix = " : $delegateConstructor(", suffix = ")"),
+ )
+ }
+ }
+
+ public val isConstructor: Boolean get() = name.isConstructor
+
+ public val isAccessor: Boolean get() = name.isAccessor
+
+ private fun kdocWithTags(): CodeBlock {
+ return with(kdoc.ensureEndsWithNewLine().toBuilder()) {
+ var newLineAdded = false
+ val isNotEmpty = isNotEmpty()
+ if (receiverKdoc.isNotEmpty()) {
+ if (isNotEmpty) {
+ add("\n")
+ newLineAdded = true
+ }
+ add("@receiver %L", receiverKdoc.ensureEndsWithNewLine())
+ }
+ parameters.forEachIndexed { index, parameterSpec ->
+ if (parameterSpec.kdoc.isNotEmpty()) {
+ if (!newLineAdded && index == 0 && isNotEmpty) {
+ add("\n")
+ newLineAdded = true
+ }
+ add("@param %L %L", parameterSpec.name, parameterSpec.kdoc.ensureEndsWithNewLine())
+ }
+ }
+ if (returnKdoc.isNotEmpty()) {
+ if (!newLineAdded && isNotEmpty) {
+ add("\n")
+ newLineAdded = true
+ }
+ add("@return %L", returnKdoc.ensureEndsWithNewLine())
+ }
+ build()
+ }
+ }
+
+ /**
+ * Returns whether [Unit] should be emitted as the return type.
+ *
+ * [Unit] is emitted as return type on a function unless:
+ * - It's a constructor
+ * - It's a getter/setter on a property
+ * - It's an expression body
+ */
+ private fun emitUnitReturnType(): Boolean {
+ if (isConstructor) {
+ return false
+ }
+ if (name == GETTER || name == SETTER) {
+ // Getter/setters don't emit return types
+ return false
+ }
+
+ return body.asExpressionBody() == null
+ }
+
+ private fun CodeBlock.asExpressionBody(): CodeBlock? {
+ val codeBlock = this.trim()
+ val asReturnExpressionBody = codeBlock.withoutPrefix(RETURN_EXPRESSION_BODY_PREFIX_SPACE)
+ ?: codeBlock.withoutPrefix(RETURN_EXPRESSION_BODY_PREFIX_NBSP)
+ if (asReturnExpressionBody != null) {
+ return asReturnExpressionBody
+ }
+ if (codeBlock.withoutPrefix(THROW_EXPRESSION_BODY_PREFIX_SPACE) != null ||
+ codeBlock.withoutPrefix(THROW_EXPRESSION_BODY_PREFIX_NBSP) != null
+ ) {
+ return codeBlock
+ }
+ return null
+ }
+
+ private fun CodeBlock.returnsWithoutLinebreak(): CodeBlock {
+ val returnWithSpace = RETURN_EXPRESSION_BODY_PREFIX_SPACE.formatParts[0]
+ val returnWithNbsp = RETURN_EXPRESSION_BODY_PREFIX_NBSP.formatParts[0]
+ var originCodeBlockBuilder: CodeBlock.Builder? = null
+ for ((i, formatPart) in formatParts.withIndex()) {
+ if (formatPart.startsWith(returnWithSpace)) {
+ val builder = originCodeBlockBuilder ?: toBuilder()
+ originCodeBlockBuilder = builder
+ builder.formatParts[i] = formatPart.replaceFirst(returnWithSpace, returnWithNbsp)
+ }
+ }
+ return originCodeBlockBuilder?.build() ?: this
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null) return false
+ if (javaClass != other.javaClass) return false
+ return toString() == other.toString()
+ }
+
+ override fun hashCode(): Int = toString().hashCode()
+
+ override fun toString(): String = buildCodeString {
+ emit(
+ codeWriter = this,
+ enclosingName = "Constructor",
+ implicitModifiers = TypeSpec.Kind.CLASS.implicitFunctionModifiers(),
+ includeKdocTags = true,
+ )
+ }
+
+ @JvmOverloads
+ public fun toBuilder(name: String = this.name): Builder {
+ val builder = Builder(name)
+ builder.kdoc.add(kdoc)
+ builder.returnKdoc = returnKdoc
+ builder.receiverKdoc = receiverKdoc
+ builder.annotations += annotations
+ builder.modifiers += modifiers
+ builder.typeVariables += typeVariables
+ builder.returnType = returnType
+ builder.parameters += parameters
+ builder.delegateConstructor = delegateConstructor
+ builder.delegateConstructorArguments += delegateConstructorArguments
+ builder.body.add(body)
+ builder.receiverType = receiverType
+ builder.tags += tagMap.tags
+ builder.originatingElements += originatingElements
+ builder.contextReceiverTypes += contextReceiverTypes
+ return builder
+ }
+
+ public class Builder internal constructor(
+ internal val name: String,
+ ) : Taggable.Builder<Builder>, OriginatingElementsHolder.Builder<Builder>, ContextReceivable.Builder<Builder> {
+ internal val kdoc = CodeBlock.builder()
+ internal var returnKdoc = CodeBlock.EMPTY
+ internal var receiverKdoc = CodeBlock.EMPTY
+ internal var receiverType: TypeName? = null
+ internal var returnType: TypeName? = null
+ internal var delegateConstructor: String? = null
+ internal var delegateConstructorArguments = listOf<CodeBlock>()
+ internal val body = CodeBlock.builder()
+
+ public val annotations: MutableList<AnnotationSpec> = mutableListOf()
+ public val modifiers: MutableList<KModifier> = mutableListOf()
+ public val typeVariables: MutableList<TypeVariableName> = mutableListOf()
+ public val parameters: MutableList<ParameterSpec> = mutableListOf()
+ override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
+ override val originatingElements: MutableList<Element> = mutableListOf()
+ override val contextReceiverTypes: MutableList<TypeName> = mutableListOf()
+
+ public fun addKdoc(format: String, vararg args: Any): Builder = apply {
+ kdoc.add(format, *args)
+ }
+
+ public fun addKdoc(block: CodeBlock): Builder = apply {
+ kdoc.add(block)
+ }
+
+ public fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>): Builder = apply {
+ this.annotations += annotationSpecs
+ }
+
+ public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
+ annotations += annotationSpec
+ }
+
+ public fun addAnnotation(annotation: ClassName): Builder = apply {
+ annotations += AnnotationSpec.builder(annotation).build()
+ }
+
+ public fun addAnnotation(annotation: Class<*>): Builder =
+ addAnnotation(annotation.asClassName())
+
+ public fun addAnnotation(annotation: KClass<*>): Builder =
+ addAnnotation(annotation.asClassName())
+
+ public fun addModifiers(vararg modifiers: KModifier): Builder = apply {
+ this.modifiers += modifiers
+ }
+
+ public fun addModifiers(modifiers: Iterable<KModifier>): Builder = apply {
+ this.modifiers += modifiers
+ }
+
+ public fun jvmModifiers(modifiers: Iterable<Modifier>) {
+ var visibility = KModifier.INTERNAL
+ for (modifier in modifiers) {
+ when (modifier) {
+ Modifier.PUBLIC -> visibility = KModifier.PUBLIC
+ Modifier.PROTECTED -> visibility = KModifier.PROTECTED
+ Modifier.PRIVATE -> visibility = KModifier.PRIVATE
+ Modifier.ABSTRACT -> this.modifiers += KModifier.ABSTRACT
+ Modifier.FINAL -> this.modifiers += KModifier.FINAL
+ Modifier.NATIVE -> this.modifiers += KModifier.EXTERNAL
+ Modifier.DEFAULT -> Unit
+ Modifier.STATIC -> addAnnotation(JvmStatic::class)
+ Modifier.SYNCHRONIZED -> addAnnotation(Synchronized::class)
+ Modifier.STRICTFP -> addAnnotation(Strictfp::class)
+ else -> throw IllegalArgumentException("unexpected fun modifier $modifier")
+ }
+ }
+ this.modifiers += visibility
+ }
+
+ public fun addTypeVariables(typeVariables: Iterable<TypeVariableName>): Builder = apply {
+ this.typeVariables += typeVariables
+ }
+
+ public fun addTypeVariable(typeVariable: TypeVariableName): Builder = apply {
+ typeVariables += typeVariable
+ }
+
+ @ExperimentalKotlinPoetApi
+ override fun contextReceivers(receiverTypes: Iterable<TypeName>): Builder = apply {
+ check(!name.isConstructor) { "constructors cannot have context receivers" }
+ check(!name.isAccessor) { "$name cannot have context receivers" }
+ contextReceiverTypes += receiverTypes
+ }
+
+ @JvmOverloads public fun receiver(
+ receiverType: TypeName,
+ kdoc: CodeBlock = CodeBlock.EMPTY,
+ ): Builder = apply {
+ check(!name.isConstructor) { "$name cannot have receiver type" }
+ this.receiverType = receiverType
+ this.receiverKdoc = kdoc
+ }
+
+ @JvmOverloads public fun receiver(
+ receiverType: Type,
+ kdoc: CodeBlock = CodeBlock.EMPTY,
+ ): Builder = receiver(receiverType.asTypeName(), kdoc)
+
+ public fun receiver(
+ receiverType: Type,
+ kdoc: String,
+ vararg args: Any,
+ ): Builder = receiver(receiverType, CodeBlock.of(kdoc, args))
+
+ @JvmOverloads public fun receiver(
+ receiverType: KClass<*>,
+ kdoc: CodeBlock = CodeBlock.EMPTY,
+ ): Builder = receiver(receiverType.asTypeName(), kdoc)
+
+ public fun receiver(
+ receiverType: KClass<*>,
+ kdoc: String,
+ vararg args: Any,
+ ): Builder = receiver(receiverType, CodeBlock.of(kdoc, args))
+
+ @JvmOverloads public fun returns(
+ returnType: TypeName,
+ kdoc: CodeBlock = CodeBlock.EMPTY,
+ ): Builder = apply {
+ check(!name.isConstructor && !name.isAccessor) { "$name cannot have a return type" }
+ this.returnType = returnType
+ this.returnKdoc = kdoc
+ }
+
+ @JvmOverloads public fun returns(returnType: Type, kdoc: CodeBlock = CodeBlock.EMPTY): Builder =
+ returns(returnType.asTypeName(), kdoc)
+
+ public fun returns(returnType: Type, kdoc: String, vararg args: Any): Builder =
+ returns(returnType.asTypeName(), CodeBlock.of(kdoc, args))
+
+ @JvmOverloads public fun returns(
+ returnType: KClass<*>,
+ kdoc: CodeBlock = CodeBlock.EMPTY,
+ ): Builder = returns(returnType.asTypeName(), kdoc)
+
+ public fun returns(returnType: KClass<*>, kdoc: String, vararg args: Any): Builder =
+ returns(returnType.asTypeName(), CodeBlock.of(kdoc, args))
+
+ public fun addParameters(parameterSpecs: Iterable<ParameterSpec>): Builder = apply {
+ for (parameterSpec in parameterSpecs) {
+ addParameter(parameterSpec)
+ }
+ }
+
+ public fun addParameter(parameterSpec: ParameterSpec): Builder = apply {
+ parameters += parameterSpec
+ }
+
+ public fun callThisConstructor(args: List<CodeBlock>): Builder = apply {
+ callConstructor("this", args)
+ }
+
+ public fun callThisConstructor(args: Iterable<CodeBlock>): Builder = apply {
+ callConstructor("this", args.toList())
+ }
+
+ public fun callThisConstructor(vararg args: String): Builder = apply {
+ callConstructor("this", args.map { CodeBlock.of(it) })
+ }
+
+ public fun callThisConstructor(vararg args: CodeBlock = emptyArray()): Builder = apply {
+ callConstructor("this", args.toList())
+ }
+
+ public fun callSuperConstructor(args: Iterable<CodeBlock>): Builder = apply {
+ callConstructor("super", args.toList())
+ }
+
+ public fun callSuperConstructor(args: List<CodeBlock>): Builder = apply {
+ callConstructor("super", args)
+ }
+
+ public fun callSuperConstructor(vararg args: String): Builder = apply {
+ callConstructor("super", args.map { CodeBlock.of(it) })
+ }
+
+ public fun callSuperConstructor(vararg args: CodeBlock = emptyArray()): Builder = apply {
+ callConstructor("super", args.toList())
+ }
+
+ private fun callConstructor(constructor: String, args: List<CodeBlock>) {
+ check(name.isConstructor) { "only constructors can delegate to other constructors!" }
+ delegateConstructor = constructor
+ delegateConstructorArguments = args
+ }
+
+ public fun addParameter(name: String, type: TypeName, vararg modifiers: KModifier): Builder =
+ addParameter(ParameterSpec.builder(name, type, *modifiers).build())
+
+ public fun addParameter(name: String, type: Type, vararg modifiers: KModifier): Builder =
+ addParameter(name, type.asTypeName(), *modifiers)
+
+ public fun addParameter(name: String, type: KClass<*>, vararg modifiers: KModifier): Builder =
+ addParameter(name, type.asTypeName(), *modifiers)
+
+ public fun addParameter(name: String, type: TypeName, modifiers: Iterable<KModifier>): Builder =
+ addParameter(ParameterSpec.builder(name, type, modifiers).build())
+
+ public fun addParameter(name: String, type: Type, modifiers: Iterable<KModifier>): Builder =
+ addParameter(name, type.asTypeName(), modifiers)
+
+ public fun addParameter(
+ name: String,
+ type: KClass<*>,
+ modifiers: Iterable<KModifier>,
+ ): Builder = addParameter(name, type.asTypeName(), modifiers)
+
+ public fun addCode(format: String, vararg args: Any?): Builder = apply {
+ body.add(format, *args)
+ }
+
+ public fun addNamedCode(format: String, args: Map<String, *>): Builder = apply {
+ body.addNamed(format, args)
+ }
+
+ public fun addCode(codeBlock: CodeBlock): Builder = apply {
+ body.add(codeBlock)
+ }
+
+ public fun addComment(format: String, vararg args: Any): Builder = apply {
+ body.add("//·${format.replace(' ', '·')}\n", *args)
+ }
+
+ /**
+ * @param controlFlow the control flow construct and its code, such as "if (foo == 5)".
+ * * Shouldn't contain braces or newline characters.
+ */
+ public fun beginControlFlow(controlFlow: String, vararg args: Any): Builder = apply {
+ body.beginControlFlow(controlFlow, *args)
+ }
+
+ /**
+ * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
+ * * Shouldn't contain braces or newline characters.
+ */
+ public fun nextControlFlow(controlFlow: String, vararg args: Any): Builder = apply {
+ body.nextControlFlow(controlFlow, *args)
+ }
+
+ public fun endControlFlow(): Builder = apply {
+ body.endControlFlow()
+ }
+
+ public fun addStatement(format: String, vararg args: Any): Builder = apply {
+ body.addStatement(format, *args)
+ }
+
+ public fun clearBody(): Builder = apply {
+ body.clear()
+ }
+
+ public fun build(): FunSpec {
+ check(typeVariables.isEmpty() || !name.isAccessor) { "$name cannot have type variables" }
+ check(!(name == GETTER && parameters.isNotEmpty())) { "$name cannot have parameters" }
+ check(!(name == SETTER && parameters.size > 1)) { "$name can have at most one parameter" }
+ return FunSpec(this)
+ }
+ }
+
+ public companion object {
+ private const val CONSTRUCTOR = "constructor()"
+ internal const val GETTER = "get()"
+ internal const val SETTER = "set()"
+
+ internal val String.isConstructor get() = this == CONSTRUCTOR
+ internal val String.isAccessor get() = this.isOneOf(GETTER, SETTER)
+
+ private val RETURN_EXPRESSION_BODY_PREFIX_SPACE = CodeBlock.of("return ")
+ private val RETURN_EXPRESSION_BODY_PREFIX_NBSP = CodeBlock.of("return·")
+ private val THROW_EXPRESSION_BODY_PREFIX_SPACE = CodeBlock.of("throw ")
+ private val THROW_EXPRESSION_BODY_PREFIX_NBSP = CodeBlock.of("throw·")
+
+ @JvmStatic public fun builder(name: String): Builder = Builder(name)
+
+ @JvmStatic public fun constructorBuilder(): Builder = Builder(CONSTRUCTOR)
+
+ @JvmStatic public fun getterBuilder(): Builder = Builder(GETTER)
+
+ @JvmStatic public fun setterBuilder(): Builder = Builder(SETTER)
+
+ @DelicateKotlinPoetApi(
+ message = "Element APIs don't give complete information on Kotlin types. Consider using" +
+ " the kotlinpoet-metadata APIs instead.",
+ )
+ @JvmStatic
+ public fun overriding(method: ExecutableElement): Builder {
+ var modifiers: Set<Modifier> = method.modifiers
+ require(
+ Modifier.PRIVATE !in modifiers &&
+ Modifier.FINAL !in modifiers &&
+ Modifier.STATIC !in modifiers,
+ ) {
+ "cannot override method with modifiers: $modifiers"
+ }
+
+ val methodName = method.simpleName.toString()
+ val funBuilder = builder(methodName)
+
+ funBuilder.addModifiers(KModifier.OVERRIDE)
+
+ modifiers = modifiers.toMutableSet()
+ modifiers.remove(Modifier.ABSTRACT)
+ funBuilder.jvmModifiers(modifiers)
+
+ method.typeParameters
+ .map { it.asType() as TypeVariable }
+ .map { it.asTypeVariableName() }
+ .forEach { funBuilder.addTypeVariable(it) }
+
+ funBuilder.returns(method.returnType.asTypeName())
+ funBuilder.addParameters(ParameterSpec.parametersOf(method))
+ if (method.isVarArgs) {
+ funBuilder.parameters[funBuilder.parameters.lastIndex] = funBuilder.parameters.last()
+ .toBuilder()
+ .addModifiers(VARARG)
+ .build()
+ }
+
+ if (method.thrownTypes.isNotEmpty()) {
+ val throwsValueString = method.thrownTypes.joinToString { "%T::class" }
+ funBuilder.addAnnotation(
+ AnnotationSpec.builder(Throws::class)
+ .addMember(throwsValueString, *method.thrownTypes.toTypedArray())
+ .build(),
+ )
+ }
+
+ return funBuilder
+ }
+
+ @Deprecated(
+ message = "Element APIs don't give complete information on Kotlin types. Consider using" +
+ " the kotlinpoet-metadata APIs instead.",
+ level = WARNING,
+ )
+ @JvmStatic
+ public fun overriding(
+ method: ExecutableElement,
+ enclosing: DeclaredType,
+ types: Types,
+ ): Builder {
+ val executableType = types.asMemberOf(enclosing, method) as ExecutableType
+ val resolvedParameterTypes = executableType.parameterTypes
+ val resolvedReturnType = executableType.returnType
+
+ val builder = overriding(method)
+ builder.returns(resolvedReturnType.asTypeName())
+ var i = 0
+ val size = builder.parameters.size
+ while (i < size) {
+ val parameter = builder.parameters[i]
+ val type = resolvedParameterTypes[i].asTypeName()
+ builder.parameters[i] = parameter.toBuilder(parameter.name, type).build()
+ i++
+ }
+
+ return builder
+ }
+ }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Import.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Import.kt
new file mode 100644
index 00000000..59b6b12e
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Import.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+public data class Import internal constructor(
+ val qualifiedName: String,
+ val alias: String? = null,
+) : Comparable<Import> {
+
+ private val importString = buildString {
+ append(qualifiedName.escapeSegmentsIfNecessary())
+ if (alias != null) {
+ append("·as·${alias.escapeIfNecessary()}")
+ }
+ }
+
+ override fun toString(): String = importString
+
+ override fun compareTo(other: Import): Int = importString.compareTo(other.importString)
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/KModifier.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/KModifier.kt
new file mode 100644
index 00000000..81d03570
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/KModifier.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.KModifier.INTERNAL
+import com.squareup.kotlinpoet.KModifier.PRIVATE
+import com.squareup.kotlinpoet.KModifier.PROTECTED
+import com.squareup.kotlinpoet.KModifier.PUBLIC
+import java.util.EnumSet
+
+public enum class KModifier(
+ internal val keyword: String,
+ private vararg val targets: Target,
+) {
+ // Modifier order defined here:
+ // https://kotlinlang.org/docs/reference/coding-conventions.html#modifiers
+
+ // Access.
+ PUBLIC("public", Target.PROPERTY),
+ PROTECTED("protected", Target.PROPERTY),
+ PRIVATE("private", Target.PROPERTY),
+ INTERNAL("internal", Target.PROPERTY),
+
+ // Multiplatform modules.
+ EXPECT("expect", Target.CLASS, Target.FUNCTION, Target.PROPERTY),
+ ACTUAL("actual", Target.CLASS, Target.FUNCTION, Target.PROPERTY),
+
+ FINAL("final", Target.CLASS, Target.FUNCTION, Target.PROPERTY),
+ OPEN("open", Target.CLASS, Target.FUNCTION, Target.PROPERTY),
+ ABSTRACT("abstract", Target.CLASS, Target.FUNCTION, Target.PROPERTY),
+ SEALED("sealed", Target.CLASS),
+ CONST("const", Target.PROPERTY),
+
+ EXTERNAL("external", Target.CLASS, Target.FUNCTION, Target.PROPERTY),
+ OVERRIDE("override", Target.FUNCTION, Target.PROPERTY),
+ LATEINIT("lateinit", Target.PROPERTY),
+ TAILREC("tailrec", Target.FUNCTION),
+ VARARG("vararg", Target.PARAMETER),
+ SUSPEND("suspend", Target.FUNCTION),
+ INNER("inner", Target.CLASS),
+
+ ENUM("enum", Target.CLASS),
+ ANNOTATION("annotation", Target.CLASS),
+ VALUE("value", Target.CLASS),
+ FUN("fun", Target.INTERFACE),
+
+ COMPANION("companion", Target.CLASS),
+
+ // Call-site compiler tips.
+ INLINE("inline", Target.FUNCTION),
+ NOINLINE("noinline", Target.PARAMETER),
+ CROSSINLINE("crossinline", Target.PARAMETER),
+ REIFIED("reified", Target.TYPE_PARAMETER),
+
+ INFIX("infix", Target.FUNCTION),
+ OPERATOR("operator", Target.FUNCTION),
+
+ DATA("data", Target.CLASS),
+
+ IN("in", Target.VARIANCE_ANNOTATION),
+ OUT("out", Target.VARIANCE_ANNOTATION),
+ ;
+
+ internal enum class Target {
+ CLASS,
+ VARIANCE_ANNOTATION,
+ PARAMETER,
+ TYPE_PARAMETER,
+ FUNCTION,
+ PROPERTY,
+ INTERFACE,
+ }
+
+ internal fun checkTarget(target: Target) {
+ require(target in targets) { "unexpected modifier $this for $target" }
+ }
+}
+
+internal val VISIBILITY_MODIFIERS: Set<KModifier> = EnumSet.of(PUBLIC, INTERNAL, PROTECTED, PRIVATE)
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/KOperator.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/KOperator.kt
new file mode 100644
index 00000000..461a39f2
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/KOperator.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+public enum class KOperator(
+ internal val operator: String,
+ internal val functionName: String,
+) {
+ UNARY_PLUS("+", "unaryPlus"),
+ PLUS("+", "plus"),
+ UNARY_MINUS("-", "unaryMinus"),
+ MINUS("-", "minus"),
+ TIMES("*", "times"),
+ DIV("/", "div"),
+ REM("%", "rem"),
+ PLUS_ASSIGN("+=", "plusAssign"),
+ MINUS_ASSIGN("-=", "minusAssign"),
+ TIMES_ASSIGN("*=", "timesAssign"),
+ DIV_ASSIGN("/=", "divAssign"),
+ REM_ASSIGN("%=", "remAssign"),
+ INC("++", "inc"),
+ DEC("--", "dec"),
+ EQUALS("==", "equals"),
+ NOT_EQUALS("!=", "equals"),
+ NOT("!", "not"),
+ RANGE_TO("..", "rangeTo"),
+ CONTAINS("in", "contains"),
+ NOT_CONTAINS("!in", "contains"),
+ GT(">", "compareTo"),
+ LT("<", "compareTo"),
+ GE(">=", "compareTo"),
+ LE("<=", "compareTo"),
+ ITERATOR("in", "iterator"),
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt
new file mode 100644
index 00000000..efb3b837
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LambdaTypeName.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import kotlin.reflect.KClass
+
+@OptIn(ExperimentalKotlinPoetApi::class)
+public class LambdaTypeName private constructor(
+ public val receiver: TypeName? = null,
+ @property:ExperimentalKotlinPoetApi
+ public val contextReceivers: List<TypeName> = emptyList(),
+ parameters: List<ParameterSpec> = emptyList(),
+ public val returnType: TypeName = UNIT,
+ nullable: Boolean = false,
+ public val isSuspending: Boolean = false,
+ annotations: List<AnnotationSpec> = emptyList(),
+ tags: Map<KClass<*>, Any> = emptyMap(),
+) : TypeName(nullable, annotations, TagMap(tags)) {
+ public val parameters: List<ParameterSpec> = parameters.toImmutableList()
+
+ init {
+ for (param in parameters) {
+ require(param.annotations.isEmpty()) { "Parameters with annotations are not allowed" }
+ require(param.modifiers.isEmpty()) { "Parameters with modifiers are not allowed" }
+ require(param.defaultValue == null) { "Parameters with default values are not allowed" }
+ }
+ }
+
+ override fun copy(
+ nullable: Boolean,
+ annotations: List<AnnotationSpec>,
+ tags: Map<KClass<*>, Any>,
+ ): LambdaTypeName {
+ return copy(nullable, annotations, this.isSuspending, tags)
+ }
+
+ public fun copy(
+ nullable: Boolean = this.isNullable,
+ annotations: List<AnnotationSpec> = this.annotations.toList(),
+ suspending: Boolean = this.isSuspending,
+ tags: Map<KClass<*>, Any> = this.tags.toMap(),
+ ): LambdaTypeName {
+ return LambdaTypeName(receiver, contextReceivers, parameters, returnType, nullable, suspending, annotations, tags)
+ }
+
+ override fun emit(out: CodeWriter): CodeWriter {
+ if (isNullable) {
+ out.emit("(")
+ }
+
+ if (isSuspending) {
+ out.emit("suspend·")
+ }
+
+ out.emitContextReceivers(contextReceivers, suffix = "·")
+
+ receiver?.let {
+ if (it.isAnnotated) {
+ out.emitCode("(%T).", it)
+ } else {
+ out.emitCode("%T.", it)
+ }
+ }
+
+ parameters.emit(out)
+ out.emitCode(if (returnType is LambdaTypeName) "·->·(%T)" else "·->·%T", returnType)
+
+ if (isNullable) {
+ out.emit(")")
+ }
+ return out
+ }
+
+ public companion object {
+ /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */
+ @ExperimentalKotlinPoetApi @JvmStatic
+ public fun get(
+ receiver: TypeName? = null,
+ parameters: List<ParameterSpec> = emptyList(),
+ returnType: TypeName,
+ contextReceivers: List<TypeName> = emptyList(),
+ ): LambdaTypeName = LambdaTypeName(receiver, contextReceivers, parameters, returnType)
+
+ /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */
+ @JvmStatic public fun get(
+ receiver: TypeName? = null,
+ parameters: List<ParameterSpec> = emptyList(),
+ returnType: TypeName,
+ ): LambdaTypeName = LambdaTypeName(receiver, emptyList(), parameters, returnType)
+
+ /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */
+ @JvmStatic public fun get(
+ receiver: TypeName? = null,
+ vararg parameters: TypeName = emptyArray(),
+ returnType: TypeName,
+ ): LambdaTypeName {
+ return LambdaTypeName(
+ receiver,
+ emptyList(),
+ parameters.toList().map { ParameterSpec.unnamed(it) },
+ returnType,
+ )
+ }
+
+ /** Returns a lambda type with `returnType` and parameters listed in `parameters`. */
+ @JvmStatic public fun get(
+ receiver: TypeName? = null,
+ vararg parameters: ParameterSpec = emptyArray(),
+ returnType: TypeName,
+ ): LambdaTypeName = LambdaTypeName(receiver, emptyList(), parameters.toList(), returnType)
+ }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LineWrapper.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LineWrapper.kt
new file mode 100644
index 00000000..83a155b0
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/LineWrapper.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import java.io.Closeable
+
+/**
+ * Implements soft line wrapping on an appendable. To use, append characters using
+ * [LineWrapper.append], which will replace spaces with newlines where necessary. Use
+ * [LineWrapper.appendNonWrapping] to append a string that never wraps.
+ */
+internal class LineWrapper(
+ private val out: Appendable,
+ private val indent: String,
+ private val columnLimit: Int,
+) : Closeable {
+
+ private var closed = false
+
+ /**
+ * Segments of the current line to be joined by spaces or wraps. Never empty, but contains a lone
+ * empty string if no data has been emitted since the last newline.
+ */
+ private val segments = mutableListOf("")
+
+ /** Number of indents in wraps. -1 if the current line has no wraps. */
+ private var indentLevel = -1
+
+ /** Optional prefix that will be prepended to wrapped lines. */
+ private var linePrefix = ""
+
+ /** @return whether or not there are pending segments for the current line. */
+ val hasPendingSegments get() = segments.size != 1 || segments[0].isNotEmpty()
+
+ /** Emit `s` replacing its spaces with line wraps as necessary. */
+ fun append(s: String, indentLevel: Int = -1, linePrefix: String = "") {
+ check(!closed) { "closed" }
+
+ var pos = 0
+ while (pos < s.length) {
+ when (s[pos]) {
+ ' ' -> {
+ // Each space starts a new empty segment.
+ this.indentLevel = indentLevel
+ this.linePrefix = linePrefix
+ segments += ""
+ pos++
+ }
+
+ '\n' -> {
+ // Each newline emits the current segments.
+ newline()
+ pos++
+ }
+
+ '·' -> {
+ // Render · as a non-breaking space.
+ segments[segments.size - 1] += " "
+ pos++
+ }
+
+ else -> {
+ var next = s.indexOfAny(SPECIAL_CHARACTERS, pos)
+ if (next == -1) next = s.length
+ segments[segments.size - 1] += s.substring(pos, next)
+ pos = next
+ }
+ }
+ }
+ }
+
+ /** Emit `s` leaving spaces as-is. */
+ fun appendNonWrapping(s: String) {
+ check(!closed) { "closed" }
+ require(!s.contains("\n"))
+
+ segments[segments.size - 1] += s
+ }
+
+ fun newline() {
+ check(!closed) { "closed" }
+
+ emitCurrentLine()
+ out.append("\n")
+ indentLevel = -1
+ }
+
+ /** Flush any outstanding text and forbid future writes to this line wrapper. */
+ override fun close() {
+ emitCurrentLine()
+ closed = true
+ }
+
+ private fun emitCurrentLine() {
+ foldUnsafeBreaks()
+
+ var start = 0
+ var columnCount = segments[0].length
+
+ for (i in 1 until segments.size) {
+ val segment = segments[i]
+ val newColumnCount = columnCount + 1 + segment.length
+
+ // If this segment doesn't fit in the current run, print the current run and start a new one.
+ if (newColumnCount > columnLimit) {
+ emitSegmentRange(start, i)
+ start = i
+ columnCount = segment.length + indent.length * indentLevel
+ continue
+ }
+
+ columnCount = newColumnCount
+ }
+
+ // Print the last run.
+ emitSegmentRange(start, segments.size)
+
+ segments.clear()
+ segments += ""
+ }
+
+ private fun emitSegmentRange(startIndex: Int, endIndex: Int) {
+ // If this is a wrapped line we need a newline and an indent.
+ if (startIndex > 0) {
+ out.append("\n")
+ for (i in 0 until indentLevel) {
+ out.append(indent)
+ }
+ out.append(linePrefix)
+ }
+
+ // Emit each segment separated by spaces.
+ out.append(segments[startIndex])
+ for (i in startIndex + 1 until endIndex) {
+ out.append(" ")
+ out.append(segments[i])
+ }
+ }
+
+ /**
+ * Any segment that starts with '+' or '-' can't have a break preceding it. Combine it with the
+ * preceding segment. Note that this doesn't apply to the first segment.
+ */
+ private fun foldUnsafeBreaks() {
+ var i = 1
+ while (i < segments.size) {
+ val segment = segments[i]
+ if (UNSAFE_LINE_START.matches(segment)) {
+ segments[i - 1] = segments[i - 1] + " " + segments[i]
+ segments.removeAt(i)
+ if (i > 1) i--
+ } else {
+ i++
+ }
+ }
+ }
+
+ companion object {
+ private val UNSAFE_LINE_START = Regex("\\s*[-+].*")
+ private val SPECIAL_CHARACTERS = " \n·".toCharArray()
+ }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/MemberName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/MemberName.kt
new file mode 100644
index 00000000..c70a10ac
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/MemberName.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import kotlin.reflect.KClass
+
+/**
+ * Represents the name of a member (such as a function or a property).
+ *
+ * @param packageName e.g. `kotlin.collections`
+ * @param enclosingClassName e.g. `Map.Entry.Companion`, if the member is declared inside the
+ * companion object of the Map.Entry class
+ * @param simpleName e.g. `isBlank`, `size`
+ * @param isExtension whether the member is an extension property or an extension function. Default
+ * is false.
+ *
+ * If there is a member with the same name as this member in a local scope, the generated code will
+ * include this member's fully-qualified name to avoid ambiguity, e.g.:
+ *
+ * ```kotlin
+ * package com.squareup.tacos
+ *
+ * import kotlin.Unit
+ *
+ * public class TacoTest {
+ * public fun test(): Unit {
+ * kotlin.error("errorText")
+ * }
+ *
+ * public fun error(): Unit {
+ * }
+ * }
+ * ```
+ *
+ * However, since Kotlin compiler does not allow fully-qualified extension members, if [isExtension]
+ * is set to true for this [MemberName], the generated code will include an import for this member
+ * and its simple name at the call site, e.g.:
+ *
+ * ```kotlin
+ * package com.squareup.tacos
+ *
+ * import kotlin.Unit
+ * import kotlin.hashCode
+ *
+ * public class TacoTest {
+ * public override fun hashCode(): Unit {
+ * var result = super.hashCode
+ * if (result == 0) {
+ * result = result * 37 + embedded_message.hashCode()
+ * super.hashCode = result
+ * }
+ * return result
+ * }
+ * }
+ * ```
+ */
+public data class MemberName internal constructor(
+ public val packageName: String,
+ public val enclosingClassName: ClassName?,
+ public val simpleName: String,
+ public val operator: KOperator? = null,
+ public val isExtension: Boolean = false,
+) {
+ // TODO(egorand): Reduce the number of overloaded constructors in KotlinPoet 2.0.
+
+ public constructor(
+ packageName: String,
+ simpleName: String,
+ ) : this(packageName, enclosingClassName = null, simpleName)
+
+ public constructor(
+ packageName: String,
+ simpleName: String,
+ isExtension: Boolean,
+ ) : this(packageName, enclosingClassName = null, simpleName, operator = null, isExtension)
+
+ public constructor(
+ enclosingClassName: ClassName,
+ simpleName: String,
+ ) : this(enclosingClassName.packageName, enclosingClassName, simpleName)
+
+ public constructor(
+ enclosingClassName: ClassName,
+ simpleName: String,
+ isExtension: Boolean,
+ ) : this(enclosingClassName.packageName, enclosingClassName, simpleName, operator = null, isExtension)
+
+ public constructor(
+ packageName: String,
+ operator: KOperator,
+ ) : this(packageName, enclosingClassName = null, operator.functionName, operator)
+
+ public constructor(
+ enclosingClassName: ClassName,
+ operator: KOperator,
+ ) : this(enclosingClassName.packageName, enclosingClassName, operator.functionName, operator)
+
+ /** Fully qualified name using `.` as a separator, like `kotlin.String.isBlank`. */
+ public val canonicalName: String = buildString {
+ if (enclosingClassName != null) {
+ append(enclosingClassName.canonicalName)
+ append('.')
+ } else if (packageName.isNotBlank()) {
+ append(packageName)
+ append('.')
+ }
+ append(simpleName)
+ }
+
+ /**
+ * Callable reference to this member. Emits [enclosingClassName] if it exists, followed by
+ * the reference operator `::`, followed by either [simpleName] or the fully-qualified
+ * name if this is a top-level member.
+ *
+ * Note: As `::$packageName.$simpleName` is not valid syntax, an aliased import may be
+ * required for a top-level member with a conflicting name.
+ */
+ public fun reference(): CodeBlock = when (enclosingClassName) {
+ null -> CodeBlock.of("::%M", this)
+ else -> CodeBlock.of("%T::%N", enclosingClassName, simpleName)
+ }
+
+ internal fun emit(out: CodeWriter) {
+ if (operator == null) {
+ out.emit(out.lookupName(this).escapeSegmentsIfNecessary())
+ } else {
+ out.lookupName(this)
+ out.emit(operator.operator)
+ }
+ }
+
+ override fun toString(): String = canonicalName
+
+ public companion object {
+ @Suppress("NOTHING_TO_INLINE")
+ @JvmSynthetic
+ @JvmStatic
+ public inline fun ClassName.member(simpleName: String): MemberName =
+ MemberName(this, simpleName)
+
+ @JvmStatic
+ @JvmName("get")
+ public fun KClass<*>.member(simpleName: String): MemberName =
+ asClassName().member(simpleName)
+
+ @DelicateKotlinPoetApi(
+ message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+ "using the kotlinpoet-metadata APIs instead.",
+ )
+ @JvmStatic
+ @JvmName("get")
+ public fun Class<*>.member(simpleName: String): MemberName =
+ asClassName().member(simpleName)
+ }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/NameAllocator.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/NameAllocator.kt
new file mode 100644
index 00000000..cda10042
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/NameAllocator.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import java.util.UUID
+
+/**
+ * Assigns Kotlin identifier names to avoid collisions, keywords, and invalid characters. To use,
+ * first create an instance and allocate all of the names that you need. Typically this is a
+ * mix of user-supplied names and constants:
+ *
+ * ```kotlin
+ * val nameAllocator = NameAllocator()
+ * for (property in properties) {
+ * nameAllocator.newName(property.name, property)
+ * }
+ * nameAllocator.newName("sb", "string builder")
+ * ```
+ *
+ * Pass a unique tag object to each allocation. The tag scopes the name, and can be used to look up
+ * the allocated name later. Typically the tag is the object that is being named. In the above
+ * example we use `property` for the user-supplied property names, and `"string builder"` for our
+ * constant string builder.
+ *
+ * Once we've allocated names we can use them when generating code:
+ *
+ * ```kotlin
+ * val builder = FunSpec.builder("toString")
+ * .addModifiers(KModifier.OVERRIDE)
+ * .returns(String::class)
+ *
+ * builder.addStatement("val %N = %T()",
+ * nameAllocator.get("string builder"), StringBuilder::class)
+ *
+ * for (property in properties) {
+ * builder.addStatement("%N.append(%N)",
+ * nameAllocator.get("string builder"), nameAllocator.get(property))
+ * }
+ * builder.addStatement("return %N.toString()", nameAllocator.get("string builder"))
+ * return builder.build()
+ * ```
+ *
+ * The above code generates unique names if presented with conflicts. Given user-supplied properties
+ * with names `ab` and `sb` this generates the following:
+ *
+ * ```kotlin
+ * override fun toString(): kotlin.String {
+ * val sb_ = java.lang.StringBuilder()
+ * sb_.append(ab)
+ * sb_.append(sb)
+ * return sb_.toString()
+ * }
+ * ```
+ *
+ * The underscore is appended to `sb` to avoid conflicting with the user-supplied `sb` property.
+ * Underscores are also prefixed for names that start with a digit, and used to replace name-unsafe
+ * characters like space or dash.
+ *
+ * When dealing with multiple independent inner scopes, use a [copy][NameAllocator.copy] of the
+ * NameAllocator used for the outer scope to further refine name allocation for a specific inner
+ * scope.
+ */
+public class NameAllocator private constructor(
+ private val allocatedNames: MutableSet<String>,
+ private val tagToName: MutableMap<Any, String>,
+) {
+ public constructor() : this(mutableSetOf(), mutableMapOf())
+
+ /**
+ * Return a new name using `suggestion` that will not be a Java identifier or clash with other
+ * names. The returned value can be queried multiple times by passing `tag` to
+ * [NameAllocator.get].
+ */
+ @JvmOverloads public fun newName(
+ suggestion: String,
+ tag: Any = UUID.randomUUID().toString(),
+ ): String {
+ var result = toJavaIdentifier(suggestion)
+ while (result.isKeyword || !allocatedNames.add(result)) {
+ result += "_"
+ }
+
+ val replaced = tagToName.put(tag, result)
+ if (replaced != null) {
+ tagToName[tag] = replaced // Put things back as they were!
+ throw IllegalArgumentException("tag $tag cannot be used for both '$replaced' and '$result'")
+ }
+
+ return result
+ }
+
+ /** Retrieve a name created with [NameAllocator.newName]. */
+ public operator fun get(tag: Any): String = requireNotNull(tagToName[tag]) { "unknown tag: $tag" }
+
+ /**
+ * Create a deep copy of this NameAllocator. Useful to create multiple independent refinements
+ * of a NameAllocator to be used in the respective definition of multiples, independently-scoped,
+ * inner code blocks.
+ *
+ * @return A deep copy of this NameAllocator.
+ */
+ public fun copy(): NameAllocator {
+ return NameAllocator(allocatedNames.toMutableSet(), tagToName.toMutableMap())
+ }
+}
+
+private fun toJavaIdentifier(suggestion: String) = buildString {
+ var i = 0
+ while (i < suggestion.length) {
+ val codePoint = suggestion.codePointAt(i)
+ if (i == 0 &&
+ !Character.isJavaIdentifierStart(codePoint) &&
+ Character.isJavaIdentifierPart(codePoint)
+ ) {
+ append("_")
+ }
+
+ val validCodePoint: Int = if (Character.isJavaIdentifierPart(codePoint)) {
+ codePoint
+ } else {
+ '_'.code
+ }
+ appendCodePoint(validCodePoint)
+ i += Character.charCount(codePoint)
+ }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/OriginatingElementsHolder.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/OriginatingElementsHolder.kt
new file mode 100644
index 00000000..27008213
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/OriginatingElementsHolder.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import javax.lang.model.element.Element
+
+/** A type that can have originating [elements][Element]. */
+public interface OriginatingElementsHolder {
+
+ /** The originating elements of this type. */
+ public val originatingElements: List<Element>
+
+ /** The builder analogue to [OriginatingElementsHolder] types. */
+ public interface Builder<out T : Builder<T>> {
+
+ /** Mutable map of the current originating elements this builder contains. */
+ public val originatingElements: MutableList<Element>
+
+ /** Adds an [originatingElement] to this type's list of originating elements. */
+ @Suppress("UNCHECKED_CAST")
+ public fun addOriginatingElement(originatingElement: Element): T = apply {
+ originatingElements += originatingElement
+ } as T
+ }
+}
+
+internal fun OriginatingElementsHolder.Builder<*>.buildOriginatingElements() =
+ OriginatingElements(originatingElements.toImmutableList())
+
+internal fun List<Element>.buildOriginatingElements() =
+ OriginatingElements(toImmutableList())
+
+@JvmInline
+internal value class OriginatingElements(
+ override val originatingElements: List<Element>,
+) : OriginatingElementsHolder
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ParameterSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ParameterSpec.kt
new file mode 100644
index 00000000..9c33ecd9
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ParameterSpec.kt
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.KModifier.CROSSINLINE
+import com.squareup.kotlinpoet.KModifier.NOINLINE
+import com.squareup.kotlinpoet.KModifier.VARARG
+import java.lang.reflect.Type
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.VariableElement
+import kotlin.DeprecationLevel.ERROR
+import kotlin.reflect.KClass
+
+/** A generated parameter declaration. */
+public class ParameterSpec private constructor(
+ builder: Builder,
+ private val tagMap: TagMap = builder.buildTagMap(),
+) : Taggable by tagMap {
+ public val name: String = builder.name
+ public val kdoc: CodeBlock = builder.kdoc.build()
+ public val annotations: List<AnnotationSpec> = builder.annotations.toImmutableList()
+ public val modifiers: Set<KModifier> = builder.modifiers
+ .also {
+ LinkedHashSet(it).apply {
+ removeAll(ALLOWED_PARAMETER_MODIFIERS)
+ if (!isEmpty()) {
+ throw IllegalArgumentException("Modifiers $this are not allowed on Kotlin parameters. Allowed modifiers: $ALLOWED_PARAMETER_MODIFIERS")
+ }
+ }
+ }
+ .toImmutableSet()
+ public val type: TypeName = builder.type
+ public val defaultValue: CodeBlock? = builder.defaultValue
+
+ public constructor(name: String, type: TypeName, vararg modifiers: KModifier) :
+ this(builder(name, type, *modifiers))
+ public constructor(name: String, type: TypeName, modifiers: Iterable<KModifier>) :
+ this(builder(name, type, modifiers))
+
+ internal fun emit(
+ codeWriter: CodeWriter,
+ includeType: Boolean = true,
+ emitKdoc: Boolean = false,
+ inlineAnnotations: Boolean = true,
+ ) {
+ if (emitKdoc) codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine())
+ codeWriter.emitAnnotations(annotations, inlineAnnotations)
+ codeWriter.emitModifiers(modifiers)
+ if (name.isNotEmpty()) codeWriter.emitCode("%N", this)
+ if (name.isNotEmpty() && includeType) codeWriter.emitCode(":·")
+ if (includeType) codeWriter.emitCode("%T", type)
+ emitDefaultValue(codeWriter)
+ }
+
+ internal fun emitDefaultValue(codeWriter: CodeWriter) {
+ if (defaultValue != null) {
+ codeWriter.emitCode(if (defaultValue.hasStatements()) " = %L" else " = «%L»", defaultValue)
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null) return false
+ if (javaClass != other.javaClass) return false
+ return toString() == other.toString()
+ }
+
+ override fun hashCode(): Int = toString().hashCode()
+
+ override fun toString(): String = buildCodeString { emit(this) }
+
+ public fun toBuilder(name: String = this.name, type: TypeName = this.type): Builder {
+ val builder = Builder(name, type)
+ builder.kdoc.add(kdoc)
+ builder.annotations += annotations
+ builder.modifiers += modifiers
+ builder.defaultValue = defaultValue
+ builder.tags += tagMap.tags
+ return builder
+ }
+
+ public class Builder internal constructor(
+ internal val name: String,
+ internal val type: TypeName,
+ ) : Taggable.Builder<Builder> {
+ internal var defaultValue: CodeBlock? = null
+
+ public val kdoc: CodeBlock.Builder = CodeBlock.builder()
+ public val annotations: MutableList<AnnotationSpec> = mutableListOf()
+ public val modifiers: MutableList<KModifier> = mutableListOf()
+ override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
+
+ public fun addKdoc(format: String, vararg args: Any): Builder = apply {
+ kdoc.add(format, *args)
+ }
+
+ public fun addKdoc(block: CodeBlock): Builder = apply {
+ kdoc.add(block)
+ }
+
+ public fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>): Builder = apply {
+ annotations += annotationSpecs
+ }
+
+ public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
+ annotations += annotationSpec
+ }
+
+ public fun addAnnotation(annotation: ClassName): Builder = apply {
+ annotations += AnnotationSpec.builder(annotation).build()
+ }
+
+ public fun addAnnotation(annotation: Class<*>): Builder =
+ addAnnotation(annotation.asClassName())
+
+ public fun addAnnotation(annotation: KClass<*>): Builder =
+ addAnnotation(annotation.asClassName())
+
+ public fun addModifiers(vararg modifiers: KModifier): Builder = apply {
+ this.modifiers += modifiers
+ }
+
+ public fun addModifiers(modifiers: Iterable<KModifier>): Builder = apply {
+ this.modifiers += modifiers
+ }
+
+ @Deprecated(
+ "There are no jvm modifiers applicable to parameters in Kotlin",
+ ReplaceWith(""),
+ level = ERROR,
+ )
+ public fun jvmModifiers(modifiers: Iterable<Modifier>): Builder = apply {
+ throw IllegalArgumentException("JVM modifiers are not permitted on parameters in Kotlin")
+ }
+
+ public fun defaultValue(format: String, vararg args: Any?): Builder =
+ defaultValue(CodeBlock.of(format, *args))
+
+ public fun defaultValue(codeBlock: CodeBlock?): Builder = apply {
+ this.defaultValue = codeBlock
+ }
+
+ public fun build(): ParameterSpec = ParameterSpec(this)
+ }
+
+ public companion object {
+ @DelicateKotlinPoetApi(
+ message = "Element APIs don't give complete information on Kotlin types. Consider using" +
+ " the kotlinpoet-metadata APIs instead.",
+ )
+ @JvmStatic
+ public fun get(element: VariableElement): ParameterSpec {
+ val name = element.simpleName.toString()
+ val type = element.asType().asTypeName()
+ return builder(name, type)
+ .build()
+ }
+
+ @DelicateKotlinPoetApi(
+ message = "Element APIs don't give complete information on Kotlin types. Consider using" +
+ " the kotlinpoet-metadata APIs instead.",
+ )
+ @JvmStatic
+ public fun parametersOf(method: ExecutableElement): List<ParameterSpec> =
+ method.parameters.map(::get)
+
+ @JvmStatic public fun builder(
+ name: String,
+ type: TypeName,
+ vararg modifiers: KModifier,
+ ): Builder {
+ return Builder(name, type).addModifiers(*modifiers)
+ }
+
+ @JvmStatic public fun builder(name: String, type: Type, vararg modifiers: KModifier): Builder =
+ builder(name, type.asTypeName(), *modifiers)
+
+ @JvmStatic public fun builder(
+ name: String,
+ type: KClass<*>,
+ vararg modifiers: KModifier,
+ ): Builder = builder(name, type.asTypeName(), *modifiers)
+
+ @JvmStatic public fun builder(
+ name: String,
+ type: TypeName,
+ modifiers: Iterable<KModifier>,
+ ): Builder {
+ return Builder(name, type).addModifiers(modifiers)
+ }
+
+ @JvmStatic public fun builder(
+ name: String,
+ type: Type,
+ modifiers: Iterable<KModifier>,
+ ): Builder = builder(name, type.asTypeName(), modifiers)
+
+ @JvmStatic public fun builder(
+ name: String,
+ type: KClass<*>,
+ modifiers: Iterable<KModifier>,
+ ): Builder = builder(name, type.asTypeName(), modifiers)
+
+ @JvmStatic public fun unnamed(type: KClass<*>): ParameterSpec = unnamed(type.asTypeName())
+
+ @JvmStatic public fun unnamed(type: Type): ParameterSpec = unnamed(type.asTypeName())
+
+ @JvmStatic public fun unnamed(type: TypeName): ParameterSpec = Builder("", type).build()
+ }
+}
+
+// From https://kotlinlang.org/spec/syntax-and-grammar.html#grammar-rule-parameterModifier
+private val ALLOWED_PARAMETER_MODIFIERS = setOf(VARARG, NOINLINE, CROSSINLINE)
+
+internal fun List<ParameterSpec>.emit(
+ codeWriter: CodeWriter,
+ forceNewLines: Boolean = false,
+ emitBlock: (ParameterSpec) -> Unit = { it.emit(codeWriter) },
+) = with(codeWriter) {
+ emit("(")
+
+ if (isNotEmpty()) {
+ val emitNewLines = size > 2 || forceNewLines
+ if (emitNewLines) {
+ emit("\n")
+ indent(1)
+ }
+ forEachIndexed { index, parameter ->
+ if (index > 0) {
+ emit(if (emitNewLines) "\n" else ", ")
+ }
+ emitBlock(parameter)
+ if (emitNewLines) {
+ emit(",")
+ }
+ }
+ if (emitNewLines) {
+ unindent(1)
+ emit("\n")
+ }
+ }
+ emit(")")
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ParameterizedTypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ParameterizedTypeName.kt
new file mode 100644
index 00000000..ffbcf388
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/ParameterizedTypeName.kt
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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.
+ */
+@file:JvmName("ParameterizedTypeNames")
+
+package com.squareup.kotlinpoet
+
+import java.lang.reflect.Modifier
+import java.lang.reflect.ParameterizedType
+import java.lang.reflect.Type
+import kotlin.reflect.KClass
+import kotlin.reflect.KType
+import kotlin.reflect.KTypeParameter
+import kotlin.reflect.KTypeProjection
+import kotlin.reflect.KVariance
+
+public class ParameterizedTypeName internal constructor(
+ private val enclosingType: TypeName?,
+ public val rawType: ClassName,
+ typeArguments: List<TypeName>,
+ nullable: Boolean = false,
+ annotations: List<AnnotationSpec> = emptyList(),
+ tags: Map<KClass<*>, Any> = emptyMap(),
+) : TypeName(nullable, annotations, TagMap(tags)) {
+ public val typeArguments: List<TypeName> = typeArguments.toImmutableList()
+
+ init {
+ require(typeArguments.isNotEmpty() || enclosingType != null) {
+ "no type arguments: $rawType"
+ }
+ }
+
+ override fun copy(
+ nullable: Boolean,
+ annotations: List<AnnotationSpec>,
+ tags: Map<KClass<*>, Any>,
+ ): ParameterizedTypeName {
+ return ParameterizedTypeName(enclosingType, rawType, typeArguments, nullable, annotations, tags)
+ }
+
+ public fun copy(
+ nullable: Boolean = this.isNullable,
+ annotations: List<AnnotationSpec> = this.annotations,
+ tags: Map<KClass<*>, Any> = this.tags,
+ typeArguments: List<TypeName> = this.typeArguments,
+ ): ParameterizedTypeName {
+ return ParameterizedTypeName(enclosingType, rawType, typeArguments, nullable, annotations, tags)
+ }
+
+ public fun plusParameter(typeArgument: TypeName): ParameterizedTypeName =
+ ParameterizedTypeName(
+ enclosingType,
+ rawType,
+ typeArguments + typeArgument,
+ isNullable,
+ annotations,
+ )
+
+ public fun plusParameter(typeArgument: KClass<*>): ParameterizedTypeName =
+ plusParameter(typeArgument.asClassName())
+
+ public fun plusParameter(typeArgument: Class<*>): ParameterizedTypeName =
+ plusParameter(typeArgument.asClassName())
+
+ override fun emit(out: CodeWriter): CodeWriter {
+ if (enclosingType != null) {
+ enclosingType.emitAnnotations(out)
+ enclosingType.emit(out)
+ out.emit("." + rawType.simpleName)
+ } else {
+ rawType.emitAnnotations(out)
+ rawType.emit(out)
+ }
+ if (typeArguments.isNotEmpty()) {
+ out.emit("<")
+ typeArguments.forEachIndexed { index, parameter ->
+ if (index > 0) out.emit(",·")
+ parameter.emitAnnotations(out)
+ parameter.emit(out)
+ parameter.emitNullable(out)
+ }
+ out.emit(">")
+ }
+ return out
+ }
+
+ /**
+ * Returns a new [ParameterizedTypeName] instance for the specified `name` as nested inside this
+ * class, with the specified `typeArguments`.
+ */
+ public fun nestedClass(name: String, typeArguments: List<TypeName>): ParameterizedTypeName =
+ ParameterizedTypeName(this, rawType.nestedClass(name), typeArguments)
+
+ public companion object {
+ /** Returns a parameterized type, applying `typeArguments` to `this`. */
+ @JvmStatic
+ @JvmName("get")
+ public fun ClassName.parameterizedBy(
+ vararg typeArguments: TypeName,
+ ): ParameterizedTypeName = ParameterizedTypeName(null, this, typeArguments.toList())
+
+ /** Returns a parameterized type, applying `typeArguments` to `this`. */
+ @JvmStatic
+ @JvmName("get")
+ public fun KClass<*>.parameterizedBy(
+ vararg typeArguments: KClass<*>,
+ ): ParameterizedTypeName =
+ ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() })
+
+ /** Returns a parameterized type, applying `typeArguments` to `this`. */
+ @JvmStatic
+ @JvmName("get")
+ public fun Class<*>.parameterizedBy(
+ vararg typeArguments: Type,
+ ): ParameterizedTypeName =
+ ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() })
+
+ /** Returns a parameterized type, applying `typeArguments` to `this`. */
+ @JvmStatic
+ @JvmName("get")
+ public fun ClassName.parameterizedBy(
+ typeArguments: List<TypeName>,
+ ): ParameterizedTypeName = ParameterizedTypeName(null, this, typeArguments)
+
+ /** Returns a parameterized type, applying `typeArguments` to `this`. */
+ @JvmStatic
+ @JvmName("get")
+ public fun KClass<*>.parameterizedBy(
+ typeArguments: Iterable<KClass<*>>,
+ ): ParameterizedTypeName =
+ ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() })
+
+ /** Returns a parameterized type, applying `typeArguments` to `this`. */
+ @JvmStatic
+ @JvmName("get")
+ public fun Class<*>.parameterizedBy(
+ typeArguments: Iterable<Type>,
+ ): ParameterizedTypeName =
+ ParameterizedTypeName(null, asClassName(), typeArguments.map { it.asTypeName() })
+
+ /** Returns a parameterized type, applying `typeArgument` to `this`. */
+ @JvmStatic
+ @JvmName("get")
+ public fun ClassName.plusParameter(
+ typeArgument: TypeName,
+ ): ParameterizedTypeName = parameterizedBy(typeArgument)
+
+ /** Returns a parameterized type, applying `typeArgument` to `this`. */
+ @JvmStatic
+ @JvmName("get")
+ public fun KClass<*>.plusParameter(
+ typeArgument: KClass<*>,
+ ): ParameterizedTypeName = parameterizedBy(typeArgument)
+
+ /** Returns a parameterized type, applying `typeArgument` to `this`. */
+ @JvmStatic
+ @JvmName("get")
+ public fun Class<*>.plusParameter(
+ typeArgument: Class<*>,
+ ): ParameterizedTypeName = parameterizedBy(typeArgument)
+
+ /** Returns a parameterized type equivalent to `type`. */
+ internal fun get(
+ type: ParameterizedType,
+ map: MutableMap<Type, TypeVariableName>,
+ ): ParameterizedTypeName {
+ val rawType = (type.rawType as Class<*>).asClassName()
+ val ownerType = if (type.ownerType is ParameterizedType &&
+ !Modifier.isStatic((type.rawType as Class<*>).modifiers)
+ ) {
+ type.ownerType as ParameterizedType
+ } else {
+ null
+ }
+
+ val typeArguments = type.actualTypeArguments.map { get(it, map = map) }
+ return if (ownerType != null) {
+ get(ownerType, map = map).nestedClass(rawType.simpleName, typeArguments)
+ } else {
+ ParameterizedTypeName(null, rawType, typeArguments)
+ }
+ }
+
+ /** Returns a type name equivalent to type with given list of type arguments. */
+ internal fun get(
+ type: KClass<*>,
+ nullable: Boolean,
+ typeArguments: List<KTypeProjection>,
+ ): TypeName {
+ if (typeArguments.isEmpty()) {
+ return type.asTypeName().run { if (nullable) copy(nullable = true) else this }
+ }
+
+ val effectiveType = if (type.java.isArray) Array<Unit>::class else type
+ val enclosingClass = type.java.enclosingClass?.kotlin
+
+ return ParameterizedTypeName(
+ enclosingClass?.let {
+ get(it, false, typeArguments.drop(effectiveType.typeParameters.size))
+ },
+ effectiveType.asTypeName(),
+ typeArguments.take(effectiveType.typeParameters.size).map { (paramVariance, paramType) ->
+ val typeName = paramType?.asTypeName() ?: return@map STAR
+ when (paramVariance) {
+ null -> STAR
+ KVariance.INVARIANT -> typeName
+ KVariance.IN -> WildcardTypeName.consumerOf(typeName)
+ KVariance.OUT -> WildcardTypeName.producerOf(typeName)
+ }
+ },
+ nullable,
+ effectiveType.annotations.map { AnnotationSpec.get(it) },
+ )
+ }
+ }
+}
+
+/** Returns a parameterized type equivalent to `type`. */
+@DelicateKotlinPoetApi(
+ message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+ "using the kotlinpoet-metadata APIs instead.",
+)
+@JvmName("get")
+public fun ParameterizedType.asParameterizedTypeName(): ParameterizedTypeName =
+ ParameterizedTypeName.get(this, mutableMapOf())
+
+/**
+ * Returns a [TypeName] equivalent to the given Kotlin KType using reflection, maybe using kotlin-reflect
+ * if required.
+ */
+public fun KType.asTypeName(): TypeName {
+ val classifier = this.classifier
+ if (classifier is KTypeParameter) {
+ return classifier.asTypeVariableName().run { if (isMarkedNullable) copy(nullable = true) else this }
+ }
+
+ if (classifier == null || classifier !is KClass<*>) {
+ throw IllegalArgumentException("Cannot build TypeName for $this")
+ }
+
+ return ParameterizedTypeName.get(classifier, this.isMarkedNullable, this.arguments)
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt
new file mode 100644
index 00000000..8fb58602
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/PropertySpec.kt
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.FunSpec.Companion.GETTER
+import com.squareup.kotlinpoet.FunSpec.Companion.SETTER
+import com.squareup.kotlinpoet.KModifier.Target.PROPERTY
+import java.lang.reflect.Type
+import java.util.EnumSet
+import javax.lang.model.element.Element
+import kotlin.reflect.KClass
+
+/** A generated property declaration. */
+@OptIn(ExperimentalKotlinPoetApi::class)
+public class PropertySpec private constructor(
+ builder: Builder,
+ private val tagMap: TagMap = builder.buildTagMap(),
+ private val delegateOriginatingElementsHolder: OriginatingElementsHolder = builder.buildOriginatingElements(),
+ private val contextReceivers: ContextReceivers = builder.buildContextReceivers(),
+) : Taggable by tagMap, OriginatingElementsHolder by delegateOriginatingElementsHolder, ContextReceivable by contextReceivers {
+ public val mutable: Boolean = builder.mutable
+ public val name: String = builder.name
+ public val type: TypeName = builder.type
+ public val kdoc: CodeBlock = builder.kdoc.build()
+ public val annotations: List<AnnotationSpec> = builder.annotations.toImmutableList()
+ public val modifiers: Set<KModifier> = builder.modifiers.toImmutableSet()
+ public val typeVariables: List<TypeVariableName> = builder.typeVariables.toImmutableList()
+ public val initializer: CodeBlock? = builder.initializer
+ public val delegated: Boolean = builder.delegated
+ public val getter: FunSpec? = builder.getter
+ public val setter: FunSpec? = builder.setter
+ public val receiverType: TypeName? = builder.receiverType
+
+ init {
+ require(
+ typeVariables.none { it.isReified } ||
+ (getter != null || setter != null) &&
+ (getter == null || KModifier.INLINE in getter.modifiers) &&
+ (setter == null || KModifier.INLINE in setter.modifiers),
+ ) {
+ "only type parameters of properties with inline getters and/or setters can be reified!"
+ }
+ require(mutable || setter == null) {
+ "only a mutable property can have a setter"
+ }
+ if (contextReceiverTypes.isNotEmpty()) {
+ requireNotNull(getter) { "properties with context receivers require a $GETTER" }
+ if (mutable) {
+ requireNotNull(setter) { "mutable properties with context receivers require a $SETTER" }
+ }
+ }
+ }
+
+ internal fun emit(
+ codeWriter: CodeWriter,
+ implicitModifiers: Set<KModifier>,
+ withInitializer: Boolean = true,
+ emitKdoc: Boolean = true,
+ inline: Boolean = false,
+ inlineAnnotations: Boolean = inline,
+ ) {
+ val isInlineProperty = getter?.modifiers?.contains(KModifier.INLINE) ?: false &&
+ (!mutable || setter?.modifiers?.contains(KModifier.INLINE) ?: false)
+ val propertyModifiers = if (isInlineProperty) modifiers + KModifier.INLINE else modifiers
+ if (emitKdoc) {
+ codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine())
+ }
+ codeWriter.emitContextReceivers(contextReceiverTypes, suffix = "\n")
+ codeWriter.emitAnnotations(annotations, inlineAnnotations)
+ codeWriter.emitModifiers(propertyModifiers, implicitModifiers)
+ codeWriter.emitCode(if (mutable) "var·" else "val·")
+ if (typeVariables.isNotEmpty()) {
+ codeWriter.emitTypeVariables(typeVariables)
+ codeWriter.emit(" ")
+ }
+ if (receiverType != null) {
+ if (receiverType is LambdaTypeName) {
+ codeWriter.emitCode("(%T).", receiverType)
+ } else {
+ codeWriter.emitCode("%T.", receiverType)
+ }
+ }
+ codeWriter.emitCode("%N: %T", this, type)
+ if (withInitializer && initializer != null) {
+ if (delegated) {
+ codeWriter.emit(" by ")
+ } else {
+ codeWriter.emitCode(" = ")
+ }
+ val initializerFormat = if (initializer.hasStatements()) "%L" else "«%L»"
+ codeWriter.emitCode(
+ codeBlock = CodeBlock.of(initializerFormat, initializer),
+ isConstantContext = KModifier.CONST in modifiers,
+ )
+ }
+ codeWriter.emitWhereBlock(typeVariables)
+ if (!inline) codeWriter.emit("\n")
+ val implicitAccessorModifiers = EnumSet.noneOf(KModifier::class.java)
+ for (modifier in implicitModifiers) {
+ // Omit visibility modifiers, accessor visibility will default to the property's visibility.
+ if (modifier !in VISIBILITY_MODIFIERS) {
+ implicitAccessorModifiers.add(modifier)
+ }
+ }
+ if (isInlineProperty) {
+ implicitAccessorModifiers.add(KModifier.INLINE)
+ }
+ if (getter != null) {
+ codeWriter.emitCode("⇥")
+ getter.emit(codeWriter, null, implicitAccessorModifiers, false)
+ codeWriter.emitCode("⇤")
+ }
+ if (setter != null) {
+ codeWriter.emitCode("⇥")
+ setter.emit(codeWriter, null, implicitAccessorModifiers, false)
+ codeWriter.emitCode("⇤")
+ }
+ }
+
+ internal fun fromPrimaryConstructorParameter(parameter: ParameterSpec): PropertySpec {
+ val builder = toBuilder()
+ .addAnnotations(parameter.annotations)
+ builder.isPrimaryConstructorParameter = true
+ builder.modifiers += parameter.modifiers
+ if (builder.kdoc.isEmpty()) {
+ builder.addKdoc(parameter.kdoc)
+ }
+ return builder.build()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null) return false
+ if (javaClass != other.javaClass) return false
+ return toString() == other.toString()
+ }
+
+ override fun hashCode(): Int = toString().hashCode()
+
+ override fun toString(): String = buildCodeString { emit(this, emptySet()) }
+
+ @JvmOverloads
+ public fun toBuilder(name: String = this.name, type: TypeName = this.type): Builder {
+ val builder = Builder(name, type)
+ builder.mutable = mutable
+ builder.kdoc.add(kdoc)
+ builder.annotations += annotations
+ builder.modifiers += modifiers
+ builder.typeVariables += typeVariables
+ builder.initializer = initializer
+ builder.delegated = delegated
+ builder.setter = setter
+ builder.getter = getter
+ builder.receiverType = receiverType
+ builder.tags += tagMap.tags
+ builder.originatingElements += originatingElements
+ builder.contextReceiverTypes += contextReceiverTypes
+ return builder
+ }
+
+ public class Builder internal constructor(
+ internal val name: String,
+ internal val type: TypeName,
+ ) : Taggable.Builder<Builder>,
+ OriginatingElementsHolder.Builder<Builder>,
+ ContextReceivable.Builder<Builder> {
+ internal var isPrimaryConstructorParameter = false
+ internal var mutable = false
+ internal val kdoc = CodeBlock.builder()
+ internal var initializer: CodeBlock? = null
+ internal var delegated = false
+ internal var getter: FunSpec? = null
+ internal var setter: FunSpec? = null
+ internal var receiverType: TypeName? = null
+
+ public val annotations: MutableList<AnnotationSpec> = mutableListOf()
+ public val modifiers: MutableList<KModifier> = mutableListOf()
+ public val typeVariables: MutableList<TypeVariableName> = mutableListOf()
+ override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
+ override val originatingElements: MutableList<Element> = mutableListOf()
+ override val contextReceiverTypes: MutableList<TypeName> = mutableListOf()
+
+ /** True to create a `var` instead of a `val`. */
+ public fun mutable(mutable: Boolean = true): Builder = apply {
+ this.mutable = mutable
+ }
+
+ public fun addKdoc(format: String, vararg args: Any): Builder = apply {
+ kdoc.add(format, *args)
+ }
+
+ public fun addKdoc(block: CodeBlock): Builder = apply {
+ kdoc.add(block)
+ }
+
+ public fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>): Builder = apply {
+ annotations += annotationSpecs
+ }
+
+ public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
+ annotations += annotationSpec
+ }
+
+ public fun addAnnotation(annotation: ClassName): Builder = apply {
+ annotations += AnnotationSpec.builder(annotation).build()
+ }
+
+ @DelicateKotlinPoetApi(
+ message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+ "using the kotlinpoet-metadata APIs instead.",
+ )
+ public fun addAnnotation(annotation: Class<*>): Builder =
+ addAnnotation(annotation.asClassName())
+
+ public fun addAnnotation(annotation: KClass<*>): Builder =
+ addAnnotation(annotation.asClassName())
+
+ public fun addModifiers(vararg modifiers: KModifier): Builder = apply {
+ this.modifiers += modifiers
+ }
+
+ public fun addModifiers(modifiers: Iterable<KModifier>): Builder = apply {
+ this.modifiers += modifiers
+ }
+
+ public fun addTypeVariables(typeVariables: Iterable<TypeVariableName>): Builder = apply {
+ this.typeVariables += typeVariables
+ }
+
+ public fun addTypeVariable(typeVariable: TypeVariableName): Builder = apply {
+ typeVariables += typeVariable
+ }
+
+ public fun initializer(format: String, vararg args: Any?): Builder =
+ initializer(CodeBlock.of(format, *args))
+
+ public fun initializer(codeBlock: CodeBlock?): Builder = apply {
+ this.initializer = codeBlock
+ this.delegated = false
+ }
+
+ public fun delegate(format: String, vararg args: Any?): Builder =
+ delegate(CodeBlock.of(format, *args))
+
+ public fun delegate(codeBlock: CodeBlock): Builder = apply {
+ this.initializer = codeBlock
+ this.delegated = true
+ }
+
+ public fun getter(getter: FunSpec?): Builder = apply {
+ require(getter == null || getter.name == GETTER) { "${getter!!.name} is not a getter" }
+ this.getter = getter
+ }
+
+ public fun setter(setter: FunSpec?): Builder = apply {
+ require(setter == null || setter.name == SETTER) { "${setter!!.name} is not a setter" }
+ this.setter = setter
+ }
+
+ public fun receiver(receiverType: TypeName?): Builder = apply {
+ this.receiverType = receiverType
+ }
+
+ @DelicateKotlinPoetApi(
+ message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+ "using the kotlinpoet-metadata APIs instead.",
+ )
+ public fun receiver(receiverType: Type): Builder = receiver(receiverType.asTypeName())
+
+ public fun receiver(receiverType: KClass<*>): Builder = receiver(receiverType.asTypeName())
+
+ public fun build(): PropertySpec {
+ if (KModifier.INLINE in modifiers) {
+ throw IllegalArgumentException(
+ "KotlinPoet doesn't allow setting the inline modifier on " +
+ "properties. You should mark either the getter, the setter, or both inline.",
+ )
+ }
+ for (it in modifiers) {
+ if (!isPrimaryConstructorParameter) it.checkTarget(PROPERTY)
+ }
+ return PropertySpec(this)
+ }
+ }
+
+ public companion object {
+ @JvmStatic public fun builder(
+ name: String,
+ type: TypeName,
+ vararg modifiers: KModifier,
+ ): Builder {
+ return Builder(name, type).addModifiers(*modifiers)
+ }
+
+ @JvmStatic public fun builder(name: String, type: Type, vararg modifiers: KModifier): Builder =
+ builder(name, type.asTypeName(), *modifiers)
+
+ @JvmStatic public fun builder(
+ name: String,
+ type: KClass<*>,
+ vararg modifiers: KModifier,
+ ): Builder = builder(name, type.asTypeName(), *modifiers)
+
+ @JvmStatic public fun builder(
+ name: String,
+ type: TypeName,
+ modifiers: Iterable<KModifier>,
+ ): Builder {
+ return Builder(name, type).addModifiers(modifiers)
+ }
+
+ @DelicateKotlinPoetApi(
+ message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+ "using the kotlinpoet-metadata APIs instead.",
+ )
+ @JvmStatic
+ public fun builder(
+ name: String,
+ type: Type,
+ modifiers: Iterable<KModifier>,
+ ): Builder = builder(name, type.asTypeName(), modifiers)
+
+ @JvmStatic public fun builder(
+ name: String,
+ type: KClass<*>,
+ modifiers: Iterable<KModifier>,
+ ): Builder = builder(name, type.asTypeName(), modifiers)
+ }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Taggable.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Taggable.kt
new file mode 100644
index 00000000..74ff690a
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Taggable.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import kotlin.reflect.KClass
+
+/** A type that can be tagged with extra metadata of the user's choice. */
+public interface Taggable {
+
+ /** Returns all tags. */
+ public val tags: Map<KClass<*>, Any> get() = emptyMap()
+
+ /** Returns the tag attached with [type] as a key, or null if no tag is attached with that key. */
+ public fun <T : Any> tag(type: Class<T>): T? = tag(type.kotlin)
+
+ /** Returns the tag attached with [type] as a key, or null if no tag is attached with that key. */
+ public fun <T : Any> tag(type: KClass<T>): T? {
+ @Suppress("UNCHECKED_CAST")
+ return tags[type] as T?
+ }
+
+ /** The builder analogue to [Taggable] types. */
+ public interface Builder<out T : Builder<T>> {
+
+ /** Mutable map of the current tags this builder contains. */
+ public val tags: MutableMap<KClass<*>, Any>
+
+ /**
+ * Attaches [tag] to the request using [type] as a key. Tags can be read from a
+ * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
+ * [type].
+ *
+ * Use this API to attach originating elements, debugging, or other application data to a spec
+ * so that you may read it in other APIs or callbacks.
+ */
+ public fun tag(type: Class<*>, tag: Any?): T = tag(type.kotlin, tag)
+
+ /**
+ * Attaches [tag] to the request using [type] as a key. Tags can be read from a
+ * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
+ * [type].
+ *
+ * Use this API to attach originating elements, debugging, or other application data to a spec
+ * so that you may read it in other APIs or callbacks.
+ */
+ @Suppress("UNCHECKED_CAST")
+ public fun tag(type: KClass<*>, tag: Any?): T = apply {
+ if (tag == null) {
+ this.tags.remove(type)
+ } else {
+ this.tags[type] = tag
+ }
+ } as T
+ }
+}
+
+/** Returns the tag attached with [T] as a key, or null if no tag is attached with that key. */
+public inline fun <reified T : Any> Taggable.tag(): T? = tag(T::class)
+
+/**
+ * Attaches [tag] to the request using [T] as a key. Tags can be read from a
+ * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
+ * [T].
+ *
+ * Use this API to attach debugging or other application data to a spec so that you may read it in
+ * other APIs or callbacks.
+ */
+
+public inline fun <reified T : Any> AnnotationSpec.Builder.tag(tag: T?): AnnotationSpec.Builder =
+ tag(T::class, tag)
+
+/**
+ * Attaches [tag] to the request using [T] as a key. Tags can be read from a
+ * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
+ * [T].
+ *
+ * Use this API to attach debugging or other application data to a spec so that you may read it in
+ * other APIs or callbacks.
+ */
+public inline fun <reified T : Any> FileSpec.Builder.tag(tag: T?): FileSpec.Builder =
+ tag(T::class, tag)
+
+/**
+ * Attaches [tag] to the request using [T] as a key. Tags can be read from a
+ * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
+ * [T].
+ *
+ * Use this API to attach debugging or other application data to a spec so that you may read it in
+ * other APIs or callbacks.
+ */
+public inline fun <reified T : Any> FunSpec.Builder.tag(tag: T?): FunSpec.Builder =
+ tag(T::class, tag)
+
+/**
+ * Attaches [tag] to the request using [T] as a key. Tags can be read from a
+ * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
+ * [T].
+ *
+ * Use this API to attach debugging or other application data to a spec so that you may read it in
+ * other APIs or callbacks.
+ */
+public inline fun <reified T : Any> ParameterSpec.Builder.tag(tag: T?): ParameterSpec.Builder =
+ tag(T::class, tag)
+
+/**
+ * Attaches [tag] to the request using [T] as a key. Tags can be read from a
+ * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
+ * [T].
+ *
+ * Use this API to attach debugging or other application data to a spec so that you may read it in
+ * other APIs or callbacks.
+ */
+public inline fun <reified T : Any> PropertySpec.Builder.tag(tag: T?): PropertySpec.Builder =
+ tag(T::class, tag)
+
+/**
+ * Attaches [tag] to the request using [T] as a key. Tags can be read from a
+ * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
+ * [T].
+ *
+ * Use this API to attach debugging or other application data to a spec so that you may read it in
+ * other APIs or callbacks.
+ */
+public inline fun <reified T : Any> TypeAliasSpec.Builder.tag(tag: T?): TypeAliasSpec.Builder =
+ tag(T::class, tag)
+
+/**
+ * Attaches [tag] to the request using [T] as a key. Tags can be read from a
+ * request using [Taggable.tag]. Use `null` to remove any existing tag assigned for
+ * [T].
+ *
+ * Use this API to attach debugging or other application data to a spec so that you may read it in
+ * other APIs or callbacks.
+ */
+public inline fun <reified T : Any> TypeSpec.Builder.tag(tag: T?): TypeSpec.Builder =
+ tag(T::class, tag)
+
+internal fun Taggable.Builder<*>.buildTagMap(): TagMap = TagMap(tags)
+
+@JvmInline
+internal value class TagMap private constructor(override val tags: Map<KClass<*>, Any>) : Taggable {
+ companion object {
+ operator fun invoke(tags: Map<KClass<*>, Any>): TagMap = TagMap(tags.toImmutableMap())
+ }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeAliasSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeAliasSpec.kt
new file mode 100644
index 00000000..a0a90e2e
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeAliasSpec.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2017 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.KModifier.ACTUAL
+import com.squareup.kotlinpoet.KModifier.INTERNAL
+import com.squareup.kotlinpoet.KModifier.PRIVATE
+import com.squareup.kotlinpoet.KModifier.PUBLIC
+import java.lang.reflect.Type
+import kotlin.reflect.KClass
+
+/** A generated typealias declaration */
+public class TypeAliasSpec private constructor(
+ builder: Builder,
+ private val tagMap: TagMap = builder.buildTagMap(),
+) : Taggable by tagMap {
+ public val name: String = builder.name
+ public val type: TypeName = builder.type
+ public val modifiers: Set<KModifier> = builder.modifiers.toImmutableSet()
+ public val typeVariables: List<TypeVariableName> = builder.typeVariables.toImmutableList()
+ public val kdoc: CodeBlock = builder.kdoc.build()
+ public val annotations: List<AnnotationSpec> = builder.annotations.toImmutableList()
+
+ internal fun emit(codeWriter: CodeWriter) {
+ codeWriter.emitKdoc(kdoc.ensureEndsWithNewLine())
+ codeWriter.emitAnnotations(annotations, false)
+ codeWriter.emitModifiers(modifiers, setOf(PUBLIC))
+ codeWriter.emitCode("typealias %N", name)
+ codeWriter.emitTypeVariables(typeVariables)
+ codeWriter.emitCode(" = %T", type)
+ codeWriter.emit("\n")
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null) return false
+ if (javaClass != other.javaClass) return false
+ return toString() == other.toString()
+ }
+
+ override fun hashCode(): Int = toString().hashCode()
+
+ override fun toString(): String = buildCodeString { emit(this) }
+
+ @JvmOverloads
+ public fun toBuilder(name: String = this.name, type: TypeName = this.type): Builder {
+ val builder = Builder(name, type)
+ builder.modifiers += modifiers
+ builder.typeVariables += typeVariables
+ builder.kdoc.add(kdoc)
+ builder.annotations += annotations
+ builder.tags += tagMap.tags
+ return builder
+ }
+
+ public class Builder internal constructor(
+ internal val name: String,
+ internal val type: TypeName,
+ ) : Taggable.Builder<Builder> {
+ internal val kdoc = CodeBlock.builder()
+
+ public val modifiers: MutableSet<KModifier> = mutableSetOf()
+ public val typeVariables: MutableSet<TypeVariableName> = mutableSetOf()
+ public val annotations: MutableList<AnnotationSpec> = mutableListOf()
+ override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
+
+ public fun addModifiers(vararg modifiers: KModifier): Builder = apply {
+ modifiers.forEach(this::addModifier)
+ }
+
+ public fun addModifiers(modifiers: Iterable<KModifier>): Builder = apply {
+ modifiers.forEach(this::addModifier)
+ }
+
+ private fun addModifier(modifier: KModifier) {
+ this.modifiers.add(modifier)
+ }
+
+ public fun addTypeVariables(typeVariables: Iterable<TypeVariableName>): Builder = apply {
+ this.typeVariables += typeVariables
+ }
+
+ public fun addTypeVariable(typeVariable: TypeVariableName): Builder = apply {
+ typeVariables += typeVariable
+ }
+
+ public fun addKdoc(format: String, vararg args: Any): Builder = apply {
+ kdoc.add(format, *args)
+ }
+
+ public fun addKdoc(block: CodeBlock): Builder = apply {
+ kdoc.add(block)
+ }
+
+ public fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>): Builder = apply {
+ this.annotations += annotationSpecs
+ }
+
+ public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
+ annotations += annotationSpec
+ }
+
+ public fun addAnnotation(annotation: ClassName): Builder = apply {
+ annotations += AnnotationSpec.builder(annotation).build()
+ }
+
+ @DelicateKotlinPoetApi(
+ message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+ "using the kotlinpoet-metadata APIs instead.",
+ )
+ public fun addAnnotation(annotation: Class<*>): Builder =
+ addAnnotation(annotation.asClassName())
+
+ public fun addAnnotation(annotation: KClass<*>): Builder =
+ addAnnotation(annotation.asClassName())
+
+ public fun build(): TypeAliasSpec {
+ for (it in modifiers) {
+ require(it in ALLOWABLE_MODIFIERS) {
+ "unexpected typealias modifier $it"
+ }
+ }
+ return TypeAliasSpec(this)
+ }
+
+ private companion object {
+ private val ALLOWABLE_MODIFIERS = setOf(PUBLIC, INTERNAL, PRIVATE, ACTUAL)
+ }
+ }
+
+ public companion object {
+ @JvmStatic public fun builder(name: String, type: TypeName): Builder = Builder(name, type)
+
+ @DelicateKotlinPoetApi(
+ message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+ "using the kotlinpoet-metadata APIs instead.",
+ )
+ @JvmStatic
+ public fun builder(name: String, type: Type): Builder =
+ builder(name, type.asTypeName())
+
+ @JvmStatic public fun builder(name: String, type: KClass<*>): Builder =
+ builder(name, type.asTypeName())
+ }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeName.kt
new file mode 100644
index 00000000..bffcf4f1
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeName.kt
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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.
+ */
+@file:JvmName("TypeNames")
+
+package com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import java.lang.reflect.GenericArrayType
+import java.lang.reflect.ParameterizedType
+import java.lang.reflect.Type
+import java.lang.reflect.TypeVariable
+import java.lang.reflect.WildcardType
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.TypeParameterElement
+import javax.lang.model.type.ArrayType
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.ErrorType
+import javax.lang.model.type.NoType
+import javax.lang.model.type.PrimitiveType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.SimpleTypeVisitor7
+import kotlin.reflect.KClass
+import kotlin.reflect.typeOf
+
+/**
+ * Any type in Kotlin's type system. This class identifies simple types like `Int` and `String`,
+ * nullable types like `Int?`, composite types like `Array<String>` and `Set<String>`, and
+ * unassignable types like `Unit`.
+ *
+ * Type names are dumb identifiers only and do not model the values they name. For example, the
+ * type name for `kotlin.List` doesn't know about the `size()` function, the fact that lists are
+ * collections, or even that it accepts a single type parameter.
+ *
+ * Instances of this class are immutable value objects that implement `equals()` and `hashCode()`
+ * properly.
+ *
+ * Referencing existing types
+ * --------------------------
+ *
+ * In an annotation processor you can get a type name instance for a type mirror by calling
+ * [asTypeName]. In reflection code, you can use [asTypeName].
+
+ * Defining new types
+ * ------------------
+ *
+ * Create new reference types like `com.example.HelloWorld` with [ClassName.bestGuess]. To build composite
+ * types like `Set<Long>`, use the factory methods on [ParameterizedTypeName], [TypeVariableName],
+ * and [WildcardTypeName].
+ */
+public sealed class TypeName constructor(
+ public val isNullable: Boolean,
+ annotations: List<AnnotationSpec>,
+ internal val tagMap: TagMap,
+) : Taggable by tagMap {
+ public val annotations: List<AnnotationSpec> = annotations.toImmutableList()
+
+ /** Lazily-initialized toString of this type name. */
+ private val cachedString: String by lazy {
+ buildCodeString {
+ emitAnnotations(this)
+ emit(this)
+ if (isNullable) emit("?")
+ }
+ }
+
+ public fun copy(
+ nullable: Boolean = this.isNullable,
+ annotations: List<AnnotationSpec> = this.annotations.toList(),
+ ): TypeName {
+ return copy(nullable, annotations, this.tags)
+ }
+
+ public abstract fun copy(
+ nullable: Boolean = this.isNullable,
+ annotations: List<AnnotationSpec> = this.annotations.toList(),
+ tags: Map<KClass<*>, Any> = this.tags,
+ ): TypeName
+
+ public val isAnnotated: Boolean get() = annotations.isNotEmpty()
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null) return false
+ if (javaClass != other.javaClass) return false
+ return toString() == other.toString()
+ }
+
+ override fun hashCode(): Int = toString().hashCode()
+
+ override fun toString(): String = cachedString
+
+ internal abstract fun emit(out: CodeWriter): CodeWriter
+
+ internal fun emitAnnotations(out: CodeWriter) {
+ for (annotation in annotations) {
+ annotation.emit(out, true)
+ out.emit(" ")
+ }
+ }
+
+ internal fun emitNullable(out: CodeWriter) {
+ if (isNullable) {
+ out.emit("?")
+ }
+ }
+
+ public companion object {
+ internal fun get(
+ mirror: TypeMirror,
+ typeVariables: Map<TypeParameterElement, TypeVariableName>,
+ ): TypeName {
+ return mirror.accept(
+ object : SimpleTypeVisitor7<TypeName, Void?>() {
+ override fun visitPrimitive(t: PrimitiveType, p: Void?): TypeName {
+ return when (t.kind) {
+ TypeKind.BOOLEAN -> BOOLEAN
+ TypeKind.BYTE -> BYTE
+ TypeKind.SHORT -> SHORT
+ TypeKind.INT -> INT
+ TypeKind.LONG -> LONG
+ TypeKind.CHAR -> CHAR
+ TypeKind.FLOAT -> FLOAT
+ TypeKind.DOUBLE -> DOUBLE
+ else -> throw AssertionError()
+ }
+ }
+
+ override fun visitDeclared(t: DeclaredType, p: Void?): TypeName {
+ val rawType: ClassName = (t.asElement() as TypeElement).asClassName()
+ val enclosingType = t.enclosingType
+ val enclosing = if (enclosingType.kind != TypeKind.NONE &&
+ Modifier.STATIC !in t.asElement().modifiers
+ ) {
+ enclosingType.accept(this, null)
+ } else {
+ null
+ }
+ if (t.typeArguments.isEmpty() && enclosing !is ParameterizedTypeName) {
+ return rawType
+ }
+
+ val typeArgumentNames = mutableListOf<TypeName>()
+ for (typeArgument in t.typeArguments) {
+ typeArgumentNames += get(typeArgument, typeVariables)
+ }
+ return if (enclosing is ParameterizedTypeName) {
+ enclosing.nestedClass(rawType.simpleName, typeArgumentNames)
+ } else {
+ ParameterizedTypeName(null, rawType, typeArgumentNames)
+ }
+ }
+
+ override fun visitError(t: ErrorType, p: Void?): TypeName {
+ return visitDeclared(t, p)
+ }
+
+ override fun visitArray(t: ArrayType, p: Void?): ParameterizedTypeName {
+ return ARRAY.parameterizedBy(get(t.componentType, typeVariables))
+ }
+
+ override fun visitTypeVariable(
+ t: javax.lang.model.type.TypeVariable,
+ p: Void?,
+ ): TypeName {
+ return TypeVariableName.get(t, typeVariables.toMutableMap())
+ }
+
+ override fun visitWildcard(t: javax.lang.model.type.WildcardType, p: Void?): TypeName {
+ return WildcardTypeName.get(t, typeVariables)
+ }
+
+ override fun visitNoType(t: NoType, p: Void?): TypeName {
+ if (t.kind == TypeKind.VOID) return UNIT
+ return super.visitUnknown(t, p)
+ }
+
+ override fun defaultAction(e: TypeMirror?, p: Void?): TypeName {
+ throw IllegalArgumentException("Unexpected type mirror: " + e!!)
+ }
+ },
+ null,
+ )
+ }
+
+ internal fun get(type: Type, map: MutableMap<Type, TypeVariableName>): TypeName {
+ return when (type) {
+ is Class<*> -> when {
+ type === Void.TYPE -> UNIT
+ type === Boolean::class.javaPrimitiveType -> BOOLEAN
+ type === Byte::class.javaPrimitiveType -> BYTE
+ type === Short::class.javaPrimitiveType -> SHORT
+ type === Int::class.javaPrimitiveType -> INT
+ type === Long::class.javaPrimitiveType -> LONG
+ type === Char::class.javaPrimitiveType -> CHAR
+ type === Float::class.javaPrimitiveType -> FLOAT
+ type === Double::class.javaPrimitiveType -> DOUBLE
+ type.isArray -> ARRAY.parameterizedBy(get(type.componentType, map))
+ else -> type.asClassName()
+ }
+ is ParameterizedType -> ParameterizedTypeName.get(type, map)
+ is WildcardType -> WildcardTypeName.get(type, map)
+ is TypeVariable<*> -> TypeVariableName.get(type, map)
+ is GenericArrayType -> ARRAY.parameterizedBy(get(type.genericComponentType, map))
+ else -> throw IllegalArgumentException("unexpected type: $type")
+ }
+ }
+ }
+}
+
+@JvmField public val ANY: ClassName = ClassName("kotlin", "Any")
+
+@JvmField public val ARRAY: ClassName = ClassName("kotlin", "Array")
+
+@JvmField public val UNIT: ClassName = ClassName("kotlin", "Unit")
+
+@JvmField public val BOOLEAN: ClassName = ClassName("kotlin", "Boolean")
+
+@JvmField public val BYTE: ClassName = ClassName("kotlin", "Byte")
+
+@JvmField public val SHORT: ClassName = ClassName("kotlin", "Short")
+
+@JvmField public val INT: ClassName = ClassName("kotlin", "Int")
+
+@JvmField public val LONG: ClassName = ClassName("kotlin", "Long")
+
+@JvmField public val CHAR: ClassName = ClassName("kotlin", "Char")
+
+@JvmField public val FLOAT: ClassName = ClassName("kotlin", "Float")
+
+@JvmField public val DOUBLE: ClassName = ClassName("kotlin", "Double")
+
+@JvmField public val STRING: ClassName = ClassName("kotlin", "String")
+
+@JvmField public val CHAR_SEQUENCE: ClassName = ClassName("kotlin", "CharSequence")
+
+@JvmField public val COMPARABLE: ClassName = ClassName("kotlin", "Comparable")
+
+@JvmField public val THROWABLE: ClassName = ClassName("kotlin", "Throwable")
+
+@JvmField public val ANNOTATION: ClassName = ClassName("kotlin", "Annotation")
+
+@JvmField public val NOTHING: ClassName = ClassName("kotlin", "Nothing")
+
+@JvmField public val NUMBER: ClassName = ClassName("kotlin", "Number")
+
+@JvmField public val ITERABLE: ClassName = ClassName("kotlin.collections", "Iterable")
+
+@JvmField public val COLLECTION: ClassName = ClassName("kotlin.collections", "Collection")
+
+@JvmField public val LIST: ClassName = ClassName("kotlin.collections", "List")
+
+@JvmField public val SET: ClassName = ClassName("kotlin.collections", "Set")
+
+@JvmField public val MAP: ClassName = ClassName("kotlin.collections", "Map")
+
+@JvmField public val MAP_ENTRY: ClassName = MAP.nestedClass("Entry")
+
+@JvmField public val MUTABLE_ITERABLE: ClassName =
+ ClassName("kotlin.collections", "MutableIterable")
+
+@JvmField public val MUTABLE_COLLECTION: ClassName =
+ ClassName("kotlin.collections", "MutableCollection")
+
+@JvmField public val MUTABLE_LIST: ClassName = ClassName("kotlin.collections", "MutableList")
+
+@JvmField public val MUTABLE_SET: ClassName = ClassName("kotlin.collections", "MutableSet")
+
+@JvmField public val MUTABLE_MAP: ClassName = ClassName("kotlin.collections", "MutableMap")
+
+@JvmField public val MUTABLE_MAP_ENTRY: ClassName = MUTABLE_MAP.nestedClass("Entry")
+
+@JvmField public val BOOLEAN_ARRAY: ClassName = ClassName("kotlin", "BooleanArray")
+
+@JvmField public val BYTE_ARRAY: ClassName = ClassName("kotlin", "ByteArray")
+
+@JvmField public val CHAR_ARRAY: ClassName = ClassName("kotlin", "CharArray")
+
+@JvmField public val SHORT_ARRAY: ClassName = ClassName("kotlin", "ShortArray")
+
+@JvmField public val INT_ARRAY: ClassName = ClassName("kotlin", "IntArray")
+
+@JvmField public val LONG_ARRAY: ClassName = ClassName("kotlin", "LongArray")
+
+@JvmField public val FLOAT_ARRAY: ClassName = ClassName("kotlin", "FloatArray")
+
+@JvmField public val DOUBLE_ARRAY: ClassName = ClassName("kotlin", "DoubleArray")
+
+@JvmField public val ENUM: ClassName = ClassName("kotlin", "Enum")
+
+@JvmField public val U_BYTE: ClassName = ClassName("kotlin", "UByte")
+
+@JvmField public val U_SHORT: ClassName = ClassName("kotlin", "UShort")
+
+@JvmField public val U_INT: ClassName = ClassName("kotlin", "UInt")
+
+@JvmField public val U_LONG: ClassName = ClassName("kotlin", "ULong")
+
+@JvmField public val U_BYTE_ARRAY: ClassName = ClassName("kotlin", "UByteArray")
+
+@JvmField public val U_SHORT_ARRAY: ClassName = ClassName("kotlin", "UShortArray")
+
+@JvmField public val U_INT_ARRAY: ClassName = ClassName("kotlin", "UIntArray")
+
+@JvmField public val U_LONG_ARRAY: ClassName = ClassName("kotlin", "ULongArray")
+
+/** The wildcard type `*` which is shorthand for `out Any?`. */
+@JvmField public val STAR: WildcardTypeName = WildcardTypeName.producerOf(ANY.copy(nullable = true))
+
+/** [Dynamic] is a singleton `object` type, so this is a shorthand for it in Java. */
+@JvmField public val DYNAMIC: Dynamic = Dynamic
+
+/** Returns a [TypeName] equivalent to this [TypeMirror]. */
+@DelicateKotlinPoetApi(
+ message = "Mirror APIs don't give complete information on Kotlin types. Consider using" +
+ " the kotlinpoet-metadata APIs instead.",
+)
+@JvmName("get")
+public fun TypeMirror.asTypeName(): TypeName = TypeName.get(this, mutableMapOf())
+
+/** Returns a [TypeName] equivalent to this [KClass]. */
+@JvmName("get")
+public fun KClass<*>.asTypeName(): ClassName = asClassName()
+
+/** Returns a [TypeName] equivalent to this [Type]. */
+@JvmName("get")
+public fun Type.asTypeName(): TypeName = TypeName.get(this, mutableMapOf())
+
+/**
+ * Returns a [TypeName] equivalent of the reified type parameter [T] using reflection, maybe using kotlin-reflect
+ * if required.
+ */
+public inline fun <reified T> typeNameOf(): TypeName = typeOf<T>().asTypeName()
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeSpec.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeSpec.kt
new file mode 100644
index 00000000..c8abca97
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeSpec.kt
@@ -0,0 +1,901 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.KModifier.ABSTRACT
+import com.squareup.kotlinpoet.KModifier.ANNOTATION
+import com.squareup.kotlinpoet.KModifier.COMPANION
+import com.squareup.kotlinpoet.KModifier.ENUM
+import com.squareup.kotlinpoet.KModifier.EXPECT
+import com.squareup.kotlinpoet.KModifier.EXTERNAL
+import com.squareup.kotlinpoet.KModifier.FUN
+import com.squareup.kotlinpoet.KModifier.INLINE
+import com.squareup.kotlinpoet.KModifier.INTERNAL
+import com.squareup.kotlinpoet.KModifier.PRIVATE
+import com.squareup.kotlinpoet.KModifier.PROTECTED
+import com.squareup.kotlinpoet.KModifier.PUBLIC
+import com.squareup.kotlinpoet.KModifier.SEALED
+import com.squareup.kotlinpoet.KModifier.VALUE
+import java.lang.reflect.Type
+import javax.lang.model.element.Element
+import kotlin.reflect.KClass
+
+/** A generated class, interface, or enum declaration. */
+@OptIn(ExperimentalKotlinPoetApi::class)
+public class TypeSpec private constructor(
+ builder: Builder,
+ private val tagMap: TagMap = builder.buildTagMap(),
+ private val delegateOriginatingElements: OriginatingElementsHolder = builder.originatingElements
+ .plus(builder.typeSpecs.flatMap(TypeSpec::originatingElements))
+ .buildOriginatingElements(),
+ private val contextReceivers: ContextReceivers = builder.buildContextReceivers(),
+) : Taggable by tagMap, OriginatingElementsHolder by delegateOriginatingElements, ContextReceivable by contextReceivers {
+ public val kind: Kind = builder.kind
+ public val name: String? = builder.name
+ public val kdoc: CodeBlock = builder.kdoc.build()
+ public val annotationSpecs: List<AnnotationSpec> = builder.annotationSpecs.toImmutableList()
+ public val modifiers: Set<KModifier> = builder.modifiers.toImmutableSet()
+ public val typeVariables: List<TypeVariableName> = builder.typeVariables.toImmutableList()
+ public val primaryConstructor: FunSpec? = builder.primaryConstructor
+ public val superclass: TypeName = builder.superclass
+ public val superclassConstructorParameters: List<CodeBlock> =
+ builder.superclassConstructorParameters.toImmutableList()
+
+ public val isEnum: Boolean = builder.isEnum
+ public val isAnnotation: Boolean = builder.isAnnotation
+ public val isCompanion: Boolean = builder.isCompanion
+ public val isAnonymousClass: Boolean = builder.isAnonymousClass
+ public val isFunctionalInterface: Boolean = builder.isFunInterface
+
+ /**
+ * Map of superinterfaces - entries with a null value represent a regular superinterface (with
+ * no delegation), while non-null [CodeBlock] values represent delegates
+ * for the corresponding [TypeSpec] interface (key) value
+ */
+ public val superinterfaces: Map<TypeName, CodeBlock?> = builder.superinterfaces.toImmutableMap()
+ public val enumConstants: Map<String, TypeSpec> = builder.enumConstants.toImmutableMap()
+ public val propertySpecs: List<PropertySpec> = builder.propertySpecs.toImmutableList()
+ public val initializerBlock: CodeBlock = builder.initializerBlock.build()
+ public val initializerIndex: Int = builder.initializerIndex
+ public val funSpecs: List<FunSpec> = builder.funSpecs.toImmutableList()
+ public val typeSpecs: List<TypeSpec> = builder.typeSpecs.toImmutableList()
+ internal val nestedTypesSimpleNames = typeSpecs.map { it.name }.toImmutableSet()
+
+ @JvmOverloads
+ public fun toBuilder(kind: Kind = this.kind, name: String? = this.name): Builder {
+ val builder = Builder(kind, name)
+ builder.modifiers += modifiers
+ builder.kdoc.add(kdoc)
+ builder.annotationSpecs += annotationSpecs
+ builder.typeVariables += typeVariables
+ builder.superclass = superclass
+ builder.superclassConstructorParameters += superclassConstructorParameters
+ builder.enumConstants += enumConstants
+ builder.propertySpecs += propertySpecs
+ builder.funSpecs += funSpecs
+ builder.typeSpecs += typeSpecs
+ builder.initializerBlock.add(initializerBlock)
+ builder.initializerIndex = initializerIndex
+ builder.superinterfaces.putAll(superinterfaces)
+ builder.primaryConstructor = primaryConstructor
+ builder.tags += tagMap.tags
+ builder.originatingElements += originatingElements
+ builder.contextReceiverTypes += contextReceiverTypes
+ return builder
+ }
+
+ internal fun emit(
+ codeWriter: CodeWriter,
+ enumName: String?,
+ implicitModifiers: Set<KModifier> = emptySet(),
+ isNestedExternal: Boolean = false,
+ ) {
+ // Types.
+ val areNestedExternal = EXTERNAL in modifiers || isNestedExternal
+
+ // Nested classes interrupt wrapped line indentation. Stash the current wrapping state and put
+ // it back afterwards when this type is complete.
+ val previousStatementLine = codeWriter.statementLine
+ codeWriter.statementLine = -1
+
+ val constructorProperties: Map<String, PropertySpec> = constructorProperties()
+ val superclassConstructorParametersBlock = superclassConstructorParameters.joinToCode()
+
+ try {
+ if (enumName != null) {
+ codeWriter.emitKdoc(kdocWithConstructorParameters())
+ codeWriter.emitAnnotations(annotationSpecs, false)
+ codeWriter.emitCode("%N", enumName)
+ if (superclassConstructorParametersBlock.isNotEmpty()) {
+ codeWriter.emit("(")
+ codeWriter.emitCode(superclassConstructorParametersBlock)
+ codeWriter.emit(")")
+ }
+ if (hasNoBody) {
+ return // Avoid unnecessary braces "{}".
+ }
+ codeWriter.emit(" {\n")
+ } else if (isAnonymousClass) {
+ codeWriter.emitCode("object")
+ val supertype = if (superclass != ANY) {
+ if (!areNestedExternal && !modifiers.contains(EXPECT)) {
+ listOf(CodeBlock.of(" %T(%L)", superclass, superclassConstructorParametersBlock))
+ } else {
+ listOf(CodeBlock.of(" %T", superclass))
+ }
+ } else {
+ listOf()
+ }
+
+ val allSuperTypes = supertype + if (superinterfaces.isNotEmpty()) {
+ superinterfaces.keys.map { CodeBlock.of(" %T", it) }
+ } else {
+ emptyList()
+ }
+
+ if (allSuperTypes.isNotEmpty()) {
+ codeWriter.emitCode(" :")
+ codeWriter.emitCode(allSuperTypes.joinToCode(","))
+ }
+ if (hasNoBody) {
+ codeWriter.emit(" {\n}")
+ return
+ }
+ codeWriter.emit(" {\n")
+ } else {
+ codeWriter.emitKdoc(kdocWithConstructorParameters())
+ codeWriter.emitContextReceivers(contextReceiverTypes, suffix = "\n")
+ codeWriter.emitAnnotations(annotationSpecs, false)
+ codeWriter.emitModifiers(
+ modifiers,
+ if (isNestedExternal) setOf(PUBLIC, EXTERNAL) else setOf(PUBLIC),
+ )
+ codeWriter.emit(kind.declarationKeyword)
+ if (name != null) {
+ codeWriter.emitCode(" %N", this)
+ }
+ codeWriter.emitTypeVariables(typeVariables)
+
+ primaryConstructor?.let {
+ codeWriter.pushType(this) // avoid name collisions when emitting primary constructor
+ val emittedAnnotations = it.annotations.isNotEmpty()
+ val useKeyword = it.annotations.isNotEmpty() || it.modifiers.isNotEmpty()
+
+ if (it.annotations.isNotEmpty()) {
+ codeWriter.emit(" ")
+ codeWriter.emitAnnotations(it.annotations, true)
+ }
+
+ if (it.modifiers.isNotEmpty()) {
+ if (!emittedAnnotations) codeWriter.emit(" ")
+ codeWriter.emitModifiers(it.modifiers)
+ }
+
+ if (useKeyword) {
+ codeWriter.emit("constructor")
+ }
+
+ it.parameters.emit(codeWriter, forceNewLines = true) { param ->
+ val property = constructorProperties[param.name]
+ if (property != null) {
+ property.emit(
+ codeWriter,
+ setOf(PUBLIC),
+ withInitializer = false,
+ inline = true,
+ inlineAnnotations = false,
+ )
+ param.emitDefaultValue(codeWriter)
+ } else {
+ param.emit(codeWriter, emitKdoc = true, inlineAnnotations = false)
+ }
+ }
+
+ codeWriter.popType()
+ }
+
+ val types = listOf(superclass).filter { it != ANY }.map {
+ if (primaryConstructor != null || funSpecs.none(FunSpec::isConstructor)) {
+ if (!areNestedExternal && !modifiers.contains(EXPECT)) {
+ CodeBlock.of("%T(%L)", it, superclassConstructorParametersBlock)
+ } else {
+ CodeBlock.of("%T", it)
+ }
+ } else {
+ CodeBlock.of("%T", it)
+ }
+ }
+ val superTypes = types + superinterfaces.entries.map { (type, init) ->
+ if (init == null) CodeBlock.of("%T", type) else CodeBlock.of("%T by %L", type, init)
+ }
+
+ if (superTypes.isNotEmpty()) {
+ codeWriter.emitCode(superTypes.joinToCode(separator = ", ", prefix = " : "))
+ }
+
+ codeWriter.emitWhereBlock(typeVariables)
+
+ if (hasNoBody) {
+ codeWriter.emit("\n")
+ return // Avoid unnecessary braces "{}".
+ }
+ codeWriter.emit(" {\n")
+ }
+
+ codeWriter.pushType(this)
+ codeWriter.indent()
+ var firstMember = true
+ for ((key, value) in enumConstants.entries) {
+ if (!firstMember) codeWriter.emit("\n")
+ value.emit(codeWriter, key)
+ codeWriter.emit(",")
+ firstMember = false
+ }
+ if (isEnum) {
+ if (!firstMember) {
+ codeWriter.emit("\n")
+ }
+ if (propertySpecs.isNotEmpty() || funSpecs.isNotEmpty() || typeSpecs.isNotEmpty()) {
+ codeWriter.emit(";\n")
+ }
+ }
+
+ val cachedHasInitializer = hasInitializer
+ var initializerEmitted = false
+ fun possiblyEmitInitializer() {
+ if (initializerEmitted) return
+ initializerEmitted = true
+ if (cachedHasInitializer) {
+ if (!firstMember) codeWriter.emit("\n")
+ codeWriter.emitCode(initializerBlock)
+ firstMember = false
+ }
+ }
+
+ // Properties and initializer block.
+ for ((index, propertySpec) in propertySpecs.withIndex()) {
+ // Initializer block.
+ if (index == initializerIndex) {
+ possiblyEmitInitializer()
+ }
+ if (constructorProperties.containsKey(propertySpec.name)) {
+ continue
+ }
+ if (!firstMember) codeWriter.emit("\n")
+ propertySpec.emit(codeWriter, kind.implicitPropertyModifiers(modifiers))
+ firstMember = false
+ }
+
+ // One last try in case the initializer index is after all properties
+ possiblyEmitInitializer()
+
+ if (primaryConstructor != null && primaryConstructor.body.isNotEmpty()) {
+ codeWriter.emit("init {\n")
+ codeWriter.indent()
+ codeWriter.emitCode(primaryConstructor.body)
+ codeWriter.unindent()
+ codeWriter.emit("}\n")
+ }
+
+ // Constructors.
+ for (funSpec in funSpecs) {
+ if (!funSpec.isConstructor) continue
+ if (!firstMember) codeWriter.emit("\n")
+ funSpec.emit(codeWriter, name, kind.implicitFunctionModifiers(modifiers + implicitModifiers), false)
+ firstMember = false
+ }
+
+ // Functions.
+ for (funSpec in funSpecs) {
+ if (funSpec.isConstructor) continue
+ if (!firstMember) codeWriter.emit("\n")
+ funSpec.emit(codeWriter, name, kind.implicitFunctionModifiers(modifiers + implicitModifiers), true)
+ firstMember = false
+ }
+
+ for (typeSpec in typeSpecs) {
+ if (!firstMember) codeWriter.emit("\n")
+ typeSpec.emit(codeWriter, null, kind.implicitTypeModifiers(modifiers + implicitModifiers), isNestedExternal = areNestedExternal)
+ firstMember = false
+ }
+
+ codeWriter.unindent()
+ codeWriter.popType()
+
+ codeWriter.emit("}")
+ if (enumName == null && !isAnonymousClass) {
+ codeWriter.emit("\n") // If this type isn't also a value, include a trailing newline.
+ }
+ } finally {
+ codeWriter.statementLine = previousStatementLine
+ }
+ }
+
+ /** Returns the properties that can be declared inline as constructor parameters. */
+ private fun constructorProperties(): Map<String, PropertySpec> {
+ if (primaryConstructor == null) return emptyMap()
+
+ // Properties added after the initializer are not permitted to be inlined into the constructor
+ // due to ordering concerns.
+ val range = if (hasInitializer) {
+ 0 until initializerIndex
+ } else {
+ propertySpecs.indices
+ }
+ val result: MutableMap<String, PropertySpec> = LinkedHashMap()
+ for (propertyIndex in range) {
+ val property = propertySpecs[propertyIndex]
+ if (property.getter != null || property.setter != null) continue
+ val parameter = primaryConstructor.parameter(property.name) ?: continue
+ if (parameter.type != property.type) continue
+ if (!isPropertyInitializerConstructorParameter(property, parameter)) {
+ continue
+ }
+
+ result[property.name] = property.fromPrimaryConstructorParameter(parameter)
+ }
+ return result
+ }
+
+ /**
+ * Returns true if the property can be declared inline as a constructor parameter
+ */
+ private fun isPropertyInitializerConstructorParameter(
+ property: PropertySpec,
+ parameter: ParameterSpec,
+ ): Boolean {
+ val parameterName = CodeBlock.of("%N", parameter).toString()
+ val initializerCode = property.initializer.toString().escapeIfNecessary(validate = false)
+ return parameterName == initializerCode
+ }
+
+ /**
+ * Returns KDoc comments including those of primary constructor parameters.
+ *
+ * If the primary constructor parameter is not mapped to a property, or if the property doesn't
+ * have its own KDoc - the parameter's KDoc will be attached to the parameter. Otherwise, if both
+ * the parameter and the property have KDoc - the property's KDoc will be attached to the
+ * property/parameter, and the parameter's KDoc will be printed in the type header.
+ */
+ private fun kdocWithConstructorParameters(): CodeBlock {
+ if (primaryConstructor == null || primaryConstructor.parameters.isEmpty()) {
+ return kdoc.ensureEndsWithNewLine()
+ }
+ val constructorProperties = constructorProperties()
+ val parametersWithKdoc = primaryConstructor.parameters.filter { parameter ->
+ val propertyKdoc = constructorProperties[parameter.name]?.kdoc ?: CodeBlock.EMPTY
+ return@filter parameter.kdoc.isNotEmpty() && propertyKdoc.isNotEmpty() &&
+ parameter.kdoc != propertyKdoc
+ }
+ return with(kdoc.ensureEndsWithNewLine().toBuilder()) {
+ parametersWithKdoc.forEachIndexed { index, parameter ->
+ if (index == 0 && kdoc.isNotEmpty()) add("\n")
+ add("@param %L %L", parameter.name, parameter.kdoc.ensureEndsWithNewLine())
+ }
+ build()
+ }
+ }
+
+ private val hasInitializer: Boolean get() = initializerIndex != -1 && initializerBlock.isNotEmpty()
+
+ private val hasNoBody: Boolean
+ get() {
+ if (propertySpecs.isNotEmpty()) {
+ val constructorProperties = constructorProperties()
+ for (propertySpec in propertySpecs) {
+ if (!constructorProperties.containsKey(propertySpec.name)) {
+ return false
+ }
+ }
+ }
+ return enumConstants.isEmpty() &&
+ initializerBlock.isEmpty() &&
+ (primaryConstructor?.body?.isEmpty() ?: true) &&
+ funSpecs.isEmpty() &&
+ typeSpecs.isEmpty()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null) return false
+ if (javaClass != other.javaClass) return false
+ return toString() == other.toString()
+ }
+
+ override fun hashCode(): Int = toString().hashCode()
+
+ override fun toString(): String = buildCodeString { emit(this, null) }
+
+ public enum class Kind(
+ internal val declarationKeyword: String,
+ internal val defaultImplicitPropertyModifiers: Set<KModifier>,
+ internal val defaultImplicitFunctionModifiers: Set<KModifier>,
+ internal val defaultImplicitTypeModifiers: Set<KModifier>,
+ ) {
+ CLASS("class", setOf(PUBLIC), setOf(PUBLIC), setOf()),
+ OBJECT("object", setOf(PUBLIC), setOf(PUBLIC), setOf()),
+ INTERFACE("interface", setOf(PUBLIC, ABSTRACT), setOf(PUBLIC, ABSTRACT), setOf()),
+ ;
+
+ internal fun implicitPropertyModifiers(modifiers: Set<KModifier>): Set<KModifier> {
+ return defaultImplicitPropertyModifiers + when {
+ ANNOTATION in modifiers -> emptySet()
+ EXPECT in modifiers -> setOf(EXPECT)
+ EXTERNAL in modifiers -> setOf(EXTERNAL)
+ else -> emptySet()
+ }
+ }
+
+ internal fun implicitFunctionModifiers(modifiers: Set<KModifier> = setOf()): Set<KModifier> {
+ return defaultImplicitFunctionModifiers + when {
+ ANNOTATION in modifiers -> setOf(ABSTRACT)
+ EXPECT in modifiers -> setOf(EXPECT)
+ EXTERNAL in modifiers -> setOf(EXTERNAL)
+ else -> emptySet()
+ }
+ }
+
+ internal fun implicitTypeModifiers(modifiers: Set<KModifier> = setOf()): Set<KModifier> {
+ return defaultImplicitTypeModifiers + when {
+ EXPECT in modifiers -> setOf(EXPECT)
+ EXTERNAL in modifiers -> setOf(EXTERNAL)
+ else -> emptySet()
+ }
+ }
+ }
+
+ public class Builder internal constructor(
+ internal var kind: Kind,
+ internal val name: String?,
+ vararg modifiers: KModifier,
+ ) : Taggable.Builder<Builder>, OriginatingElementsHolder.Builder<Builder>, ContextReceivable.Builder<Builder> {
+ internal val kdoc = CodeBlock.builder()
+ internal var primaryConstructor: FunSpec? = null
+ internal var superclass: TypeName = ANY
+ internal val initializerBlock = CodeBlock.builder()
+ public var initializerIndex: Int = -1
+ internal val isAnonymousClass get() = name == null && kind == Kind.CLASS
+ internal val isExternal get() = EXTERNAL in modifiers
+ internal val isEnum get() = kind == Kind.CLASS && ENUM in modifiers
+ internal val isAnnotation get() = kind == Kind.CLASS && ANNOTATION in modifiers
+ internal val isCompanion get() = kind == Kind.OBJECT && COMPANION in modifiers
+ internal val isInlineOrValClass get() = kind == Kind.CLASS &&
+ (INLINE in modifiers || VALUE in modifiers)
+ internal val isSimpleClass get() = kind == Kind.CLASS && !isEnum && !isAnnotation
+ internal val isFunInterface get() = kind == Kind.INTERFACE && FUN in modifiers
+
+ override val tags: MutableMap<KClass<*>, Any> = mutableMapOf()
+ override val originatingElements: MutableList<Element> = mutableListOf()
+
+ @ExperimentalKotlinPoetApi
+ override val contextReceiverTypes: MutableList<TypeName> = mutableListOf()
+ public val modifiers: MutableSet<KModifier> = mutableSetOf(*modifiers)
+ public val superinterfaces: MutableMap<TypeName, CodeBlock?> = mutableMapOf()
+ public val enumConstants: MutableMap<String, TypeSpec> = mutableMapOf()
+ public val annotationSpecs: MutableList<AnnotationSpec> = mutableListOf()
+ public val typeVariables: MutableList<TypeVariableName> = mutableListOf()
+ public val superclassConstructorParameters: MutableList<CodeBlock> = mutableListOf()
+ public val propertySpecs: MutableList<PropertySpec> = mutableListOf()
+ public val funSpecs: MutableList<FunSpec> = mutableListOf()
+ public val typeSpecs: MutableList<TypeSpec> = mutableListOf()
+
+ public fun addKdoc(format: String, vararg args: Any): Builder = apply {
+ kdoc.add(format, *args)
+ }
+
+ public fun addKdoc(block: CodeBlock): Builder = apply {
+ kdoc.add(block)
+ }
+
+ public fun addAnnotations(annotationSpecs: Iterable<AnnotationSpec>): Builder = apply {
+ this.annotationSpecs += annotationSpecs
+ }
+
+ public fun addAnnotation(annotationSpec: AnnotationSpec): Builder = apply {
+ annotationSpecs += annotationSpec
+ }
+
+ public fun addAnnotation(annotation: ClassName): Builder =
+ addAnnotation(AnnotationSpec.builder(annotation).build())
+
+ @DelicateKotlinPoetApi(
+ message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+ "using the kotlinpoet-metadata APIs instead.",
+ )
+ public fun addAnnotation(annotation: Class<*>): Builder =
+ addAnnotation(annotation.asClassName())
+
+ public fun addAnnotation(annotation: KClass<*>): Builder =
+ addAnnotation(annotation.asClassName())
+
+ public fun addModifiers(vararg modifiers: KModifier): Builder = apply {
+ check(!isAnonymousClass) { "forbidden on anonymous types." }
+ this.modifiers += modifiers
+ }
+
+ public fun addModifiers(modifiers: Iterable<KModifier>): Builder = apply {
+ check(!isAnonymousClass) { "forbidden on anonymous types." }
+ this.modifiers += modifiers
+ }
+
+ public fun addTypeVariables(typeVariables: Iterable<TypeVariableName>): Builder = apply {
+ this.typeVariables += typeVariables
+ }
+
+ public fun addTypeVariable(typeVariable: TypeVariableName): Builder = apply {
+ typeVariables += typeVariable
+ }
+
+ public fun primaryConstructor(primaryConstructor: FunSpec?): Builder = apply {
+ check(kind == Kind.CLASS) {
+ "$kind can't have a primary constructor"
+ }
+ if (primaryConstructor != null) {
+ require(primaryConstructor.isConstructor) {
+ "expected a constructor but was ${primaryConstructor.name}"
+ }
+
+ if (isInlineOrValClass) {
+ check(primaryConstructor.parameters.size == 1) {
+ "value/inline classes must have 1 parameter in constructor"
+ }
+ }
+ }
+ this.primaryConstructor = primaryConstructor
+ }
+
+ public fun superclass(superclass: TypeName): Builder = apply {
+ checkCanHaveSuperclass()
+ check(this.superclass === ANY) { "superclass already set to ${this.superclass}" }
+ this.superclass = superclass
+ }
+
+ private fun checkCanHaveSuperclass() {
+ check(isSimpleClass || kind == Kind.OBJECT) {
+ "only classes can have super classes, not $kind"
+ }
+ check(!isInlineOrValClass) {
+ "value/inline classes cannot have super classes"
+ }
+ }
+
+ private fun checkCanHaveInitializerBlocks() {
+ check(isSimpleClass || isEnum || kind == Kind.OBJECT) {
+ "$kind can't have initializer blocks"
+ }
+ check(EXPECT !in modifiers) {
+ "expect $kind can't have initializer blocks"
+ }
+ }
+
+ @DelicateKotlinPoetApi(
+ message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+ "using the kotlinpoet-metadata APIs instead.",
+ )
+ public fun superclass(superclass: Type): Builder = superclass(superclass.asTypeName())
+
+ public fun superclass(superclass: KClass<*>): Builder = superclass(superclass.asTypeName())
+
+ public fun addSuperclassConstructorParameter(
+ format: String,
+ vararg args: Any,
+ ): Builder = apply {
+ addSuperclassConstructorParameter(CodeBlock.of(format, *args))
+ }
+
+ public fun addSuperclassConstructorParameter(codeBlock: CodeBlock): Builder = apply {
+ checkCanHaveSuperclass()
+ this.superclassConstructorParameters += codeBlock
+ }
+
+ public fun addSuperinterfaces(superinterfaces: Iterable<TypeName>): Builder = apply {
+ this.superinterfaces.putAll(superinterfaces.map { it to null })
+ }
+
+ public fun addSuperinterface(
+ superinterface: TypeName,
+ delegate: CodeBlock = CodeBlock.EMPTY,
+ ): Builder = apply {
+ if (delegate.isEmpty()) {
+ this.superinterfaces[superinterface] = null
+ } else {
+ require(isSimpleClass || kind == Kind.OBJECT) {
+ "delegation only allowed for classes and objects (found $kind '$name')"
+ }
+ require(!superinterface.isNullable) {
+ "expected non-nullable type but was '${superinterface.copy(nullable = false)}'"
+ }
+ require(this.superinterfaces[superinterface] == null) {
+ "'$name' can not delegate to $superinterface by $delegate with existing declaration by " +
+ "${this.superinterfaces[superinterface]}"
+ }
+ this.superinterfaces[superinterface] = delegate
+ }
+ }
+
+ @DelicateKotlinPoetApi(
+ message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+ "using the kotlinpoet-metadata APIs instead.",
+ )
+ public fun addSuperinterface(
+ superinterface: Type,
+ delegate: CodeBlock = CodeBlock.EMPTY,
+ ): Builder = addSuperinterface(superinterface.asTypeName(), delegate)
+
+ public fun addSuperinterface(
+ superinterface: KClass<*>,
+ delegate: CodeBlock = CodeBlock.EMPTY,
+ ): Builder = addSuperinterface(superinterface.asTypeName(), delegate)
+
+ public fun addSuperinterface(
+ superinterface: KClass<*>,
+ constructorParameterName: String,
+ ): Builder = addSuperinterface(superinterface.asTypeName(), constructorParameterName)
+
+ public fun addSuperinterface(
+ superinterface: TypeName,
+ constructorParameter: String,
+ ): Builder = apply {
+ requireNotNull(primaryConstructor) {
+ "delegating to constructor parameter requires not-null constructor"
+ }
+ val parameter = primaryConstructor?.parameter(constructorParameter)
+ requireNotNull(parameter) {
+ "no such constructor parameter '$constructorParameter' to delegate to for type '$name'"
+ }
+ addSuperinterface(superinterface, CodeBlock.of(constructorParameter))
+ }
+
+ @JvmOverloads public fun addEnumConstant(
+ name: String,
+ typeSpec: TypeSpec = anonymousClassBuilder().build(),
+ ): Builder = apply {
+ require(name != "name" && name != "ordinal") {
+ "constant with name \"$name\" conflicts with a supertype member with the same name"
+ }
+ enumConstants[name] = typeSpec
+ }
+
+ public fun addProperties(propertySpecs: Iterable<PropertySpec>): Builder = apply {
+ propertySpecs.map(this::addProperty)
+ }
+
+ public fun addProperty(propertySpec: PropertySpec): Builder = apply {
+ if (EXPECT in modifiers) {
+ require(propertySpec.initializer == null) {
+ "properties in expect classes can't have initializers"
+ }
+ require(propertySpec.getter == null && propertySpec.setter == null) {
+ "properties in expect classes can't have getters and setters"
+ }
+ }
+ if (isEnum) {
+ require(propertySpec.name != "name" && propertySpec.name != "ordinal") {
+ "${propertySpec.name} is a final supertype member and can't be redeclared or overridden"
+ }
+ }
+ propertySpecs += propertySpec
+ }
+
+ public fun addProperty(name: String, type: TypeName, vararg modifiers: KModifier): Builder =
+ addProperty(PropertySpec.builder(name, type, *modifiers).build())
+
+ @DelicateKotlinPoetApi(
+ message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+ "using the kotlinpoet-metadata APIs instead.",
+ )
+ public fun addProperty(name: String, type: Type, vararg modifiers: KModifier): Builder =
+ addProperty(name, type.asTypeName(), *modifiers)
+
+ public fun addProperty(name: String, type: KClass<*>, vararg modifiers: KModifier): Builder =
+ addProperty(name, type.asTypeName(), *modifiers)
+
+ public fun addProperty(name: String, type: TypeName, modifiers: Iterable<KModifier>): Builder =
+ addProperty(PropertySpec.builder(name, type, modifiers).build())
+
+ @DelicateKotlinPoetApi(
+ message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+ "using the kotlinpoet-metadata APIs instead.",
+ )
+ public fun addProperty(name: String, type: Type, modifiers: Iterable<KModifier>): Builder =
+ addProperty(name, type.asTypeName(), modifiers)
+
+ public fun addProperty(name: String, type: KClass<*>, modifiers: Iterable<KModifier>): Builder =
+ addProperty(name, type.asTypeName(), modifiers)
+
+ public fun addInitializerBlock(block: CodeBlock): Builder = apply {
+ checkCanHaveInitializerBlocks()
+ // Set index to however many properties we have
+ // All properties added after this point are declared as such, including any that initialize
+ // to a constructor param.
+ initializerIndex = propertySpecs.size
+ initializerBlock.add("init {\n")
+ .indent()
+ .add(block)
+ .unindent()
+ .add("}\n")
+ }
+
+ public fun addFunctions(funSpecs: Iterable<FunSpec>): Builder = apply {
+ funSpecs.forEach { addFunction(it) }
+ }
+
+ public fun addFunction(funSpec: FunSpec): Builder = apply {
+ funSpecs += funSpec
+ }
+
+ public fun addTypes(typeSpecs: Iterable<TypeSpec>): Builder = apply {
+ this.typeSpecs += typeSpecs
+ }
+
+ public fun addType(typeSpec: TypeSpec): Builder = apply {
+ typeSpecs += typeSpec
+ }
+
+ @ExperimentalKotlinPoetApi
+ override fun contextReceivers(receiverTypes: Iterable<TypeName>): Builder = apply {
+ check(isSimpleClass) { "contextReceivers can only be applied on simple classes" }
+ contextReceiverTypes += receiverTypes
+ }
+
+ public fun build(): TypeSpec {
+ if (enumConstants.isNotEmpty()) {
+ check(isEnum) { "$name is not an enum and cannot have enum constants" }
+ }
+
+ if (superclassConstructorParameters.isNotEmpty()) {
+ checkCanHaveSuperclass()
+
+ check(!isExternal) {
+ "delegated constructor call in external class is not allowed"
+ }
+ }
+ check(!(isExternal && funSpecs.any { it.delegateConstructor != null })) {
+ "delegated constructor call in external class is not allowed"
+ }
+
+ check(!(isAnonymousClass && typeVariables.isNotEmpty())) {
+ "typevariables are forbidden on anonymous types"
+ }
+
+ val isAbstract = ABSTRACT in modifiers || SEALED in modifiers || kind != Kind.CLASS ||
+ !isSimpleClass
+ for (funSpec in funSpecs) {
+ require(isAbstract || ABSTRACT !in funSpec.modifiers) {
+ "non-abstract type $name cannot declare abstract function ${funSpec.name}"
+ }
+ when {
+ kind == Kind.INTERFACE -> {
+ requireNoneOf(funSpec.modifiers, INTERNAL, PROTECTED)
+ requireNoneOrOneOf(funSpec.modifiers, ABSTRACT, PRIVATE)
+ }
+ isAnnotation -> require(funSpec.modifiers == kind.implicitFunctionModifiers(modifiers)) {
+ "annotation class $name.${funSpec.name} " +
+ "requires modifiers ${kind.implicitFunctionModifiers(modifiers)}"
+ }
+ EXPECT in modifiers -> require(funSpec.body.isEmpty()) {
+ "functions in expect classes can't have bodies"
+ }
+ }
+ }
+
+ if (primaryConstructor == null) {
+ require(funSpecs.none { it.isConstructor } || superclassConstructorParameters.isEmpty()) {
+ "types without a primary constructor cannot specify secondary constructors and " +
+ "superclass constructor parameters"
+ }
+ }
+
+ if (isInlineOrValClass) {
+ primaryConstructor?.let {
+ check(it.parameters.size == 1) {
+ "value/inline classes must have 1 parameter in constructor"
+ }
+ }
+
+ check(propertySpecs.size > 0) {
+ "value/inline classes must have at least 1 property"
+ }
+
+ val constructorParamName = primaryConstructor?.parameters?.firstOrNull()?.name
+ constructorParamName?.let { paramName ->
+ val underlyingProperty = propertySpecs.find { it.name == paramName }
+ requireNotNull(underlyingProperty) {
+ "value/inline classes must have a single read-only (val) property parameter."
+ }
+ check(!underlyingProperty.mutable) {
+ "value/inline classes must have a single read-only (val) property parameter."
+ }
+ }
+ check(superclass == Any::class.asTypeName()) {
+ "value/inline classes cannot have super classes"
+ }
+ }
+
+ if (isFunInterface) {
+ // Note: Functional interfaces can contain any number of non-abstract functions.
+ val abstractFunSpecs = funSpecs.filter { ABSTRACT in it.modifiers }
+ check(abstractFunSpecs.size == 1) {
+ "Functional interfaces must have exactly one abstract function. Contained " +
+ "${abstractFunSpecs.size}: ${abstractFunSpecs.map { it.name }}"
+ }
+ }
+
+ when (typeSpecs.count { it.isCompanion }) {
+ 0 -> Unit
+ 1 -> {
+ require(isSimpleClass || kind == Kind.INTERFACE || isEnum || isAnnotation) {
+ "$kind types can't have a companion object"
+ }
+ }
+ else -> {
+ throw IllegalArgumentException("Multiple companion objects are present but only one is allowed.")
+ }
+ }
+
+ return TypeSpec(this)
+ }
+ }
+
+ public companion object {
+ @JvmStatic public fun classBuilder(name: String): Builder = Builder(Kind.CLASS, name)
+
+ @JvmStatic public fun classBuilder(className: ClassName): Builder =
+ classBuilder(className.simpleName)
+
+ @JvmStatic public fun expectClassBuilder(name: String): Builder =
+ Builder(Kind.CLASS, name, EXPECT)
+
+ @JvmStatic public fun expectClassBuilder(className: ClassName): Builder =
+ expectClassBuilder(className.simpleName)
+
+ @JvmStatic public fun valueClassBuilder(name: String): Builder =
+ Builder(Kind.CLASS, name, VALUE)
+
+ @JvmStatic public fun objectBuilder(name: String): Builder = Builder(Kind.OBJECT, name)
+
+ @JvmStatic public fun objectBuilder(className: ClassName): Builder =
+ objectBuilder(className.simpleName)
+
+ @JvmStatic @JvmOverloads
+ public fun companionObjectBuilder(name: String? = null): Builder =
+ Builder(Kind.OBJECT, name, COMPANION)
+
+ @JvmStatic public fun interfaceBuilder(name: String): Builder = Builder(Kind.INTERFACE, name)
+
+ @JvmStatic public fun interfaceBuilder(className: ClassName): Builder =
+ interfaceBuilder(className.simpleName)
+
+ @JvmStatic public fun funInterfaceBuilder(name: String): Builder =
+ Builder(Kind.INTERFACE, name, FUN)
+
+ @JvmStatic public fun funInterfaceBuilder(className: ClassName): Builder =
+ funInterfaceBuilder(className.simpleName)
+
+ @JvmStatic public fun enumBuilder(name: String): Builder = Builder(Kind.CLASS, name, ENUM)
+
+ @JvmStatic public fun enumBuilder(className: ClassName): Builder =
+ enumBuilder(className.simpleName)
+
+ @JvmStatic public fun anonymousClassBuilder(): Builder = Builder(Kind.CLASS, null)
+
+ @JvmStatic public fun annotationBuilder(name: String): Builder =
+ Builder(Kind.CLASS, name, ANNOTATION)
+
+ @JvmStatic public fun annotationBuilder(className: ClassName): Builder =
+ annotationBuilder(className.simpleName)
+ }
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeVariableName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeVariableName.kt
new file mode 100644
index 00000000..b2d7517a
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeVariableName.kt
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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.
+ */
+@file:JvmName("TypeVariableNames")
+
+package com.squareup.kotlinpoet
+
+import java.lang.reflect.Type
+import java.util.Collections
+import javax.lang.model.element.TypeParameterElement
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.type.TypeVariable
+import kotlin.reflect.KClass
+import kotlin.reflect.KType
+import kotlin.reflect.KTypeParameter
+import kotlin.reflect.KVariance
+
+public class TypeVariableName private constructor(
+ public val name: String,
+ public val bounds: List<TypeName>,
+
+ /** Either [KModifier.IN], [KModifier.OUT], or null. */
+ public val variance: KModifier? = null,
+ public val isReified: Boolean = false,
+ nullable: Boolean = false,
+ annotations: List<AnnotationSpec> = emptyList(),
+ tags: Map<KClass<*>, Any> = emptyMap(),
+) : TypeName(nullable, annotations, TagMap(tags)) {
+
+ override fun copy(
+ nullable: Boolean,
+ annotations: List<AnnotationSpec>,
+ tags: Map<KClass<*>, Any>,
+ ): TypeVariableName {
+ return copy(nullable, annotations, this.bounds, this.isReified, tags)
+ }
+
+ public fun copy(
+ nullable: Boolean = this.isNullable,
+ annotations: List<AnnotationSpec> = this.annotations.toList(),
+ bounds: List<TypeName> = this.bounds.toList(),
+ reified: Boolean = this.isReified,
+ tags: Map<KClass<*>, Any> = this.tagMap.tags,
+ ): TypeVariableName {
+ return TypeVariableName(
+ name,
+ bounds.withoutImplicitBound(),
+ variance,
+ reified,
+ nullable,
+ annotations,
+ tags,
+ )
+ }
+
+ private fun List<TypeName>.withoutImplicitBound(): List<TypeName> {
+ return if (size == 1) this else filterNot { it == NULLABLE_ANY }
+ }
+
+ override fun emit(out: CodeWriter) = out.emit(name)
+
+ public companion object {
+ internal fun of(
+ name: String,
+ bounds: List<TypeName>,
+ variance: KModifier?,
+ ): TypeVariableName {
+ require(variance == null || variance.isOneOf(KModifier.IN, KModifier.OUT)) {
+ "$variance is an invalid variance modifier, the only allowed values are in and out!"
+ }
+ require(bounds.isNotEmpty()) {
+ "$name has no bounds"
+ }
+ // Strip Any? from bounds if it is present.
+ return TypeVariableName(name, bounds, variance)
+ }
+
+ /** Returns type variable named `name` with `variance` and without bounds. */
+ @JvmStatic
+ @JvmName("get")
+ @JvmOverloads
+ public operator fun invoke(name: String, variance: KModifier? = null): TypeVariableName =
+ of(name = name, bounds = NULLABLE_ANY_LIST, variance = variance)
+
+ /** Returns type variable named `name` with `variance` and `bounds`. */
+ @JvmStatic
+ @JvmName("get")
+ @JvmOverloads
+ public operator fun invoke(
+ name: String,
+ vararg bounds: TypeName,
+ variance: KModifier? = null,
+ ): TypeVariableName =
+ of(
+ name = name,
+ bounds = bounds.toList().ifEmpty(::NULLABLE_ANY_LIST),
+ variance = variance,
+ )
+
+ /** Returns type variable named `name` with `variance` and `bounds`. */
+ @JvmStatic
+ @JvmName("get")
+ @JvmOverloads
+ public operator fun invoke(
+ name: String,
+ vararg bounds: KClass<*>,
+ variance: KModifier? = null,
+ ): TypeVariableName =
+ of(
+ name = name,
+ bounds = bounds.map(KClass<*>::asTypeName).ifEmpty(::NULLABLE_ANY_LIST),
+ variance = variance,
+ )
+
+ /** Returns type variable named `name` with `variance` and `bounds`. */
+ @JvmStatic
+ @JvmName("get")
+ @JvmOverloads
+ public operator fun invoke(
+ name: String,
+ vararg bounds: Type,
+ variance: KModifier? = null,
+ ): TypeVariableName =
+ of(
+ name = name,
+ bounds = bounds.map(Type::asTypeName).ifEmpty(::NULLABLE_ANY_LIST),
+ variance = variance,
+ )
+
+ /** Returns type variable named `name` with `variance` and `bounds`. */
+ @JvmStatic
+ @JvmName("get")
+ @JvmOverloads
+ public operator fun invoke(
+ name: String,
+ bounds: List<TypeName>,
+ variance: KModifier? = null,
+ ): TypeVariableName = of(name, bounds.ifEmpty(::NULLABLE_ANY_LIST), variance)
+
+ /** Returns type variable named `name` with `variance` and `bounds`. */
+ @JvmStatic
+ @JvmName("getWithClasses")
+ @JvmOverloads
+ public operator fun invoke(
+ name: String,
+ bounds: Iterable<KClass<*>>,
+ variance: KModifier? = null,
+ ): TypeVariableName =
+ of(
+ name,
+ bounds.map { it.asTypeName() }.ifEmpty(::NULLABLE_ANY_LIST),
+ variance,
+ )
+
+ /** Returns type variable named `name` with `variance` and `bounds`. */
+ @JvmStatic
+ @JvmName("getWithTypes")
+ @JvmOverloads
+ public operator fun invoke(
+ name: String,
+ bounds: Iterable<Type>,
+ variance: KModifier? = null,
+ ): TypeVariableName =
+ of(
+ name,
+ bounds.map { it.asTypeName() }.ifEmpty(::NULLABLE_ANY_LIST),
+ variance,
+ )
+
+ /**
+ * Make a TypeVariableName for the given TypeMirror. This form is used internally to avoid
+ * infinite recursion in cases like `Enum<E extends Enum<E>>`. When we encounter such a
+ * thing, we will make a TypeVariableName without bounds and add that to the `typeVariables`
+ * map before looking up the bounds. Then if we encounter this TypeVariable again while
+ * constructing the bounds, we can just return it from the map. And, the code that put the entry
+ * in `variables` will make sure that the bounds are filled in before returning.
+ */
+ internal fun get(
+ mirror: javax.lang.model.type.TypeVariable,
+ typeVariables: MutableMap<TypeParameterElement, TypeVariableName>,
+ ): TypeVariableName {
+ val element = mirror.asElement() as TypeParameterElement
+ var typeVariableName: TypeVariableName? = typeVariables[element]
+ if (typeVariableName == null) {
+ // Since the bounds field is public, we need to make it an unmodifiableList. But we control
+ // the List that that wraps, which means we can change it before returning.
+ val bounds = mutableListOf<TypeName>()
+ val visibleBounds = Collections.unmodifiableList(bounds)
+ typeVariableName = TypeVariableName(element.simpleName.toString(), visibleBounds)
+ typeVariables[element] = typeVariableName
+ for (typeMirror in element.bounds) {
+ bounds += get(typeMirror, typeVariables)
+ }
+ bounds.remove(ANY)
+ bounds.remove(JAVA_OBJECT)
+ if (bounds.isEmpty()) {
+ bounds.add(NULLABLE_ANY)
+ }
+ }
+ return typeVariableName
+ }
+
+ /** Returns type variable equivalent to `type`. */
+ internal fun get(
+ type: java.lang.reflect.TypeVariable<*>,
+ map: MutableMap<Type, TypeVariableName> = mutableMapOf(),
+ ): TypeVariableName {
+ var result: TypeVariableName? = map[type]
+ if (result == null) {
+ val bounds = mutableListOf<TypeName>()
+ val visibleBounds = Collections.unmodifiableList(bounds)
+ result = TypeVariableName(type.name, visibleBounds)
+ map[type] = result
+ for (bound in type.bounds) {
+ bounds += get(bound, map)
+ }
+ bounds.remove(ANY)
+ bounds.remove(JAVA_OBJECT)
+ if (bounds.isEmpty()) {
+ bounds.add(NULLABLE_ANY)
+ }
+ }
+ return result
+ }
+
+ internal val NULLABLE_ANY_LIST = listOf(NULLABLE_ANY)
+ private val JAVA_OBJECT = ClassName("java.lang", "Object")
+ }
+}
+
+/** Returns type variable equivalent to `mirror`. */
+@DelicateKotlinPoetApi(
+ message = "Java reflection APIs don't give complete information on Kotlin types. Consider using" +
+ " the kotlinpoet-metadata APIs instead.",
+)
+@JvmName("get")
+public fun TypeVariable.asTypeVariableName(): TypeVariableName =
+ (asElement() as TypeParameterElement).asTypeVariableName()
+
+/** Returns type variable equivalent to `element`. */
+@DelicateKotlinPoetApi(
+ message = "Element APIs don't give complete information on Kotlin types. Consider using" +
+ " the kotlinpoet-metadata APIs instead.",
+)
+@JvmName("get")
+public fun TypeParameterElement.asTypeVariableName(): TypeVariableName {
+ val name = simpleName.toString()
+ val boundsTypeNames = bounds.map(TypeMirror::asTypeName)
+ .ifEmpty(TypeVariableName.Companion::NULLABLE_ANY_LIST)
+ return TypeVariableName.of(name, boundsTypeNames, variance = null)
+}
+
+public fun KTypeParameter.asTypeVariableName(): TypeVariableName {
+ return TypeVariableName.of(
+ name = name,
+ bounds = upperBounds.map(KType::asTypeName)
+ .ifEmpty(TypeVariableName.Companion::NULLABLE_ANY_LIST),
+ variance = when (variance) {
+ KVariance.INVARIANT -> null
+ KVariance.IN -> KModifier.IN
+ KVariance.OUT -> KModifier.OUT
+ },
+ )
+}
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Util.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Util.kt
new file mode 100644
index 00000000..16a443dd
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/Util.kt
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.CodeBlock.Companion.isPlaceholder
+import java.util.Collections
+
+internal object NullAppendable : Appendable {
+ override fun append(charSequence: CharSequence) = this
+ override fun append(charSequence: CharSequence, start: Int, end: Int) = this
+ override fun append(c: Char) = this
+}
+
+internal fun <K, V> Map<K, V>.toImmutableMap(): Map<K, V> =
+ Collections.unmodifiableMap(LinkedHashMap(this))
+
+internal fun <T> Collection<T>.toImmutableList(): List<T> =
+ Collections.unmodifiableList(ArrayList(this))
+
+internal fun <T> Collection<T>.toImmutableSet(): Set<T> =
+ Collections.unmodifiableSet(LinkedHashSet(this))
+
+internal inline fun <reified T : Enum<T>> Collection<T>.toEnumSet(): Set<T> =
+ enumValues<T>().filterTo(mutableSetOf(), this::contains)
+
+internal fun requireNoneOrOneOf(modifiers: Set<KModifier>, vararg mutuallyExclusive: KModifier) {
+ val count = mutuallyExclusive.count(modifiers::contains)
+ require(count <= 1) {
+ "modifiers $modifiers must contain none or only one of ${mutuallyExclusive.contentToString()}"
+ }
+}
+
+internal fun requireNoneOf(modifiers: Set<KModifier>, vararg forbidden: KModifier) {
+ require(forbidden.none(modifiers::contains)) {
+ "modifiers $modifiers must contain none of ${forbidden.contentToString()}"
+ }
+}
+
+internal fun <T> T.isOneOf(t1: T, t2: T, t3: T? = null, t4: T? = null, t5: T? = null, t6: T? = null) =
+ this == t1 || this == t2 || this == t3 || this == t4 || this == t5 || this == t6
+
+internal fun <T> Collection<T>.containsAnyOf(vararg t: T) = t.any(this::contains)
+
+// see https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6
+internal fun characterLiteralWithoutSingleQuotes(c: Char) = when {
+ c == '\b' -> "\\b" // \u0008: backspace (BS)
+ c == '\t' -> "\\t" // \u0009: horizontal tab (HT)
+ c == '\n' -> "\\n" // \u000a: linefeed (LF)
+ c == '\r' -> "\\r" // \u000d: carriage return (CR)
+ c == '\"' -> "\"" // \u0022: double quote (")
+ c == '\'' -> "\\'" // \u0027: single quote (')
+ c == '\\' -> "\\\\" // \u005c: backslash (\)
+ c.isIsoControl -> String.format("\\u%04x", c.code)
+ else -> c.toString()
+}
+
+internal fun escapeCharacterLiterals(s: String) = buildString {
+ for (c in s) append(characterLiteralWithoutSingleQuotes(c))
+}
+
+private val Char.isIsoControl: Boolean
+ get() {
+ return this in '\u0000'..'\u001F' || this in '\u007F'..'\u009F'
+ }
+
+/** Returns the string literal representing `value`, including wrapping double quotes. */
+internal fun stringLiteralWithQuotes(
+ value: String,
+ isInsideRawString: Boolean = false,
+ isConstantContext: Boolean = false,
+): String {
+ if (!isConstantContext && '\n' in value) {
+ val result = StringBuilder(value.length + 32)
+ result.append("\"\"\"\n|")
+ var i = 0
+ while (i < value.length) {
+ val c = value[i]
+ if (value.regionMatches(i, "\"\"\"", 0, 3)) {
+ // Don't inadvertently end the raw string too early
+ result.append("\"\"\${'\"'}")
+ i += 2
+ } else if (c == '\n') {
+ // Add a '|' after newlines. This pipe will be removed by trimMargin().
+ result.append("\n|")
+ } else if (c == '$' && !isInsideRawString) {
+ // Escape '$' symbols with ${'$'}.
+ result.append("\${\'\$\'}")
+ } else {
+ result.append(c)
+ }
+ i++
+ }
+ // If the last-emitted character wasn't a margin '|', add a blank line. This will get removed
+ // by trimMargin().
+ if (!value.endsWith("\n")) result.append("\n")
+ result.append("\"\"\".trimMargin()")
+ return result.toString()
+ } else {
+ val result = StringBuilder(value.length + 32)
+ // using pre-formatted strings allows us to get away with not escaping symbols that would
+ // normally require escaping, e.g. "foo ${"bar"} baz"
+ if (isInsideRawString) result.append("\"\"\"") else result.append('"')
+ for (c in value) {
+ // Trivial case: single quote must not be escaped.
+ if (c == '\'') {
+ result.append("'")
+ continue
+ }
+ // Trivial case: double quotes must be escaped.
+ if (c == '\"' && !isInsideRawString) {
+ result.append("\\\"")
+ continue
+ }
+ // Trivial case: $ signs must be escaped.
+ if (c == '$' && !isInsideRawString) {
+ result.append("\${\'\$\'}")
+ continue
+ }
+ // Default case: just let character literal do its work.
+ result.append(if (isInsideRawString) c else characterLiteralWithoutSingleQuotes(c))
+ // Need to append indent after linefeed?
+ }
+ if (isInsideRawString) result.append("\"\"\"") else result.append('"')
+ return result.toString()
+ }
+}
+
+internal fun CodeBlock.ensureEndsWithNewLine() = if (isEmpty()) {
+ this
+} else {
+ with(toBuilder()) {
+ val lastFormatPart = trim().formatParts.last()
+ if (lastFormatPart.isPlaceholder && args.isNotEmpty()) {
+ val lastArg = args.last()
+ if (lastArg is String) {
+ args[args.size - 1] = lastArg.trimEnd('\n') + '\n'
+ }
+ } else {
+ formatParts[formatParts.lastIndexOf(lastFormatPart)] = lastFormatPart.trimEnd('\n')
+ formatParts += "\n"
+ }
+ return@with build()
+ }
+}
+
+private val IDENTIFIER_REGEX =
+ (
+ "((\\p{gc=Lu}+|\\p{gc=Ll}+|\\p{gc=Lt}+|\\p{gc=Lm}+|\\p{gc=Lo}+|\\p{gc=Nl}+)+" +
+ "\\d*" +
+ "\\p{gc=Lu}*\\p{gc=Ll}*\\p{gc=Lt}*\\p{gc=Lm}*\\p{gc=Lo}*\\p{gc=Nl}*)" +
+ "|" +
+ "(`[^\n\r`]+`)"
+ )
+ .toRegex()
+
+internal val String.isIdentifier get() = IDENTIFIER_REGEX.matches(this)
+
+// https://kotlinlang.org/docs/reference/keyword-reference.html
+private val KEYWORDS = setOf(
+ // Hard keywords
+ "as",
+ "break",
+ "class",
+ "continue",
+ "do",
+ "else",
+ "false",
+ "for",
+ "fun",
+ "if",
+ "in",
+ "interface",
+ "is",
+ "null",
+ "object",
+ "package",
+ "return",
+ "super",
+ "this",
+ "throw",
+ "true",
+ "try",
+ "typealias",
+ "typeof",
+ "val",
+ "var",
+ "when",
+ "while",
+
+ // Soft keywords
+ "by",
+ "catch",
+ "constructor",
+ "delegate",
+ "dynamic",
+ "field",
+ "file",
+ "finally",
+ "get",
+ "import",
+ "init",
+ "param",
+ "property",
+ "receiver",
+ "set",
+ "setparam",
+ "where",
+
+ // Modifier keywords
+ "actual",
+ "abstract",
+ "annotation",
+ "companion",
+ "const",
+ "crossinline",
+ "data",
+ "enum",
+ "expect",
+ "external",
+ "final",
+ "infix",
+ "inline",
+ "inner",
+ "internal",
+ "lateinit",
+ "noinline",
+ "open",
+ "operator",
+ "out",
+ "override",
+ "private",
+ "protected",
+ "public",
+ "reified",
+ "sealed",
+ "suspend",
+ "tailrec",
+ "value",
+ "vararg",
+
+ // These aren't keywords anymore but still break some code if unescaped. https://youtrack.jetbrains.com/issue/KT-52315
+ "header",
+ "impl",
+
+ // Other reserved keywords
+ "yield",
+)
+
+private const val ALLOWED_CHARACTER = '$'
+
+private const val UNDERSCORE_CHARACTER = '_'
+
+internal val String.isKeyword get() = this in KEYWORDS
+
+internal val String.hasAllowedCharacters get() = this.any { it == ALLOWED_CHARACTER }
+
+internal val String.allCharactersAreUnderscore get() = this.all { it == UNDERSCORE_CHARACTER }
+
+// https://github.com/JetBrains/kotlin/blob/master/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmSimpleNameBacktickChecker.kt
+private val ILLEGAL_CHARACTERS_TO_ESCAPE = setOf('.', ';', '[', ']', '/', '<', '>', ':', '\\')
+
+private fun String.failIfEscapeInvalid() {
+ require(!any { it in ILLEGAL_CHARACTERS_TO_ESCAPE }) {
+ "Can't escape identifier $this because it contains illegal characters: " +
+ ILLEGAL_CHARACTERS_TO_ESCAPE.intersect(this.toSet()).joinToString("")
+ }
+}
+
+internal fun String.escapeIfNecessary(validate: Boolean = true): String = escapeIfNotJavaIdentifier()
+ .escapeIfKeyword()
+ .escapeIfHasAllowedCharacters()
+ .escapeIfAllCharactersAreUnderscore()
+ .apply { if (validate) failIfEscapeInvalid() }
+
+private fun String.alreadyEscaped() = startsWith("`") && endsWith("`")
+
+private fun String.escapeIfKeyword() = if (isKeyword && !alreadyEscaped()) "`$this`" else this
+
+private fun String.escapeIfHasAllowedCharacters() = if (hasAllowedCharacters && !alreadyEscaped()) "`$this`" else this
+
+private fun String.escapeIfAllCharactersAreUnderscore() = if (allCharactersAreUnderscore && !alreadyEscaped()) "`$this`" else this
+
+private fun String.escapeIfNotJavaIdentifier(): String {
+ return if ((
+ !Character.isJavaIdentifierStart(first()) ||
+ drop(1).any { !Character.isJavaIdentifierPart(it) }
+ ) &&
+ !alreadyEscaped()
+ ) {
+ "`$this`".replace(' ', '·')
+ } else {
+ this
+ }
+}
+
+internal fun String.escapeSegmentsIfNecessary(delimiter: Char = '.') = split(delimiter)
+ .filter { it.isNotEmpty() }
+ .joinToString(delimiter.toString()) { it.escapeIfNecessary() }
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/WildcardTypeName.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/WildcardTypeName.kt
new file mode 100644
index 00000000..f7c4dc0c
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/WildcardTypeName.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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.
+ */
+@file:JvmName("WildcardTypeNames")
+
+package com.squareup.kotlinpoet
+
+import java.lang.reflect.Type
+import java.lang.reflect.WildcardType
+import javax.lang.model.element.TypeParameterElement
+import kotlin.reflect.KClass
+
+public class WildcardTypeName private constructor(
+ outTypes: List<TypeName>,
+ inTypes: List<TypeName>,
+ nullable: Boolean = false,
+ annotations: List<AnnotationSpec> = emptyList(),
+ tags: Map<KClass<*>, Any> = emptyMap(),
+) : TypeName(nullable, annotations, TagMap(tags)) {
+ public val outTypes: List<TypeName> = outTypes.toImmutableList()
+ public val inTypes: List<TypeName> = inTypes.toImmutableList()
+
+ init {
+ require(this.outTypes.size == 1) { "unexpected out types: $outTypes" }
+ }
+
+ override fun copy(
+ nullable: Boolean,
+ annotations: List<AnnotationSpec>,
+ tags: Map<KClass<*>, Any>,
+ ): WildcardTypeName {
+ return WildcardTypeName(outTypes, inTypes, nullable, annotations, tags)
+ }
+
+ override fun emit(out: CodeWriter): CodeWriter {
+ return when {
+ inTypes.size == 1 -> out.emitCode("in·%T", inTypes[0])
+ outTypes == STAR.outTypes -> out.emit("*")
+ else -> out.emitCode("out·%T", outTypes[0])
+ }
+ }
+
+ public companion object {
+ /**
+ * Returns a type that represents an unknown type that produces `outType`. For example, if
+ * `outType` is `CharSequence`, this returns `out CharSequence`. If `outType` is `Any?`, this
+ * returns `*`, which is shorthand for `out Any?`.
+ */
+ @JvmStatic public fun producerOf(outType: TypeName): WildcardTypeName =
+ WildcardTypeName(listOf(outType), emptyList())
+
+ @DelicateKotlinPoetApi(
+ message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+ "using the kotlinpoet-metadata APIs instead.",
+ )
+ @JvmStatic
+ public fun producerOf(outType: Type): WildcardTypeName =
+ producerOf(outType.asTypeName())
+
+ @JvmStatic public fun producerOf(outType: KClass<*>): WildcardTypeName =
+ producerOf(outType.asTypeName())
+
+ /**
+ * Returns a type that represents an unknown type that consumes `inType`. For example, if
+ * `inType` is `String`, this returns `in String`.
+ */
+ @JvmStatic public fun consumerOf(inType: TypeName): WildcardTypeName =
+ WildcardTypeName(listOf(ANY), listOf(inType))
+
+ @DelicateKotlinPoetApi(
+ message = "Java reflection APIs don't give complete information on Kotlin types. Consider " +
+ "using the kotlinpoet-metadata APIs instead.",
+ )
+ @JvmStatic
+ public fun consumerOf(inType: Type): WildcardTypeName =
+ consumerOf(inType.asTypeName())
+
+ @JvmStatic public fun consumerOf(inType: KClass<*>): WildcardTypeName =
+ consumerOf(inType.asTypeName())
+
+ internal fun get(
+ mirror: javax.lang.model.type.WildcardType,
+ typeVariables: Map<TypeParameterElement, TypeVariableName>,
+ ): TypeName {
+ val outType = mirror.extendsBound
+ return if (outType == null) {
+ val inType = mirror.superBound
+ if (inType == null) {
+ STAR
+ } else {
+ consumerOf(get(inType, typeVariables))
+ }
+ } else {
+ producerOf(get(outType, typeVariables))
+ }
+ }
+
+ internal fun get(
+ wildcardName: WildcardType,
+ map: MutableMap<Type, TypeVariableName>,
+ ): TypeName {
+ return WildcardTypeName(
+ wildcardName.upperBounds.map { get(it, map = map) },
+ wildcardName.lowerBounds.map { get(it, map = map) },
+ )
+ }
+ }
+}
+
+@DelicateKotlinPoetApi(
+ message = "Mirror APIs don't give complete information on Kotlin types. Consider using" +
+ " the kotlinpoet-metadata APIs instead.",
+)
+@JvmName("get")
+public fun javax.lang.model.type.WildcardType.asWildcardTypeName(): TypeName =
+ WildcardTypeName.get(this, mutableMapOf())
+
+@DelicateKotlinPoetApi(
+ message = "Java reflection APIs don't give complete information on Kotlin types. Consider using" +
+ " the kotlinpoet-metadata APIs instead.",
+)
+@JvmName("get")
+public fun WildcardType.asWildcardTypeName(): TypeName =
+ WildcardTypeName.get(this, mutableMapOf())
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/jvm/JvmAnnotations.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/jvm/JvmAnnotations.kt
new file mode 100644
index 00000000..6f18620e
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/jvm/JvmAnnotations.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2018 Square, Inc.
+ *
+ * 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.
+ */
+@file:JvmName("JvmAnnotations")
+
+package com.squareup.kotlinpoet.jvm
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.FunSpec.Companion.isAccessor
+import com.squareup.kotlinpoet.FunSpec.Companion.isConstructor
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.asTypeName
+import java.lang.reflect.Type
+import kotlin.reflect.KClass
+
+public fun FileSpec.Builder.jvmName(name: String): FileSpec.Builder = addAnnotation(
+ AnnotationSpec.builder(JvmName::class)
+ .useSiteTarget(FILE)
+ .addMember("%S", name)
+ .build(),
+)
+
+public fun FileSpec.Builder.jvmMultifileClass(): FileSpec.Builder = addAnnotation(
+ AnnotationSpec.builder(JvmMultifileClass::class)
+ .useSiteTarget(FILE)
+ .build(),
+)
+
+public fun TypeSpec.Builder.jvmSuppressWildcards(suppress: Boolean = true): TypeSpec.Builder =
+ addAnnotation(jvmSuppressWildcardsAnnotation(suppress))
+
+private fun jvmSuppressWildcardsAnnotation(suppress: Boolean = true) =
+ AnnotationSpec.builder(JvmSuppressWildcards::class)
+ .apply { if (!suppress) addMember("suppress = false") }
+ .build()
+
+public fun TypeSpec.Builder.jvmInline(): TypeSpec.Builder = addAnnotation(JvmInline::class)
+
+public fun TypeSpec.Builder.jvmRecord(): TypeSpec.Builder = addAnnotation(JvmRecord::class)
+
+public fun FunSpec.Builder.jvmStatic(): FunSpec.Builder = apply {
+ check(!name.isConstructor) { "Can't apply @JvmStatic to a constructor!" }
+ addAnnotation(JvmStatic::class)
+}
+
+public fun FunSpec.Builder.jvmOverloads(): FunSpec.Builder = apply {
+ check(!name.isAccessor) {
+ "Can't apply @JvmOverloads to a " + if (name == FunSpec.GETTER) "getter!" else "setter!"
+ }
+ addAnnotation(JvmOverloads::class)
+}
+
+public fun FunSpec.Builder.jvmName(name: String): FunSpec.Builder = apply {
+ check(!this.name.isConstructor) { "Can't apply @JvmName to a constructor!" }
+ addAnnotation(
+ AnnotationSpec.builder(JvmName::class)
+ .addMember("%S", name)
+ .build(),
+ )
+}
+
+public fun FunSpec.Builder.throws(vararg exceptionClasses: KClass<out Throwable>): FunSpec.Builder =
+ throws(exceptionClasses.map(KClass<*>::asTypeName))
+
+public fun FunSpec.Builder.throws(vararg exceptionClasses: Type): FunSpec.Builder =
+ throws(exceptionClasses.map(Type::asTypeName))
+
+public fun FunSpec.Builder.throws(vararg exceptionClasses: TypeName): FunSpec.Builder =
+ throws(exceptionClasses.toList())
+
+public fun FunSpec.Builder.throws(exceptionClasses: Iterable<TypeName>): FunSpec.Builder =
+ addAnnotation(
+ AnnotationSpec.builder(Throws::class)
+ .apply { exceptionClasses.forEach { addMember("%T::class", it) } }
+ .build(),
+ )
+
+public fun FunSpec.Builder.jvmSuppressWildcards(suppress: Boolean = true): FunSpec.Builder = apply {
+ check(!name.isConstructor) { "Can't apply @JvmSuppressWildcards to a constructor!" }
+ check(!name.isAccessor) {
+ "Can't apply @JvmSuppressWildcards to a " + if (name == FunSpec.GETTER) "getter!" else "setter!"
+ }
+ addAnnotation(jvmSuppressWildcardsAnnotation(suppress))
+}
+
+public fun FunSpec.Builder.synchronized(): FunSpec.Builder = apply {
+ check(!name.isConstructor) { "Can't apply @Synchronized to a constructor!" }
+ addAnnotation(Synchronized::class)
+}
+
+public fun FunSpec.Builder.strictfp(): FunSpec.Builder = addAnnotation(Strictfp::class)
+
+public fun PropertySpec.Builder.jvmField(): PropertySpec.Builder = addAnnotation(JvmField::class)
+
+public fun PropertySpec.Builder.jvmStatic(): PropertySpec.Builder = addAnnotation(JvmStatic::class)
+
+public fun PropertySpec.Builder.jvmSuppressWildcards(
+ suppress: Boolean = true,
+): PropertySpec.Builder = addAnnotation(jvmSuppressWildcardsAnnotation(suppress))
+
+public fun PropertySpec.Builder.transient(): PropertySpec.Builder = addAnnotation(Transient::class)
+
+public fun PropertySpec.Builder.volatile(): PropertySpec.Builder = addAnnotation(Volatile::class)
+
+public fun TypeName.jvmSuppressWildcards(suppress: Boolean = true): TypeName =
+ copy(annotations = this.annotations + jvmSuppressWildcardsAnnotation(suppress))
+
+public fun TypeName.jvmWildcard(): TypeName =
+ copy(annotations = this.annotations + AnnotationSpec.builder(JvmWildcard::class).build())
+
+@Suppress("DEPRECATION")
+@Deprecated("'JvmDefault' is deprecated. Switch to new -Xjvm-default modes: `all` or `all-compatibility`")
+public fun PropertySpec.Builder.jvmDefault(): PropertySpec.Builder =
+ addAnnotation(JvmDefault::class)
+
+@Suppress("DEPRECATION")
+@Deprecated("'JvmDefault' is deprecated. Switch to new -Xjvm-default modes: `all` or `all-compatibility`")
+public fun FunSpec.Builder.jvmDefault(): FunSpec.Builder = addAnnotation(JvmDefault::class)
diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/tags/TypeAliasTag.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/tags/TypeAliasTag.kt
new file mode 100644
index 00000000..22be037b
--- /dev/null
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/tags/TypeAliasTag.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.tags
+
+import com.squareup.kotlinpoet.TypeName
+
+/**
+ * This tag indicates that this [TypeName] represents a `typealias` type.
+ *
+ * @property [abbreviatedType] the underlying type for this alias.
+ */
+public class TypeAliasTag(public val abbreviatedType: TypeName)
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AbstractTypesTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AbstractTypesTest.kt
new file mode 100644
index 00000000..b78a96e8
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AbstractTypesTest.kt
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import java.io.Serializable
+import java.nio.charset.Charset
+import javax.lang.model.element.Element
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.ErrorType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.type.TypeVisitor
+import javax.lang.model.util.Elements
+import javax.lang.model.util.Types
+import kotlin.reflect.KTypeProjection
+import kotlin.reflect.KVariance
+import kotlin.reflect.full.createType
+import kotlin.reflect.full.declaredFunctions
+import kotlin.reflect.full.starProjectedType
+import kotlin.reflect.full.withNullability
+import kotlin.test.Ignore
+import kotlin.test.Test
+import kotlin.test.fail
+
+abstract class AbstractTypesTest {
+ protected abstract val elements: Elements
+ protected abstract val types: Types
+
+ private fun getElement(`class`: Class<*>) = elements.getTypeElement(`class`.canonicalName)
+
+ private fun getMirror(`class`: Class<*>) = getElement(`class`).asType()
+
+ @Test fun getBasicTypeMirror() {
+ assertThat(getMirror(Any::class.java).asTypeName())
+ .isEqualTo(Any::class.java.asClassName())
+ assertThat(getMirror(Charset::class.java).asTypeName())
+ .isEqualTo(Charset::class.asClassName())
+ assertThat(getMirror(AbstractTypesTest::class.java).asTypeName())
+ .isEqualTo(AbstractTypesTest::class.asClassName())
+ }
+
+ @Test fun getParameterizedTypeMirror() {
+ val setType = types.getDeclaredType(getElement(Set::class.java), getMirror(String::class.java))
+ assertThat(setType.asTypeName())
+ .isEqualTo(Set::class.asClassName().parameterizedBy(String::class.asClassName()))
+ }
+
+ @Test fun getErrorType() {
+ val errorType = DeclaredTypeAsErrorType(types.getDeclaredType(getElement(Set::class.java)))
+ assertThat(errorType.asTypeName()).isEqualTo(Set::class.asClassName())
+ }
+
+ internal class Parameterized<
+ Simple,
+ ExtendsClass : Number,
+ ExtendsInterface : Runnable,
+ ExtendsTypeVariable : Simple,
+ Intersection,
+ IntersectionOfInterfaces>
+ where IntersectionOfInterfaces : Runnable, Intersection : Number, Intersection : Runnable,
+ IntersectionOfInterfaces : Serializable
+
+ @Test fun getTypeVariableTypeMirror() {
+ val typeVariables = getElement(Parameterized::class.java).typeParameters
+
+ // Members of converted types use ClassName and not Class<?>.
+ val number = Number::class.asClassName()
+ val runnable = Runnable::class.asClassName()
+ val serializable = Serializable::class.asClassName()
+
+ assertThat(typeVariables[0].asType().asTypeName())
+ .isEqualTo(TypeVariableName("Simple"))
+ assertThat(typeVariables[1].asType().asTypeName())
+ .isEqualTo(TypeVariableName("ExtendsClass", number))
+ assertThat(typeVariables[2].asType().asTypeName())
+ .isEqualTo(TypeVariableName("ExtendsInterface", runnable))
+ assertThat(typeVariables[3].asType().asTypeName())
+ .isEqualTo(TypeVariableName("ExtendsTypeVariable", TypeVariableName("Simple")))
+ assertThat(typeVariables[4].asType().asTypeName())
+ .isEqualTo(TypeVariableName("Intersection", number, runnable))
+ assertThat(typeVariables[5].asType().asTypeName())
+ .isEqualTo(TypeVariableName("IntersectionOfInterfaces", runnable, serializable))
+ assertThat((typeVariables[4].asType().asTypeName() as TypeVariableName).bounds)
+ .containsExactly(number, runnable)
+ }
+
+ internal class Recursive<T : Map<List<T>, Set<Array<T>>>>
+
+ @Test fun getTypeVariableTypeMirrorRecursive() {
+ val typeMirror = getElement(Recursive::class.java).asType()
+ val typeName = typeMirror.asTypeName() as ParameterizedTypeName
+ val className = Recursive::class.java.canonicalName
+ assertThat(typeName.toString()).isEqualTo("$className<T>")
+
+ val typeVariableName = typeName.typeArguments[0] as TypeVariableName
+ assertThat(typeVariableName.toString()).isEqualTo("T")
+ assertThat(typeVariableName.bounds.toString())
+ .isEqualTo("[kotlin.collections.Map<kotlin.collections.List<out T>, out kotlin.collections.Set<out kotlin.Array<T>>>]")
+ }
+
+ @Test fun getPrimitiveTypeMirror() {
+ assertThat(types.getPrimitiveType(TypeKind.BOOLEAN).asTypeName()).isEqualTo(BOOLEAN)
+ assertThat(types.getPrimitiveType(TypeKind.BYTE).asTypeName()).isEqualTo(BYTE)
+ assertThat(types.getPrimitiveType(TypeKind.SHORT).asTypeName()).isEqualTo(SHORT)
+ assertThat(types.getPrimitiveType(TypeKind.INT).asTypeName()).isEqualTo(INT)
+ assertThat(types.getPrimitiveType(TypeKind.LONG).asTypeName()).isEqualTo(LONG)
+ assertThat(types.getPrimitiveType(TypeKind.CHAR).asTypeName()).isEqualTo(CHAR)
+ assertThat(types.getPrimitiveType(TypeKind.FLOAT).asTypeName()).isEqualTo(FLOAT)
+ assertThat(types.getPrimitiveType(TypeKind.DOUBLE).asTypeName()).isEqualTo(DOUBLE)
+ }
+
+ @Test fun getArrayTypeMirror() {
+ assertThat(types.getArrayType(getMirror(String::class.java)).asTypeName())
+ .isEqualTo(ARRAY.parameterizedBy(String::class.asClassName()))
+ }
+
+ @Test fun getVoidTypeMirror() {
+ assertThat(types.getNoType(TypeKind.VOID).asTypeName()).isEqualTo(UNIT)
+ }
+
+ @Test fun getNullTypeMirror() {
+ try {
+ types.nullType.asTypeName()
+ fail()
+ } catch (expected: IllegalArgumentException) {
+ }
+ }
+
+ @Test fun parameterizedType() {
+ val type = Map::class.parameterizedBy(String::class, Long::class)
+ assertThat(type.toString()).isEqualTo("kotlin.collections.Map<kotlin.String, kotlin.Long>")
+ }
+
+ @Test fun starProjection() {
+ assertThat(STAR.toString()).isEqualTo("*")
+ }
+
+ @Ignore("Figure out what this maps to in Kotlin.")
+ @Test fun starProjectionFromMirror() {
+ val wildcard = types.getWildcardType(null, null)
+ val type = wildcard.asTypeName()
+ assertThat(type.toString()).isEqualTo("*")
+ }
+
+ @Test fun varianceOutType() {
+ val type = WildcardTypeName.producerOf(CharSequence::class)
+ assertThat(type.toString()).isEqualTo("out java.lang.CharSequence")
+ }
+
+ @Test fun varianceOutTypeFromMirror() {
+ val types = types
+ val elements = elements
+ val charSequence = elements.getTypeElement(CharSequence::class.java.name).asType()
+ val wildcard = types.getWildcardType(charSequence, null)
+ val type = wildcard.asTypeName()
+ assertThat(type.toString()).isEqualTo("out java.lang.CharSequence")
+ }
+
+ @Test fun varianceInType() {
+ val type = WildcardTypeName.consumerOf(String::class)
+ assertThat(type.toString()).isEqualTo("in kotlin.String")
+ }
+
+ @Test fun varianceInTypeFromMirror() {
+ val types = types
+ val elements = elements
+ val string = elements.getTypeElement(String::class.java.name).asType()
+ val wildcard = types.getWildcardType(null, string)
+ val type = wildcard.asTypeName()
+ assertThat(type.toString()).isEqualTo("in kotlin.String")
+ }
+
+ @Test fun typeVariable() {
+ val type = TypeVariableName("T", CharSequence::class)
+ assertThat(type.toString()).isEqualTo("T") // (Bounds are only emitted in declaration.)
+ }
+
+ @Test fun kType() {
+ assertThat(Map::class.starProjectedType.asTypeName().toString())
+ .isEqualTo("kotlin.collections.Map<*, *>")
+ assertThat(Map::class.createType(listOf(KTypeProjection(KVariance.INVARIANT, String::class.createType(emptyList())), KTypeProjection.STAR)).asTypeName().toString())
+ .isEqualTo("kotlin.collections.Map<kotlin.String, *>")
+ assertThat(Map.Entry::class.createType(listOf(KTypeProjection(KVariance.INVARIANT, String::class.createType(emptyList())), KTypeProjection.STAR)).asTypeName().toString())
+ .isEqualTo("kotlin.collections.Map.Entry<kotlin.String, *>")
+ assertThat(Any::class.starProjectedType.withNullability(true).asTypeName().toString())
+ .isEqualTo("kotlin.Any?")
+
+ val treeMapClass = java.util.TreeMap::class
+ assertThat(treeMapClass.declaredFunctions.find { it.name == "parentOf" }!!.returnType.asTypeName().toString())
+ .isEqualTo("java.util.TreeMap.Entry<K, V>")
+ }
+
+ private class DeclaredTypeAsErrorType(private val declaredType: DeclaredType) : ErrorType {
+ override fun asElement(): Element = declaredType.asElement()
+
+ override fun getEnclosingType(): TypeMirror = declaredType.enclosingType
+
+ override fun getTypeArguments(): MutableList<out TypeMirror> = declaredType.typeArguments
+
+ override fun getKind(): TypeKind = declaredType.kind
+
+ override fun <R, P> accept(typeVisitor: TypeVisitor<R, P>, p: P): R = typeVisitor.visitError(this, p)
+
+ override fun <A : Annotation> getAnnotationsByType(annotationType: Class<A>): Array<A> =
+ throw UnsupportedOperationException()
+
+ override fun <A : Annotation> getAnnotation(annotationType: Class<A>): A = throw UnsupportedOperationException()
+
+ override fun getAnnotationMirrors() = throw UnsupportedOperationException()
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AnnotatedTypeNameTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AnnotatedTypeNameTest.kt
new file mode 100644
index 00000000..3e7b766c
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AnnotatedTypeNameTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import kotlin.test.Ignore
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertTrue
+
+class AnnotatedTypeNameTest {
+ private val NEVER_NULL = AnnotationSpec.builder(NeverNull::class).build()
+ private val NN = NeverNull::class.java.canonicalName
+
+ annotation class NeverNull
+
+ @Test fun annotated() {
+ val simpleString = String::class.asTypeName()
+ assertFalse(simpleString.isAnnotated)
+ assertEquals(simpleString, String::class.asTypeName())
+ val annotated = simpleString.copy(annotations = simpleString.annotations + NEVER_NULL)
+ assertTrue(annotated.isAnnotated)
+ }
+
+ @Test fun annotatedType() {
+ val expected = "@$NN kotlin.String"
+ val type = String::class.asTypeName()
+ val actual = type.copy(annotations = type.annotations + NEVER_NULL).toString()
+ assertEquals(expected, actual)
+ }
+
+ @Test fun annotatedTwice() {
+ val expected = "@$NN @java.lang.Override kotlin.String"
+ val type = String::class.asTypeName()
+ val actual = type
+ .copy(
+ annotations = type.annotations + NEVER_NULL +
+ AnnotationSpec.builder(Override::class).build(),
+ )
+ .toString()
+ assertEquals(expected, actual)
+ }
+
+ @Test fun annotatedParameterizedType() {
+ val expected = "@$NN kotlin.collections.List<kotlin.String>"
+ val type = List::class.parameterizedBy(String::class)
+ val actual = type.copy(annotations = type.annotations + NEVER_NULL).toString()
+ assertEquals(expected, actual)
+ }
+
+ @Test fun annotatedArgumentOfParameterizedType() {
+ val expected = "kotlin.collections.List<@$NN kotlin.String>"
+ val type = String::class.asTypeName().copy(annotations = listOf(NEVER_NULL))
+ val list = List::class.asClassName()
+ val actual = list.parameterizedBy(type).toString()
+ assertEquals(expected, actual)
+ }
+
+ @Test fun annotatedWildcardTypeNameWithSuper() {
+ val expected = "in @$NN kotlin.String"
+ val type = String::class.asTypeName().copy(annotations = listOf(NEVER_NULL))
+ val actual = WildcardTypeName.consumerOf(type).toString()
+ assertEquals(expected, actual)
+ }
+
+ @Test fun annotatedWildcardTypeNameWithExtends() {
+ val expected = "out @$NN kotlin.String"
+ val type = String::class.asTypeName().copy(annotations = listOf(NEVER_NULL))
+ val actual = WildcardTypeName.producerOf(type).toString()
+ assertEquals(expected, actual)
+ }
+
+ @Test fun annotatedEquivalence() {
+ annotatedEquivalence(UNIT)
+ annotatedEquivalence(Any::class.asClassName())
+ annotatedEquivalence(List::class.parameterizedBy(Any::class))
+ annotatedEquivalence(TypeVariableName("A"))
+ annotatedEquivalence(WildcardTypeName.producerOf(Object::class))
+ }
+
+ private fun annotatedEquivalence(type: TypeName) {
+ assertFalse(type.isAnnotated)
+ assertEquals(type, type)
+ assertEquals(
+ type.copy(annotations = listOf(NEVER_NULL)),
+ type.copy(annotations = listOf(NEVER_NULL)),
+ )
+ assertNotEquals(type, type.copy(annotations = listOf(NEVER_NULL)))
+ assertEquals(type.hashCode().toLong(), type.hashCode().toLong())
+ assertEquals(
+ type.copy(annotations = listOf(NEVER_NULL)).hashCode().toLong(),
+ type.copy(annotations = listOf(NEVER_NULL)).hashCode().toLong(),
+ )
+ assertNotEquals(
+ type.hashCode().toLong(),
+ type.copy(annotations = listOf(NEVER_NULL)).hashCode().toLong(),
+ )
+ }
+
+ // https://github.com/square/javapoet/issues/431
+ // @Target(ElementType.TYPE_USE) requires Java 1.8
+ annotation class TypeUseAnnotation
+
+ // https://github.com/square/javapoet/issues/431
+ @Ignore @Test
+ fun annotatedNestedType() {
+ val expected = "kotlin.collections.Map.@" + TypeUseAnnotation::class.java.canonicalName + " Entry"
+ val typeUseAnnotation = AnnotationSpec.builder(TypeUseAnnotation::class).build()
+ val type = Map.Entry::class.asTypeName().copy(annotations = listOf(typeUseAnnotation))
+ val actual = type.toString()
+ assertEquals(expected, actual)
+ }
+
+ // https://github.com/square/javapoet/issues/431
+ @Ignore @Test
+ fun annotatedNestedParameterizedType() {
+ val expected = "kotlin.collections.Map.@" + TypeUseAnnotation::class.java.canonicalName +
+ " Entry<kotlin.Byte, kotlin.Byte>"
+ val typeUseAnnotation = AnnotationSpec.builder(TypeUseAnnotation::class).build()
+ val type = Map.Entry::class.parameterizedBy(Byte::class, Byte::class)
+ .copy(annotations = listOf(typeUseAnnotation))
+ val actual = type.toString()
+ assertEquals(expected, actual)
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AnnotationSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AnnotationSpecTest.kt
new file mode 100644
index 00000000..4f770406
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AnnotationSpecTest.kt
@@ -0,0 +1,601 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.compile.CompilationRule
+import com.squareup.kotlinpoet.KModifier.OVERRIDE
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.plusParameter
+import java.lang.annotation.Inherited
+import kotlin.reflect.KClass
+import kotlin.test.Test
+import org.junit.Rule
+
+class AnnotationSpecTest {
+
+ @Retention(AnnotationRetention.RUNTIME)
+ annotation class AnnotationA
+
+ @Inherited
+ @Retention(AnnotationRetention.RUNTIME)
+ annotation class AnnotationB
+
+ @Retention(AnnotationRetention.RUNTIME)
+ annotation class AnnotationC(val value: String)
+
+ enum class Breakfast {
+ WAFFLES, PANCAKES;
+
+ override fun toString(): String {
+ return "$name with cherries!"
+ }
+ }
+
+ @Retention(AnnotationRetention.RUNTIME)
+ annotation class HasDefaultsAnnotation(
+ val a: Byte = 5,
+ val b: Short = 6,
+ val c: Int = 7,
+ val d: Long = 8,
+ val e: Float = 9.0f,
+ val f: Double = 10.0,
+ val g: CharArray = ['\u0000', '\uCAFE', 'z', '€', 'ℕ', '"', '\'', '\t', '\n'],
+ val h: Boolean = true,
+ val i: Breakfast = Breakfast.WAFFLES,
+ val j: AnnotationA = AnnotationA(),
+ val k: String = "maple",
+ val l: KClass<out Annotation> = AnnotationB::class,
+ val m: IntArray = [1, 2, 3],
+ val n: Array<Breakfast> = [Breakfast.WAFFLES, Breakfast.PANCAKES],
+ val o: Breakfast,
+ val p: Int,
+ val q: AnnotationC = AnnotationC("foo"),
+ val r: Array<KClass<out Number>> = [Byte::class, Short::class, Int::class, Long::class],
+ )
+
+ @HasDefaultsAnnotation(
+ o = Breakfast.PANCAKES,
+ p = 1701,
+ f = 11.1,
+ m = [9, 8, 1],
+ l = Override::class,
+ j = AnnotationA(),
+ q = AnnotationC("bar"),
+ r = [Float::class, Double::class],
+ )
+ inner class IsAnnotated
+
+ @Rule @JvmField
+ val compilation = CompilationRule()
+
+ @Test fun equalsAndHashCode() {
+ var a = AnnotationSpec.builder(AnnotationC::class.java).build()
+ var b = AnnotationSpec.builder(AnnotationC::class.java).build()
+ assertThat(a == b).isTrue()
+ assertThat(a.hashCode()).isEqualTo(b.hashCode())
+ a = AnnotationSpec.builder(AnnotationC::class.java).addMember("value", "%S", "123").build()
+ b = AnnotationSpec.builder(AnnotationC::class.java).addMember("value", "%S", "123").build()
+ assertThat(a == b).isTrue()
+ assertThat(a.hashCode()).isEqualTo(b.hashCode())
+ }
+
+ @Test fun defaultAnnotation() {
+ val name = IsAnnotated::class.java.canonicalName
+ val element = compilation.elements.getTypeElement(name)
+ val annotation = AnnotationSpec.get(element.annotationMirrors[0])
+
+ assertThat(toString(annotation)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.kotlinpoet.AnnotationSpecTest
+ |import java.lang.Override
+ |import kotlin.Double
+ |import kotlin.Float
+ |
+ |@AnnotationSpecTest.HasDefaultsAnnotation(
+ | f = 11.1,
+ | j = AnnotationSpecTest.AnnotationA(),
+ | l = Override::class,
+ | m = arrayOf(9, 8, 1),
+ | o = AnnotationSpecTest.Breakfast.PANCAKES,
+ | p = 1_701,
+ | q = AnnotationSpecTest.AnnotationC(value = "bar"),
+ | r = arrayOf(Float::class, Double::class),
+ |)
+ |public class Taco
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun defaultAnnotationWithImport() {
+ val name = IsAnnotated::class.java.canonicalName
+ val element = compilation.elements.getTypeElement(name)
+ val annotation = AnnotationSpec.get(element.annotationMirrors[0])
+ val typeBuilder = TypeSpec.classBuilder(IsAnnotated::class.java.simpleName)
+ typeBuilder.addAnnotation(annotation)
+ val file = FileSpec.get("com.squareup.kotlinpoet", typeBuilder.build())
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.kotlinpoet
+ |
+ |import java.lang.Override
+ |import kotlin.Double
+ |import kotlin.Float
+ |
+ |@AnnotationSpecTest.HasDefaultsAnnotation(
+ | f = 11.1,
+ | j = AnnotationSpecTest.AnnotationA(),
+ | l = Override::class,
+ | m = arrayOf(9, 8, 1),
+ | o = AnnotationSpecTest.Breakfast.PANCAKES,
+ | p = 1_701,
+ | q = AnnotationSpecTest.AnnotationC(value = "bar"),
+ | r = arrayOf(Float::class, Double::class),
+ |)
+ |public class IsAnnotated
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun emptyArray() {
+ val builder = AnnotationSpec.builder(HasDefaultsAnnotation::class.java)
+ builder.addMember("%L = %L", "n", "[]")
+ assertThat(builder.build().toString()).isEqualTo(
+ "" +
+ "@com.squareup.kotlinpoet.AnnotationSpecTest.HasDefaultsAnnotation(" +
+ "n = []" +
+ ")",
+ )
+ builder.addMember("%L = %L", "m", "[]")
+ assertThat(builder.build().toString()).isEqualTo(
+ "" +
+ "@com.squareup.kotlinpoet.AnnotationSpecTest.HasDefaultsAnnotation(" +
+ "n = [], " +
+ "m = []" +
+ ")",
+ )
+ }
+
+ @Test fun reflectAnnotation() {
+ val annotation = IsAnnotated::class.java.getAnnotation(HasDefaultsAnnotation::class.java)
+ val spec = AnnotationSpec.get(annotation)
+
+ assertThat(toString(spec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.kotlinpoet.AnnotationSpecTest
+ |import java.lang.Override
+ |import kotlin.Double
+ |import kotlin.Float
+ |
+ |@AnnotationSpecTest.HasDefaultsAnnotation(
+ | f = 11.1,
+ | l = Override::class,
+ | m = arrayOf(9, 8, 1),
+ | o = AnnotationSpecTest.Breakfast.PANCAKES,
+ | p = 1_701,
+ | q = AnnotationSpecTest.AnnotationC(value = "bar"),
+ | r = arrayOf(Float::class, Double::class),
+ |)
+ |public class Taco
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun reflectAnnotationWithDefaults() {
+ val annotation = IsAnnotated::class.java.getAnnotation(HasDefaultsAnnotation::class.java)
+ val spec = AnnotationSpec.get(annotation, true)
+
+ assertThat(toString(spec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.kotlinpoet.AnnotationSpecTest
+ |import java.lang.Override
+ |import kotlin.Double
+ |import kotlin.Float
+ |
+ |@AnnotationSpecTest.HasDefaultsAnnotation(
+ | a = 5,
+ | b = 6,
+ | c = 7,
+ | d = 8,
+ | e = 9.0f,
+ | f = 11.1,
+ | g = arrayOf('\u0000', '쫾', 'z', '€', 'ℕ', '"', '\'', '\t', '\n'),
+ | h = true,
+ | i = AnnotationSpecTest.Breakfast.WAFFLES,
+ | j = AnnotationSpecTest.AnnotationA(),
+ | k = "maple",
+ | l = Override::class,
+ | m = arrayOf(9, 8, 1),
+ | n = arrayOf(AnnotationSpecTest.Breakfast.WAFFLES, AnnotationSpecTest.Breakfast.PANCAKES),
+ | o = AnnotationSpecTest.Breakfast.PANCAKES,
+ | p = 1_701,
+ | q = AnnotationSpecTest.AnnotationC(value = "bar"),
+ | r = arrayOf(Float::class, Double::class),
+ |)
+ |public class Taco
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun useSiteTarget() {
+ val builder = AnnotationSpec.builder(AnnotationA::class)
+ assertThat(builder.build().toString()).isEqualTo(
+ "" +
+ "@com.squareup.kotlinpoet.AnnotationSpecTest.AnnotationA",
+ )
+ builder.useSiteTarget(AnnotationSpec.UseSiteTarget.FIELD)
+ assertThat(builder.build().toString()).isEqualTo(
+ "" +
+ "@field:com.squareup.kotlinpoet.AnnotationSpecTest.AnnotationA",
+ )
+ builder.useSiteTarget(AnnotationSpec.UseSiteTarget.GET)
+ assertThat(builder.build().toString()).isEqualTo(
+ "" +
+ "@get:com.squareup.kotlinpoet.AnnotationSpecTest.AnnotationA",
+ )
+ builder.useSiteTarget(null)
+ assertThat(builder.build().toString()).isEqualTo(
+ "" +
+ "@com.squareup.kotlinpoet.AnnotationSpecTest.AnnotationA",
+ )
+ }
+
+ @Test fun deprecatedTest() {
+ val annotation = AnnotationSpec.builder(Deprecated::class)
+ .addMember("%S", "Nope")
+ .addMember("%T(%S)", ReplaceWith::class, "Yep")
+ .build()
+
+ assertThat(annotation.toString()).isEqualTo(
+ "" +
+ "@kotlin.Deprecated(\"Nope\", kotlin.ReplaceWith(\"Yep\"))",
+ )
+ }
+
+ @Test fun modifyMembers() {
+ val builder = AnnotationSpec.builder(Deprecated::class)
+ .addMember("%S", "Nope")
+ .addMember("%T(%S)", ReplaceWith::class, "Yep")
+
+ builder.members.removeAt(1)
+ builder.members.add(CodeBlock.of("%T(%S)", ReplaceWith::class, "Nope"))
+
+ assertThat(builder.build().toString()).isEqualTo(
+ "" +
+ "@kotlin.Deprecated(\"Nope\", kotlin.ReplaceWith(\"Nope\"))",
+ )
+ }
+
+ @Test fun annotationStringsAreConstant() {
+ val text = "This is a long string with a newline\nin the middle."
+ val builder = AnnotationSpec.builder(Deprecated::class)
+ .addMember("%S", text)
+
+ assertThat(builder.build().toString()).isEqualTo(
+ "" +
+ "@kotlin.Deprecated(\"This is a long string with a newline\\nin the middle.\")",
+ )
+ }
+
+ @Test fun literalAnnotation() {
+ val annotationSpec = AnnotationSpec.builder(Suppress::class)
+ .addMember("%S", "Things")
+ .build()
+
+ val file = FileSpec.builder("test", "Test")
+ .addFunction(
+ FunSpec.builder("test")
+ .addStatement("%L", annotationSpec)
+ .addStatement("val annotatedString = %S", "AnnotatedString")
+ .build(),
+ )
+ .build()
+ assertThat(file.toString().trim()).isEqualTo(
+ """
+ |package test
+ |
+ |import kotlin.Suppress
+ |import kotlin.Unit
+ |
+ |public fun test(): Unit {
+ | @Suppress("Things")
+ | val annotatedString = "AnnotatedString"
+ |}
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun functionOnlyLiteralAnnotation() {
+ val annotation = AnnotationSpec
+ .builder(ClassName.bestGuess("Suppress"))
+ .addMember("%S", "UNCHECKED_CAST")
+ .build()
+ val funSpec = FunSpec.builder("operation")
+ .addStatement("%L", annotation)
+ .build()
+
+ assertThat(funSpec.toString().trim()).isEqualTo(
+ """
+ |public fun operation(): kotlin.Unit {
+ | @Suppress("UNCHECKED_CAST")
+ |}
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun getOnValueArrayTypeMirrorShouldNameValueArg() {
+ val myClazz = compilation.elements
+ .getTypeElement(JavaClassWithArrayValueAnnotation::class.java.canonicalName)
+ val classBuilder = TypeSpec.classBuilder("Result")
+
+ myClazz.annotationMirrors.map { AnnotationSpec.get(it) }
+ .forEach {
+ classBuilder.addAnnotation(it)
+ }
+
+ assertThat(toString(classBuilder.build())).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.kotlinpoet.JavaClassWithArrayValueAnnotation
+ |import java.lang.Boolean
+ |import java.lang.Object
+ |
+ |@JavaClassWithArrayValueAnnotation.AnnotationWithArrayValue(value = arrayOf(Object::class,
+ | Boolean::class))
+ |public class Result
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun getOnVarargMirrorShouldNameValueArg() {
+ val myClazz = compilation.elements
+ .getTypeElement(KotlinClassWithVarargAnnotation::class.java.canonicalName)
+ val classBuilder = TypeSpec.classBuilder("Result")
+
+ myClazz.annotationMirrors.map { AnnotationSpec.get(it) }
+ .filter {
+ val typeName = it.typeName
+ return@filter typeName is ClassName && typeName.simpleName == "AnnotationWithArrayValue"
+ }
+ .forEach {
+ classBuilder.addAnnotation(it)
+ }
+
+ assertThat(toString(classBuilder.build()).trim()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.kotlinpoet.AnnotationSpecTest
+ |import java.lang.Object
+ |import kotlin.Boolean
+ |
+ |@AnnotationSpecTest.AnnotationWithArrayValue(value = arrayOf(Object::class, Boolean::class))
+ |public class Result
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun getOnValueArrayTypeAnnotationShouldNameValueArg() {
+ val annotation = JavaClassWithArrayValueAnnotation::class.java.getAnnotation(
+ JavaClassWithArrayValueAnnotation.AnnotationWithArrayValue::class.java,
+ )
+ val classBuilder = TypeSpec.classBuilder("Result")
+ .addAnnotation(AnnotationSpec.get(annotation))
+
+ assertThat(toString(classBuilder.build()).trim()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.kotlinpoet.JavaClassWithArrayValueAnnotation
+ |import java.lang.Boolean
+ |import java.lang.Object
+ |
+ |@JavaClassWithArrayValueAnnotation.AnnotationWithArrayValue(value = arrayOf(Object::class,
+ | Boolean::class))
+ |public class Result
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun getOnVarargAnnotationShouldNameValueArg() {
+ val annotation = KotlinClassWithVarargAnnotation::class.java
+ .getAnnotation(AnnotationWithArrayValue::class.java)
+ val classBuilder = TypeSpec.classBuilder("Result")
+ .addAnnotation(AnnotationSpec.get(annotation))
+
+ assertThat(toString(classBuilder.build()).trim()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.kotlinpoet.AnnotationSpecTest
+ |import java.lang.Object
+ |import kotlin.Boolean
+ |
+ |@AnnotationSpecTest.AnnotationWithArrayValue(value = arrayOf(Object::class, Boolean::class))
+ |public class Result
+ """.trimMargin(),
+ )
+ }
+
+ @AnnotationWithArrayValue(Any::class, Boolean::class)
+ class KotlinClassWithVarargAnnotation
+
+ @Retention(AnnotationRetention.RUNTIME)
+ internal annotation class AnnotationWithArrayValue(vararg val value: KClass<*>)
+
+ @Test fun annotationsWithTypeParameters() {
+ // Example from https://kotlinlang.org/docs/tutorials/android-plugin.html
+ val externalClass = ClassName("com.squareup.parceler", "ExternalClass")
+ val externalClassSpec = TypeSpec.classBuilder(externalClass)
+ .addProperty(
+ PropertySpec.builder("value", Int::class)
+ .initializer("value")
+ .build(),
+ )
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("value", Int::class)
+ .build(),
+ )
+ .build()
+ val externalClassParceler = ClassName("com.squareup.parceler", "ExternalClassParceler")
+ val parcel = ClassName("com.squareup.parceler", "Parcel")
+ val externalClassParcelerSpec = TypeSpec.objectBuilder(externalClassParceler)
+ .addSuperinterface(
+ ClassName("com.squareup.parceler", "Parceler")
+ .parameterizedBy(externalClass),
+ )
+ .addFunction(
+ FunSpec.builder("create")
+ .addModifiers(OVERRIDE)
+ .addParameter("parcel", parcel)
+ .addStatement("return %T(parcel.readInt())", externalClass)
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("write")
+ .addModifiers(OVERRIDE)
+ .receiver(externalClass)
+ .addParameter("parcel", parcel)
+ .addParameter("flags", Int::class)
+ .addStatement("parcel.writeInt(value)")
+ .build(),
+ )
+ .build()
+ val parcelize = ClassName("com.squareup.parceler", "Parcelize")
+ val typeParceler = ClassName("com.squareup.parceler", "TypeParceler")
+ val typeParcelerAnnotation = AnnotationSpec.builder(
+ typeParceler
+ .plusParameter(externalClass)
+ .plusParameter(externalClassParceler),
+ )
+ .build()
+ val classLocalParceler = TypeSpec.classBuilder("MyClass")
+ .addAnnotation(parcelize)
+ .addAnnotation(typeParcelerAnnotation)
+ .addProperty(
+ PropertySpec.builder("external", externalClass)
+ .initializer("external")
+ .build(),
+ )
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("external", externalClass)
+ .build(),
+ )
+ .build()
+ val propertyLocalParceler = TypeSpec.classBuilder("MyClass")
+ .addAnnotation(parcelize)
+ .addProperty(
+ PropertySpec.builder("external", externalClass)
+ .addAnnotation(typeParcelerAnnotation)
+ .initializer("external")
+ .build(),
+ )
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("external", externalClass)
+ .build(),
+ )
+ .build()
+ val writeWith = ClassName("com.squareup.parceler", "WriteWith")
+ val writeWithExternalClass = externalClass
+ .copy(
+ annotations = listOf(
+ AnnotationSpec
+ .builder(writeWith.plusParameter(externalClassParceler))
+ .build(),
+ ),
+ )
+ val typeLocalParceler = TypeSpec.classBuilder("MyClass")
+ .addAnnotation(parcelize)
+ .addProperty(
+ PropertySpec.builder("external", writeWithExternalClass)
+ .initializer("external")
+ .build(),
+ )
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("external", writeWithExternalClass)
+ .build(),
+ )
+ .build()
+ val file = FileSpec.builder("com.squareup.parceler", "Test")
+ .addType(externalClassSpec)
+ .addType(externalClassParcelerSpec)
+ .addType(classLocalParceler)
+ .addType(propertyLocalParceler)
+ .addType(typeLocalParceler)
+ .build()
+ //language=kotlin
+ assertThat(file.toString()).isEqualTo(
+ """
+ package com.squareup.parceler
+
+ import kotlin.Int
+ import kotlin.Unit
+
+ public class ExternalClass(
+ public val `value`: Int,
+ )
+
+ public object ExternalClassParceler : Parceler<ExternalClass> {
+ public override fun create(parcel: Parcel) = ExternalClass(parcel.readInt())
+
+ public override fun ExternalClass.write(parcel: Parcel, flags: Int): Unit {
+ parcel.writeInt(value)
+ }
+ }
+
+ @Parcelize
+ @TypeParceler<ExternalClass, ExternalClassParceler>
+ public class MyClass(
+ public val `external`: ExternalClass,
+ )
+
+ @Parcelize
+ public class MyClass(
+ @TypeParceler<ExternalClass, ExternalClassParceler>
+ public val `external`: ExternalClass,
+ )
+
+ @Parcelize
+ public class MyClass(
+ public val `external`: @WriteWith<ExternalClassParceler> ExternalClass,
+ )
+
+ """.trimIndent(),
+ )
+ }
+
+ private fun toString(annotationSpec: AnnotationSpec) =
+ toString(TypeSpec.classBuilder("Taco").addAnnotation(annotationSpec).build())
+
+ private fun toString(typeSpec: TypeSpec) =
+ FileSpec.get("com.squareup.tacos", typeSpec).toString()
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AssertThrows.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AssertThrows.kt
new file mode 100644
index 00000000..facd7022
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/AssertThrows.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.ThrowableSubject
+import com.google.common.truth.Truth.assertThat
+
+inline fun <reified T> assertThrows(block: () -> Unit): ThrowableSubject {
+ try {
+ block()
+ } catch (e: Throwable) {
+ if (e is T) {
+ return assertThat(e)
+ } else {
+ throw e
+ }
+ }
+ throw AssertionError("Expected ${T::class.simpleName}")
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ClassNameTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ClassNameTest.kt
new file mode 100644
index 00000000..6d06806c
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ClassNameTest.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.compile.CompilationRule
+import org.junit.Rule
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class ClassNameTest {
+ @Rule @JvmField var compilationRule = CompilationRule()
+
+ @Test fun bestGuessForString_simpleClass() {
+ assertThat(ClassName.bestGuess(String::class.java.name))
+ .isEqualTo(ClassName("java.lang", "String"))
+ }
+
+ @Test fun bestGuessNonAscii() {
+ val className = ClassName.bestGuess(
+ "com.\ud835\udc1andro\ud835\udc22d.\ud835\udc00ctiv\ud835\udc22ty"
+ )
+ assertEquals("com.\ud835\udc1andro\ud835\udc22d", className.packageName)
+ assertEquals("\ud835\udc00ctiv\ud835\udc22ty", className.simpleName)
+ }
+
+ internal class OuterClass {
+ internal class InnerClass
+ }
+
+ @Test fun bestGuessForString_nestedClass() {
+ assertThat(ClassName.bestGuess(Map.Entry::class.java.canonicalName))
+ .isEqualTo(ClassName("java.util", "Map", "Entry"))
+ assertThat(ClassName.bestGuess(OuterClass.InnerClass::class.java.canonicalName))
+ .isEqualTo(
+ ClassName(
+ "com.squareup.kotlinpoet",
+ "ClassNameTest", "OuterClass", "InnerClass"
+ )
+ )
+ }
+
+ @Test fun bestGuessForString_defaultPackage() {
+ assertThat(ClassName.bestGuess("SomeClass"))
+ .isEqualTo(ClassName("", "SomeClass"))
+ assertThat(ClassName.bestGuess("SomeClass.Nested"))
+ .isEqualTo(ClassName("", "SomeClass", "Nested"))
+ assertThat(ClassName.bestGuess("SomeClass.Nested.EvenMore"))
+ .isEqualTo(ClassName("", "SomeClass", "Nested", "EvenMore"))
+ }
+
+ @Test fun bestGuessForString_confusingInput() {
+ assertBestGuessThrows("")
+ assertBestGuessThrows(".")
+ assertBestGuessThrows(".Map")
+ assertBestGuessThrows("java")
+ assertBestGuessThrows("java.util")
+ assertBestGuessThrows("java.util.")
+ assertBestGuessThrows("java..util.Map.Entry")
+ assertBestGuessThrows("java.util..Map.Entry")
+ assertBestGuessThrows("kotlin.collections.Map..Entry")
+ assertBestGuessThrows("com.test.$")
+ assertBestGuessThrows("com.test.LooksLikeAClass.pkg")
+ assertBestGuessThrows("!@#\$gibberish%^&*")
+ }
+
+ private fun assertBestGuessThrows(s: String) {
+ assertThrows<IllegalArgumentException> {
+ ClassName.bestGuess(s)
+ }
+ }
+
+ @Test fun createNestedClass() {
+ val foo = ClassName("com.example", "Foo")
+ val bar = foo.nestedClass("Bar")
+ assertThat(bar).isEqualTo(ClassName("com.example", "Foo", "Bar"))
+ val baz = bar.nestedClass("Baz")
+ assertThat(baz).isEqualTo(ClassName("com.example", "Foo", "Bar", "Baz"))
+ }
+
+ @Test fun classNameFromTypeElement() {
+ val elements = compilationRule.elements
+ val element = elements.getTypeElement(Any::class.java.canonicalName)
+ assertThat(element.asClassName().toString()).isEqualTo("java.lang.Object")
+ }
+
+ @Test fun classNameFromClass() {
+ assertThat(Any::class.java.asClassName().toString())
+ .isEqualTo("java.lang.Object")
+ assertThat(OuterClass.InnerClass::class.java.asClassName().toString())
+ .isEqualTo("com.squareup.kotlinpoet.ClassNameTest.OuterClass.InnerClass")
+ }
+
+ @Test fun classNameFromKClass() {
+ assertThat(Any::class.asClassName().toString())
+ .isEqualTo("kotlin.Any")
+ assertThat(OuterClass.InnerClass::class.asClassName().toString())
+ .isEqualTo("com.squareup.kotlinpoet.ClassNameTest.OuterClass.InnerClass")
+ }
+
+ @Test fun peerClass() {
+ assertThat(java.lang.Double::class.asClassName().peerClass("Short"))
+ .isEqualTo(java.lang.Short::class.asClassName())
+ assertThat(ClassName("", "Double").peerClass("Short"))
+ .isEqualTo(ClassName("", "Short"))
+ assertThat(ClassName("a.b", "Combo", "Taco").peerClass("Burrito"))
+ .isEqualTo(ClassName("a.b", "Combo", "Burrito"))
+ }
+
+ @Test fun fromClassRejectionTypes() {
+ assertThrows<IllegalArgumentException> {
+ java.lang.Integer.TYPE.asClassName()
+ }
+
+ assertThrows<IllegalArgumentException> {
+ Void.TYPE.asClassName()
+ }
+
+ assertThrows<IllegalArgumentException> {
+ Array<Any>::class.java.asClassName()
+ }
+
+ // TODO
+ // assertThrows<IllegalArgumentException> {
+ // Array<Int>::class.asClassName()
+ // }
+ }
+
+ @Suppress("DEPRECATION_ERROR") // Ensure still throws in case called from Java.
+ @Test fun fromEmptySimpleName() {
+ assertThrows<IllegalArgumentException> {
+ ClassName("foo" /* no simple name */)
+ }
+ }
+
+ @Test fun reflectionName() {
+ assertThat(ANY.reflectionName())
+ .isEqualTo("kotlin.Any")
+ assertThat(Thread.State::class.asClassName().reflectionName())
+ .isEqualTo("java.lang.Thread\$State")
+ assertThat(Map.Entry::class.asClassName().reflectionName())
+ .isEqualTo("kotlin.collections.Map\$Entry")
+ assertThat(ClassName("", "Foo").reflectionName())
+ .isEqualTo("Foo")
+ assertThat(ClassName("", "Foo", "Bar", "Baz").reflectionName())
+ .isEqualTo("Foo\$Bar\$Baz")
+ assertThat(ClassName("a.b.c", "Foo", "Bar", "Baz").reflectionName())
+ .isEqualTo("a.b.c.Foo\$Bar\$Baz")
+ }
+
+ @Test fun constructorReferences() {
+ assertThat(String::class.asClassName().constructorReference().toString())
+ .isEqualTo("::kotlin.String")
+ assertThat(Thread.State::class.asClassName().constructorReference().toString())
+ .isEqualTo("java.lang.Thread::State")
+ assertThat(ClassName("", "Foo").constructorReference().toString())
+ .isEqualTo("::Foo")
+ assertThat(ClassName("", "Foo", "Bar", "Baz").constructorReference().toString())
+ .isEqualTo("Foo.Bar::Baz")
+ assertThat(ClassName("a.b.c", "Foo", "Bar", "Baz").constructorReference().toString())
+ .isEqualTo("a.b.c.Foo.Bar::Baz")
+ }
+
+ @Test fun spacesEscaping() {
+ val tacoFactory = ClassName("com.squareup.taco factory", "Taco Factory")
+ val file = FileSpec.builder("com.squareup.tacos", "TacoTest")
+ .addFunction(
+ FunSpec.builder("main")
+ .addStatement("println(%T.produceTacos())", tacoFactory)
+ .build()
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.`taco factory`.`Taco Factory`
+ |import kotlin.Unit
+ |
+ |public fun main(): Unit {
+ | println(`Taco Factory`.produceTacos())
+ |}
+ |""".trimMargin()
+ )
+ }
+
+ @Test fun emptySimpleNamesForbidden() {
+ assertThrows<IllegalArgumentException> {
+ ClassName(packageName = "", simpleNames = emptyArray())
+ }.hasMessageThat().isEqualTo("simpleNames must not be empty")
+
+ assertThrows<IllegalArgumentException> {
+ ClassName(packageName = "", simpleNames = arrayOf("Foo", "Bar", ""))
+ }.hasMessageThat().isEqualTo(
+ "simpleNames must not contain empty items: " +
+ "[Foo, Bar, ]"
+ )
+
+ assertThrows<IllegalArgumentException> {
+ ClassName(packageName = "", simpleNames = emptyList())
+ }.hasMessageThat().isEqualTo("simpleNames must not be empty")
+
+ assertThrows<IllegalArgumentException> {
+ ClassName(packageName = "", simpleNames = listOf("Foo", "Bar", ""))
+ }.hasMessageThat().isEqualTo(
+ "simpleNames must not contain empty items: " +
+ "[Foo, Bar, ]"
+ )
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/CodeBlockTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/CodeBlockTest.kt
new file mode 100644
index 00000000..23ef0568
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/CodeBlockTest.kt
@@ -0,0 +1,632 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import kotlin.test.Test
+
+class CodeBlockTest {
+ @Test fun equalsAndHashCode() {
+ var a = CodeBlock.builder().build()
+ var b = CodeBlock.builder().build()
+ assertThat(a == b).isTrue()
+ assertThat(a.hashCode()).isEqualTo(b.hashCode())
+ a = CodeBlock.builder().add("%L", "taco").build()
+ b = CodeBlock.builder().add("%L", "taco").build()
+ assertThat(a == b).isTrue()
+ assertThat(a.hashCode()).isEqualTo(b.hashCode())
+ }
+
+ @Test fun of() {
+ val a = CodeBlock.of("%L taco", "delicious")
+ assertThat(a.toString()).isEqualTo("delicious taco")
+ }
+
+ @Test fun percentEscapeCannotBeIndexed() {
+ assertThrows<IllegalArgumentException> {
+ CodeBlock.builder().add("%1%", "taco").build()
+ }.hasMessageThat().isEqualTo("%% may not have an index")
+ }
+
+ @Test fun nameFormatCanBeIndexed() {
+ val block = CodeBlock.builder().add("%1N", "taco").build()
+ assertThat(block.toString()).isEqualTo("taco")
+ }
+
+ @Test fun literalFormatCanBeIndexed() {
+ val block = CodeBlock.builder().add("%1L", "taco").build()
+ assertThat(block.toString()).isEqualTo("taco")
+ }
+
+ @Test fun stringFormatCanBeIndexed() {
+ val block = CodeBlock.builder().add("%1S", "taco").build()
+ assertThat(block.toString()).isEqualTo("\"taco\"")
+ }
+
+ @Test fun typeFormatCanBeIndexed() {
+ val block = CodeBlock.builder().add("%1T", String::class).build()
+ assertThat(block.toString()).isEqualTo("kotlin.String")
+ }
+
+ @Test fun simpleNamedArgument() {
+ val map = LinkedHashMap<String, Any>()
+ map["text"] = "taco"
+ val block = CodeBlock.builder().addNamed("%text:S", map).build()
+ assertThat(block.toString()).isEqualTo("\"taco\"")
+ }
+
+ @Test fun repeatedNamedArgument() {
+ val map = LinkedHashMap<String, Any>()
+ map["text"] = "tacos"
+ val block = CodeBlock.builder()
+ .addNamed("\"I like \" + %text:S + \". Do you like \" + %text:S + \"?\"", map)
+ .build()
+ assertThat(block.toString()).isEqualTo(
+ "\"I like \" + \"tacos\" + \". Do you like \" + \"tacos\" + \"?\"",
+ )
+ }
+
+ @Test fun namedAndNoArgFormat() {
+ val map = LinkedHashMap<String, Any>()
+ map["text"] = "tacos"
+ val block = CodeBlock.builder()
+ .addNamed("⇥\n%text:L for\n⇤%%3.50", map).build()
+ assertThat(block.toString()).isEqualTo("\n tacos for\n%3.50")
+ }
+
+ @Test fun missingNamedArgument() {
+ assertThrows<IllegalArgumentException> {
+ val map = LinkedHashMap<String, Any>()
+ CodeBlock.builder().addNamed("%text:S", map).build()
+ }.hasMessageThat().isEqualTo("Missing named argument for %text")
+ }
+
+ @Test fun lowerCaseNamed() {
+ assertThrows<IllegalArgumentException> {
+ val map = LinkedHashMap<String, Any>()
+ map["Text"] = "tacos"
+ CodeBlock.builder().addNamed("%Text:S", map).build()
+ }.hasMessageThat().isEqualTo("argument 'Text' must start with a lowercase character")
+ }
+
+ @Test fun multipleNamedArguments() {
+ val map = LinkedHashMap<String, Any>()
+ map["pipe"] = System::class
+ map["text"] = "tacos"
+
+ val block = CodeBlock.builder()
+ .addNamed("%pipe:T.out.println(\"Let's eat some %text:L\");", map)
+ .build()
+
+ assertThat(block.toString()).isEqualTo(
+ "java.lang.System.out.println(\"Let's eat some tacos\");",
+ )
+ }
+
+ @Test fun namedNewline() {
+ val map = LinkedHashMap<String, Any>()
+ map["clazz"] = java.lang.Integer::class
+ val block = CodeBlock.builder().addNamed("%clazz:T\n", map).build()
+ assertThat(block.toString()).isEqualTo("kotlin.Int\n")
+ }
+
+ @Test fun danglingNamed() {
+ val map = LinkedHashMap<String, Any>()
+ map["clazz"] = Int::class
+ assertThrows<IllegalArgumentException> {
+ CodeBlock.builder().addNamed("%clazz:T%", map).build()
+ }.hasMessageThat().isEqualTo("dangling % at end")
+ }
+
+ @Test fun indexTooHigh() {
+ assertThrows<IllegalArgumentException> {
+ CodeBlock.builder().add("%2T", String::class).build()
+ }.hasMessageThat().isEqualTo("index 2 for '%2T' not in range (received 1 arguments)")
+ }
+
+ @Test fun indexIsZero() {
+ assertThrows<IllegalArgumentException> {
+ CodeBlock.builder().add("%0T", String::class).build()
+ }.hasMessageThat().isEqualTo("index 0 for '%0T' not in range (received 1 arguments)")
+ }
+
+ @Test fun indexIsNegative() {
+ assertThrows<IllegalArgumentException> {
+ CodeBlock.builder().add("%-1T", String::class).build()
+ }.hasMessageThat().isEqualTo("invalid format string: '%-1T'")
+ }
+
+ @Test fun indexWithoutFormatType() {
+ assertThrows<IllegalArgumentException> {
+ CodeBlock.builder().add("%1", String::class).build()
+ }.hasMessageThat().isEqualTo("dangling format characters in '%1'")
+ }
+
+ @Test fun indexWithoutFormatTypeNotAtStringEnd() {
+ assertThrows<IllegalArgumentException> {
+ CodeBlock.builder().add("%1 taco", String::class).build()
+ }.hasMessageThat().isEqualTo("invalid format string: '%1 taco'")
+ }
+
+ @Test fun indexButNoArguments() {
+ assertThrows<IllegalArgumentException> {
+ CodeBlock.builder().add("%1T").build()
+ }.hasMessageThat().isEqualTo("index 1 for '%1T' not in range (received 0 arguments)")
+ }
+
+ @Test fun formatIndicatorAlone() {
+ assertThrows<IllegalArgumentException> {
+ CodeBlock.builder().add("%", String::class).build()
+ }.hasMessageThat().isEqualTo("dangling format characters in '%'")
+ }
+
+ @Test fun formatIndicatorWithoutIndexOrFormatType() {
+ assertThrows<IllegalArgumentException> {
+ CodeBlock.builder().add("% tacoString", String::class).build()
+ }.hasMessageThat().isEqualTo("invalid format string: '% tacoString'")
+ }
+
+ @Test fun sameIndexCanBeUsedWithDifferentFormats() {
+ val block = CodeBlock.builder()
+ .add("%1T.out.println(%1S)", System::class.asClassName())
+ .build()
+ assertThat(block.toString()).isEqualTo("java.lang.System.out.println(\"java.lang.System\")")
+ }
+
+ @Test fun tooManyStatementEnters() {
+ val codeBlock = CodeBlock.builder()
+ .addStatement("print(«%L»)", "1 + 1")
+ .build()
+ assertThrows<IllegalStateException> {
+ // We can't report this error until rendering type because code blocks might be composed.
+ codeBlock.toString()
+ }.hasMessageThat().isEqualTo(
+ """
+ |Can't open a new statement until the current statement is closed (opening « followed
+ |by another « without a closing »).
+ |Current code block:
+ |- Format parts: [«, print(, «, %L, », ), \n, »]
+ |- Arguments: [1 + 1]
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun statementExitWithoutStatementEnter() {
+ val codeBlock = CodeBlock.builder()
+ .addStatement("print(%L»)", "1 + 1")
+ .build()
+ assertThrows<IllegalStateException> {
+ // We can't report this error until rendering type because code blocks might be composed.
+ codeBlock.toString()
+ }.hasMessageThat().isEqualTo(
+ """
+ |Can't close a statement that hasn't been opened (closing » is not preceded by an
+ |opening «).
+ |Current code block:
+ |- Format parts: [«, print(, %L, », ), \n, »]
+ |- Arguments: [1 + 1]
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun nullableType() {
+ val type = String::class.asTypeName().copy(nullable = true)
+ val typeBlock = CodeBlock.of("%T", type)
+ assertThat(typeBlock.toString()).isEqualTo("kotlin.String?")
+
+ val list = (List::class.asClassName().copy(nullable = true) as ClassName)
+ .parameterizedBy(Int::class.asTypeName().copy(nullable = true))
+ .copy(nullable = true)
+ val listBlock = CodeBlock.of("%T", list)
+ assertThat(listBlock.toString()).isEqualTo("kotlin.collections.List<kotlin.Int?>?")
+
+ val map = (Map::class.asClassName().copy(nullable = true) as ClassName)
+ .parameterizedBy(String::class.asTypeName().copy(nullable = true), list)
+ .copy(nullable = true)
+ val mapBlock = CodeBlock.of("%T", map)
+ assertThat(mapBlock.toString())
+ .isEqualTo("kotlin.collections.Map<kotlin.String?, kotlin.collections.List<kotlin.Int?>?>?")
+
+ val rarr = WildcardTypeName.producerOf(String::class.asTypeName().copy(nullable = true))
+ val rarrBlock = CodeBlock.of("%T", rarr)
+ assertThat(rarrBlock.toString()).isEqualTo("out kotlin.String?")
+ }
+
+ @Test fun withoutPrefixMatching() {
+ assertThat(
+ CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+ .withoutPrefix(CodeBlock.of("")),
+ )
+ .isEqualTo(CodeBlock.of("abcd %S efgh %S ijkl", "x", "y"))
+ assertThat(
+ CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+ .withoutPrefix(CodeBlock.of("ab")),
+ )
+ .isEqualTo(CodeBlock.of("cd %S efgh %S ijkl", "x", "y"))
+ assertThat(
+ CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+ .withoutPrefix(CodeBlock.of("abcd ")),
+ )
+ .isEqualTo(CodeBlock.of("%S efgh %S ijkl", "x", "y"))
+ assertThat(
+ CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+ .withoutPrefix(CodeBlock.of("abcd %S", "x")),
+ )
+ .isEqualTo(CodeBlock.of(" efgh %S ijkl", "y"))
+ assertThat(
+ CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+ .withoutPrefix(CodeBlock.of("abcd %S ef", "x")),
+ )
+ .isEqualTo(CodeBlock.of("gh %S ijkl", "y"))
+ assertThat(
+ CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+ .withoutPrefix(CodeBlock.of("abcd %S efgh ", "x")),
+ )
+ .isEqualTo(CodeBlock.of("%S ijkl", "y"))
+ assertThat(
+ CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+ .withoutPrefix(CodeBlock.of("abcd %S efgh %S", "x", "y")),
+ )
+ .isEqualTo(CodeBlock.of(" ijkl"))
+ assertThat(
+ CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+ .withoutPrefix(CodeBlock.of("abcd %S efgh %S ij", "x", "y")),
+ )
+ .isEqualTo(CodeBlock.of("kl"))
+ assertThat(
+ CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+ .withoutPrefix(CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")),
+ )
+ .isEqualTo(CodeBlock.of(""))
+ }
+
+ @Test fun withoutPrefixNoArgs() {
+ assertThat(
+ CodeBlock.of("abcd %% efgh %% ijkl")
+ .withoutPrefix(CodeBlock.of("")),
+ )
+ .isEqualTo(CodeBlock.of("abcd %% efgh %% ijkl"))
+ assertThat(
+ CodeBlock.of("abcd %% efgh %% ijkl")
+ .withoutPrefix(CodeBlock.of("ab")),
+ )
+ .isEqualTo(CodeBlock.of("cd %% efgh %% ijkl"))
+ assertThat(
+ CodeBlock.of("abcd %% efgh %% ijkl")
+ .withoutPrefix(CodeBlock.of("abcd ")),
+ )
+ .isEqualTo(CodeBlock.of("%% efgh %% ijkl"))
+ assertThat(
+ CodeBlock.of("abcd %% efgh %% ijkl")
+ .withoutPrefix(CodeBlock.of("abcd %%")),
+ )
+ .isEqualTo(CodeBlock.of(" efgh %% ijkl"))
+ assertThat(
+ CodeBlock.of("abcd %% efgh %% ijkl")
+ .withoutPrefix(CodeBlock.of("abcd %% ef")),
+ )
+ .isEqualTo(CodeBlock.of("gh %% ijkl"))
+ assertThat(
+ CodeBlock.of("abcd %% efgh %% ijkl")
+ .withoutPrefix(CodeBlock.of("abcd %% efgh ")),
+ )
+ .isEqualTo(CodeBlock.of("%% ijkl"))
+ assertThat(
+ CodeBlock.of("abcd %% efgh %% ijkl")
+ .withoutPrefix(CodeBlock.of("abcd %% efgh %%")),
+ )
+ .isEqualTo(CodeBlock.of(" ijkl"))
+ assertThat(
+ CodeBlock.of("abcd %% efgh %% ijkl")
+ .withoutPrefix(CodeBlock.of("abcd %% efgh %% ij")),
+ )
+ .isEqualTo(CodeBlock.of("kl"))
+ assertThat(
+ CodeBlock.of("abcd %% efgh %% ijkl")
+ .withoutPrefix(CodeBlock.of("abcd %% efgh %% ijkl")),
+ )
+ .isEqualTo(CodeBlock.of(""))
+ }
+
+ @Test fun withoutPrefixArgMismatch() {
+ assertThat(
+ CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+ .withoutPrefix(CodeBlock.of("abcd %S efgh %S ij", "x", "z")),
+ )
+ .isNull()
+ assertThat(
+ CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+ .withoutPrefix(CodeBlock.of("abcd %S efgh %S ij", "z", "y")),
+ )
+ .isNull()
+ }
+
+ @Test fun withoutPrefixFormatPartMismatch() {
+ assertThat(
+ CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")
+ .withoutPrefix(CodeBlock.of("abcd %S efgx %S ij", "x", "y")),
+ )
+ .isNull()
+ assertThat(
+ CodeBlock.of("abcd %S efgh %% ijkl", "x")
+ .withoutPrefix(CodeBlock.of("abcd %% efgh %S ij", "x")),
+ )
+ .isNull()
+ }
+
+ @Test fun withoutPrefixTooShort() {
+ assertThat(
+ CodeBlock.of("abcd %S efgh %S", "x", "y")
+ .withoutPrefix(CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")),
+ )
+ .isNull()
+ assertThat(
+ CodeBlock.of("abcd %S efgh", "x")
+ .withoutPrefix(CodeBlock.of("abcd %S efgh %S ijkl", "x", "y")),
+ )
+ .isNull()
+ }
+
+ @Test fun trimEmpty() {
+ assertThat(CodeBlock.of("").trim())
+ .isEqualTo(CodeBlock.of(""))
+ }
+
+ @Test fun trimNoPlaceholders() {
+ assertThat(CodeBlock.of("return null").trim())
+ .isEqualTo(CodeBlock.of("return null"))
+ }
+
+ @Test fun trimPlaceholdersWithArgs() {
+ assertThat(CodeBlock.of("return %S", "taco").trim())
+ .isEqualTo(CodeBlock.of("return %S", "taco"))
+ }
+
+ @Test fun trimNoArgPlaceholderMiddle() {
+ assertThat(CodeBlock.of("this.taco = %S", "taco").trim())
+ .isEqualTo(CodeBlock.of("this.taco = %S", "taco"))
+ }
+
+ @Test fun trimNoArgPlaceholderStart() {
+ assertThat(CodeBlock.of("⇥return ").trim())
+ .isEqualTo(CodeBlock.of("return "))
+ }
+
+ @Test fun trimNoArgPlaceholderEnd() {
+ assertThat(CodeBlock.of("return ⇥").trim())
+ .isEqualTo(CodeBlock.of("return "))
+ }
+
+ @Test fun trimNoArgPlaceholdersStartEnd() {
+ assertThat(CodeBlock.of("«return this»").trim())
+ .isEqualTo(CodeBlock.of("return this"))
+ }
+
+ @Test fun trimMultipleNoArgPlaceholders() {
+ assertThat(
+ CodeBlock.of("«return if (x > %L) %S else %S»", 1, "a", "b").trim(),
+ )
+ .isEqualTo(CodeBlock.of("return if (x > %L) %S else %S", 1, "a", "b"))
+ }
+
+ @Test fun trimOnlyNoArgPlaceholders() {
+ assertThat(CodeBlock.of("«»⇥⇤").trim())
+ .isEqualTo(CodeBlock.of(""))
+ }
+
+ @Test fun replaceSimple() {
+ assertThat(CodeBlock.of("%%⇥%%").replaceAll("%%", ""))
+ .isEqualTo(CodeBlock.of("⇥"))
+ }
+
+ @Test fun replaceNoMatches() {
+ assertThat(CodeBlock.of("%%⇥%%").replaceAll("⇤", ""))
+ .isEqualTo(CodeBlock.of("%%⇥%%"))
+ }
+
+ @Test fun replaceRegex() {
+ assertThat(CodeBlock.of("%%⇥%%⇤").replaceAll("[⇥|⇤]", ""))
+ .isEqualTo(CodeBlock.of("%%%%"))
+ }
+
+ @Test fun joinToCode() {
+ val blocks = listOf(CodeBlock.of("%L", "taco1"), CodeBlock.of("%L", "taco2"), CodeBlock.of("%L", "taco3"))
+ assertThat(blocks.joinToCode(prefix = "(", suffix = ")"))
+ .isEqualTo(CodeBlock.of("(%L, %L, %L)", "taco1", "taco2", "taco3"))
+ }
+
+ @Test fun beginControlFlowWithParams() {
+ val controlFlow = CodeBlock.builder()
+ .beginControlFlow("list.forEach { element ->")
+ .addStatement("println(element)")
+ .endControlFlow()
+ .build()
+ assertThat(controlFlow.toString()).isEqualTo(
+ """
+ |list.forEach { element ->
+ | println(element)
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun beginControlFlowWithParamsAndTemplateString() {
+ val controlFlow = CodeBlock.builder()
+ .beginControlFlow("listOf(\"\${1.toString()}\").forEach { element ->")
+ .addStatement("println(element)")
+ .endControlFlow()
+ .build()
+ assertThat(controlFlow.toString()).isEqualTo(
+ """
+ |listOf("${'$'}{1.toString()}").forEach { element ->
+ | println(element)
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun buildCodeBlock() {
+ val codeBlock = buildCodeBlock {
+ beginControlFlow("if (2 == 2)")
+ addStatement("println(%S)", "foo")
+ nextControlFlow("else")
+ addStatement("println(%S)", "bar")
+ endControlFlow()
+ }
+ assertThat(codeBlock.toString()).isEqualTo(
+ """
+ |if (2 == 2) {
+ | println("foo")
+ |} else {
+ | println("bar")
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun nonWrappingControlFlow() {
+ val file = FileSpec.builder("com.squareup.tacos", "Test")
+ .addFunction(
+ FunSpec.builder("test")
+ .beginControlFlow("if (%1S == %1S)", "Very long string that would wrap the line ")
+ .nextControlFlow("else if (%1S == %1S)", "Long string that would wrap the line 2 ")
+ .endControlFlow()
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public fun test(): Unit {
+ | if ("Very long string that would wrap the line " ==
+ | "Very long string that would wrap the line ") {
+ | } else if ("Long string that would wrap the line 2 " ==
+ | "Long string that would wrap the line 2 ") {
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun ensureEndsWithNewLineWithNoArgs() {
+ val codeBlock = CodeBlock.builder()
+ .addStatement("Modeling a kdoc")
+ .add("\n")
+ .addStatement("Statement with no args")
+ .build()
+
+ assertThat(codeBlock.ensureEndsWithNewLine().toString()).isEqualTo(
+ """
+ |Modeling a kdoc
+ |
+ |Statement with no args
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun `%N escapes keywords`() {
+ val funSpec = FunSpec.builder("object").build()
+ assertThat(CodeBlock.of("%N", funSpec).toString()).isEqualTo("`object`")
+ }
+
+ @Test fun `%N escapes spaces`() {
+ val funSpec = FunSpec.builder("create taco").build()
+ assertThat(CodeBlock.of("%N", funSpec).toString()).isEqualTo("`create taco`")
+ }
+
+ @Test fun clear() {
+ val blockBuilder = CodeBlock.builder().addStatement("%S is some existing code", "This")
+
+ blockBuilder.clear()
+
+ assertThat(blockBuilder.build().toString()).isEmpty()
+ }
+
+ @Test fun withIndent() {
+ val codeBlock = CodeBlock.Builder()
+ .apply {
+ addStatement("User(")
+ withIndent {
+ addStatement("age = 42,")
+ addStatement("cities = listOf(")
+ withIndent {
+ addStatement("%S,", "Berlin")
+ addStatement("%S,", "London")
+ }
+ addStatement(")")
+ }
+ addStatement(")")
+ }
+ .build()
+
+ assertThat(codeBlock.toString()).isEqualTo(
+ """
+ |User(
+ | age = 42,
+ | cities = listOf(
+ | "Berlin",
+ | "London",
+ | )
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/1236
+ @Test fun dontEscapeBackslashesInRawStrings() {
+ // println("ESCAPE '\\'") -> ESCAPE '\'
+ assertThat(CodeBlock.of("%S", "ESCAPE '\\'").toString()).isEqualTo("\"ESCAPE '\\\\'\"")
+ // println("""ESCAPE '\'""") -> ESCAPE '\'
+ assertThat(CodeBlock.of("%P", """ESCAPE '\'""").toString()).isEqualTo("\"\"\"ESCAPE '\\'\"\"\"")
+ }
+
+ // https://github.com/square/kotlinpoet/issues/1381
+ @Test fun useUnderscoresOnLargeDecimalLiterals() {
+ assertThat(CodeBlock.of("%L", 10000).toString()).isEqualTo("10_000")
+ assertThat(CodeBlock.of("%L", 100000L).toString()).isEqualTo("100_000")
+ assertThat(CodeBlock.of("%L", Int.MIN_VALUE).toString()).isEqualTo("-2_147_483_648")
+ assertThat(CodeBlock.of("%L", Int.MAX_VALUE).toString()).isEqualTo("2_147_483_647")
+ assertThat(CodeBlock.of("%L", Long.MIN_VALUE).toString()).isEqualTo("-9_223_372_036_854_775_808")
+ assertThat(CodeBlock.of("%L", 10000.123).toString()).isEqualTo("10_000.123")
+ assertThat(CodeBlock.of("%L", 3.0).toString()).isEqualTo("3.0")
+ assertThat(CodeBlock.of("%L", 10000.123f).toString()).isEqualTo("10_000.123")
+ assertThat(CodeBlock.of("%L", 10000.123456789012).toString()).isEqualTo("10_000.123456789011")
+ assertThat(CodeBlock.of("%L", 1281.toShort()).toString()).isEqualTo("1_281")
+
+ assertThat(CodeBlock.of("%S", 10000).toString()).isEqualTo("\"10000\"")
+ assertThat(CodeBlock.of("%S", 100000L).toString()).isEqualTo("\"100000\"")
+ assertThat(CodeBlock.of("%S", Int.MIN_VALUE).toString()).isEqualTo("\"-2147483648\"")
+ assertThat(CodeBlock.of("%S", Int.MAX_VALUE).toString()).isEqualTo("\"2147483647\"")
+ assertThat(CodeBlock.of("%S", Long.MIN_VALUE).toString()).isEqualTo("\"-9223372036854775808\"")
+ assertThat(CodeBlock.of("%S", 10000.123).toString()).isEqualTo("\"10000.123\"")
+ assertThat(CodeBlock.of("%S", 3.0).toString()).isEqualTo("\"3.0\"")
+ assertThat(CodeBlock.of("%S", 10000.123f).toString()).isEqualTo("\"10000.123\"")
+ assertThat(CodeBlock.of("%S", 10000.12345678901).toString()).isEqualTo("\"10000.12345678901\"")
+ assertThat(CodeBlock.of("%S", 1281.toShort()).toString()).isEqualTo("\"1281\"")
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/CrossplatformTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/CrossplatformTest.kt
new file mode 100644
index 00000000..5074b913
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/CrossplatformTest.kt
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2017 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import java.util.concurrent.atomic.AtomicReference
+import kotlin.test.Test
+
+class CrossplatformTest {
+
+ @Test fun crossplatform() {
+ val expectTypeParam = TypeVariableName("V")
+ val expectType = "AtomicRef"
+ val expectSpec = TypeSpec.expectClassBuilder(expectType)
+ .addTypeVariable(expectTypeParam)
+ .addModifiers(KModifier.INTERNAL)
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("value", expectTypeParam)
+ .build(),
+ )
+ .addProperty(PropertySpec.builder("value", expectTypeParam).build())
+ .addFunction(
+ FunSpec.builder("get")
+ .returns(expectTypeParam)
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("set")
+ .addParameter("value", expectTypeParam)
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("getAndSet")
+ .addParameter("value", expectTypeParam)
+ .returns(expectTypeParam)
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("compareAndSet")
+ .addParameter("expect", expectTypeParam)
+ .addParameter("update", expectTypeParam)
+ .returns(Boolean::class)
+ .build(),
+ )
+ .build()
+ val actualName = AtomicReference::class.asTypeName().parameterizedBy(expectTypeParam)
+ val actualSpec = TypeAliasSpec.builder(expectType, actualName)
+ .addTypeVariable(expectTypeParam)
+ .addModifiers(KModifier.ACTUAL)
+ .build()
+ val fileSpec = FileSpec.builder("", "Test")
+ .addType(expectSpec)
+ .addTypeAlias(actualSpec)
+ .build()
+
+ assertThat(fileSpec.toString()).isEqualTo(
+ """
+ |import java.util.concurrent.atomic.AtomicReference
+ |import kotlin.Boolean
+ |import kotlin.Unit
+ |
+ |internal expect class AtomicRef<V>(
+ | `value`: V,
+ |) {
+ | public val `value`: V
+ |
+ | public fun `get`(): V
+ |
+ | public fun `set`(`value`: V): Unit
+ |
+ | public fun getAndSet(`value`: V): V
+ |
+ | public fun compareAndSet(`expect`: V, update: V): Boolean
+ |}
+ |
+ |public actual typealias AtomicRef<V> = AtomicReference<V>
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun expectWithSecondaryConstructors() {
+ val expectSpec = TypeSpec.expectClassBuilder("IoException")
+ .addModifiers(KModifier.OPEN)
+ .superclass(Exception::class)
+ .addFunction(FunSpec.constructorBuilder().build())
+ .addFunction(
+ FunSpec.constructorBuilder()
+ .addParameter("message", String::class)
+ .build(),
+ )
+ .build()
+ val fileSpec = FileSpec.builder("", "Test")
+ .addType(expectSpec)
+ .build()
+
+ assertThat(fileSpec.toString()).isEqualTo(
+ """
+ |import java.lang.Exception
+ |import kotlin.String
+ |
+ |public expect open class IoException : Exception {
+ | public constructor()
+ |
+ | public constructor(message: String)
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun topLevelProperties() {
+ val fileSpec = FileSpec.builder("", "Test")
+ .addProperty(PropertySpec.builder("bar", String::class, KModifier.EXPECT).build())
+ .addProperty(
+ PropertySpec.builder("bar", String::class, KModifier.ACTUAL)
+ .initializer(CodeBlock.of("%S", "Hello"))
+ .build(),
+ )
+ .build()
+
+ assertThat(fileSpec.toString()).isEqualTo(
+ """
+ |import kotlin.String
+ |
+ |public expect val bar: String
+ |
+ |public actual val bar: String = "Hello"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun topLevelFunctions() {
+ val fileSpec = FileSpec.builder("", "Test")
+ .addFunction(
+ FunSpec.builder("f1")
+ .addModifiers(KModifier.EXPECT)
+ .returns(Int::class)
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("f1")
+ .addModifiers(KModifier.ACTUAL)
+ .addStatement("return 1")
+ .build(),
+ )
+ .build()
+
+ assertThat(fileSpec.toString()).isEqualTo(
+ """
+ |import kotlin.Int
+ |
+ |public expect fun f1(): Int
+ |
+ |public actual fun f1() = 1
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun initBlockInExpectForbidden() {
+ assertThrows<IllegalStateException> {
+ TypeSpec.expectClassBuilder("AtomicRef")
+ .addInitializerBlock(CodeBlock.of("println()"))
+ }.hasMessageThat().isEqualTo("expect CLASS can't have initializer blocks")
+ }
+
+ @Test fun expectFunctionBodyForbidden() {
+ assertThrows<IllegalArgumentException> {
+ TypeSpec.expectClassBuilder("AtomicRef")
+ .addFunction(
+ FunSpec.builder("print")
+ .addStatement("println()")
+ .build(),
+ )
+ .build()
+ }.hasMessageThat().isEqualTo("functions in expect classes can't have bodies")
+ }
+
+ @Test fun expectPropertyInitializerForbidden() {
+ assertThrows<IllegalArgumentException> {
+ TypeSpec.expectClassBuilder("AtomicRef")
+ .addProperty(
+ PropertySpec.builder("a", Boolean::class)
+ .initializer("true")
+ .build(),
+ )
+ }.hasMessageThat().isEqualTo("properties in expect classes can't have initializers")
+ }
+
+ @Test fun expectPropertyGetterForbidden() {
+ assertThrows<IllegalArgumentException> {
+ TypeSpec.expectClassBuilder("AtomicRef")
+ .addProperty(
+ PropertySpec.builder("a", Boolean::class)
+ .getter(
+ FunSpec.getterBuilder()
+ .addStatement("return true")
+ .build(),
+ )
+ .build(),
+ )
+ }.hasMessageThat().isEqualTo("properties in expect classes can't have getters and setters")
+ }
+
+ @Test fun expectPropertySetterForbidden() {
+ assertThrows<IllegalArgumentException> {
+ TypeSpec.expectClassBuilder("AtomicRef")
+ .addProperty(
+ PropertySpec.builder("a", Boolean::class)
+ .mutable()
+ .setter(
+ FunSpec.setterBuilder()
+ .addParameter("value", Boolean::class)
+ .addStatement("field = true")
+ .build(),
+ )
+ .build(),
+ )
+ }.hasMessageThat().isEqualTo("properties in expect classes can't have getters and setters")
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/DelegatedConstructorCallTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/DelegatedConstructorCallTest.kt
new file mode 100644
index 00000000..80a90358
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/DelegatedConstructorCallTest.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+
+class DelegatedConstructorCallTest {
+ @Test
+ fun defaultPresentInClass() {
+ val builder = TypeSpec.classBuilder("Test")
+ .superclass(ClassName("testpackage", "TestSuper"))
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ |public class Test : testpackage.TestSuper()
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test
+ fun defaultPresentInObject() {
+ val builder = TypeSpec.objectBuilder("Test")
+ .superclass(ClassName("testpackage", "TestSuper"))
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ |public object Test : testpackage.TestSuper()
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test
+ fun defaultNotPresentInExternalClass() {
+ val builder = TypeSpec.classBuilder("Test")
+ .addModifiers(KModifier.EXTERNAL)
+ .superclass(ClassName("testpackage", "TestSuper"))
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ |public external class Test : testpackage.TestSuper
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test
+ fun defaultNotPresentInExpectClass() {
+ val builder = TypeSpec.classBuilder("Test")
+ .addModifiers(KModifier.EXPECT)
+ .superclass(ClassName("testpackage", "TestSuper"))
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ |public expect class Test : testpackage.TestSuper
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test
+ fun defaultNotPresentInExpectObject() {
+ val builder = TypeSpec.objectBuilder("Test")
+ .addModifiers(KModifier.EXPECT)
+ .superclass(ClassName("testpackage", "TestSuper"))
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ |public expect object Test : testpackage.TestSuper
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test
+ fun defaultNotPresentInExternalObject() {
+ val builder = TypeSpec.objectBuilder("Test")
+ .addModifiers(KModifier.EXTERNAL)
+ .superclass(ClassName("testpackage", "TestSuper"))
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ |public external object Test : testpackage.TestSuper
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test
+ fun allowedInClass() {
+ val builder = TypeSpec.classBuilder("Test")
+ .superclass(ClassName("testpackage", "TestSuper"))
+ .addSuperclassConstructorParameter("anything")
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ |public class Test : testpackage.TestSuper(anything)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test
+ fun allowedInObject() {
+ val builder = TypeSpec.objectBuilder("Test")
+ .superclass(ClassName("testpackage", "TestSuper"))
+ .addSuperclassConstructorParameter("anything")
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ |public object Test : testpackage.TestSuper(anything)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test
+ fun allowedInClassSecondary() {
+ val builder = TypeSpec.classBuilder("Test")
+ val primaryConstructorBuilder = FunSpec.constructorBuilder()
+ val primaryConstructor = primaryConstructorBuilder.build()
+ builder.primaryConstructor(primaryConstructor)
+ val secondaryConstructorBuilder = FunSpec.constructorBuilder()
+ .addParameter(ParameterSpec("foo", ClassName("kotlin", "String")))
+ .callThisConstructor()
+ builder.addFunction(secondaryConstructorBuilder.build())
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ |public class Test() {
+ | public constructor(foo: kotlin.String) : this()
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test
+ fun notAllowedInExternalClass() {
+ val builder = TypeSpec.classBuilder("Test")
+ .addModifiers(KModifier.EXTERNAL)
+ .superclass(ClassName("testpackage", "TestSuper"))
+ .addSuperclassConstructorParameter("anything")
+ assertThrows<IllegalStateException> {
+ builder.build()
+ }
+ }
+
+ @Test
+ fun notAllowedInExternalObject() {
+ val builder = TypeSpec.objectBuilder("Test")
+ .addModifiers(KModifier.EXTERNAL)
+ .superclass(ClassName("testpackage", "TestSuper"))
+ .addSuperclassConstructorParameter("anything")
+ assertThrows<IllegalStateException> {
+ builder.build()
+ }
+ }
+
+ @Test
+ fun notAllowedInExternalClassSecondary() {
+ val builder = TypeSpec.classBuilder("Test")
+ .addModifiers(KModifier.EXTERNAL)
+ val primaryConstructorBuilder = FunSpec.constructorBuilder()
+ builder.primaryConstructor(primaryConstructorBuilder.build())
+ val secondaryConstructorBuilder = FunSpec.constructorBuilder()
+ .addParameter(ParameterSpec("foo", ClassName("kotlin", "String")))
+ .callThisConstructor()
+ builder.addFunction(secondaryConstructorBuilder.build())
+ assertThrows<IllegalStateException> {
+ builder.build()
+ }
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ExpectDeclarationsTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ExpectDeclarationsTest.kt
new file mode 100644
index 00000000..5cb7753c
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ExpectDeclarationsTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+
+class ExpectDeclarationsTest {
+ @Test fun expectFunDeclaration() {
+ val methodSpec = FunSpec.builder("function")
+ .addModifiers(KModifier.EXPECT)
+ .build()
+
+ assertThat(methodSpec.toString()).isEqualTo(
+ """
+ |public expect fun function(): kotlin.Unit
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun implicitExpectFunDeclaration() {
+ val builder = TypeSpec.classBuilder("Test")
+ .addModifiers(KModifier.EXPECT)
+ val methodSpec = FunSpec.builder("function")
+ .build()
+ builder.addFunction(methodSpec)
+
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ |public expect class Test {
+ | public fun function(): kotlin.Unit
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun expectPropertyDeclaration() {
+ val propertySpec = PropertySpec.builder("prop", String::class)
+ .addModifiers(KModifier.EXPECT)
+ .build()
+
+ assertThat(propertySpec.toString()).isEqualTo(
+ """
+ |expect val prop: kotlin.String
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun implicitExpectPropertyDeclaration() {
+ val builder = TypeSpec.classBuilder("Test")
+ .addModifiers(KModifier.EXPECT)
+ val propertySpec = PropertySpec.builder("prop", String::class)
+ .build()
+ builder.addProperty(propertySpec)
+
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ |public expect class Test {
+ | public val prop: kotlin.String
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ExternalDeclarationsTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ExternalDeclarationsTest.kt
new file mode 100644
index 00000000..42719c41
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ExternalDeclarationsTest.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+
+class ExternalDeclarationsTest {
+ @Test fun externalFunDeclarationWithoutBody() {
+ val methodSpec = FunSpec.builder("function")
+ .addModifiers(KModifier.EXTERNAL)
+ .build()
+
+ assertThat(methodSpec.toString()).isEqualTo(
+ """
+ |public external fun function(): kotlin.Unit
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun externalFunDeclarationWithDefinedExternally() {
+ val methodSpec = FunSpec.builder("function")
+ .addModifiers(KModifier.EXTERNAL)
+ .addCode("return kotlin.js.definedExternally")
+ .build()
+
+ assertThat(methodSpec.toString()).isEqualTo(
+ """
+ |public external fun function() = kotlin.js.definedExternally
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun externalFunDeclarationWithDefinedExternally2() {
+ val methodSpec = FunSpec.builder("function")
+ .addModifiers(KModifier.EXTERNAL)
+ .addCode("kotlin.js.definedExternally")
+ .build()
+
+ assertThat(methodSpec.toString()).isEqualTo(
+ """
+ |public external fun function(): kotlin.Unit {
+ | kotlin.js.definedExternally
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun implicitExternalFunDeclarationWithoutBody() {
+ val builder = TypeSpec.classBuilder("Test")
+ .addModifiers(KModifier.EXTERNAL)
+ val methodSpec = FunSpec.builder("function")
+ .build()
+ builder.addFunction(methodSpec)
+
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ |public external class Test {
+ | public fun function(): kotlin.Unit
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun implicitExternalFunDeclarationWithDefinedExternally() {
+ val builder = TypeSpec.classBuilder("Test")
+ .addModifiers(KModifier.EXTERNAL)
+ val methodSpec = FunSpec.builder("function")
+ .addCode("return kotlin.js.definedExternally")
+ .build()
+ builder.addFunction(methodSpec)
+
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ |public external class Test {
+ | public fun function() = kotlin.js.definedExternally
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun implicitExternalFunDeclarationWithDefinedExternally2() {
+ val builder = TypeSpec.classBuilder("Test")
+ .addModifiers(KModifier.EXTERNAL)
+ val methodSpec = FunSpec.builder("function")
+ .addModifiers(KModifier.EXTERNAL)
+ .addCode("kotlin.js.definedExternally")
+ .build()
+ builder.addFunction(methodSpec)
+
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ |public external class Test {
+ | public fun function(): kotlin.Unit {
+ | kotlin.js.definedExternally
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun externalPropertyDeclarationWithoutInitializer() {
+ val propertySpec = PropertySpec.builder("prop", String::class)
+ .addModifiers(KModifier.EXTERNAL)
+ .build()
+
+ assertThat(propertySpec.toString()).isEqualTo(
+ """
+ |external val prop: kotlin.String
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun externalPropertyDeclarationWithDefinedExternally() {
+ val propertySpec = PropertySpec.builder("prop", String::class)
+ .addModifiers(KModifier.EXTERNAL)
+ .initializer("kotlin.js.definedExternally")
+ .build()
+
+ assertThat(propertySpec.toString()).isEqualTo(
+ """
+ |external val prop: kotlin.String = kotlin.js.definedExternally
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun implicitExternalPropertyDeclarationWithoutInitializer() {
+ val builder = TypeSpec.classBuilder("Test")
+ .addModifiers(KModifier.EXTERNAL)
+ val propertySpec = PropertySpec.builder("prop", String::class)
+ .build()
+ builder.addProperty(propertySpec)
+
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ |public external class Test {
+ | public val prop: kotlin.String
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun implicitExternalPropertyDeclarationWithDefinedExternally() {
+ val builder = TypeSpec.classBuilder("Test")
+ .addModifiers(KModifier.EXTERNAL)
+ val propertySpec = PropertySpec.builder("prop", String::class)
+ .initializer("kotlin.js.definedExternally")
+ .build()
+ builder.addProperty(propertySpec)
+
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ |public external class Test {
+ | public val prop: kotlin.String = kotlin.js.definedExternally
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileReadingTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileReadingTest.kt
new file mode 100644
index 00000000..35719cf8
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileReadingTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.io.ByteStreams
+import com.google.common.truth.Truth.assertThat
+import java.net.URI
+import java.nio.charset.StandardCharsets.UTF_8
+import javax.tools.JavaFileObject.Kind
+import kotlin.test.Test
+
+class FileReadingTest {
+ @Test fun javaFileObjectUri() {
+ val type = TypeSpec.classBuilder("Test").build()
+ assertThat(FileSpec.get("", type).toJavaFileObject().toUri())
+ .isEqualTo(URI.create("Test.kt"))
+ assertThat(FileSpec.get("foo", type).toJavaFileObject().toUri())
+ .isEqualTo(URI.create("foo/Test.kt"))
+ assertThat(FileSpec.get("com.example", type).toJavaFileObject().toUri())
+ .isEqualTo(URI.create("com/example/Test.kt"))
+ }
+
+ @Test fun javaFileObjectKind() {
+ val source = FileSpec.get("", TypeSpec.classBuilder("Test").build())
+ assertThat(source.toJavaFileObject().kind).isEqualTo(Kind.SOURCE)
+ }
+
+ @Test fun javaFileObjectCharacterContent() {
+ val type = TypeSpec.classBuilder("Test")
+ .addKdoc("Pi\u00f1ata\u00a1")
+ .addFunction(FunSpec.builder("fooBar").build())
+ .build()
+ val source = FileSpec.get("foo", type)
+ val javaFileObject = source.toJavaFileObject()
+
+ // We can never have encoding issues (everything is in process)
+ assertThat(javaFileObject.getCharContent(true)).isEqualTo(source.toString())
+ assertThat(javaFileObject.getCharContent(false)).isEqualTo(source.toString())
+ }
+
+ @Test fun javaFileObjectInputStreamIsUtf8() {
+ val source = FileSpec.builder("foo", "Test")
+ .addType(TypeSpec.classBuilder("Test").build())
+ .addFileComment("Pi\u00f1ata\u00a1")
+ .build()
+ val bytes = ByteStreams.toByteArray(source.toJavaFileObject().openInputStream())
+
+ // KotlinPoet always uses UTF-8.
+ assertThat(bytes).isEqualTo(source.toString().toByteArray(UTF_8))
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileSpecTest.kt
new file mode 100644
index 00000000..03b3cfc0
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileSpecTest.kt
@@ -0,0 +1,1215 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.SET
+import com.squareup.kotlinpoet.KModifier.VARARG
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import java.util.Collections
+import java.util.Date
+import java.util.concurrent.Callable
+import java.util.concurrent.TimeUnit
+import java.util.function.Function
+import kotlin.test.Ignore
+import kotlin.test.Test
+
+class FileSpecTest {
+ @Test fun importStaticReadmeExample() {
+ val hoverboard = ClassName("com.mattel", "Hoverboard")
+ val namedBoards = ClassName("com.mattel", "Hoverboard", "Boards")
+ val list = List::class.asClassName()
+ val arrayList = ClassName("java.util", "ArrayList").parameterizedBy(hoverboard)
+ val listOfHoverboards = list.parameterizedBy(hoverboard)
+ val beyond = FunSpec.builder("beyond")
+ .returns(listOfHoverboards)
+ .addStatement("val result = %T()", arrayList)
+ .addStatement("result.add(%T.createNimbus(2000))", hoverboard)
+ .addStatement("result.add(%T.createNimbus(\"2001\"))", hoverboard)
+ .addStatement("result.add(%T.createNimbus(%T.THUNDERBOLT))", hoverboard, namedBoards)
+ .addStatement("%T.sort(result)", Collections::class)
+ .addStatement("return if (result.isEmpty()) %T.emptyList() else result", Collections::class)
+ .build()
+ val hello = TypeSpec.classBuilder("HelloWorld")
+ .addFunction(beyond)
+ .build()
+ val source = FileSpec.builder("com.example.helloworld", "HelloWorld")
+ .addType(hello)
+ .addImport(hoverboard, "createNimbus")
+ .addImport(namedBoards, "THUNDERBOLT")
+ .addImport(Collections::class, "sort", "emptyList")
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.example.helloworld
+ |
+ |import com.mattel.Hoverboard
+ |import com.mattel.Hoverboard.Boards.THUNDERBOLT
+ |import com.mattel.Hoverboard.createNimbus
+ |import java.util.ArrayList
+ |import java.util.Collections.emptyList
+ |import java.util.Collections.sort
+ |import kotlin.collections.List
+ |
+ |public class HelloWorld {
+ | public fun beyond(): List<Hoverboard> {
+ | val result = ArrayList<Hoverboard>()
+ | result.add(createNimbus(2000))
+ | result.add(createNimbus("2001"))
+ | result.add(createNimbus(THUNDERBOLT))
+ | sort(result)
+ | return if (result.isEmpty()) emptyList() else result
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun importStaticMixed() {
+ val source = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .addInitializerBlock(
+ CodeBlock.builder()
+ .addStatement("assert %1T.valueOf(\"BLOCKED\") == %1T.BLOCKED", Thread.State::class)
+ .addStatement("%T.gc()", System::class)
+ .addStatement("%1T.out.println(%1T.nanoTime())", System::class)
+ .build(),
+ )
+ .addFunction(
+ FunSpec.constructorBuilder()
+ .addParameter("states", Thread.State::class.asClassName(), VARARG)
+ .build(),
+ )
+ .build(),
+ )
+ .addImport(Thread.State.BLOCKED)
+ .addImport(System::class, "gc", "out", "nanoTime")
+ .addImport(Thread.State::class, "valueOf")
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.lang.System.`out`
+ |import java.lang.System.gc
+ |import java.lang.System.nanoTime
+ |import java.lang.Thread
+ |import java.lang.Thread.State.BLOCKED
+ |import java.lang.Thread.State.valueOf
+ |
+ |public class Taco {
+ | init {
+ | assert valueOf("BLOCKED") == BLOCKED
+ | gc()
+ | out.println(nanoTime())
+ | }
+ |
+ | public constructor(vararg states: Thread.State)
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun importTopLevel() {
+ val source = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addImport("com.squareup.tacos.internal", "INGREDIENTS", "wrap")
+ .addFunction(
+ FunSpec.builder("prepareTacos")
+ .returns(
+ List::class.asClassName()
+ .parameterizedBy(ClassName("com.squareup.tacos", "Taco")),
+ )
+ .addCode("return wrap(INGREDIENTS)\n")
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.tacos.`internal`.INGREDIENTS
+ |import com.squareup.tacos.`internal`.wrap
+ |import kotlin.collections.List
+ |
+ |public fun prepareTacos(): List<Taco> = wrap(INGREDIENTS)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Ignore("addImport doesn't support members with %L")
+ @Test
+ fun importStaticDynamic() {
+ val source = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("main")
+ .addStatement("%T.%L.println(%S)", System::class, "out", "hello")
+ .build(),
+ )
+ .build(),
+ )
+ .addImport(System::class, "out")
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos;
+ |
+ |import static java.lang.System.out;
+ |
+ |class Taco {
+ | void main() {
+ | out.println("hello");
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun importStaticNone() {
+ val source = FileSpec.builder("readme", "Util")
+ .addType(importStaticTypeSpec("Util"))
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package readme
+ |
+ |import java.lang.System
+ |import java.util.concurrent.TimeUnit
+ |import kotlin.Long
+ |
+ |public class Util {
+ | public fun minutesToSeconds(minutes: Long): Long {
+ | System.gc()
+ | return TimeUnit.SECONDS.convert(minutes, TimeUnit.MINUTES)
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun importStaticOnce() {
+ val source = FileSpec.builder("readme", "Util")
+ .addType(importStaticTypeSpec("Util"))
+ .addImport(TimeUnit.SECONDS).build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package readme
+ |
+ |import java.lang.System
+ |import java.util.concurrent.TimeUnit
+ |import java.util.concurrent.TimeUnit.SECONDS
+ |import kotlin.Long
+ |
+ |public class Util {
+ | public fun minutesToSeconds(minutes: Long): Long {
+ | System.gc()
+ | return SECONDS.convert(minutes, TimeUnit.MINUTES)
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun importStaticTwice() {
+ val source = FileSpec.builder("readme", "Util")
+ .addType(importStaticTypeSpec("Util"))
+ .addImport(TimeUnit.SECONDS)
+ .addImport(TimeUnit.MINUTES)
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package readme
+ |
+ |import java.lang.System
+ |import java.util.concurrent.TimeUnit.MINUTES
+ |import java.util.concurrent.TimeUnit.SECONDS
+ |import kotlin.Long
+ |
+ |public class Util {
+ | public fun minutesToSeconds(minutes: Long): Long {
+ | System.gc()
+ | return SECONDS.convert(minutes, MINUTES)
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun importStaticWildcardsForbidden() {
+ assertThrows<IllegalArgumentException> {
+ FileSpec.builder("readme", "Util")
+ .addType(importStaticTypeSpec("Util"))
+ .addImport(TimeUnit::class, "*")
+ }.hasMessageThat().isEqualTo("Wildcard imports are not allowed")
+ }
+
+ private fun importStaticTypeSpec(name: String): TypeSpec {
+ val funSpec = FunSpec.builder("minutesToSeconds")
+ .addModifiers(KModifier.PUBLIC)
+ .returns(Long::class)
+ .addParameter("minutes", Long::class)
+ .addStatement("%T.gc()", System::class)
+ .addStatement("return %1T.SECONDS.convert(minutes, %1T.MINUTES)", TimeUnit::class)
+ .build()
+ return TypeSpec.classBuilder(name).addFunction(funSpec).build()
+ }
+
+ @Test fun noImports() {
+ val source = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(TypeSpec.classBuilder("Taco").build())
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |public class Taco
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun singleImport() {
+ val source = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .addProperty("madeFreshDate", Date::class)
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.util.Date
+ |
+ |public class Taco {
+ | public val madeFreshDate: Date
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun singleImportEscapeKeywords() {
+ val source = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .addProperty("madeFreshDate", ClassName("com.squareup.is.fun.in", "Date"))
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.`is`.`fun`.`in`.Date
+ |
+ |public class Taco {
+ | public val madeFreshDate: Date
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun escapeSpacesInPackageName() {
+ val file = FileSpec.builder("com.squareup.taco factory", "TacoFactory")
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.`taco factory`
+ |
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun conflictingImports() {
+ val source = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .addProperty("madeFreshDate", Date::class)
+ .addProperty("madeFreshDatabaseDate", ClassName("java.sql", "Date"))
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.sql.Date as SqlDate
+ |import java.util.Date as UtilDate
+ |
+ |public class Taco {
+ | public val madeFreshDate: UtilDate
+ |
+ | public val madeFreshDatabaseDate: SqlDate
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun conflictingImportsEscapeKeywords() {
+ val source = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .addProperty("madeFreshDate1", ClassName("com.squareup.is.fun.in", "Date"))
+ .addProperty("madeFreshDate2", ClassName("com.squareup.do.val.var", "Date"))
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.`do`.`val`.`var`.Date as VarDate
+ |import com.squareup.`is`.`fun`.`in`.Date as InDate
+ |
+ |public class Taco {
+ | public val madeFreshDate1: InDate
+ |
+ | public val madeFreshDate2: VarDate
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun escapeSpacesInImports() {
+ val tacoFactory = ClassName("com.squareup.taco factory", "TacoFactory")
+ val file = FileSpec.builder("com.example", "TacoFactoryDemo")
+ .addFunction(
+ FunSpec.builder("main")
+ .addStatement("println(%T.produceTacos())", tacoFactory)
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.example
+ |
+ |import com.squareup.`taco factory`.TacoFactory
+ |import kotlin.Unit
+ |
+ |public fun main(): Unit {
+ | println(TacoFactory.produceTacos())
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun escapeSpacesInAliasedImports() {
+ val tacoFactory = ClassName("com.squareup.taco factory", "TacoFactory")
+ val file = FileSpec.builder("com.example", "TacoFactoryDemo")
+ .addAliasedImport(tacoFactory, "La Taqueria")
+ .addFunction(
+ FunSpec.builder("main")
+ .addStatement("println(%T.produceTacos())", tacoFactory)
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.example
+ |
+ |import kotlin.Unit
+ |import com.squareup.`taco factory`.TacoFactory as `La Taqueria`
+ |
+ |public fun main(): Unit {
+ | println(`La Taqueria`.produceTacos())
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun aliasedImports() {
+ val source = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addAliasedImport(java.lang.String::class.java, "JString")
+ .addAliasedImport(String::class, "KString")
+ .addProperty(
+ PropertySpec.builder("a", java.lang.String::class.java)
+ .initializer("%T(%S)", java.lang.String::class.java, "a")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("b", String::class)
+ .initializer("%S", "b")
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.lang.String as JString
+ |import kotlin.String as KString
+ |
+ |public val a: JString = JString("a")
+ |
+ |public val b: KString = "b"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun enumAliasedImport() {
+ val minsAlias = "MINS"
+ val source = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addAliasedImport(TimeUnit::class.asClassName(), "MINUTES", minsAlias)
+ .addFunction(
+ FunSpec.builder("sleepForFiveMins")
+ .addStatement("%T.MINUTES.sleep(5)", TimeUnit::class)
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |import java.util.concurrent.TimeUnit.MINUTES as MINS
+ |
+ |public fun sleepForFiveMins(): Unit {
+ | MINS.sleep(5)
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun conflictingParentName() {
+ val source = FileSpec.builder("com.squareup.tacos", "A")
+ .addType(
+ TypeSpec.classBuilder("A")
+ .addType(
+ TypeSpec.classBuilder("B")
+ .addType(TypeSpec.classBuilder("Twin").build())
+ .addType(
+ TypeSpec.classBuilder("C")
+ .addProperty("d", ClassName("com.squareup.tacos", "A", "Twin", "D"))
+ .build(),
+ )
+ .build(),
+ )
+ .addType(
+ TypeSpec.classBuilder("Twin")
+ .addType(
+ TypeSpec.classBuilder("D")
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |public class A {
+ | public class B {
+ | public class Twin
+ |
+ | public class C {
+ | public val d: A.Twin.D
+ | }
+ | }
+ |
+ | public class Twin {
+ | public class D
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun conflictingChildName() {
+ val source = FileSpec.builder("com.squareup.tacos", "A")
+ .addType(
+ TypeSpec.classBuilder("A")
+ .addType(
+ TypeSpec.classBuilder("B")
+ .addType(
+ TypeSpec.classBuilder("C")
+ .addProperty("d", ClassName("com.squareup.tacos", "A", "Twin", "D"))
+ .addType(TypeSpec.classBuilder("Twin").build())
+ .build(),
+ )
+ .build(),
+ )
+ .addType(
+ TypeSpec.classBuilder("Twin")
+ .addType(
+ TypeSpec.classBuilder("D")
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |public class A {
+ | public class B {
+ | public class C {
+ | public val d: A.Twin.D
+ |
+ | public class Twin
+ | }
+ | }
+ |
+ | public class Twin {
+ | public class D
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun conflictingNameOutOfScope() {
+ val source = FileSpec.builder("com.squareup.tacos", "A")
+ .addType(
+ TypeSpec.classBuilder("A")
+ .addType(
+ TypeSpec.classBuilder("B")
+ .addType(
+ TypeSpec.classBuilder("C")
+ .addProperty("d", ClassName("com.squareup.tacos", "A", "Twin", "D"))
+ .addType(
+ TypeSpec.classBuilder("Nested")
+ .addType(TypeSpec.classBuilder("Twin").build())
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .addType(
+ TypeSpec.classBuilder("Twin")
+ .addType(
+ TypeSpec.classBuilder("D")
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |public class A {
+ | public class B {
+ | public class C {
+ | public val d: Twin.D
+ |
+ | public class Nested {
+ | public class Twin
+ | }
+ | }
+ | }
+ |
+ | public class Twin {
+ | public class D
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun nestedClassAndSuperclassShareName() {
+ val source = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .superclass(ClassName("com.squareup.wire", "Message"))
+ .addType(
+ TypeSpec.classBuilder("Builder")
+ .superclass(ClassName("com.squareup.wire", "Message", "Builder"))
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.wire.Message
+ |
+ |public class Taco : Message() {
+ | public class Builder : Message.Builder()
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ /** https://github.com/square/javapoet/issues/366 */
+ @Test fun annotationIsNestedClass() {
+ val source = FileSpec.builder("com.squareup.tacos", "TestComponent")
+ .addType(
+ TypeSpec.classBuilder("TestComponent")
+ .addAnnotation(ClassName("dagger", "Component"))
+ .addType(
+ TypeSpec.classBuilder("Builder")
+ .addAnnotation(ClassName("dagger", "Component", "Builder"))
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import dagger.Component
+ |
+ |@Component
+ |public class TestComponent {
+ | @Component.Builder
+ | public class Builder
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun defaultPackage() {
+ val source = FileSpec.builder("", "HelloWorld")
+ .addType(
+ TypeSpec.classBuilder("HelloWorld")
+ .addFunction(
+ FunSpec.builder("main")
+ .addModifiers(KModifier.PUBLIC)
+ .addParameter("args", ARRAY.parameterizedBy(String::class.asClassName()))
+ .addCode("%T.out.println(%S);\n", System::class, "Hello World!")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |import java.lang.System
+ |import kotlin.Array
+ |import kotlin.String
+ |import kotlin.Unit
+ |
+ |public class HelloWorld {
+ | public fun main(args: Array<String>): Unit {
+ | System.out.println("Hello World!");
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun defaultPackageTypesAreImported() {
+ val source = FileSpec.builder("hello", "World")
+ .addType(
+ TypeSpec.classBuilder("World")
+ .addSuperinterface(ClassName("", "Test"))
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package hello
+ |
+ |import Test
+ |
+ |public class World : Test
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun topOfFileComment() {
+ val source = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(TypeSpec.classBuilder("Taco").build())
+ .addFileComment("Generated %L by KotlinPoet. DO NOT EDIT!", "2015-01-13")
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |// Generated 2015-01-13 by KotlinPoet. DO NOT EDIT!
+ |package com.squareup.tacos
+ |
+ |public class Taco
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun emptyLinesInTopOfFileComment() {
+ val source = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(TypeSpec.classBuilder("Taco").build())
+ .addFileComment("\nGENERATED FILE:\n\nDO NOT EDIT!\n")
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |//
+ |// GENERATED FILE:
+ |//
+ |// DO NOT EDIT!
+ |//
+ |package com.squareup.tacos
+ |
+ |public class Taco
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun packageClassConflictsWithNestedClass() {
+ val source = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .addProperty("a", ClassName("com.squareup.tacos", "A"))
+ .addType(TypeSpec.classBuilder("A").build())
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |public class Taco {
+ | public val a: com.squareup.tacos.A
+ |
+ | public class A
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun multipleTypesInOneFile() {
+ val source = FileSpec.builder("com.squareup.tacos", "AB")
+ .addType(TypeSpec.classBuilder("A").build())
+ .addType(TypeSpec.classBuilder("B").build())
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |public class A
+ |
+ |public class B
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun simpleTypeAliases() {
+ val source = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addTypeAlias(TypeAliasSpec.builder("Int8", Byte::class).build())
+ .addTypeAlias(
+ TypeAliasSpec.builder(
+ "FileTable",
+ Map::class.parameterizedBy(String::class, Int::class),
+ ).build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Byte
+ |import kotlin.Int
+ |import kotlin.String
+ |import kotlin.collections.Map
+ |
+ |public typealias Int8 = Byte
+ |
+ |public typealias FileTable = Map<String, Int>
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun fileAnnotations() {
+ val source = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addAnnotation(
+ AnnotationSpec.builder(JvmName::class)
+ .useSiteTarget(FILE)
+ .addMember("%S", "TacoUtils")
+ .build(),
+ )
+ .addAnnotation(JvmMultifileClass::class)
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |@file:JvmName("TacoUtils")
+ |@file:JvmMultifileClass
+ |
+ |package com.squareup.tacos
+ |
+ |import kotlin.jvm.JvmMultifileClass
+ |import kotlin.jvm.JvmName
+ |
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun fileAnnotationMustHaveCorrectUseSiteTarget() {
+ val builder = FileSpec.builder("com.squareup.tacos", "Taco")
+ val annotation = AnnotationSpec.builder(JvmName::class)
+ .useSiteTarget(SET)
+ .addMember("value", "%S", "TacoUtils")
+ .build()
+ assertThrows<IllegalStateException> {
+ builder.addAnnotation(annotation)
+ }.hasMessageThat().isEqualTo("Use-site target SET not supported for file annotations.")
+ }
+
+ @Test fun escapeKeywordInPackageName() {
+ val source = FileSpec.builder("com.squareup.is.fun.in", "California")
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.`is`.`fun`.`in`
+ |
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun generalBuilderEqualityTest() {
+ val source = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addAnnotation(JvmMultifileClass::class)
+ .addFileComment("Generated 2015-01-13 by KotlinPoet. DO NOT EDIT!")
+ .addImport("com.squareup.tacos.internal", "INGREDIENTS")
+ .addTypeAlias(TypeAliasSpec.builder("Int8", Byte::class).build())
+ .indent(" ")
+ .addFunction(
+ FunSpec.builder("defaultIngredients")
+ .addCode("println(INGREDIENTS)\n")
+ .build(),
+ )
+ .build()
+
+ assertThat(source.toBuilder().build()).isEqualTo(source)
+ }
+
+ @Test fun modifyAnnotations() {
+ val builder = FileSpec.builder("com.taco", "Taco")
+ .addAnnotation(
+ AnnotationSpec.builder(JvmName::class.asClassName())
+ .useSiteTarget(FILE)
+ .addMember("name = %S", "JvmTaco")
+ .build(),
+ )
+
+ val javaWord = AnnotationSpec.builder(JvmName::class.asClassName())
+ .useSiteTarget(FILE)
+ .addMember("name = %S", "JavaTaco")
+ .build()
+ builder.annotations.clear()
+ builder.annotations.add(javaWord)
+
+ assertThat(builder.build().annotations).containsExactly(javaWord)
+ }
+
+ @Test fun modifyImports() {
+ val builder = FileSpec.builder("com.taco", "Taco")
+ .addImport("com.foo", "Foo")
+
+ val currentImports = builder.imports
+ builder.clearImports()
+ builder.addImport("com.foo", "Foo2")
+ .apply {
+ for (current in currentImports) {
+ addImport(current)
+ }
+ }
+ .indent("")
+
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ package com.taco
+
+ import com.foo.Foo
+ import com.foo.Foo2
+
+
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun modifyMembers() {
+ val builder = FileSpec.builder("com.taco", "Taco")
+ .addFunction(FunSpec.builder("aFunction").build())
+ .addProperty(PropertySpec.builder("aProperty", INT).initializer("1").build())
+ .addTypeAlias(TypeAliasSpec.builder("ATypeAlias", INT).build())
+ .addType(TypeSpec.classBuilder("AClass").build())
+
+ builder.members.removeAll { it !is TypeSpec }
+
+ check(builder.build().members.all { it is TypeSpec })
+ }
+
+ @Test fun clearComment() {
+ val builder = FileSpec.builder("com.taco", "Taco")
+ .addFunction(FunSpec.builder("aFunction").build())
+ .addFileComment("Hello!")
+
+ builder.clearComment()
+ .addFileComment("Goodbye!")
+
+ assertThat(builder.build().comment.toString()).isEqualTo("Goodbye!")
+ }
+
+ // https://github.com/square/kotlinpoet/issues/480
+ @Test fun defaultPackageMemberImport() {
+ val bigInteger = ClassName.bestGuess("bigInt.BigInteger")
+ val spec = FileSpec.builder("testsrc", "Test")
+ .addImport("", "bigInt")
+ .addFunction(
+ FunSpec.builder("add5ToInput")
+ .addParameter("input", Int::class)
+ .returns(bigInteger)
+ .addCode(
+ """
+ |val inputBigInt = bigInt(input)
+ |return inputBigInt.add(5)
+ |
+ """.trimMargin(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(spec.toString()).isEqualTo(
+ """
+ |package testsrc
+ |
+ |import bigInt
+ |import bigInt.BigInteger
+ |import kotlin.Int
+ |
+ |public fun add5ToInput(input: Int): BigInteger {
+ | val inputBigInt = bigInt(input)
+ | return inputBigInt.add(5)
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun longFilePackageName() {
+ val spec = FileSpec.builder("com.squareup.taco.enchilada.quesadillas.tamales.burritos.super.burritos.trying.to.get.a.really.large.packagename", "Test")
+ .addFunction(
+ FunSpec.builder("foo")
+ .build(),
+ )
+ .build()
+ assertThat(spec.toString()).isEqualTo(
+ """
+ |package com.squareup.taco.enchilada.quesadillas.tamales.burritos.`super`.burritos.trying.to.`get`.a.really.large.packagename
+ |
+ |import kotlin.Unit
+ |
+ |public fun foo(): Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun importLongPackageName() {
+ val spec = FileSpec.builder("testsrc", "Test")
+ .addImport("a.really.veryveryveryveryveryveryvery.long.pkgname.that.will.definitely.cause.a.wrap.duetoitslength", "MyClass")
+ .build()
+ assertThat(spec.toString()).isEqualTo(
+ """
+ |package testsrc
+ |
+ |import a.really.veryveryveryveryveryveryvery.long.pkgname.that.will.definitely.cause.a.wrap.duetoitslength.MyClass
+ |
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun importAliasedLongPackageName() {
+ val spec = FileSpec.builder("testsrc", "Test")
+ .addAliasedImport(ClassName("a.really.veryveryveryveryveryveryvery.long.pkgname.that.will.definitely.cause.a.wrap.duetoitslength", "MyClass"), "MyClassAlias")
+ .build()
+ assertThat(spec.toString()).isEqualTo(
+ """
+ |package testsrc
+ |
+ |import a.really.veryveryveryveryveryveryvery.long.pkgname.that.will.definitely.cause.a.wrap.duetoitslength.MyClass as MyClassAlias
+ |
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun longComment() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addFileComment(
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
+ "eiusmod tempor incididunt ut labore et dolore magna aliqua.",
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |// Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
+ |package com.squareup.tacos
+ |
+ |
+ """.trimMargin(),
+ )
+ }
+
+ class WackyKey
+ class OhNoThisDoesNotCompile
+
+ @Test fun longCommentWithTypes() {
+ val someLongParameterizedTypeName = typeNameOf<List<Map<in String, Collection<Map<WackyKey, out OhNoThisDoesNotCompile>>>>>()
+ val param = ParameterSpec.builder("foo", someLongParameterizedTypeName).build()
+ val someLongLambdaTypeName = LambdaTypeName.get(STRING, listOf(param), STRING).copy(suspending = true)
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addFunction(
+ FunSpec.builder("f1")
+ .addComment("this is a long line with a possibly long parameterized type with annotation: %T", someLongParameterizedTypeName)
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("f2")
+ .addComment("this is a very very very very very very very very very very long line with a very long lambda type: %T", someLongLambdaTypeName)
+ .build(),
+ )
+ .build()
+
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.kotlinpoet.FileSpecTest
+ |import kotlin.String
+ |import kotlin.Unit
+ |import kotlin.collections.Collection
+ |import kotlin.collections.List
+ |import kotlin.collections.Map
+ |
+ |public fun f1(): Unit {
+ | // this is a long line with a possibly long parameterized type with annotation: List<Map<in String, Collection<Map<FileSpecTest.WackyKey, out FileSpecTest.OhNoThisDoesNotCompile>>>>
+ |}
+ |
+ |public fun f2(): Unit {
+ | // this is a very very very very very very very very very very long line with a very long lambda type: suspend String.(foo: List<Map<in String, Collection<Map<FileSpecTest.WackyKey, out FileSpecTest.OhNoThisDoesNotCompile>>>>) -> String
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun simpleScriptTest() {
+ val spec = FileSpec.scriptBuilder("Taco")
+ .addProperty(PropertySpec.builder("prop", String::class).initializer("\"hi\"").build())
+ .addCode("\n")
+ .addStatement("println(%S)", "hello!")
+ .addCode("\n")
+ .addFunction(
+ FunSpec.builder("localFun")
+ .build(),
+ )
+ .addCode("\n")
+ .addType(TypeSpec.classBuilder("Yay").build())
+ .addCode("\n")
+ .addStatement("val yayInstance = Yay()")
+ .build()
+ assertThat(spec.toString()).isEqualTo(
+ """
+ |import kotlin.String
+ |import kotlin.Unit
+ |
+ |val prop: String = "hi"
+ |
+ |println("hello!")
+ |
+ |public fun localFun(): Unit {
+ |}
+ |
+ |public class Yay
+ |
+ |val yayInstance = Yay()
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun defaultImports() {
+ val spec = FileSpec.scriptBuilder("Taco")
+ .addProperty(PropertySpec.builder("prop0", STRING.copy(nullable = true)).initializer("null").build())
+ .addProperty(PropertySpec.builder("prop1", INT.copy(nullable = true)).initializer("null").build())
+ .addProperty(PropertySpec.builder("prop2", typeNameOf<Map<String, Any>?>()).initializer("null").build())
+ .addProperty(PropertySpec.builder("prop3", typeNameOf<Callable<String>?>()).initializer("null").build())
+ .addProperty(PropertySpec.builder("prop4", typeNameOf<Function<Int, Int>?>()).initializer("null").build())
+ .addKotlinDefaultImports()
+ .addDefaultPackageImport("java.util.function")
+ .build()
+ assertThat(spec.toString()).isEqualTo(
+ """
+ |import java.util.concurrent.Callable
+ |
+ |val prop0: String? = null
+ |val prop1: Int? = null
+ |val prop2: Map<String, Any>? = null
+ |val prop3: @FunctionalInterface Callable<String>? = null
+ |val prop4: @FunctionalInterface Function<Int, Int>? = null
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun classNameFactory() {
+ val className = ClassName("com.example", "Example")
+ val spec = FileSpec.builder(className).build()
+ assertThat(spec.packageName).isEqualTo(className.packageName)
+ assertThat(spec.name).isEqualTo(className.simpleName)
+ }
+
+ @Test fun classNameFactoryIllegalArgumentExceptionOnNestedType() {
+ val className = ClassName("com.example", "Example", "Nested")
+ assertThrows<IllegalArgumentException> {
+ FileSpec.builder(className)
+ }
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileWritingTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileWritingTest.kt
new file mode 100644
index 00000000..39807095
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FileWritingTest.kt
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.jimfs.Configuration
+import com.google.common.jimfs.Jimfs
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import java.nio.charset.StandardCharsets.UTF_8
+import java.nio.file.Files
+import java.util.Date
+import kotlin.test.Test
+import org.junit.Rule
+import org.junit.rules.TemporaryFolder
+
+class FileWritingTest {
+ // Used for testing java.io File behavior.
+ @JvmField @Rule
+ val tmp = TemporaryFolder()
+
+ // Used for testing java.nio.file Path behavior.
+ private val fs = Jimfs.newFileSystem(Configuration.unix())
+ private val fsRoot = fs.rootDirectories.iterator().next()
+
+ // Used for testing annotation processor Filer behavior.
+ private val filer = TestFiler(fs, fsRoot)
+
+ @Test fun pathNotDirectory() {
+ val type = TypeSpec.classBuilder("Test").build()
+ val source = FileSpec.get("example", type)
+ val path = fs.getPath("/foo/bar")
+ Files.createDirectories(path.parent)
+ Files.createFile(path)
+ assertThrows<IllegalArgumentException> {
+ source.writeTo(path)
+ }.hasMessageThat().isEqualTo("path /foo/bar exists but is not a directory.")
+ }
+
+ @Test fun fileNotDirectory() {
+ val type = TypeSpec.classBuilder("Test").build()
+ val source = FileSpec.get("example", type)
+ val file = File(tmp.newFolder("foo"), "bar")
+ file.createNewFile()
+ assertThrows<IllegalArgumentException> {
+ source.writeTo(file)
+ }.hasMessageThat().isEqualTo("path ${file.path} exists but is not a directory.")
+ }
+
+ @Test fun filerDefaultPackage() {
+ val type = TypeSpec.classBuilder("Test").build()
+ FileSpec.get("", type).writeTo(filer)
+
+ val testPath = fsRoot.resolve("Test.kt")
+ assertThat(Files.exists(testPath)).isTrue()
+ }
+
+ @Test fun pathDefaultPackage() {
+ val type = TypeSpec.classBuilder("Test").build()
+ FileSpec.get("", type).writeTo(fsRoot)
+
+ val testPath = fsRoot.resolve("Test.kt")
+ assertThat(Files.exists(testPath)).isTrue()
+ }
+
+ @Test fun pathDefaultPackageWithSubdirectory() {
+ val type = TypeSpec.classBuilder("Test").build()
+ FileSpec.get("", type).writeTo(fsRoot.resolve("sub"))
+
+ val testPath = fsRoot.resolve("sub/Test.kt")
+ assertThat(Files.exists(testPath)).isTrue()
+ }
+
+ @Test fun fileDefaultPackage() {
+ val type = TypeSpec.classBuilder("Test").build()
+ FileSpec.get("", type).writeTo(tmp.root)
+
+ val testFile = File(tmp.root, "Test.kt")
+ assertThat(testFile.exists()).isTrue()
+ }
+
+ @Test fun pathNestedClasses() {
+ val type = TypeSpec.classBuilder("Test").build()
+ FileSpec.get("foo", type).writeTo(fsRoot)
+ FileSpec.get("foo.bar", type).writeTo(fsRoot)
+ FileSpec.get("foo.bar.baz", type).writeTo(fsRoot)
+
+ val fooPath = fsRoot.resolve(fs.getPath("foo", "Test.kt"))
+ val barPath = fsRoot.resolve(fs.getPath("foo", "bar", "Test.kt"))
+ val bazPath = fsRoot.resolve(fs.getPath("foo", "bar", "baz", "Test.kt"))
+ assertThat(Files.exists(fooPath)).isTrue()
+ assertThat(Files.exists(barPath)).isTrue()
+ assertThat(Files.exists(bazPath)).isTrue()
+ }
+
+ @Test fun fileNestedClasses() {
+ val type = TypeSpec.classBuilder("Test").build()
+ FileSpec.get("foo", type).writeTo(tmp.root)
+ FileSpec.get("foo.bar", type).writeTo(tmp.root)
+ FileSpec.get("foo.bar.baz", type).writeTo(tmp.root)
+
+ val fooDir = File(tmp.root, "foo")
+ val fooFile = File(fooDir, "Test.kt")
+ val barDir = File(fooDir, "bar")
+ val barFile = File(barDir, "Test.kt")
+ val bazDir = File(barDir, "baz")
+ val bazFile = File(bazDir, "Test.kt")
+ assertThat(fooFile.exists()).isTrue()
+ assertThat(barFile.exists()).isTrue()
+ assertThat(bazFile.exists()).isTrue()
+ }
+
+ @Test fun filerNestedClasses() {
+ val type = TypeSpec.classBuilder("Test").build()
+ FileSpec.get("foo", type).writeTo(filer)
+ FileSpec.get("foo.bar", type).writeTo(filer)
+ FileSpec.get("foo.bar.baz", type).writeTo(filer)
+
+ val fooPath = fsRoot.resolve(fs.getPath("foo", "Test.kt"))
+ val barPath = fsRoot.resolve(fs.getPath("foo", "bar", "Test.kt"))
+ val bazPath = fsRoot.resolve(fs.getPath("foo", "bar", "baz", "Test.kt"))
+ assertThat(Files.exists(fooPath)).isTrue()
+ assertThat(Files.exists(barPath)).isTrue()
+ assertThat(Files.exists(bazPath)).isTrue()
+ }
+
+ @Suppress("LocalVariableName")
+ @Test
+ fun filerPassesOriginatingElements() {
+ // TypeSpecs
+ val element1_1 = FakeElement()
+ val test1 = TypeSpec.classBuilder("Test1")
+ .addOriginatingElement(element1_1)
+ .build()
+
+ val element2_1 = FakeElement()
+ val element2_2 = FakeElement()
+ val test2 = TypeSpec.classBuilder("Test2")
+ .addOriginatingElement(element2_1)
+ .addOriginatingElement(element2_2)
+ .build()
+
+ // FunSpecs
+ val element3_1 = FakeElement()
+ val element3_2 = FakeElement()
+ val test3 = FunSpec.builder("fun3")
+ .addOriginatingElement(element3_1)
+ .addOriginatingElement(element3_2)
+ .build()
+
+ // PropertySpecs
+ val element4_1 = FakeElement()
+ val element4_2 = FakeElement()
+ val test4 = PropertySpec.builder("property4", String::class)
+ .addOriginatingElement(element4_1)
+ .addOriginatingElement(element4_2)
+ .build()
+
+ FileSpec.get("example", test1).writeTo(filer)
+ FileSpec.get("example", test2).writeTo(filer)
+ FileSpec.builder("example", "Test3")
+ .addFunction(test3)
+ .build()
+ .writeTo(filer)
+ FileSpec.builder("example", "Test4")
+ .addProperty(test4)
+ .build()
+ .writeTo(filer)
+
+ // Mixed
+ FileSpec.builder("example", "Mixed")
+ .addType(test1)
+ .addType(test2)
+ .addFunction(test3)
+ .addProperty(test4)
+ .build()
+ .writeTo(filer)
+
+ val testPath1 = fsRoot.resolve(fs.getPath("example", "Test1.kt"))
+ assertThat(filer.getOriginatingElements(testPath1)).containsExactly(element1_1)
+ val testPath2 = fsRoot.resolve(fs.getPath("example", "Test2.kt"))
+ assertThat(filer.getOriginatingElements(testPath2)).containsExactly(element2_1, element2_2)
+ val testPath3 = fsRoot.resolve(fs.getPath("example", "Test3.kt"))
+ assertThat(filer.getOriginatingElements(testPath3)).containsExactly(element3_1, element3_2)
+ val testPath4 = fsRoot.resolve(fs.getPath("example", "Test4.kt"))
+ assertThat(filer.getOriginatingElements(testPath4)).containsExactly(element4_1, element4_2)
+
+ val mixed = fsRoot.resolve(fs.getPath("example", "Mixed.kt"))
+ assertThat(filer.getOriginatingElements(mixed)).containsExactly(
+ element1_1,
+ element2_1,
+ element2_2,
+ element3_1,
+ element3_2,
+ element4_1,
+ element4_2,
+ )
+ }
+
+ @Test fun filerPassesOnlyUniqueOriginatingElements() {
+ val element1 = FakeElement()
+ val fun1 = FunSpec.builder("test1")
+ .addOriginatingElement(element1)
+ .build()
+
+ val element2 = FakeElement()
+ val fun2 = FunSpec.builder("test2")
+ .addOriginatingElement(element1)
+ .addOriginatingElement(element2)
+ .build()
+
+ FileSpec.builder("example", "File")
+ .addFunction(fun1)
+ .addFunction(fun2)
+ .build()
+ .writeTo(filer)
+
+ val file = fsRoot.resolve(fs.getPath("example", "File.kt"))
+ assertThat(filer.getOriginatingElements(file)).containsExactly(element1, element2)
+ }
+
+ @Test fun filerClassesWithTabIndent() {
+ val test = TypeSpec.classBuilder("Test")
+ .addProperty("madeFreshDate", Date::class)
+ .addFunction(
+ FunSpec.builder("main")
+ .addModifiers(KModifier.PUBLIC)
+ .addParameter("args", Array<String>::class.java)
+ .addCode("%T.out.println(%S);\n", System::class, "Hello World!")
+ .build(),
+ )
+ .build()
+ FileSpec.builder("foo", "Test")
+ .addType(test)
+ .indent("\t")
+ .build()
+ .writeTo(filer)
+
+ val fooPath = fsRoot.resolve(fs.getPath("foo", "Test.kt"))
+ assertThat(Files.exists(fooPath)).isTrue()
+ val source = String(Files.readAllBytes(fooPath))
+
+ assertThat(source).isEqualTo(
+ """
+ |package foo
+ |
+ |import java.lang.String
+ |import java.lang.System
+ |import java.util.Date
+ |import kotlin.Array
+ |import kotlin.Unit
+ |
+ |public class Test {
+ |${"\t"}public val madeFreshDate: Date
+ |
+ |${"\t"}public fun main(args: Array<String>): Unit {
+ |${"\t\t"}System.out.println("Hello World!");
+ |${"\t"}}
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ /**
+ * This test confirms that KotlinPoet ignores the host charset and always uses UTF-8. The host
+ * charset is customized with `-Dfile.encoding=ISO-8859-1`.
+ */
+ @Test fun fileIsUtf8() {
+ val source = FileSpec.builder("foo", "Taco")
+ .addType(TypeSpec.classBuilder("Taco").build())
+ .addFileComment("Pi\u00f1ata\u00a1")
+ .build()
+ source.writeTo(fsRoot)
+
+ val fooPath = fsRoot.resolve(fs.getPath("foo", "Taco.kt"))
+ assertThat(String(Files.readAllBytes(fooPath), UTF_8)).isEqualTo(
+ """
+ |// Piñata¡
+ |package foo
+ |
+ |public class Taco
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun fileWithKeywordName() {
+ val type = TypeSpec.classBuilder("fun").build()
+ FileSpec.get("", type).writeTo(filer)
+
+ val testPath = fsRoot.resolve("fun.kt")
+ assertThat(Files.exists(testPath)).isTrue()
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt
new file mode 100644
index 00000000..1376cafc
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/FunSpecTest.kt
@@ -0,0 +1,1219 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.collect.Iterables.getOnlyElement
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.compile.CompilationRule
+import com.squareup.kotlinpoet.FunSpec.Companion.GETTER
+import com.squareup.kotlinpoet.FunSpec.Companion.SETTER
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import java.io.Closeable
+import java.io.IOException
+import java.util.concurrent.Callable
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.util.ElementFilter.methodsIn
+import javax.lang.model.util.Elements
+import javax.lang.model.util.Types
+import kotlin.test.BeforeTest
+import kotlin.test.Test
+import org.junit.Rule
+
+@OptIn(ExperimentalKotlinPoetApi::class)
+class FunSpecTest {
+ @Rule @JvmField
+ val compilation = CompilationRule()
+
+ private lateinit var elements: Elements
+ private lateinit var types: Types
+
+ @BeforeTest fun setUp() {
+ elements = compilation.elements
+ types = compilation.types
+ }
+
+ private fun getElement(`class`: Class<*>): TypeElement {
+ return elements.getTypeElement(`class`.canonicalName)
+ }
+
+ private fun findFirst(elements: Collection<ExecutableElement>, name: String) =
+ elements.firstOrNull { it.simpleName.toString() == name }
+ ?: throw IllegalArgumentException("$name not found in $elements")
+
+ @Target(AnnotationTarget.VALUE_PARAMETER)
+ internal annotation class Nullable
+
+ internal abstract class Everything {
+ @Deprecated("")
+ @Throws(IOException::class, SecurityException::class)
+ protected abstract fun <T> everything(
+ @Nullable thing: String,
+ things: List<T>,
+ ): Runnable where T : Runnable, T : Closeable
+ }
+
+ internal abstract class HasAnnotation {
+ abstract override fun toString(): String
+ }
+
+ internal interface ExtendsOthers : Callable<Int>, Comparable<Long>
+
+ annotation class TestAnnotation
+
+ abstract class InvalidOverrideMethods {
+ fun finalMethod() {
+ }
+
+ private fun privateMethod() {
+ }
+
+ companion object {
+ @JvmStatic open fun staticMethod() {
+ }
+ }
+ }
+
+ @Test fun overrideEverything() {
+ val classElement = getElement(Everything::class.java)
+ val methodElement = getOnlyElement(methodsIn(classElement.enclosedElements))
+ val funSpec = FunSpec.overriding(methodElement).build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |@kotlin.jvm.Throws(java.io.IOException::class, java.lang.SecurityException::class)
+ |protected override fun <T> everything(arg0: java.lang.String, arg1: java.util.List<out T>): java.lang.Runnable where T : java.lang.Runnable, T : java.io.Closeable {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun overrideDoesNotCopyOverrideAnnotation() {
+ val classElement = getElement(HasAnnotation::class.java)
+ val exec = getOnlyElement(methodsIn(classElement.enclosedElements))
+ val funSpec = FunSpec.overriding(exec).build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public override fun toString(): java.lang.String {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun overrideExtendsOthersWorksWithActualTypeParameters() {
+ val classElement = getElement(ExtendsOthers::class.java)
+ val classType = classElement.asType() as DeclaredType
+ val methods = methodsIn(elements.getAllMembers(classElement))
+ var exec = findFirst(methods, "call")
+ var funSpec = FunSpec.overriding(exec, classType, types).build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |@kotlin.jvm.Throws(java.lang.Exception::class)
+ |public override fun call(): java.lang.Integer {
+ |}
+ |
+ """.trimMargin(),
+ )
+ exec = findFirst(methods, "compareTo")
+ funSpec = FunSpec.overriding(exec, classType, types).build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public override fun compareTo(arg0: java.lang.Long): kotlin.Int {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun overrideInvalidModifiers() {
+ val classElement = getElement(InvalidOverrideMethods::class.java)
+ val methods = methodsIn(elements.getAllMembers(classElement))
+
+ assertThrows<IllegalArgumentException> {
+ FunSpec.overriding(findFirst(methods, "finalMethod"))
+ }.hasMessageThat().isEqualTo("cannot override method with modifiers: [public, final]")
+
+ assertThrows<IllegalArgumentException> {
+ FunSpec.overriding(findFirst(methods, "privateMethod"))
+ }.hasMessageThat().isEqualTo("cannot override method with modifiers: [private, final]")
+
+ assertThrows<IllegalArgumentException> {
+ FunSpec.overriding(findFirst(methods, "staticMethod"))
+ }.hasMessageThat().isEqualTo("cannot override method with modifiers: [public, static]")
+ }
+
+ @Test fun nullableParam() {
+ val funSpec = FunSpec.builder("foo")
+ .addParameter(
+ ParameterSpec
+ .builder("string", String::class.asTypeName().copy(nullable = true))
+ .build(),
+ )
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun foo(string: kotlin.String?): kotlin.Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun nullableReturnType() {
+ val funSpec = FunSpec.builder("foo")
+ .returns(String::class.asTypeName().copy(nullable = true))
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun foo(): kotlin.String? {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun returnsUnitWithoutExpressionBody() {
+ val funSpec = FunSpec.builder("foo")
+ .returns(Unit::class)
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun foo(): kotlin.Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun returnsUnitWithExpressionBody() {
+ val funSpec = FunSpec.builder("foo")
+ .returns(Unit::class)
+ .addStatement("return bar()")
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun foo(): kotlin.Unit = bar()
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun returnsLongExpression() {
+ val funSpec = FunSpec.builder("foo")
+ .returns(String::class)
+ .addStatement("val placeholder = 1")
+ .addStatement("return %S", "Loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong")
+ .build()
+ val sb = StringBuilder()
+ // The FunSpec#toString columnLimit is Integer.MAX_VALUE,
+ // It will not cause problems with returns long expressions.
+ CodeWriter(sb).use {
+ funSpec.emit(
+ codeWriter = it,
+ enclosingName = null,
+ implicitModifiers = setOf(KModifier.PUBLIC),
+ includeKdocTags = false,
+ )
+ }
+ assertThat(sb.toString()).isEqualTo(
+ """
+ |public fun foo(): kotlin.String {
+ | val placeholder = 1
+ | return "Loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong"
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun functionParamWithKdoc() {
+ val funSpec = FunSpec.builder("foo")
+ .addParameter(
+ ParameterSpec.builder("string", String::class.asTypeName())
+ .addKdoc("A string parameter.")
+ .build(),
+ )
+ .addParameter(
+ ParameterSpec.builder("number", Int::class.asTypeName())
+ .addKdoc("A number with a multi-line doc comment.\nYes,\nthese\nthings\nhappen.")
+ .build(),
+ )
+ .addParameter(ParameterSpec.builder("nodoc", Boolean::class.asTypeName()).build())
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |/**
+ | * @param string A string parameter.
+ | * @param number A number with a multi-line doc comment.
+ | * Yes,
+ | * these
+ | * things
+ | * happen.
+ | */
+ |public fun foo(
+ | string: kotlin.String,
+ | number: kotlin.Int,
+ | nodoc: kotlin.Boolean,
+ |): kotlin.Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun functionParamWithKdocToBuilder() {
+ val funSpec = FunSpec.builder("foo")
+ .addParameter(
+ ParameterSpec.builder("string", String::class.asTypeName())
+ .addKdoc("A string parameter.")
+ .build()
+ .toBuilder()
+ .addKdoc(" This is non null")
+ .build(),
+ )
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |/**
+ | * @param string A string parameter. This is non null
+ | */
+ |public fun foo(string: kotlin.String): kotlin.Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun originatingElementToBuilder() {
+ val originatingElement = FakeElement()
+ val funSpec = FunSpec.builder("foo")
+ .addOriginatingElement(originatingElement)
+ .build()
+
+ val newSpec = funSpec.toBuilder().build()
+ assertThat(newSpec.originatingElements).containsExactly(originatingElement)
+ }
+
+ @Test fun functionParamWithKdocAndReturnKdoc() {
+ val funSpec = FunSpec.builder("foo")
+ .addParameter(
+ ParameterSpec.builder("string", String::class)
+ .addKdoc("A string parameter.")
+ .build(),
+ )
+ .addParameter(ParameterSpec.builder("nodoc", Boolean::class).build())
+ .returns(String::class, kdoc = "the foo.")
+ .addStatement("return %S", "foo")
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |/**
+ | * @param string A string parameter.
+ | * @return the foo.
+ | */
+ |public fun foo(string: kotlin.String, nodoc: kotlin.Boolean): kotlin.String = "foo"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun functionWithModifiedReturnKdoc() {
+ val funSpec = FunSpec.builder("foo")
+ .addParameter("nodoc", Boolean::class)
+ .returns(String::class, kdoc = "the foo.")
+ .addStatement("return %S", "foo")
+ .build()
+ .toBuilder()
+ .returns(String::class, kdoc = "the modified foo.")
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |/**
+ | * @return the modified foo.
+ | */
+ |public fun foo(nodoc: kotlin.Boolean): kotlin.String = "foo"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun functionWithThrows() {
+ val funSpec = FunSpec.builder("foo")
+ .addStatement("throw %T()", AssertionError::class)
+ .returns(NOTHING)
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun foo(): kotlin.Nothing = throw java.lang.AssertionError()
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun functionWithWordThrowDoesntConvertToExpressionFunction() {
+ val throwSomethingElseFun = FunSpec.builder("throwOrDoSomethingElse")
+ .build()
+
+ val funSpec = FunSpec.builder("foo")
+ .addStatement("%N()", throwSomethingElseFun)
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun foo(): kotlin.Unit {
+ | throwOrDoSomethingElse()
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun expressionBodyIsDetectedReturnWithNonBreakingSpace() {
+ val funSpec = FunSpec.builder("foo")
+ .addStatement("return·1")
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun foo() = 1
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun expressionBodyIsDetectedThrowWithNonBreakingSpace() {
+ val funSpec = FunSpec.builder("foo")
+ .addStatement("throw·%T()", AssertionError::class)
+ .returns(NOTHING)
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun foo(): kotlin.Nothing = throw java.lang.AssertionError()
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun functionWithReturnKDocAndMainKdoc() {
+ val funSpec = FunSpec.builder("foo")
+ .addParameter("nodoc", Boolean::class)
+ .returns(String::class, kdoc = "the foo.")
+ .addStatement("return %S", "foo")
+ .addKdoc("Do the foo")
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |/**
+ | * Do the foo
+ | *
+ | * @return the foo.
+ | */
+ |public fun foo(nodoc: kotlin.Boolean): kotlin.String = "foo"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun functionParamNoLambdaParam() {
+ val unitType = UNIT
+ val funSpec = FunSpec.builder("foo")
+ .addParameter(ParameterSpec.builder("f", LambdaTypeName.get(returnType = unitType)).build())
+ .returns(String::class)
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun foo(f: () -> kotlin.Unit): kotlin.String {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun functionWithReturnKDoc() {
+ val funSpec = FunSpec.builder("foo")
+ .addParameter(ParameterSpec.builder("f", LambdaTypeName.get(returnType = UNIT)).build())
+ .returns(String::class, CodeBlock.of("the foo."))
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |/**
+ | * @return the foo.
+ | */
+ |public fun foo(f: () -> kotlin.Unit): kotlin.String {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun functionParamNoLambdaParamWithReceiver() {
+ val unitType = UNIT
+ val lambdaTypeName = LambdaTypeName.get(receiver = INT, returnType = unitType)
+ val funSpec = FunSpec.builder("foo")
+ .addParameter(ParameterSpec.builder("f", lambdaTypeName).build())
+ .returns(String::class)
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun foo(f: kotlin.Int.() -> kotlin.Unit): kotlin.String {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun functionWithContextReceiver() {
+ val stringType = STRING
+ val funSpec = FunSpec.builder("foo")
+ .contextReceivers(stringType)
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |context(kotlin.String)
+ |public fun foo(): kotlin.Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun functionWithMultipleContextReceivers() {
+ val stringType = STRING
+ val intType = INT
+ val booleanType = BOOLEAN
+ val funSpec = FunSpec.builder("foo")
+ .contextReceivers(stringType, intType, booleanType)
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |context(kotlin.String, kotlin.Int, kotlin.Boolean)
+ |public fun foo(): kotlin.Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun functionWithGenericContextReceiver() {
+ val genericType = TypeVariableName("T")
+ val funSpec = FunSpec.builder("foo")
+ .addTypeVariable(genericType)
+ .contextReceivers(genericType)
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |context(T)
+ |public fun <T> foo(): kotlin.Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun annotatedFunctionWithContextReceiver() {
+ val funSpec = FunSpec.builder("foo")
+ .addAnnotation(AnnotationSpec.get(TestAnnotation()))
+ .contextReceivers(STRING)
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |context(kotlin.String)
+ |@com.squareup.kotlinpoet.FunSpecTest.TestAnnotation
+ |public fun foo(): kotlin.Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun functionWithAnnotatedContextReceiver() {
+ val genericType = STRING.copy(annotations = listOf(AnnotationSpec.get(TestAnnotation())))
+ val funSpec = FunSpec.builder("foo")
+ .contextReceivers(genericType)
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |context(@com.squareup.kotlinpoet.FunSpecTest.TestAnnotation kotlin.String)
+ |public fun foo(): kotlin.Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun constructorWithContextReceiver() {
+ assertThrows<IllegalStateException> {
+ FunSpec.constructorBuilder()
+ .contextReceivers(STRING)
+ }.hasMessageThat().isEqualTo("constructors cannot have context receivers")
+ }
+
+ @Test fun accessorWithContextReceiver() {
+ assertThrows<IllegalStateException> {
+ FunSpec.getterBuilder()
+ .contextReceivers(STRING)
+ }.hasMessageThat().isEqualTo("$GETTER cannot have context receivers")
+
+ assertThrows<IllegalStateException> {
+ FunSpec.setterBuilder()
+ .contextReceivers(STRING)
+ }.hasMessageThat().isEqualTo("$SETTER cannot have context receivers")
+ }
+
+ @Test fun functionParamSingleLambdaParam() {
+ val unitType = UNIT
+ val booleanType = BOOLEAN
+ val funSpec = FunSpec.builder("foo")
+ .addParameter(
+ ParameterSpec.builder(
+ "f",
+ LambdaTypeName.get(
+ parameters = arrayOf(booleanType),
+ returnType = unitType,
+ ),
+ )
+ .build(),
+ )
+ .returns(String::class)
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun foo(f: (kotlin.Boolean) -> kotlin.Unit): kotlin.String {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun functionParamMultipleLambdaParam() {
+ val unitType = UNIT
+ val booleanType = BOOLEAN
+ val stringType = String::class.asClassName()
+ val lambdaType = LambdaTypeName.get(parameters = arrayOf(booleanType, stringType), returnType = unitType)
+ val funSpec = FunSpec.builder("foo")
+ .addParameter(ParameterSpec.builder("f", lambdaType).build())
+ .returns(String::class)
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun foo(f: (kotlin.Boolean, kotlin.String) -> kotlin.Unit): kotlin.String {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun functionParamMultipleLambdaParamNullableLambda() {
+ val unitType = Unit::class.asClassName()
+ val booleanType = Boolean::class.asClassName()
+ val stringType = String::class.asClassName()
+ val lambdaTypeName = LambdaTypeName
+ .get(parameters = arrayOf(booleanType, stringType), returnType = unitType)
+ .copy(nullable = true)
+ val funSpec = FunSpec.builder("foo")
+ .addParameter(ParameterSpec.builder("f", lambdaTypeName).build())
+ .returns(String::class)
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun foo(f: ((kotlin.Boolean, kotlin.String) -> kotlin.Unit)?): kotlin.String {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun functionParamMultipleNullableLambdaParam() {
+ val unitType = Unit::class.asClassName()
+ val booleanType = Boolean::class.asClassName()
+ val stringType = String::class.asClassName().copy(nullable = true)
+ val lambdaTypeName = LambdaTypeName
+ .get(parameters = arrayOf(booleanType, stringType), returnType = unitType)
+ .copy(nullable = true)
+ val funSpec = FunSpec.builder("foo")
+ .addParameter(ParameterSpec.builder("f", lambdaTypeName).build())
+ .returns(String::class)
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun foo(f: ((kotlin.Boolean, kotlin.String?) -> kotlin.Unit)?): kotlin.String {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun setterWithPublicModifier() {
+ val funSpec = FunSpec.setterBuilder()
+ .addParameter("value", String::class.asClassName())
+ .addStatement("this.value = this.value")
+ .addModifiers(KModifier.PUBLIC)
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public set(`value`) {
+ | this.value = this.value
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun getterWithPublicModifier() {
+ val funSpec = FunSpec.getterBuilder()
+ .addStatement("return value")
+ .addModifiers(KModifier.PUBLIC)
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public get() = value
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // This does not produce correct Kotlin, but it does at least verify that we do not drop the
+ // explicitly specified public modifier.
+ @Test fun methodWithMultipleVisibilityModifiers() {
+ val funSpec =
+ FunSpec.builder("myMethod")
+ .addModifiers(KModifier.PUBLIC, KModifier.INTERNAL, KModifier.PRIVATE)
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public private internal fun myMethod(): kotlin.Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun methodWithRepeatedVisibilityModifier() {
+ val funSpec =
+ FunSpec.builder("myMethod")
+ .addModifiers(KModifier.PUBLIC, KModifier.PUBLIC, KModifier.PUBLIC)
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun myMethod(): kotlin.Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun thisConstructorDelegate() {
+ val funSpec = FunSpec.constructorBuilder()
+ .addParameter("list", List::class.parameterizedBy(Int::class))
+ .callThisConstructor("list[0]", "list[1]")
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public constructor(list: kotlin.collections.List<kotlin.Int>) : this(list[0], list[1])
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun superConstructorDelegate() {
+ val funSpec = FunSpec.constructorBuilder()
+ .addParameter("list", List::class.parameterizedBy(Int::class))
+ .callSuperConstructor("list[0]", "list[1]")
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public constructor(list: kotlin.collections.List<kotlin.Int>) : super(list[0], list[1])
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun emptyConstructorDelegate() {
+ val funSpec = FunSpec.constructorBuilder()
+ .addParameter("a", Int::class)
+ .callThisConstructor()
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public constructor(a: kotlin.Int) : this()
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun constructorDelegateWithBody() {
+ val funSpec = FunSpec.constructorBuilder()
+ .addParameter("a", Int::class)
+ .callThisConstructor("a")
+ .addStatement("println()")
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public constructor(a: kotlin.Int) : this(a) {
+ | println()
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun addingDelegateParametersToNonConstructorForbidden() {
+ assertThrows<IllegalStateException> {
+ FunSpec.builder("main")
+ .callThisConstructor("a", "b", "c")
+ }.hasMessageThat().isEqualTo("only constructors can delegate to other constructors!")
+ }
+
+ @Test fun emptySecondaryConstructor() {
+ val constructorSpec = FunSpec.constructorBuilder()
+ .addParameter("a", Int::class)
+ .build()
+
+ assertThat(constructorSpec.toString()).isEqualTo(
+ """
+ |public constructor(a: kotlin.Int)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun reifiedTypesOnNonInlineFunctionsForbidden() {
+ assertThrows<IllegalArgumentException> {
+ FunSpec.builder("foo")
+ .addTypeVariable(TypeVariableName("T").copy(reified = true))
+ .build()
+ }.hasMessageThat().isEqualTo("only type parameters of inline functions can be reified!")
+ }
+
+ @Test fun equalsAndHashCode() {
+ var a = FunSpec.constructorBuilder().build()
+ var b = FunSpec.constructorBuilder().build()
+ assertThat(a == b).isTrue()
+ assertThat(a.hashCode()).isEqualTo(b.hashCode())
+ a = FunSpec.builder("taco").build()
+ b = FunSpec.builder("taco").build()
+ assertThat(a == b).isTrue()
+ assertThat(a.hashCode()).isEqualTo(b.hashCode())
+ val classElement = getElement(Everything::class.java)
+ val methodElement = getOnlyElement(methodsIn(classElement.enclosedElements))
+ a = FunSpec.overriding(methodElement).build()
+ b = FunSpec.overriding(methodElement).build()
+ assertThat(a == b).isTrue()
+ assertThat(a.hashCode()).isEqualTo(b.hashCode())
+ }
+
+ @Test fun escapeKeywordInFunctionName() {
+ val funSpec = FunSpec.builder("if")
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun `if`(): kotlin.Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun escapePunctuationInFunctionName() {
+ val funSpec = FunSpec.builder("with-hyphen")
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun `with-hyphen`(): kotlin.Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun generalBuilderEqualityTest() {
+ val funSpec = FunSpec.Builder("getConfig")
+ .addKdoc("Fix me")
+ .addAnnotation(
+ AnnotationSpec.builder(SuppressWarnings::class)
+ .build(),
+ )
+ .addModifiers(KModifier.PROTECTED)
+ .addTypeVariable(TypeVariableName("T"))
+ .receiver(String::class)
+ .returns(String::class)
+ .addParameter(
+ ParameterSpec.builder("config", String::class)
+ .build(),
+ )
+ .addParameter(
+ ParameterSpec.builder("override", TypeVariableName("T"))
+ .build(),
+ )
+ .beginControlFlow("return when")
+ .addStatement(" override is String -> config + override")
+ .addStatement(" else -> config + %S", "{ttl:500}")
+ .endControlFlow()
+ .build()
+
+ assertThat(funSpec.toBuilder().build()).isEqualTo(funSpec)
+ }
+
+ @Test fun receiverWithKdoc() {
+ val funSpec = FunSpec.builder("toBar")
+ .receiver(String::class, kdoc = "the string to transform.")
+ .returns(String::class)
+ .addStatement("return %S", "bar")
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |/**
+ | * @receiver the string to transform.
+ | */
+ |public fun kotlin.String.toBar(): kotlin.String = "bar"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun receiverWithKdocAndMainKDoc() {
+ val funSpec = FunSpec.builder("toBar")
+ .receiver(String::class, kdoc = "the string to transform.")
+ .returns(String::class)
+ .addKdoc("%L", "Converts to bar")
+ .addStatement("return %S", "bar")
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |/**
+ | * Converts to bar
+ | *
+ | * @receiver the string to transform.
+ | */
+ |public fun kotlin.String.toBar(): kotlin.String = "bar"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun withAllKdocTags() {
+ val funSpec = FunSpec.builder("charAt")
+ .receiver(String::class, kdoc = "the string you want the char from.")
+ .returns(Char::class, kdoc = "The char at the given [position].")
+ .addParameter(
+ ParameterSpec.builder("position", Int::class)
+ .addKdoc("the index of the character that is returned.")
+ .build(),
+ )
+ .addKdoc("Returns the character at the given [position].\n\n")
+ .addStatement("return -1")
+ .build()
+
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |/**
+ | * Returns the character at the given [position].
+ | *
+ | * @receiver the string you want the char from.
+ | * @param position the index of the character that is returned.
+ | * @return The char at the given [position].
+ | */
+ |public fun kotlin.String.charAt(position: kotlin.Int): kotlin.Char = -1
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun constructorBuilderEqualityTest() {
+ val funSpec = FunSpec.constructorBuilder()
+ .addParameter("list", List::class.parameterizedBy(Int::class))
+ .callThisConstructor("list[0]", "list[1]")
+ .build()
+
+ assertThat(funSpec.toBuilder().build()).isEqualTo(funSpec)
+ }
+
+ // https://github.com/square/kotlinpoet/issues/398
+ @Test fun changingDelegateConstructorOverridesArgs() {
+ val funSpec = FunSpec.constructorBuilder()
+ .addParameter("values", List::class.parameterizedBy(String::class))
+ .callSuperConstructor("values")
+ .build()
+ val updatedFunSpec = funSpec.toBuilder()
+ .callSuperConstructor("values.toImmutableList()")
+ .build()
+ assertThat(updatedFunSpec.toString()).isEqualTo(
+ """
+ |public constructor(values: kotlin.collections.List<kotlin.String>) : super(values.toImmutableList())
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun modifyModifiers() {
+ val builder = FunSpec.builder("taco")
+ .addModifiers(KModifier.PRIVATE)
+
+ builder.modifiers.clear()
+ builder.modifiers.add(KModifier.INTERNAL)
+
+ assertThat(builder.build().modifiers).containsExactly(KModifier.INTERNAL)
+ }
+
+ @Test fun modifyAnnotations() {
+ val builder = FunSpec.builder("taco")
+ .addAnnotation(
+ AnnotationSpec.builder(JvmName::class.asClassName())
+ .addMember("name = %S", "jvmWord")
+ .build(),
+ )
+
+ val javaWord = AnnotationSpec.builder(JvmName::class.asClassName())
+ .addMember("name = %S", "javaWord")
+ .build()
+ builder.annotations.clear()
+ builder.annotations.add(javaWord)
+
+ assertThat(builder.build().annotations).containsExactly(javaWord)
+ }
+
+ @Test fun modifyTypeVariableNames() {
+ val builder = FunSpec.builder("taco")
+ .addTypeVariable(TypeVariableName("V"))
+
+ val tVar = TypeVariableName("T")
+ builder.typeVariables.clear()
+ builder.typeVariables.add(tVar)
+
+ assertThat(builder.build().typeVariables).containsExactly(tVar)
+ }
+
+ @Test fun modifyParameters() {
+ val builder = FunSpec.builder("taco")
+ .addParameter(ParameterSpec.builder("topping", String::class.asClassName()).build())
+
+ val seasoning = ParameterSpec.builder("seasoning", String::class.asClassName()).build()
+ builder.parameters.clear()
+ builder.parameters.add(seasoning)
+
+ assertThat(builder.build().parameters).containsExactly(seasoning)
+ }
+
+ @Test fun jvmStaticModifier() {
+ val builder = FunSpec.builder("staticMethod")
+ builder.jvmModifiers(listOf(Modifier.STATIC))
+
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ |@kotlin.jvm.JvmStatic
+ |internal fun staticMethod(): kotlin.Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmFinalModifier() {
+ val builder = FunSpec.builder("finalMethod")
+ builder.jvmModifiers(listOf(Modifier.FINAL))
+
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ |internal final fun finalMethod(): kotlin.Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmSynchronizedModifier() {
+ val builder = FunSpec.builder("synchronizedMethod")
+ builder.jvmModifiers(listOf(Modifier.SYNCHRONIZED))
+
+ assertThat(builder.build().toString()).isEqualTo(
+ """
+ |@kotlin.jvm.Synchronized
+ |internal fun synchronizedMethod(): kotlin.Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun ensureTrailingNewline() {
+ val methodSpec = FunSpec.builder("function")
+ .addCode("codeWithNoNewline()")
+ .build()
+
+ assertThat(methodSpec.toString()).isEqualTo(
+ """
+ |public fun function(): kotlin.Unit {
+ | codeWithNoNewline()
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ /** Ensures that we don't add a duplicate newline if one is already present. */
+ @Test fun ensureTrailingNewlineWithExistingNewline() {
+ val methodSpec = FunSpec.builder("function")
+ .addCode("codeWithNoNewline()\n") // Have a newline already, so ensure we're not adding one
+ .build()
+
+ assertThat(methodSpec.toString()).isEqualTo(
+ """
+ |public fun function(): kotlin.Unit {
+ | codeWithNoNewline()
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/947
+ @Test fun ensureTrailingNewlineWithExpressionBody() {
+ val methodSpec = FunSpec.builder("function")
+ .addCode("return codeWithNoNewline()")
+ .build()
+
+ assertThat(methodSpec.toString()).isEqualTo(
+ """
+ |public fun function() = codeWithNoNewline()
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun ensureTrailingNewlineWithExpressionBodyAndExistingNewline() {
+ val methodSpec = FunSpec.builder("function")
+ .addCode("return codeWithNoNewline()\n") // Have a newline already, so ensure we're not adding one
+ .build()
+
+ assertThat(methodSpec.toString()).isEqualTo(
+ """
+ |public fun function() = codeWithNoNewline()
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun ensureKdocTrailingNewline() {
+ val methodSpec = FunSpec.builder("function")
+ .addKdoc("This is a comment with no initial newline")
+ .build()
+
+ assertThat(methodSpec.toString()).isEqualTo(
+ """
+ |/**
+ | * This is a comment with no initial newline
+ | */
+ |public fun function(): kotlin.Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ /** Ensures that we don't add a duplicate newline if one is already present. */
+ @Test fun ensureKdocTrailingNewlineWithExistingNewline() {
+ val methodSpec = FunSpec.builder("function")
+ .addKdoc("This is a comment with an initial newline\n")
+ .build()
+
+ assertThat(methodSpec.toString()).isEqualTo(
+ """
+ |/**
+ | * This is a comment with an initial newline
+ | */
+ |public fun function(): kotlin.Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun annotatedLambdaReceiverType() {
+ val annotation = AnnotationSpec.builder(ClassName("com.squareup.tacos", "Annotation")).build()
+ val type = LambdaTypeName.get(returnType = UNIT).copy(annotations = listOf(annotation))
+ val spec = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addFunction(
+ FunSpec.builder("foo")
+ .receiver(type)
+ .build(),
+ )
+ .build()
+ assertThat(spec.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public fun (@Annotation () -> Unit).foo(): Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun annotatedLambdaReturnType() {
+ val annotation = AnnotationSpec.builder(ClassName("com.squareup.tacos", "Annotation")).build()
+ val type = LambdaTypeName.get(returnType = UNIT).copy(annotations = listOf(annotation))
+ val spec = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addFunction(
+ FunSpec.builder("foo")
+ .returns(type)
+ .build(),
+ )
+ .build()
+ assertThat(spec.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public fun foo(): @Annotation () -> Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/JavaClassWithArrayValueAnnotation.java b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/JavaClassWithArrayValueAnnotation.java
new file mode 100644
index 00000000..c48b3a4c
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/JavaClassWithArrayValueAnnotation.java
@@ -0,0 +1,18 @@
+package com.squareup.kotlinpoet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import static com.squareup.kotlinpoet.JavaClassWithArrayValueAnnotation.AnnotationWithArrayValue;
+
+@AnnotationWithArrayValue({
+ Object.class, Boolean.class
+})
+public class JavaClassWithArrayValueAnnotation {
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface AnnotationWithArrayValue {
+ Class[] value();
+ }
+
+} \ No newline at end of file
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/KotlinPoetTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/KotlinPoetTest.kt
new file mode 100644
index 00000000..9d3f1711
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/KotlinPoetTest.kt
@@ -0,0 +1,1375 @@
+/*
+ * Copyright (C) 2017 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.jvm.jvmField
+import com.squareup.kotlinpoet.jvm.jvmSuppressWildcards
+import java.util.concurrent.TimeUnit
+import kotlin.test.Test
+
+class KotlinPoetTest {
+ private val tacosPackage = "com.squareup.tacos"
+
+ @Test fun topLevelMembersRetainOrder() {
+ val source = FileSpec.builder(tacosPackage, "Taco")
+ .addFunction(FunSpec.builder("a").addModifiers(KModifier.PUBLIC).build())
+ .addType(TypeSpec.classBuilder("B").build())
+ .addProperty(
+ PropertySpec.builder("c", String::class, KModifier.PUBLIC)
+ .initializer("%S", "C")
+ .build(),
+ )
+ .addFunction(FunSpec.builder("d").build())
+ .addType(TypeSpec.classBuilder("E").build())
+ .addProperty(
+ PropertySpec.builder("f", String::class, KModifier.PUBLIC)
+ .initializer("%S", "F")
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.Unit
+ |
+ |public fun a(): Unit {
+ |}
+ |
+ |public class B
+ |
+ |public val c: String = "C"
+ |
+ |public fun d(): Unit {
+ |}
+ |
+ |public class E
+ |
+ |public val f: String = "F"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun noTopLevelConstructor() {
+ assertThrows<IllegalArgumentException> {
+ FileSpec.builder(tacosPackage, "Taco")
+ .addFunction(FunSpec.constructorBuilder().build())
+ }
+ }
+
+ @Test fun primaryConstructor() {
+ val source = FileSpec.get(
+ tacosPackage,
+ TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("cheese", String::class)
+ .beginControlFlow("require(cheese.isNotEmpty())")
+ .addStatement("%S", "cheese cannot be empty")
+ .endControlFlow()
+ .build(),
+ )
+ .build(),
+ )
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public class Taco(
+ | cheese: String,
+ |) {
+ | init {
+ | require(cheese.isNotEmpty()) {
+ | "cheese cannot be empty"
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun primaryConstructorProperties() {
+ val source = FileSpec.get(
+ tacosPackage,
+ TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("cheese", String::class)
+ .addParameter("cilantro", String::class)
+ .addParameter("lettuce", String::class)
+ .beginControlFlow("require(!cheese.isEmpty())")
+ .addStatement("%S", "cheese cannot be empty")
+ .endControlFlow()
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("cheese", String::class)
+ .initializer("cheese")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("cilantro", String::class.asTypeName())
+ .mutable()
+ .initializer("cilantro")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("lettuce", String::class)
+ .initializer("lettuce.trim()")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("onion", Boolean::class)
+ .initializer("true")
+ .build(),
+ )
+ .build(),
+ )
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Boolean
+ |import kotlin.String
+ |
+ |public class Taco(
+ | public val cheese: String,
+ | public var cilantro: String,
+ | lettuce: String,
+ |) {
+ | public val lettuce: String = lettuce.trim()
+ |
+ | public val onion: Boolean = true
+ | init {
+ | require(!cheese.isEmpty()) {
+ | "cheese cannot be empty"
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun propertyModifiers() {
+ val source = FileSpec.get(
+ tacosPackage,
+ TypeSpec.classBuilder("Taco")
+ .addProperty(
+ PropertySpec.builder("CHEESE", String::class, KModifier.PRIVATE, KModifier.CONST)
+ .initializer("%S", "monterey jack")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("sauce", String::class.asTypeName(), KModifier.PUBLIC)
+ .mutable()
+ .initializer("%S", "chipotle mayo")
+ .build(),
+ )
+ .build(),
+ )
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public class Taco {
+ | private const val CHEESE: String = "monterey jack"
+ |
+ | public var sauce: String = "chipotle mayo"
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun mistargetedModifier() {
+ assertThrows<IllegalArgumentException> {
+ PropertySpec.builder("CHEESE", String::class, KModifier.DATA).build()
+ }
+ }
+
+ @Test fun visibilityModifiers() {
+ val source = FileSpec.get(
+ tacosPackage,
+ TypeSpec.classBuilder("Taco")
+ .addFunction(FunSpec.builder("a").addModifiers(KModifier.PUBLIC).build())
+ .addFunction(FunSpec.builder("b").addModifiers(KModifier.PROTECTED).build())
+ .addFunction(FunSpec.builder("c").addModifiers(KModifier.INTERNAL).build())
+ .addFunction(FunSpec.builder("d").addModifiers(KModifier.PRIVATE).build())
+ .build(),
+ )
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public class Taco {
+ | public fun a(): Unit {
+ | }
+ |
+ | protected fun b(): Unit {
+ | }
+ |
+ | internal fun c(): Unit {
+ | }
+ |
+ | private fun d(): Unit {
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun strings() {
+ val source = FileSpec.get(
+ tacosPackage,
+ TypeSpec.classBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("strings")
+ .addStatement("val a = %S", "basic string")
+ .addStatement("val b = %S", "string with a \$ dollar sign")
+ .build(),
+ )
+ .build(),
+ )
+ assertThat(source.toString()).isEqualTo(
+ "" +
+ "package com.squareup.tacos\n" +
+ "\n" +
+ "import kotlin.Unit\n" +
+ "\n" +
+ "public class Taco {\n" +
+ " public fun strings(): Unit {\n" +
+ " val a = \"basic string\"\n" +
+ " val b = \"string with a \${\'\$\'} dollar sign\"\n" +
+ " }\n" +
+ "}\n",
+ )
+ }
+
+ /** When emitting a triple quote, KotlinPoet escapes the 3rd quote in the triplet. */
+ @Test fun rawStrings() {
+ val source = FileSpec.get(
+ tacosPackage,
+ TypeSpec.classBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("strings")
+ .addStatement("val a = %S", "\"\n")
+ .addStatement("val b = %S", "a\"\"\"b\"\"\"\"\"\"c\n")
+ .addStatement(
+ "val c = %S",
+ """
+ |whoa
+ |"raw"
+ |string
+ """.trimMargin(),
+ )
+ .addStatement(
+ "val d = %S",
+ """
+ |"raw"
+ |string
+ |with
+ |${'$'}a interpolated value
+ """.trimMargin(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ assertThat(source.toString()).isEqualTo(
+ "" +
+ "package com.squareup.tacos\n" +
+ "\n" +
+ "import kotlin.Unit\n" +
+ "\n" +
+ "public class Taco {\n" +
+ " public fun strings(): Unit {\n" +
+ " val a = \"\"\"\n" +
+ " |\"\n" +
+ " |\"\"\".trimMargin()\n" +
+ " val b = \"\"\"\n" +
+ " |a\"\"\${'\"'}b\"\"\${'\"'}\"\"\${'\"'}c\n" +
+ " |\"\"\".trimMargin()\n" +
+ " val c = \"\"\"\n" +
+ " |whoa\n" +
+ " |\"raw\"\n" +
+ " |string\n" +
+ " \"\"\".trimMargin()\n" +
+ " val d = \"\"\"\n" +
+ " |\"raw\"\n" +
+ " |string\n" +
+ " |with\n" +
+ " |\${\'\$\'}a interpolated value\n" +
+ " \"\"\".trimMargin()\n" +
+ " }\n" +
+ "}\n",
+ )
+ }
+
+ /**
+ * When a string literal ends in a newline, there's a pipe `|` immediately preceding the closing
+ * triple quote. Otherwise the closing triple quote has no preceding `|`.
+ */
+ @Test fun edgeCaseStrings() {
+ val source = FileSpec.get(
+ tacosPackage,
+ TypeSpec.classBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("strings")
+ .addStatement("val a = %S", "\n")
+ .addStatement("val b = %S", " \n ")
+ .build(),
+ )
+ .build(),
+ )
+ assertThat(source.toString()).isEqualTo(
+ "" +
+ "package com.squareup.tacos\n" +
+ "\n" +
+ "import kotlin.Unit\n" +
+ "\n" +
+ "public class Taco {\n" +
+ " public fun strings(): Unit {\n" +
+ " val a = \"\"\"\n" +
+ " |\n" +
+ " |\"\"\".trimMargin()\n" +
+ " val b = \"\"\"\n" +
+ " | \n" +
+ " | \n" +
+ " \"\"\".trimMargin()\n" +
+ " }\n" +
+ "}\n",
+ )
+ }
+
+ @Test fun parameterDefaultValue() {
+ val source = FileSpec.get(
+ tacosPackage,
+ TypeSpec.classBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("addCheese")
+ .addParameter(
+ ParameterSpec.builder("kind", String::class)
+ .defaultValue("%S", "monterey jack")
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.Unit
+ |
+ |public class Taco {
+ | public fun addCheese(kind: String = "monterey jack"): Unit {
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun extensionFunction() {
+ val source = FileSpec.builder(tacosPackage, "Taco")
+ .addFunction(
+ FunSpec.builder("shrink")
+ .returns(String::class)
+ .receiver(String::class)
+ .addStatement("return substring(0, length - 1)")
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public fun String.shrink(): String = substring(0, length - 1)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun extensionFunctionLambda() {
+ val source = FileSpec.builder(tacosPackage, "Taco")
+ .addFunction(
+ FunSpec.builder("shrink")
+ .returns(String::class)
+ .receiver(
+ LambdaTypeName.get(
+ parameters = arrayOf(String::class.asClassName()),
+ returnType = String::class.asTypeName(),
+ ),
+ )
+ .addStatement("return substring(0, length - 1)")
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public fun ((String) -> String).shrink(): String = substring(0, length - 1)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun extensionFunctionLambdaWithParamName() {
+ val source = FileSpec.builder(tacosPackage, "Taco")
+ .addFunction(
+ FunSpec.builder("whatever")
+ .returns(Unit::class)
+ .receiver(
+ LambdaTypeName.get(
+ parameters = arrayOf(ParameterSpec.builder("name", String::class).build()),
+ returnType = Unit::class.asClassName(),
+ ),
+ )
+ .addStatement("return Unit")
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.Unit
+ |
+ |public fun ((name: String) -> Unit).whatever(): Unit = Unit
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun extensionFunctionLambdaWithMultipleParams() {
+ val source = FileSpec.builder(tacosPackage, "Taco")
+ .addFunction(
+ FunSpec.builder("whatever")
+ .returns(Unit::class)
+ .receiver(
+ LambdaTypeName.get(
+ parameters = listOf(
+ ParameterSpec.builder("name", String::class).build(),
+ ParameterSpec.unnamed(Int::class),
+ ParameterSpec.builder("age", Long::class).build(),
+ ),
+ returnType = Unit::class.asClassName(),
+ ),
+ )
+ .addStatement("return Unit")
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |import kotlin.Long
+ |import kotlin.String
+ |import kotlin.Unit
+ |
+ |public fun ((
+ | name: String,
+ | Int,
+ | age: Long,
+ |) -> Unit).whatever(): Unit = Unit
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun extensionProperty() {
+ val source = FileSpec.builder(tacosPackage, "Taco")
+ .addProperty(
+ PropertySpec.builder("extensionProperty", Int::class)
+ .receiver(String::class)
+ .getter(
+ FunSpec.getterBuilder()
+ .addStatement("return length")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |import kotlin.String
+ |
+ |public val String.extensionProperty: Int
+ | get() = length
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun extensionPropertyLambda() {
+ val source = FileSpec.builder(tacosPackage, "Taco")
+ .addProperty(
+ PropertySpec.builder("extensionProperty", Int::class)
+ .receiver(
+ LambdaTypeName.get(
+ parameters = arrayOf(String::class.asClassName()),
+ returnType = String::class.asClassName(),
+ ),
+ )
+ .getter(
+ FunSpec.getterBuilder()
+ .addStatement("return length")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |import kotlin.String
+ |
+ |public val ((String) -> String).extensionProperty: Int
+ | get() = length
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun nullableTypes() {
+ val list = (List::class.asClassName().copy(nullable = true) as ClassName)
+ .parameterizedBy(Int::class.asClassName().copy(nullable = true))
+ .copy(nullable = true)
+ assertThat(list.toString()).isEqualTo("kotlin.collections.List<kotlin.Int?>?")
+ }
+
+ @Test fun getAndSet() {
+ val source = FileSpec.builder(tacosPackage, "Taco")
+ .addProperty(
+ PropertySpec.builder("propertyWithCustomAccessors", Int::class.asTypeName())
+ .mutable()
+ .initializer("%L", 1)
+ .getter(
+ FunSpec.getterBuilder()
+ .addStatement("println(%S)", "getter")
+ .addStatement("return field")
+ .build(),
+ )
+ .setter(
+ FunSpec.setterBuilder()
+ .addParameter("value", Int::class)
+ .addStatement("println(%S)", "setter")
+ .addStatement("field = value")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |
+ |public var propertyWithCustomAccessors: Int = 1
+ | get() {
+ | println("getter")
+ | return field
+ | }
+ | set(`value`) {
+ | println("setter")
+ | field = value
+ | }
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun propertyWithLongInitializerWrapping() {
+ val source = FileSpec.builder(tacosPackage, "Taco")
+ .addProperty(
+ PropertySpec
+ .builder("foo", ClassName(tacosPackage, "Foo").copy(nullable = true))
+ .addModifiers(KModifier.PRIVATE)
+ .initializer("DefaultFooRegistry.getInstance().getDefaultFooInstanceForPropertiesFiles(file)")
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |private val foo: Foo? =
+ | DefaultFooRegistry.getInstance().getDefaultFooInstanceForPropertiesFiles(file)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun stackedPropertyModifiers() {
+ val source = FileSpec.builder(tacosPackage, "Taco")
+ .addType(
+ TypeSpec.classBuilder("A")
+ .addModifiers(KModifier.ABSTRACT)
+ .addProperty(
+ PropertySpec.builder("q", String::class.asTypeName())
+ .mutable()
+ .addModifiers(KModifier.ABSTRACT, KModifier.PROTECTED)
+ .build(),
+ )
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("p", String::class)
+ .addModifiers(KModifier.CONST, KModifier.INTERNAL)
+ .initializer("%S", "a")
+ .build(),
+ )
+ .addType(
+ TypeSpec.classBuilder("B")
+ .superclass(ClassName(tacosPackage, "A"))
+ .addModifiers(KModifier.ABSTRACT)
+ .addProperty(
+ PropertySpec.builder("q", String::class.asTypeName())
+ .mutable()
+ .addModifiers(
+ KModifier.FINAL,
+ KModifier.LATEINIT,
+ KModifier.OVERRIDE,
+ KModifier.PUBLIC,
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public abstract class A {
+ | protected abstract var q: String
+ |}
+ |
+ |internal const val p: String = "a"
+ |
+ |public abstract class B : A() {
+ | public final override lateinit var q: String
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun stackedFunModifiers() {
+ val source = FileSpec.get(
+ tacosPackage,
+ TypeSpec.classBuilder("A")
+ .addModifiers(KModifier.OPEN)
+ .addFunction(
+ FunSpec.builder("get")
+ .addModifiers(
+ KModifier.EXTERNAL,
+ KModifier.INFIX,
+ KModifier.OPEN,
+ KModifier.OPERATOR,
+ KModifier.PROTECTED,
+ )
+ .addParameter("v", String::class)
+ .returns(String::class)
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("loop")
+ .addModifiers(KModifier.FINAL, KModifier.INLINE, KModifier.INTERNAL, KModifier.TAILREC)
+ .returns(String::class)
+ .addStatement("return %S", "a")
+ .build(),
+ )
+ .build(),
+ )
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public open class A {
+ | protected open external infix operator fun `get`(v: String): String
+ |
+ | internal final tailrec inline fun loop(): String = "a"
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun basicExpressionBody() {
+ val source = FileSpec.builder(tacosPackage, "Taco")
+ .addFunction(
+ FunSpec.builder("addA")
+ .addParameter("s", String::class)
+ .returns(String::class)
+ .addStatement("return s + %S", "a")
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public fun addA(s: String): String = s + "a"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun suspendingLambdas() {
+ val barType = ClassName(tacosPackage, "Bar")
+ val suspendingLambda = LambdaTypeName
+ .get(parameters = arrayOf(ClassName(tacosPackage, "Foo")), returnType = barType)
+ .copy(suspending = true)
+ val source = FileSpec.builder(tacosPackage, "Taco")
+ .addProperty(
+ PropertySpec.builder("bar", suspendingLambda)
+ .mutable()
+ .initializer("{ %T() }", barType)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("nullBar", suspendingLambda.copy(nullable = true))
+ .mutable()
+ .initializer("null")
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("foo")
+ .addParameter("bar", suspendingLambda)
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public var bar: suspend (Foo) -> Bar = { Bar() }
+ |
+ |public var nullBar: (suspend (Foo) -> Bar)? = null
+ |
+ |public fun foo(bar: suspend (Foo) -> Bar): Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun enumAsDefaultArgument() {
+ val source = FileSpec.builder(tacosPackage, "Taco")
+ .addFunction(
+ FunSpec.builder("timeout")
+ .addParameter("duration", Long::class)
+ .addParameter(
+ ParameterSpec.builder("timeUnit", TimeUnit::class)
+ .defaultValue("%T.%L", TimeUnit::class, TimeUnit.MILLISECONDS.name)
+ .build(),
+ )
+ .addStatement("this.timeout = timeUnit.toMillis(duration)")
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.util.concurrent.TimeUnit
+ |import kotlin.Long
+ |import kotlin.Unit
+ |
+ |public fun timeout(duration: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS): Unit {
+ | this.timeout = timeUnit.toMillis(duration)
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun dynamicType() {
+ val source = FileSpec.builder(tacosPackage, "Taco")
+ .addFunction(
+ FunSpec.builder("dynamicTest")
+ .addCode(
+ CodeBlock.of(
+ "%L",
+ PropertySpec.builder("d1", DYNAMIC)
+ .initializer("%S", "Taco")
+ .build(),
+ ),
+ )
+ .addCode(
+ CodeBlock.of(
+ "%L",
+ PropertySpec.builder("d2", DYNAMIC)
+ .initializer("1f")
+ .build(),
+ ),
+ )
+ .addStatement("// dynamics are dangerous!")
+ .addStatement("println(d1 - d2)")
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public fun dynamicTest(): Unit {
+ | val d1: dynamic = "Taco"
+ | val d2: dynamic = 1f
+ | // dynamics are dangerous!
+ | println(d1 - d2)
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun primaryConstructorParameterAnnotation() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("foo", String::class)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("foo", String::class)
+ .jvmField()
+ .initializer("foo")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.JvmField
+ |
+ |public class Taco(
+ | @JvmField
+ | public val foo: String,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/346
+ @Test fun importTypeArgumentInParameterizedTypeName() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addFunction(
+ FunSpec.builder("foo")
+ .addParameter(
+ "a",
+ List::class.asTypeName()
+ .parameterizedBy(Int::class.asTypeName().jvmSuppressWildcards()),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |import kotlin.Unit
+ |import kotlin.collections.List
+ |import kotlin.jvm.JvmSuppressWildcards
+ |
+ |public fun foo(a: List<@JvmSuppressWildcards Int>): Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/462
+ @Test fun foldingPropertyWithLambdaInitializer() {
+ val param = ParameterSpec.builder("arg", ANY).build()
+ val initializer = CodeBlock.builder()
+ .beginControlFlow("{ %L ->", param)
+ .addStatement("println(\"arg=\$%N\")", param)
+ .endControlFlow()
+ .build()
+ val lambdaTypeName = ClassName.bestGuess("com.example.SomeTypeAlias")
+ val property = PropertySpec.builder("foo", lambdaTypeName)
+ .initializer("foo")
+ .build()
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter(
+ ParameterSpec.builder("foo", lambdaTypeName)
+ .defaultValue(initializer)
+ .build(),
+ )
+ .build(),
+ )
+ .addProperty(property)
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.example.SomeTypeAlias
+ |
+ |public class Taco(
+ | public val foo: SomeTypeAlias = { arg: kotlin.Any ->
+ | println("arg=${'$'}arg")
+ | }
+ | ,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/483
+ @Test fun foldingPropertyWithEscapedName() {
+ val file = FileSpec.builder("com.squareup.tacos", "AlarmInfo")
+ .addType(
+ TypeSpec.classBuilder("AlarmInfo")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("when", Float::class)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("when", Float::class)
+ .initializer("when")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Float
+ |
+ |public class AlarmInfo(
+ | public val `when`: Float,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/577
+ @Test fun noWrappingBetweenParamNameAndType() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addFunction(
+ FunSpec.builder("functionWithAPrettyLongNameThatWouldCauseWrapping")
+ .addParameter("parameterWithALongNameThatWouldAlsoCauseWrapping", String::class)
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.Unit
+ |
+ |public
+ | fun functionWithAPrettyLongNameThatWouldCauseWrapping(parameterWithALongNameThatWouldAlsoCauseWrapping: String):
+ | Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/576
+ @Test fun noWrappingBetweenValAndPropertyName() {
+ val wireField = ClassName("com.squareup.wire", "WireField")
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .addModifiers(KModifier.DATA)
+ .addProperty(
+ PropertySpec.builder("name", String::class)
+ .addAnnotation(
+ AnnotationSpec.builder(wireField)
+ .addMember("tag = %L", 1)
+ .addMember("adapter = %S", "CustomStringAdapterWithALongNameThatCauses")
+ .build(),
+ )
+ .initializer("name")
+ .build(),
+ )
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter(
+ ParameterSpec.builder("name", String::class)
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.wire.WireField
+ |import kotlin.String
+ |
+ |public data class Taco(
+ | @WireField(
+ | tag = 1,
+ | adapter = "CustomStringAdapterWithALongNameThatCauses",
+ | )
+ | public val name: String,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/578
+ @Test fun wrappingInsideKdocKeepsKdocFormatting() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Builder")
+ .addKdoc(
+ "Builder class for Foo. Allows creating instances of Foo by initializing " +
+ "a subset of their fields, following the Builder pattern.\n",
+ )
+ .addFunction(
+ FunSpec.builder("summary_text")
+ .addKdoc(
+ "The description for the choice, e.g. \"Currently unavailable due to " +
+ "high demand. Please try later.\" May be null.",
+ )
+ .addParameter("summary_text", String::class.asClassName().copy(nullable = true))
+ .returns(ClassName("com.squareup.tacos", "Builder"))
+ .addStatement("this.summary_text = summary_text")
+ .addStatement("return this")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |/**
+ | * Builder class for Foo. Allows creating instances of Foo by initializing a subset of their fields,
+ | * following the Builder pattern.
+ | */
+ |public class Builder {
+ | /**
+ | * The description for the choice, e.g. "Currently unavailable due to high demand. Please try
+ | * later." May be null.
+ | */
+ | public fun summary_text(summary_text: String?): Builder {
+ | this.summary_text = summary_text
+ | return this
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/606
+ @Test fun typeNamesInsideTemplateStringsGetImported() {
+ val taco = ClassName("com.squareup.tacos", "Taco")
+ val file = FileSpec.builder("com.squareup.example", "Tacos")
+ .addFunction(
+ FunSpec.builder("main")
+ .addStatement("println(%P)", CodeBlock.of("Here's a taco: \${%T()}", taco))
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.example
+ |
+ |import com.squareup.tacos.Taco
+ |import kotlin.Unit
+ |
+ |public fun main(): Unit {
+ | println(${'"'}""Here's a taco: ${'$'}{Taco()}""${'"'})
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/606
+ @Test fun memberNamesInsideTemplateStringsGetImported() {
+ val contentToString = MemberName("kotlin.collections", "contentToString")
+ val file = FileSpec.builder("com.squareup.example", "Tacos")
+ .addFunction(
+ FunSpec.builder("main")
+ .addStatement("val ints = arrayOf(1, 2, 3)")
+ .addStatement("println(%P)", CodeBlock.of("\${ints.%M()}", contentToString))
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.example
+ |
+ |import kotlin.Unit
+ |import kotlin.collections.contentToString
+ |
+ |public fun main(): Unit {
+ | val ints = arrayOf(1, 2, 3)
+ | println(${'"'}""${'$'}{ints.contentToString()}""${'"'})
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/701
+ @Test fun noIllegalCharacterInIdentifier() {
+ assertThrows<IllegalArgumentException> {
+ TypeSpec.enumBuilder("MyEnum")
+ .addEnumConstant("with.dots") // dots are illegal, so this should fail
+ .build().toString()
+ }.hasMessageThat().isEqualTo("Can't escape identifier `with.dots` because it contains illegal characters: .")
+ }
+
+ // https://github.com/square/kotlinpoet/issues/814
+ @Test fun percentAtTheEndOfKdoc() {
+ val paramSpec1 = ParameterSpec.builder("a", Int::class)
+ .addKdoc("Progress in %%")
+ .build()
+ val paramSpec2 = ParameterSpec.builder("b", Int::class)
+ .addKdoc("Some other parameter with %%")
+ .build()
+ val funSpec = FunSpec.builder("test")
+ .addParameters(listOf(paramSpec1, paramSpec2))
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |/**
+ | * @param a Progress in %
+ | * @param b Some other parameter with %
+ | */
+ |public fun test(a: kotlin.Int, b: kotlin.Int): kotlin.Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/1031
+ @Test fun superClassGetsFullyQualifiedOnConflict() {
+ val namespace = "test"
+
+ val kotlinExceptionName = ClassName("kotlin", "Exception")
+ val customExceptionName = ClassName(namespace, "Exception")
+ val customException = TypeSpec
+ .classBuilder("Exception")
+ .superclass(kotlinExceptionName)
+ .addFunction(
+ FunSpec
+ .builder("test")
+ .addParameter("e", customExceptionName)
+ .build(),
+ )
+ .build()
+
+ val file = FileSpec.builder(namespace, "Exception")
+ .addType(customException)
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package test
+ |
+ |import kotlin.Unit
+ |
+ |public class Exception : kotlin.Exception() {
+ | public fun test(e: Exception): Unit {
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun allStringsAreUnderscore() {
+ val file = FileSpec.builder("com.squareup.tacos", "SourceWithUnderscores")
+ .addType(
+ TypeSpec.classBuilder("SourceWithUnderscores")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("_", Float::class)
+ .addParameter("____", Float::class)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("_", Float::class)
+ .initializer("_")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("____", Float::class)
+ .initializer("____")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Float
+ |
+ |public class SourceWithUnderscores(
+ | public val `_`: Float,
+ | public val `____`: Float,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun generatedImportAliases() {
+ val squareTaco = ClassName("com.squareup.tacos", "Taco")
+ val blockTaco = ClassName("xyz.block.tacos", "Taco")
+ val kotlinIsNullOrEmpty = MemberName("kotlin.text", "isNullOrEmpty")
+ val cashIsNullOrEmpty = MemberName("com.squareup.cash.util", "isNullOrEmpty")
+ val file = FileSpec.builder("com.example", "Test")
+ .addFunction(
+ FunSpec.builder("main")
+ .addStatement("val squareTaco = %L", squareTaco.constructorReference())
+ .addStatement("val blockTaco = %L", blockTaco.constructorReference())
+ .addStatement("val isSquareTacoNull = %S.%M()", "Taco", kotlinIsNullOrEmpty)
+ .addStatement("val isBlockTacoNull = %S.%M()", "Taco", cashIsNullOrEmpty)
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.example
+ |
+ |import kotlin.Unit
+ |import com.squareup.cash.util.isNullOrEmpty as utilIsNullOrEmpty
+ |import com.squareup.tacos.Taco as SquareupTacosTaco
+ |import kotlin.text.isNullOrEmpty as textIsNullOrEmpty
+ |import xyz.block.tacos.Taco as BlockTacosTaco
+ |
+ |public fun main(): Unit {
+ | val squareTaco = ::SquareupTacosTaco
+ | val blockTaco = ::BlockTacosTaco
+ | val isSquareTacoNull = "Taco".textIsNullOrEmpty()
+ | val isBlockTacoNull = "Taco".utilIsNullOrEmpty()
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun memberImportsOverGeneratedImportAliases() {
+ val squareTaco = ClassName("com.squareup.tacos", "Taco")
+ val blockTaco = ClassName("xyz.block.tacos", "Taco")
+ val kotlinIsNullOrEmpty = MemberName("kotlin.text", "isNullOrEmpty")
+ val cashIsNullOrEmpty = MemberName("com.squareup.cash.util", "isNullOrEmpty")
+ val file = FileSpec.builder("com.example", "Test")
+ .addAliasedImport(squareTaco, "SquareTaco")
+ .addAliasedImport(blockTaco, "BlockTaco")
+ .addAliasedImport(kotlinIsNullOrEmpty, "kotlinIsNullOrEmpty")
+ .addAliasedImport(cashIsNullOrEmpty, "cashIsNullOrEmpty")
+ .addFunction(
+ FunSpec.builder("main")
+ .addStatement("val squareTaco = %L", squareTaco.constructorReference())
+ .addStatement("val blockTaco = %L", blockTaco.constructorReference())
+ .addStatement("val isSquareTacoNull = %S.%M()", "Taco", kotlinIsNullOrEmpty)
+ .addStatement("val isBlockTacoNull = %S.%M()", "Taco", cashIsNullOrEmpty)
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.example
+ |
+ |import kotlin.Unit
+ |import com.squareup.cash.util.isNullOrEmpty as cashIsNullOrEmpty
+ |import com.squareup.tacos.Taco as SquareTaco
+ |import kotlin.text.isNullOrEmpty as kotlinIsNullOrEmpty
+ |import xyz.block.tacos.Taco as BlockTaco
+ |
+ |public fun main(): Unit {
+ | val squareTaco = ::SquareTaco
+ | val blockTaco = ::BlockTaco
+ | val isSquareTacoNull = "Taco".kotlinIsNullOrEmpty()
+ | val isBlockTacoNull = "Taco".cashIsNullOrEmpty()
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt
new file mode 100644
index 00000000..5b72b79d
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LambdaTypeNameTest.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2017 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.KModifier.VARARG
+import javax.annotation.Nullable
+import kotlin.test.Test
+
+@OptIn(ExperimentalKotlinPoetApi::class)
+class LambdaTypeNameTest {
+
+ @Retention(AnnotationRetention.RUNTIME)
+ annotation class HasSomeAnnotation
+
+ @HasSomeAnnotation
+ inner class IsAnnotated
+
+ @Test fun receiverWithoutAnnotationHasNoParens() {
+ val typeName = LambdaTypeName.get(
+ receiver = Int::class.asClassName(),
+ parameters = listOf(),
+ returnType = Unit::class.asTypeName(),
+ )
+ assertThat(typeName.toString()).isEqualTo("kotlin.Int.() -> kotlin.Unit")
+ }
+
+ @Test fun receiverWithAnnotationHasParens() {
+ val annotation = IsAnnotated::class.java.getAnnotation(HasSomeAnnotation::class.java)
+ val typeName = LambdaTypeName.get(
+ receiver = Int::class.asClassName().copy(
+ annotations = listOf(AnnotationSpec.get(annotation, includeDefaultValues = true)),
+ ),
+ parameters = listOf(),
+ returnType = Unit::class.asTypeName(),
+ )
+ assertThat(typeName.toString()).isEqualTo(
+ "(@com.squareup.kotlinpoet.LambdaTypeNameTest.HasSomeAnnotation kotlin.Int).() -> kotlin.Unit",
+ )
+ }
+
+ @Test fun contextReceiver() {
+ val typeName = LambdaTypeName.get(
+ receiver = Int::class.asTypeName(),
+ parameters = listOf(),
+ returnType = Unit::class.asTypeName(),
+ contextReceivers = listOf(STRING),
+ )
+ assertThat(typeName.toString()).isEqualTo(
+ "context(kotlin.String) kotlin.Int.() -> kotlin.Unit",
+ )
+ }
+
+ @Test fun nullableFunctionWithContextReceiver() {
+ val typeName = LambdaTypeName.get(
+ receiver = Int::class.asTypeName(),
+ parameters = listOf(),
+ returnType = Unit::class.asTypeName(),
+ contextReceivers = listOf(STRING),
+ ).copy(nullable = true)
+ assertThat(typeName.toString()).isEqualTo(
+ "(context(kotlin.String) kotlin.Int.() -> kotlin.Unit)?",
+ )
+ }
+
+ @Test fun suspendingFunctionWithContextReceiver() {
+ val typeName = LambdaTypeName.get(
+ receiver = Int::class.asTypeName(),
+ parameters = listOf(),
+ returnType = Unit::class.asTypeName(),
+ contextReceivers = listOf(STRING),
+ ).copy(suspending = true)
+ assertThat(typeName.toString()).isEqualTo(
+ "suspend context(kotlin.String) kotlin.Int.() -> kotlin.Unit",
+ )
+ }
+
+ @Test fun functionWithMultipleContextReceivers() {
+ val typeName = LambdaTypeName.get(
+ Int::class.asTypeName(),
+ listOf(),
+ Unit::class.asTypeName(),
+ listOf(STRING, BOOLEAN),
+ )
+ assertThat(typeName.toString()).isEqualTo(
+ "context(kotlin.String, kotlin.Boolean) kotlin.Int.() -> kotlin.Unit",
+ )
+ }
+
+ @Test fun functionWithGenericContextReceiver() {
+ val genericType = TypeVariableName("T")
+ val typeName = LambdaTypeName.get(
+ Int::class.asTypeName(),
+ listOf(),
+ Unit::class.asTypeName(),
+ listOf(genericType),
+ )
+
+ assertThat(typeName.toString()).isEqualTo(
+ "context(T) kotlin.Int.() -> kotlin.Unit",
+ )
+ }
+
+ @Test fun functionWithAnnotatedContextReceiver() {
+ val annotatedType = STRING.copy(annotations = listOf(AnnotationSpec.get(FunSpecTest.TestAnnotation())))
+ val typeName = LambdaTypeName.get(
+ Int::class.asTypeName(),
+ listOf(),
+ Unit::class.asTypeName(),
+ listOf(annotatedType),
+ )
+
+ assertThat(typeName.toString()).isEqualTo(
+ "context(@com.squareup.kotlinpoet.FunSpecTest.TestAnnotation kotlin.String) kotlin.Int.() -> kotlin.Unit",
+ )
+ }
+
+ @Test fun paramsWithAnnotationsForbidden() {
+ assertThrows<IllegalArgumentException> {
+ LambdaTypeName.get(
+ parameters = arrayOf(
+ ParameterSpec.builder("foo", Int::class)
+ .addAnnotation(Nullable::class)
+ .build(),
+ ),
+ returnType = Unit::class.asTypeName(),
+ )
+ }.hasMessageThat().isEqualTo("Parameters with annotations are not allowed")
+ }
+
+ @Test fun paramsWithModifiersForbidden() {
+ assertThrows<IllegalArgumentException> {
+ LambdaTypeName.get(
+ parameters = arrayOf(
+ ParameterSpec.builder("foo", Int::class)
+ .addModifiers(VARARG)
+ .build(),
+ ),
+ returnType = Unit::class.asTypeName(),
+ )
+ }.hasMessageThat().isEqualTo("Parameters with modifiers are not allowed")
+ }
+
+ @Test fun paramsWithDefaultValueForbidden() {
+ assertThrows<IllegalArgumentException> {
+ LambdaTypeName.get(
+ parameters = arrayOf(
+ ParameterSpec.builder("foo", Int::class)
+ .defaultValue("42")
+ .build(),
+ ),
+ returnType = Unit::class.asTypeName(),
+ )
+ }.hasMessageThat().isEqualTo("Parameters with default values are not allowed")
+ }
+
+ @Test fun lambdaReturnType() {
+ val returnTypeName = LambdaTypeName.get(
+ parameters = arrayOf(Int::class.asTypeName()),
+ returnType = Unit::class.asTypeName(),
+ )
+ val typeName = LambdaTypeName.get(
+ parameters = arrayOf(Int::class.asTypeName()),
+ returnType = returnTypeName,
+ )
+ assertThat(typeName.toString())
+ .isEqualTo("(kotlin.Int) -> ((kotlin.Int) -> kotlin.Unit)")
+ }
+
+ @Test fun lambdaParameterType() {
+ val parameterTypeName = LambdaTypeName.get(
+ parameters = arrayOf(Int::class.asTypeName()),
+ returnType = Int::class.asTypeName(),
+ )
+ val typeName = LambdaTypeName.get(
+ parameters = arrayOf(parameterTypeName),
+ returnType = Unit::class.asTypeName(),
+ )
+ assertThat(typeName.toString())
+ .isEqualTo("((kotlin.Int) -> kotlin.Int) -> kotlin.Unit")
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LineWrapperTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LineWrapperTest.kt
new file mode 100644
index 00000000..8fdbda83
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LineWrapperTest.kt
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+
+class LineWrapperTest {
+ @Test fun wrap() {
+ val out = StringBuffer()
+ val lineWrapper = LineWrapper(out, " ", 10)
+ lineWrapper.append("abcde fghij", indentLevel = 2)
+ lineWrapper.close()
+ assertThat(out.toString()).isEqualTo(
+ """
+ |abcde
+ | fghij
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun noWrap() {
+ val out = StringBuffer()
+ val lineWrapper = LineWrapper(out, " ", 10)
+ lineWrapper.append("abcde fghi", indentLevel = 2)
+ lineWrapper.close()
+ assertThat(out.toString()).isEqualTo("abcde fghi")
+ }
+
+ @Test fun multipleWrite() {
+ val out = StringBuffer()
+ val lineWrapper = LineWrapper(out, " ", 10)
+ lineWrapper.append("ab cd ef gh ij kl mn op qr", indentLevel = 1)
+ lineWrapper.close()
+ assertThat(out.toString()).isEqualTo(
+ """
+ |ab cd ef
+ | gh ij kl
+ | mn op qr
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun fencepost() {
+ val out = StringBuffer()
+ val lineWrapper = LineWrapper(out, " ", 10)
+ lineWrapper.append("abcde", indentLevel = 2)
+ lineWrapper.append("fghij k", indentLevel = 2)
+ lineWrapper.append("lmnop", indentLevel = 2)
+ lineWrapper.close()
+ assertThat(out.toString()).isEqualTo(
+ """
+ |abcdefghij
+ | klmnop
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun overlyLongLinesWithoutLeadingSpace() {
+ val out = StringBuffer()
+ val lineWrapper = LineWrapper(out, " ", 10)
+ lineWrapper.append("abcdefghijkl", indentLevel = 2)
+ lineWrapper.close()
+ assertThat(out.toString()).isEqualTo("abcdefghijkl")
+ }
+
+ @Test fun overlyLongLinesWithLeadingSpace() {
+ val out = StringBuffer()
+ val lineWrapper = LineWrapper(out, " ", 10)
+ lineWrapper.append(" abcdefghijkl", indentLevel = 2)
+ lineWrapper.close()
+ assertThat(out.toString()).isEqualTo("\n abcdefghijkl")
+ }
+
+ @Test fun noWrapEmbeddedNewlines() {
+ val out = StringBuffer()
+ val lineWrapper = LineWrapper(out, " ", 10)
+ lineWrapper.append("abcde fghi\njklmn", indentLevel = 2)
+ lineWrapper.append("opqrstuvwxy", indentLevel = 2)
+ lineWrapper.close()
+ assertThat(out.toString()).isEqualTo(
+ """
+ |abcde fghi
+ |jklmnopqrstuvwxy
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun wrapEmbeddedNewlines() {
+ val out = StringBuffer()
+ val lineWrapper = LineWrapper(out, " ", 10)
+ lineWrapper.append("abcde fghij\nklmn", indentLevel = 2)
+ lineWrapper.append("opqrstuvwxy", indentLevel = 2)
+ lineWrapper.close()
+ assertThat(out.toString()).isEqualTo(
+ """
+ |abcde
+ | fghij
+ |klmnopqrstuvwxy
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun noWrapMultipleNewlines() {
+ val out = StringBuffer()
+ val lineWrapper = LineWrapper(out, " ", 10)
+ lineWrapper.append("abcde fghi\nklmnopq\nr stuvwxyz", indentLevel = 2)
+ lineWrapper.close()
+ assertThat(out.toString()).isEqualTo(
+ """
+ |abcde fghi
+ |klmnopq
+ |r stuvwxyz
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun wrapMultipleNewlines() {
+ val out = StringBuffer()
+ val lineWrapper = LineWrapper(out, " ", 10)
+ lineWrapper.append("abcde fghi\nklmnopq\nrs tuvwxyz1", indentLevel = 2)
+ lineWrapper.close()
+ assertThat(out.toString()).isEqualTo(
+ """
+ |abcde fghi
+ |klmnopq
+ |rs
+ | tuvwxyz1
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun noWrapPrecedingUnaryPlus() {
+ val out = StringBuffer()
+ val lineWrapper = LineWrapper(out, " ", 10)
+ lineWrapper.append("a + b + c", indentLevel = 2)
+ lineWrapper.close()
+ assertThat(out.toString()).isEqualTo(
+ """
+ |a +
+ | b +
+ | c
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun noWrapPrecedingUnaryMinus() {
+ val out = StringBuffer()
+ val lineWrapper = LineWrapper(out, " ", 10)
+ lineWrapper.append("a - b - c", indentLevel = 2)
+ lineWrapper.close()
+ assertThat(out.toString()).isEqualTo(
+ """
+ |a -
+ | b -
+ | c
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun appendNonWrapping() {
+ val out = StringBuffer()
+ val lineWrapper = LineWrapper(out, " ", 10)
+ lineWrapper.append("ab cd ef", indentLevel = 2)
+ lineWrapper.appendNonWrapping("gh ij kl mn")
+ lineWrapper.close()
+ assertThat(out.toString()).isEqualTo(
+ """
+ |ab cd
+ | efgh ij kl mn
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun appendNonWrappingSpace() {
+ val out = StringBuffer()
+ val lineWrapper = LineWrapper(out, " ", 10)
+ lineWrapper.append("ab cd ef", indentLevel = 2)
+ lineWrapper.append("gh·ij·kl·mn", indentLevel = 2)
+ lineWrapper.close()
+ assertThat(out.toString()).isEqualTo(
+ """
+ |ab cd
+ | efgh ij kl mn
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun loneUnsafeUnaryOperator() {
+ val out = StringBuffer()
+ val lineWrapper = LineWrapper(out, " ", 10)
+ lineWrapper.append(" -1", indentLevel = 2)
+ lineWrapper.close()
+ assertThat(out.toString()).isEqualTo(
+ """
+ | -1
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun linePrefix() {
+ val out = StringBuffer()
+ val lineWrapper = LineWrapper(out, " ", 10)
+ lineWrapper.append("/**\n")
+ lineWrapper.append(" * ")
+ lineWrapper.append("a b c d e f g h i j k l m n\n", linePrefix = " * ")
+ lineWrapper.append(" */")
+ lineWrapper.close()
+ assertThat(out.toString()).isEqualTo(
+ """
+ |/**
+ | * a b c d
+ | * e f g h i j
+ | * k l m n
+ | */
+ """.trimMargin(),
+ )
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LineWrappingTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LineWrappingTest.kt
new file mode 100644
index 00000000..332a25b9
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/LineWrappingTest.kt
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2018 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class LineWrappingTest {
+ @Test fun codeSpacesWrap() {
+ val wrapMe = FunSpec.builder("wrapMe")
+ .addStatement(
+ "return %L * %L * %L * %L * %L * %L * %L * %L * %L * %L * %L * %L",
+ 10000000000, 20000000000, 30000000000, 40000000000, 50000000000, 60000000000,
+ 70000000000, 80000000000, 90000000000, 10000000000, 20000000000, 30000000000,
+ )
+ .build()
+ assertThat(toString(wrapMe)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |public fun wrapMe() = 10_000_000_000 * 20_000_000_000 * 30_000_000_000 * 40_000_000_000 *
+ | 50_000_000_000 * 60_000_000_000 * 70_000_000_000 * 80_000_000_000 * 90_000_000_000 *
+ | 10_000_000_000 * 20_000_000_000 * 30_000_000_000
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun stringSpacesDoNotWrap() {
+ val wrapMe = FunSpec.builder("wrapMe")
+ .addStatement(
+ "return %S+%S+%S+%S+%S+%S+%S+%S+%S+%S+%S+%S",
+ "Aaaa Aaaa", "Bbbb Bbbb", "Cccc Cccc", "Dddd Dddd", "Eeee Eeee", "Ffff Ffff",
+ "Gggg Gggg", "Hhhh Hhhh", "Iiii Iiii", "Jjjj Jjjj", "Kkkk Kkkk", "Llll Llll",
+ )
+ .build()
+ assertThat(toString(wrapMe)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |public fun wrapMe() =
+ | "Aaaa Aaaa"+"Bbbb Bbbb"+"Cccc Cccc"+"Dddd Dddd"+"Eeee Eeee"+"Ffff Ffff"+"Gggg Gggg"+"Hhhh Hhhh"+"Iiii Iiii"+"Jjjj Jjjj"+"Kkkk Kkkk"+"Llll Llll"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun nonwrappingWhitespaceDoesNotWrap() {
+ val wrapMe = FunSpec.builder("wrapMe")
+ .addStatement(
+ "return %L·*·%L·*·%L·*·%L·*·%L·*·%L·*·%L·*·%L·*·%L·*·%L·*·%L·*·%L",
+ 10000000000, 20000000000, 30000000000, 40000000000, 50000000000, 60000000000,
+ 70000000000, 80000000000, 90000000000, 10000000000, 20000000000, 30000000000,
+ )
+ .build()
+ assertThat(toString(wrapMe)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |public fun wrapMe() =
+ | 10_000_000_000 * 20_000_000_000 * 30_000_000_000 * 40_000_000_000 * 50_000_000_000 * 60_000_000_000 * 70_000_000_000 * 80_000_000_000 * 90_000_000_000 * 10_000_000_000 * 20_000_000_000 * 30_000_000_000
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun nonwrappingWhitespaceIsRetainedInStrings() {
+ val wrapMe = FunSpec.builder("wrapMe")
+ .addStatement("return %S", "a·b")
+ .build()
+ assertThat(toString(wrapMe)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |public fun wrapMe() = "a·b"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun insignificantWhitespaceRetained() {
+ val wrapMe = FunSpec.builder("wrapMe")
+ .addStatement("val a = 8")
+ .addStatement("val b = 64")
+ .addStatement("val c = 512")
+ .addStatement("val d = 4096")
+ .build()
+ assertThat(toString(wrapMe)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public fun wrapMe(): Unit {
+ | val a = 8
+ | val b = 64
+ | val c = 512
+ | val d = 4096
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun spacesPrecedingUnaryOperatorsDoNotWrap() {
+ val wrapMe = FunSpec.builder("wrapMe")
+ .addStatement("val aaaaaa = %S +1", "x".repeat(80))
+ .addStatement("val bbbbbb = %S +1", "x".repeat(81))
+ .addStatement("val cccccc = %S -1", "x".repeat(80))
+ .addStatement("val dddddd = %S -1", "x".repeat(81))
+ .build()
+ assertThat(toString(wrapMe)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public fun wrapMe(): Unit {
+ | val aaaaaa = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +1
+ | val bbbbbb =
+ | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +1
+ | val cccccc = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -1
+ | val dddddd =
+ | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -1
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun parameterWrapping() {
+ val funSpecBuilder = FunSpec.builder("call")
+ funSpecBuilder.addCode("«call(")
+ for (i in 0..31) {
+ funSpecBuilder.addParameter("s$i", String::class)
+ funSpecBuilder.addCode(if (i > 0) ", %S" else "%S", i)
+ }
+ funSpecBuilder.addCode(")»\n")
+
+ val taco = TypeSpec.classBuilder("Taco")
+ .addFunction(funSpecBuilder.build())
+ .build()
+ assertThat(toString(taco))
+ .isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.Unit
+ |
+ |public class Taco {
+ | public fun call(
+ | s0: String,
+ | s1: String,
+ | s2: String,
+ | s3: String,
+ | s4: String,
+ | s5: String,
+ | s6: String,
+ | s7: String,
+ | s8: String,
+ | s9: String,
+ | s10: String,
+ | s11: String,
+ | s12: String,
+ | s13: String,
+ | s14: String,
+ | s15: String,
+ | s16: String,
+ | s17: String,
+ | s18: String,
+ | s19: String,
+ | s20: String,
+ | s21: String,
+ | s22: String,
+ | s23: String,
+ | s24: String,
+ | s25: String,
+ | s26: String,
+ | s27: String,
+ | s28: String,
+ | s29: String,
+ | s30: String,
+ | s31: String,
+ | ): Unit {
+ | call("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16",
+ | "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31")
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ private fun toString(typeSpec: TypeSpec): String {
+ return FileSpec.get("com.squareup.tacos", typeSpec).toString()
+ }
+
+ private fun toString(funSpec: FunSpec): String {
+ val fileSpec = FileSpec.builder("com.squareup.tacos", "${funSpec.name}.kt")
+ .addFunction(funSpec)
+ .build()
+ return fileSpec.toString()
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/MemberNameTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/MemberNameTest.kt
new file mode 100644
index 00000000..308aeb2c
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/MemberNameTest.kt
@@ -0,0 +1,612 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.KModifier.OVERRIDE
+import com.squareup.kotlinpoet.MemberName.Companion.member
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import org.junit.Before
+import org.junit.Test
+
+class MemberNameTest {
+ @Test fun memberNames() {
+ val randomTaco = MemberName("com.squareup.tacos", "randomTaco")
+ val bestTacoEver = MemberName("com.squareup.tacos", "bestTacoEver")
+ val funSpec = FunSpec.builder("makeTastyTacos")
+ .addStatement("val randomTaco = %M()", randomTaco)
+ .addStatement("val bestTaco = %M", bestTacoEver)
+ .build()
+ val file = FileSpec.builder("com.example", "Tacos")
+ .addFunction(funSpec)
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.example
+ |
+ |import com.squareup.tacos.bestTacoEver
+ |import com.squareup.tacos.randomTaco
+ |import kotlin.Unit
+ |
+ |public fun makeTastyTacos(): Unit {
+ | val randomTaco = randomTaco()
+ | val bestTaco = bestTacoEver
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun memberInsideCompanionObject() {
+ val companion = ClassName("com.squareup.tacos", "Taco").nestedClass("Companion")
+ val createTaco = MemberName(companion, "createTaco")
+ val file = FileSpec.builder("com.example", "Tacos")
+ .addFunction(
+ FunSpec.builder("makeTastyTacos")
+ .addStatement("%M()", createTaco)
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.example
+ |
+ |import com.squareup.tacos.Taco.Companion.createTaco
+ |import kotlin.Unit
+ |
+ |public fun makeTastyTacos(): Unit {
+ | createTaco()
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun memberInsideSamePackage() {
+ val createTaco = MemberName("com.squareup.tacos", "createTaco")
+ val file = FileSpec.builder("com.squareup.tacos", "Tacos")
+ .addFunction(
+ FunSpec.builder("makeTastyTacos")
+ .addStatement("%M()", createTaco)
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public fun makeTastyTacos(): Unit {
+ | createTaco()
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun memberInsideClassInSamePackage() {
+ val createTaco = MemberName(
+ ClassName("com.squareup.tacos", "Town"),
+ "createTaco",
+ )
+ val file = FileSpec.builder("com.squareup.tacos", "Tacos")
+ .addFunction(
+ FunSpec.builder("makeTastyTacos")
+ .addStatement("%M()", createTaco)
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.tacos.Town.createTaco
+ |import kotlin.Unit
+ |
+ |public fun makeTastyTacos(): Unit {
+ | createTaco()
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun memberNamesClash() {
+ val createSquareTaco = MemberName("com.squareup.tacos", "createTaco")
+ val createTwitterTaco = MemberName("com.twitter.tacos", "createTaco")
+ val file = FileSpec.builder("com.example", "Tacos")
+ .addFunction(
+ FunSpec.builder("makeTastyTacos")
+ .addStatement("%M()", createSquareTaco)
+ .addStatement("%M()", createTwitterTaco)
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.example
+ |
+ |import kotlin.Unit
+ |import com.squareup.tacos.createTaco as squareupTacosCreateTaco
+ |import com.twitter.tacos.createTaco as twitterTacosCreateTaco
+ |
+ |public fun makeTastyTacos(): Unit {
+ | squareupTacosCreateTaco()
+ | twitterTacosCreateTaco()
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun memberNamesInsideCompanionsClash() {
+ val squareTacos = ClassName("com.squareup.tacos", "SquareTacos")
+ val twitterTacos = ClassName("com.twitter.tacos", "TwitterTacos")
+ val createSquareTaco = MemberName(squareTacos.nestedClass("Companion"), "createTaco")
+ val createTwitterTaco = MemberName(twitterTacos.nestedClass("Companion"), "createTaco")
+ val file = FileSpec.builder("com.example", "Tacos")
+ .addFunction(
+ FunSpec.builder("makeTastyTacos")
+ .addStatement("%M()", createSquareTaco)
+ .addStatement("%M()", createTwitterTaco)
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.example
+ |
+ |import kotlin.Unit
+ |import com.squareup.tacos.SquareTacos.Companion.createTaco as squareupTacosCreateTaco
+ |import com.twitter.tacos.TwitterTacos.Companion.createTaco as twitterTacosCreateTaco
+ |
+ |public fun makeTastyTacos(): Unit {
+ | squareupTacosCreateTaco()
+ | twitterTacosCreateTaco()
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun memberAndClassNamesClash() {
+ val squareTacosClass = ClassName("com.squareup.tacos", "SquareTacos")
+ val squareTacosFunction = MemberName("com.squareup.tacos.math", "SquareTacos")
+ val file = FileSpec.builder("com.example", "Tacos")
+ .addFunction(
+ FunSpec.builder("makeTastyTacos")
+ .addStatement("val tacos = %T()", squareTacosClass)
+ .addStatement("%M(tacos)", squareTacosFunction)
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.example
+ |
+ |import com.squareup.tacos.SquareTacos
+ |import kotlin.Unit
+ |
+ |public fun makeTastyTacos(): Unit {
+ | val tacos = SquareTacos()
+ | com.squareup.tacos.math.SquareTacos(tacos)
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun importedMemberAndClassFunctionNameClash() {
+ val kotlinErrorMember = MemberName("kotlin", "error")
+ val file = FileSpec.builder("com.squareup.tacos", "TacoTest")
+ .addType(
+ TypeSpec.classBuilder("TacoTest")
+ .addFunction(
+ FunSpec.builder("test")
+ .addStatement("%M(%S)", kotlinErrorMember, "errorText")
+ .build(),
+ )
+ .addFunction(
+ FunSpec
+ .builder("error")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public class TacoTest {
+ | public fun test(): Unit {
+ | kotlin.error("errorText")
+ | }
+ |
+ | public fun error(): Unit {
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun importedMemberAndSuperClassFunctionNameClashForInnerClass() {
+ val kotlinErrorMember = MemberName("kotlin", "error")
+ val file = FileSpec.builder("com.squareup.tacos", "TacoTest")
+ .addType(
+ TypeSpec.classBuilder("Test")
+ .addFunction(
+ FunSpec
+ .builder("error")
+ .build(),
+ )
+ .addType(
+ TypeSpec.classBuilder("TacoTest")
+ .addModifiers(KModifier.INNER)
+ .addFunction(
+ FunSpec.builder("test")
+ .addStatement("%M(%S)", kotlinErrorMember, "errorText")
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public class Test {
+ | public fun error(): Unit {
+ | }
+ |
+ | public inner class TacoTest {
+ | public fun test(): Unit {
+ | kotlin.error("errorText")
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun importedMemberAndSuperClassFunctionNameDontClashForNonInnerClass() {
+ val kotlinErrorMember = MemberName("kotlin", "error")
+ val file = FileSpec.builder("com.squareup.tacos", "TacoTest")
+ .addType(
+ TypeSpec.classBuilder("Test")
+ .addFunction(
+ FunSpec
+ .builder("error")
+ .build(),
+ )
+ .addType(
+ TypeSpec.classBuilder("TacoTest")
+ .addFunction(
+ FunSpec.builder("test")
+ .addStatement("%M(%S)", kotlinErrorMember, "errorText")
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |import kotlin.error
+ |
+ |public class Test {
+ | public fun error(): Unit {
+ | }
+ |
+ | public class TacoTest {
+ | public fun test(): Unit {
+ | error("errorText")
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun memberNameAliases() {
+ val createSquareTaco = MemberName("com.squareup.tacos", "createTaco")
+ val createTwitterTaco = MemberName("com.twitter.tacos", "createTaco")
+ val file = FileSpec.builder("com.example", "Tacos")
+ .addAliasedImport(createSquareTaco, "createSquareTaco")
+ .addAliasedImport(createTwitterTaco, "createTwitterTaco")
+ .addFunction(
+ FunSpec.builder("makeTastyTacos")
+ .addStatement("%M()", createSquareTaco)
+ .addStatement("%M()", createTwitterTaco)
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.example
+ |
+ |import kotlin.Unit
+ |import com.squareup.tacos.createTaco as createSquareTaco
+ |import com.twitter.tacos.createTaco as createTwitterTaco
+ |
+ |public fun makeTastyTacos(): Unit {
+ | createSquareTaco()
+ | createTwitterTaco()
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun keywordsEscaping() {
+ val `when` = MemberName("org.mockito", "when")
+ val file = FileSpec.builder("com.squareup.tacos", "TacoTest")
+ .addType(
+ TypeSpec.classBuilder("TacoTest")
+ .addFunction(
+ FunSpec.builder("setUp")
+ .addAnnotation(Before::class)
+ .addStatement("%M(tacoService.createTaco()).thenReturn(tastyTaco())", `when`)
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |import org.junit.Before
+ |import org.mockito.`when`
+ |
+ |public class TacoTest {
+ | @Before
+ | public fun setUp(): Unit {
+ | `when`(tacoService.createTaco()).thenReturn(tastyTaco())
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun clashingNamesKeywordsEscaping() {
+ val squareTacos = ClassName("com.squareup.tacos", "SquareTacos")
+ val twitterTacos = ClassName("com.twitter.tacos", "TwitterTacos")
+ val whenSquareTaco = MemberName(squareTacos.nestedClass("Companion"), "when")
+ val whenTwitterTaco = MemberName(twitterTacos.nestedClass("Companion"), "when")
+ val file = FileSpec.builder("com.example", "Tacos")
+ .addFunction(
+ FunSpec.builder("whenTastyTacos")
+ .addStatement("%M()", whenSquareTaco)
+ .addStatement("%M()", whenTwitterTaco)
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.example
+ |
+ |import kotlin.Unit
+ |import com.squareup.tacos.SquareTacos.Companion.`when` as squareupTacosWhen
+ |import com.twitter.tacos.TwitterTacos.Companion.`when` as twitterTacosWhen
+ |
+ |public fun whenTastyTacos(): Unit {
+ | squareupTacosWhen()
+ | twitterTacosWhen()
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun memberReferences() {
+ val randomTaco = MemberName("com.squareup.tacos", "randomTaco")
+ val bestTacoEver = ClassName("com.squareup.tacos", "TacoTruck")
+ .member("bestTacoEver")
+ val funSpec = FunSpec.builder("makeTastyTacos")
+ .addStatement("val randomTacoFactory = %L", randomTaco.reference())
+ .addStatement("val bestTacoFactory = %L", bestTacoEver.reference())
+ .build()
+ val file = FileSpec.builder("com.example", "Tacos")
+ .addFunction(funSpec)
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.example
+ |
+ |import com.squareup.tacos.TacoTruck
+ |import com.squareup.tacos.randomTaco
+ |import kotlin.Unit
+ |
+ |public fun makeTastyTacos(): Unit {
+ | val randomTacoFactory = ::randomTaco
+ | val bestTacoFactory = TacoTruck::bestTacoEver
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun spacesEscaping() {
+ val produceTacos = MemberName("com.squareup.taco factory", "produce tacos")
+ val file = FileSpec.builder("com.squareup.tacos", "TacoTest")
+ .addFunction(
+ FunSpec.builder("main")
+ .addStatement("println(%M())", produceTacos)
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.`taco factory`.`produce tacos`
+ |import kotlin.Unit
+ |
+ |public fun main(): Unit {
+ | println(`produce tacos`())
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun memberExtension_className() {
+ val regex = ClassName("kotlin.text", "Regex")
+ assertThat(regex.member("fromLiteral"))
+ .isEqualTo(MemberName(regex, "fromLiteral"))
+ }
+
+ @Test fun memberExtension_kclass() {
+ assertThat(Regex::class.member("fromLiteral"))
+ .isEqualTo(MemberName(ClassName("kotlin.text", "Regex"), "fromLiteral"))
+ }
+
+ @Test fun memberExtension_class() {
+ assertThat(Regex::class.java.member("fromLiteral"))
+ .isEqualTo(MemberName(ClassName("kotlin.text", "Regex"), "fromLiteral"))
+ }
+
+ @Test fun `%N escapes MemberNames`() {
+ val taco = ClassName("com.squareup.tacos", "Taco")
+ val packager = ClassName("com.squareup.tacos", "TacoPackager")
+ val file = FileSpec.builder("com.example", "Test")
+ .addFunction(
+ FunSpec.builder("packageTacos")
+ .addParameter("tacos", LIST.parameterizedBy(taco))
+ .addParameter("packager", packager)
+ .addStatement("packager.%N(tacos)", packager.member("package"))
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.example
+ |
+ |import com.squareup.tacos.Taco
+ |import com.squareup.tacos.TacoPackager
+ |import kotlin.Unit
+ |import kotlin.collections.List
+ |
+ |public fun packageTacos(tacos: List<Taco>, packager: TacoPackager): Unit {
+ | packager.`package`(tacos)
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun importOperator() {
+ val taco = ClassName("com.squareup.tacos", "Taco")
+ val meat = ClassName("com.squareup.tacos.ingredient", "Meat")
+ val iterator = MemberName("com.squareup.tacos.internal", KOperator.ITERATOR)
+ val minusAssign = MemberName("com.squareup.tacos.internal", KOperator.MINUS_ASSIGN)
+ val file = FileSpec.builder("com.example", "Test")
+ .addFunction(
+ FunSpec.builder("makeTacoHealthy")
+ .addParameter("taco", taco)
+ .beginControlFlow("for (ingredient %M taco)", iterator)
+ .addStatement("if (ingredient is %T) taco %M ingredient", meat, minusAssign)
+ .endControlFlow()
+ .addStatement("return taco")
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.example
+ |
+ |import com.squareup.tacos.Taco
+ |import com.squareup.tacos.`internal`.iterator
+ |import com.squareup.tacos.`internal`.minusAssign
+ |import com.squareup.tacos.ingredient.Meat
+ |import kotlin.Unit
+ |
+ |public fun makeTacoHealthy(taco: Taco): Unit {
+ | for (ingredient in taco) {
+ | if (ingredient is Meat) taco -= ingredient
+ | }
+ | return taco
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/1089
+ @Test fun `extension MemberName imported if name clash`() {
+ val hashCode = MemberName("kotlin", "hashCode", isExtension = true)
+ val file = FileSpec.builder("com.squareup.tacos", "Message")
+ .addType(
+ TypeSpec.classBuilder("Message")
+ .addFunction(
+ FunSpec.builder("hashCode")
+ .addModifiers(OVERRIDE)
+ .returns(INT)
+ .addCode(
+ buildCodeBlock {
+ addStatement("var result = super.hashCode")
+ beginControlFlow("if (result == 0)")
+ addStatement("result = result * 37 + embedded_message.%M()", hashCode)
+ addStatement("super.hashCode = result")
+ endControlFlow()
+ addStatement("return result")
+ },
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ //language=kotlin
+ assertThat(file.toString()).isEqualTo(
+ """
+ package com.squareup.tacos
+
+ import kotlin.Int
+ import kotlin.hashCode
+
+ public class Message {
+ public override fun hashCode(): Int {
+ var result = super.hashCode
+ if (result == 0) {
+ result = result * 37 + embedded_message.hashCode()
+ super.hashCode = result
+ }
+ return result
+ }
+ }
+
+ """.trimIndent(),
+ )
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/NameAllocatorTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/NameAllocatorTest.kt
new file mode 100644
index 00000000..28c7d34d
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/NameAllocatorTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+
+class NameAllocatorTest {
+ @Test fun usage() {
+ val nameAllocator = NameAllocator()
+ assertThat(nameAllocator.newName("foo", 1)).isEqualTo("foo")
+ assertThat(nameAllocator.newName("bar", 2)).isEqualTo("bar")
+ assertThat(nameAllocator[1]).isEqualTo("foo")
+ assertThat(nameAllocator[2]).isEqualTo("bar")
+ }
+
+ @Test fun nameCollision() {
+ val nameAllocator = NameAllocator()
+ assertThat(nameAllocator.newName("foo")).isEqualTo("foo")
+ assertThat(nameAllocator.newName("foo")).isEqualTo("foo_")
+ assertThat(nameAllocator.newName("foo")).isEqualTo("foo__")
+ }
+
+ @Test fun nameCollisionWithTag() {
+ val nameAllocator = NameAllocator()
+ assertThat(nameAllocator.newName("foo", 1)).isEqualTo("foo")
+ assertThat(nameAllocator.newName("foo", 2)).isEqualTo("foo_")
+ assertThat(nameAllocator.newName("foo", 3)).isEqualTo("foo__")
+ assertThat(nameAllocator[1]).isEqualTo("foo")
+ assertThat(nameAllocator[2]).isEqualTo("foo_")
+ assertThat(nameAllocator[3]).isEqualTo("foo__")
+ }
+
+ @Test fun characterMappingSubstitute() {
+ val nameAllocator = NameAllocator()
+ assertThat(nameAllocator.newName("a-b", 1)).isEqualTo("a_b")
+ }
+
+ @Test fun characterMappingSurrogate() {
+ val nameAllocator = NameAllocator()
+ assertThat(nameAllocator.newName("a\uD83C\uDF7Ab", 1)).isEqualTo("a_b")
+ }
+
+ @Test fun characterMappingInvalidStartButValidPart() {
+ val nameAllocator = NameAllocator()
+ assertThat(nameAllocator.newName("1ab", 1)).isEqualTo("_1ab")
+ }
+
+ @Test fun characterMappingInvalidStartIsInvalidPart() {
+ val nameAllocator = NameAllocator()
+ assertThat(nameAllocator.newName("&ab", 1)).isEqualTo("_ab")
+ }
+
+ @Test fun kotlinKeyword() {
+ val nameAllocator = NameAllocator()
+ assertThat(nameAllocator.newName("when", 1)).isEqualTo("when_")
+ assertThat(nameAllocator[1]).isEqualTo("when_")
+ }
+
+ @Test fun tagReuseForbidden() {
+ val nameAllocator = NameAllocator()
+ nameAllocator.newName("foo", 1)
+ assertThrows<IllegalArgumentException> {
+ nameAllocator.newName("bar", 1)
+ }.hasMessageThat().isEqualTo("tag 1 cannot be used for both 'foo' and 'bar'")
+ }
+
+ @Test fun useBeforeAllocateForbidden() {
+ val nameAllocator = NameAllocator()
+ assertThrows<IllegalArgumentException> {
+ nameAllocator[1]
+ }.hasMessageThat().isEqualTo("unknown tag: 1")
+ }
+
+ @Test fun cloneUsage() {
+ val outerAllocator = NameAllocator()
+ outerAllocator.newName("foo", 1)
+
+ val innerAllocator1 = outerAllocator.copy()
+ assertThat(innerAllocator1.newName("bar", 2)).isEqualTo("bar")
+ assertThat(innerAllocator1.newName("foo", 3)).isEqualTo("foo_")
+
+ val innerAllocator2 = outerAllocator.copy()
+ assertThat(innerAllocator2.newName("foo", 2)).isEqualTo("foo_")
+ assertThat(innerAllocator2.newName("bar", 3)).isEqualTo("bar")
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ParameterSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ParameterSpecTest.kt
new file mode 100644
index 00000000..d764f441
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ParameterSpecTest.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import javax.lang.model.element.Modifier
+import kotlin.test.Test
+import kotlin.test.assertFailsWith
+
+class ParameterSpecTest {
+ @Test fun equalsAndHashCode() {
+ var a = ParameterSpec.builder("foo", Int::class)
+ .build()
+ var b = ParameterSpec.builder("foo", Int::class)
+ .build()
+ assertThat(a == b).isTrue()
+ assertThat(a.hashCode()).isEqualTo(b.hashCode())
+ a = ParameterSpec.builder("i", Int::class)
+ .addModifiers(KModifier.NOINLINE)
+ .build()
+ b = ParameterSpec.builder("i", Int::class)
+ .addModifiers(KModifier.NOINLINE)
+ .build()
+ assertThat(a == b).isTrue()
+ assertThat(a.hashCode()).isEqualTo(b.hashCode())
+ }
+
+ @Test fun escapeKeywordInParameterName() {
+ val parameterSpec = ParameterSpec.builder("if", String::class)
+ .build()
+ assertThat(parameterSpec.toString()).isEqualTo("`if`: kotlin.String")
+ }
+
+ @Test fun escapePunctuationInParameterName() {
+ val parameterSpec = ParameterSpec.builder("with-hyphen", String::class)
+ .build()
+ assertThat(parameterSpec.toString()).isEqualTo("`with-hyphen`: kotlin.String")
+ }
+
+ @Test fun generalBuilderEqualityTest() {
+ val parameterSpec = ParameterSpec.builder("Nuts", String::class)
+ .addAnnotation(ClassName("com.squareup.kotlinpoet", "Food"))
+ .addModifiers(KModifier.VARARG)
+ .defaultValue("Almonds")
+ .build()
+
+ assertThat(parameterSpec.toBuilder().build()).isEqualTo(parameterSpec)
+ }
+
+ @Test fun modifyModifiers() {
+ val builder = ParameterSpec
+ .builder("word", String::class)
+ .addModifiers(KModifier.NOINLINE)
+
+ builder.modifiers.clear()
+ builder.modifiers.add(KModifier.CROSSINLINE)
+
+ assertThat(builder.build().modifiers).containsExactly(KModifier.CROSSINLINE)
+ }
+
+ @Test fun modifyAnnotations() {
+ val builder = ParameterSpec
+ .builder("word", String::class)
+ .addAnnotation(
+ AnnotationSpec.builder(JvmName::class.asClassName())
+ .addMember("name = %S", "jvmWord")
+ .build(),
+ )
+
+ val javaWord = AnnotationSpec.builder(JvmName::class.asClassName())
+ .addMember("name = %S", "javaWord")
+ .build()
+ builder.annotations.clear()
+ builder.annotations.add(javaWord)
+
+ assertThat(builder.build().annotations).containsExactly(javaWord)
+ }
+
+ // https://github.com/square/kotlinpoet/issues/462
+ @Test fun codeBlockDefaultValue() {
+ val param = ParameterSpec.builder("arg", ANY).build()
+ val defaultValue = CodeBlock.builder()
+ .beginControlFlow("{ %L ->", param)
+ .addStatement("println(\"arg=\$%N\")", param)
+ .endControlFlow()
+ .build()
+ val lambdaTypeName = ClassName.bestGuess("com.example.SomeTypeAlias")
+ val paramSpec = ParameterSpec.builder("parameter", lambdaTypeName)
+ .defaultValue(defaultValue)
+ .build()
+ assertThat(paramSpec.toString()).isEqualTo(
+ """
+ |parameter: com.example.SomeTypeAlias = { arg: kotlin.Any ->
+ | println("arg=${'$'}arg")
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun annotatedLambdaType() {
+ val annotation = AnnotationSpec.builder(ClassName("com.squareup.tacos", "Annotation")).build()
+ val type = LambdaTypeName.get(returnType = UNIT).copy(annotations = listOf(annotation))
+ val spec = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addFunction(
+ FunSpec.builder("foo")
+ .addParameter("bar", type)
+ .build(),
+ )
+ .build()
+ assertThat(spec.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public fun foo(bar: @Annotation () -> Unit): Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun doublePropertyInitialization() {
+ val codeBlockDefaultValue = ParameterSpec.builder("listA", String::class)
+ .defaultValue(CodeBlock.builder().add("foo").build())
+ .defaultValue(CodeBlock.builder().add("bar").build())
+ .build()
+
+ assertThat(CodeBlock.of("bar")).isEqualTo(codeBlockDefaultValue.defaultValue)
+
+ val formatDefaultValue = ParameterSpec.builder("listA", String::class)
+ .defaultValue("foo")
+ .defaultValue("bar")
+ .build()
+
+ assertThat(CodeBlock.of("bar")).isEqualTo(formatDefaultValue.defaultValue)
+ }
+
+ @Suppress("DEPRECATION_ERROR")
+ @Test
+ fun jvmModifiersAreNotAllowed() {
+ val e = assertFailsWith<IllegalArgumentException> {
+ ParameterSpec.builder("value", INT)
+ .jvmModifiers(listOf(Modifier.FINAL))
+ .build()
+ }
+ assertThat(e).hasMessageThat().contains("JVM modifiers are not permitted on parameters in Kotlin")
+ }
+
+ @Test
+ fun illegalModifiers() {
+ val builder = ParameterSpec.builder("value", INT)
+
+ val e = assertFailsWith<IllegalArgumentException> {
+ // Legal
+ builder.addModifiers(KModifier.NOINLINE)
+ builder.addModifiers(KModifier.CROSSINLINE)
+ builder.addModifiers(KModifier.VARARG)
+ // Everything else is illegal
+ builder.addModifiers(KModifier.FINAL)
+ builder.addModifiers(KModifier.PRIVATE)
+ builder.build()
+ }
+ assertThat(e).hasMessageThat().contains("Modifiers [FINAL, PRIVATE] are not allowed on Kotlin parameters. Allowed modifiers: [VARARG, NOINLINE, CROSSINLINE]")
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ParameterizedTypeNameTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ParameterizedTypeNameTest.kt
new file mode 100644
index 00000000..f4e5625b
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ParameterizedTypeNameTest.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2018 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.plusParameter
+import java.io.Closeable
+import kotlin.reflect.KClass
+import kotlin.reflect.KFunction
+import kotlin.reflect.KMutableProperty
+import kotlin.reflect.KType
+import kotlin.reflect.KTypeProjection
+import kotlin.reflect.KVariance
+import kotlin.reflect.full.createType
+import org.junit.Test
+
+class ParameterizedTypeNameTest {
+ @Test fun classNamePlusParameter() {
+ val typeName = ClassName("kotlin.collections", "List")
+ .plusParameter(ClassName("kotlin", "String"))
+ assertThat(typeName.toString()).isEqualTo("kotlin.collections.List<kotlin.String>")
+ }
+
+ @Test fun classNamePlusTwoParameters() {
+ val typeName = ClassName("kotlin.collections", "Map")
+ .plusParameter(ClassName("kotlin", "String"))
+ .plusParameter(ClassName("kotlin", "Int"))
+ assertThat(typeName.toString()).isEqualTo("kotlin.collections.Map<kotlin.String, kotlin.Int>")
+ }
+
+ @Test fun classNamePlusTypeVariableParameter() {
+ val t = TypeVariableName("T")
+ val mapOfT = Map::class.asTypeName().plusParameter(t)
+ assertThat(mapOfT.toString()).isEqualTo("kotlin.collections.Map<T>")
+ }
+
+ @Test fun kClassPlusParameter() {
+ val typeName = List::class.plusParameter(String::class)
+ assertThat(typeName.toString()).isEqualTo("kotlin.collections.List<kotlin.String>")
+ }
+
+ @Test fun kClassPlusTwoParameters() {
+ val typeName = Map::class
+ .plusParameter(String::class)
+ .plusParameter(Int::class)
+ assertThat(typeName.toString()).isEqualTo("kotlin.collections.Map<kotlin.String, kotlin.Int>")
+ }
+
+ @Test fun classPlusParameter() {
+ val typeName = java.util.List::class.java.plusParameter(java.lang.String::class.java)
+ assertThat(typeName.toString()).isEqualTo("java.util.List<java.lang.String>")
+ }
+
+ @Test fun primitiveArray() {
+ assertThat(ByteArray::class.asTypeName().toString()).isEqualTo("kotlin.ByteArray")
+ assertThat(CharArray::class.asTypeName().toString()).isEqualTo("kotlin.CharArray")
+ assertThat(ShortArray::class.asTypeName().toString()).isEqualTo("kotlin.ShortArray")
+ assertThat(IntArray::class.asTypeName().toString()).isEqualTo("kotlin.IntArray")
+ assertThat(LongArray::class.asTypeName().toString()).isEqualTo("kotlin.LongArray")
+ assertThat(FloatArray::class.asTypeName().toString()).isEqualTo("kotlin.FloatArray")
+ assertThat(DoubleArray::class.asTypeName().toString()).isEqualTo("kotlin.DoubleArray")
+ }
+
+ @Test fun arrayPlusPrimitiveParameter() {
+ val invariantInt = KTypeProjection(KVariance.INVARIANT, Int::class.createType())
+ val typeName = Array<Unit>::class.createType(listOf(invariantInt)).asTypeName()
+ assertThat(typeName.toString()).isEqualTo("kotlin.Array<kotlin.Int>")
+ }
+
+ @Test fun arrayPlusObjectParameter() {
+ val invariantCloseable = KTypeProjection(KVariance.INVARIANT, Closeable::class.createType())
+ val typeName = Array<Unit>::class.createType(listOf(invariantCloseable)).asTypeName()
+ assertThat(typeName.toString()).isEqualTo("kotlin.Array<java.io.Closeable>")
+ }
+
+ @Test fun arrayPlusNullableParameter() {
+ val invariantNullableCloseable = KTypeProjection(KVariance.INVARIANT, Closeable::class.createType(nullable = true))
+ val typeName = Array<Unit>::class.createType(listOf(invariantNullableCloseable)).asTypeName()
+ assertThat(typeName.toString()).isEqualTo("kotlin.Array<java.io.Closeable?>")
+ }
+
+ @Test fun typeParameter() {
+ val funWithParam: () -> Closeable = this::withParam
+ val typeName = (funWithParam as KFunction<*>).returnType.asTypeName()
+ assertThat(typeName.toString()).isEqualTo("Param")
+ }
+
+ @Test fun nullableTypeParameter() {
+ val funWithParam: () -> Closeable? = this::withNullableParam
+ val typeName = (funWithParam as KFunction<*>).returnType.asTypeName()
+ assertThat(typeName.toString()).isEqualTo("Param?")
+ }
+
+ @Test fun classPlusTwoParameters() {
+ val typeName = java.util.Map::class.java
+ .plusParameter(java.lang.String::class.java)
+ .plusParameter(java.lang.Integer::class.java)
+ assertThat(typeName.toString()).isEqualTo("java.util.Map<java.lang.String, java.lang.Integer>")
+ }
+
+ @Test fun copyingTypeArguments() {
+ val typeName = java.util.Map::class.java
+ .plusParameter(java.lang.String::class.java)
+ .plusParameter(java.lang.Integer::class.java)
+ .nestedClass(
+ "Entry",
+ listOf(
+ java.lang.String::class.java.asClassName(),
+ java.lang.Integer::class.java.asClassName(),
+ ),
+ )
+ .copy(typeArguments = listOf(STAR, STAR))
+ assertThat(typeName.toString()).isEqualTo("java.util.Map<java.lang.String, java.lang.Integer>.Entry<*, *>")
+ }
+
+ interface Projections {
+ val outVariance: KClass<out Annotation>
+ val inVariance: KClass<in Test>
+ val invariantNullable: KClass<Test>?
+ val star: KClass<*>
+ val multiVariant: Map<in String, List<Map<KClass<out Number>, *>?>>
+ val outAnyOnTypeWithoutBoundsAndVariance: KMutableProperty<out Any>
+ }
+
+ private fun assertKTypeProjections(kType: KType) = assertThat(kType.asTypeName().toString()).isEqualTo(kType.toString())
+
+ @Test fun kTypeOutProjection() = assertKTypeProjections(Projections::outVariance.returnType)
+
+ @Test fun kTypeInProjection() = assertKTypeProjections(Projections::inVariance.returnType)
+
+ @Test fun kTypeInvariantNullableProjection() = assertKTypeProjections(Projections::invariantNullable.returnType)
+
+ @Test fun kTypeStarProjection() = assertKTypeProjections(Projections::star.returnType)
+
+ @Test fun kTypeMultiVariantProjection() = assertKTypeProjections(Projections::multiVariant.returnType)
+
+ @Test fun kTypeOutAnyOnTypeWithoutBoundsVariance() = assertKTypeProjections(Projections::outAnyOnTypeWithoutBoundsAndVariance.returnType)
+
+ private fun <Param : Closeable> withParam(): Param = throw NotImplementedError("for testing purposes")
+
+ private fun <Param : Closeable> withNullableParam(): Param? = throw NotImplementedError("for testing purposes")
+
+ @Test fun annotatedLambdaTypeParameter() {
+ val annotation = AnnotationSpec.builder(ClassName("", "Annotation")).build()
+ val typeName = Map::class.asTypeName()
+ .plusParameter(String::class.asTypeName())
+ .plusParameter(LambdaTypeName.get(returnType = UNIT).copy(annotations = listOf(annotation)))
+ assertThat(typeName.toString())
+ .isEqualTo("kotlin.collections.Map<kotlin.String, @Annotation () -> kotlin.Unit>")
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt
new file mode 100644
index 00000000..5eda0f78
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/PropertySpecTest.kt
@@ -0,0 +1,769 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.FunSpec.Companion.GETTER
+import com.squareup.kotlinpoet.FunSpec.Companion.SETTER
+import com.squareup.kotlinpoet.KModifier.EXTERNAL
+import com.squareup.kotlinpoet.KModifier.PRIVATE
+import com.squareup.kotlinpoet.KModifier.PUBLIC
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import java.io.Serializable
+import java.util.function.Function
+import kotlin.reflect.KClass
+import kotlin.test.Test
+
+@OptIn(ExperimentalKotlinPoetApi::class)
+class PropertySpecTest {
+ annotation class TestAnnotation
+
+ @Test fun nullable() {
+ val type = String::class.asClassName().copy(nullable = true)
+ val a = PropertySpec.builder("foo", type).build()
+ assertThat(a.toString()).isEqualTo("val foo: kotlin.String?\n")
+ }
+
+ @Test fun delegated() {
+ val prop = PropertySpec.builder("foo", String::class)
+ .delegate("Delegates.notNull()")
+ .build()
+ assertThat(prop.toString()).isEqualTo("val foo: kotlin.String by Delegates.notNull()\n")
+ }
+
+ @Test fun emptySetter() {
+ val prop = PropertySpec.builder("foo", String::class)
+ .mutable()
+ .setter(
+ FunSpec.setterBuilder()
+ .addModifiers(PRIVATE)
+ .build(),
+ )
+ .build()
+
+ assertThat(prop.toString()).isEqualTo(
+ """
+ |var foo: kotlin.String
+ | private set
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/952
+ @Test fun emptySetterCannotHaveBody() {
+ assertThrows<IllegalArgumentException> {
+ PropertySpec.builder("foo", String::class)
+ .mutable()
+ .setter(
+ FunSpec.setterBuilder()
+ .addStatement("body()")
+ .build(),
+ )
+ .build()
+ }.hasMessageThat().isEqualTo("parameterless setter cannot have code")
+ }
+
+ @Test fun externalGetterAndSetter() {
+ val prop = PropertySpec.builder("foo", String::class)
+ .mutable()
+ .getter(
+ FunSpec.getterBuilder()
+ .addModifiers(EXTERNAL)
+ .build(),
+ )
+ .setter(
+ FunSpec.setterBuilder()
+ .addModifiers(EXTERNAL)
+ .build(),
+ )
+ .build()
+
+ assertThat(prop.toString()).isEqualTo(
+ """
+ |var foo: kotlin.String
+ | external get
+ | external set
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun externalGetterCannotHaveBody() {
+ assertThrows<IllegalArgumentException> {
+ PropertySpec.builder("foo", String::class)
+ .getter(
+ FunSpec.getterBuilder()
+ .addModifiers(EXTERNAL)
+ .addStatement("return %S", "foo")
+ .build(),
+ )
+ .build()
+ }.hasMessageThat().isEqualTo("external getter cannot have code")
+ }
+
+ @Test fun publicGetterAndSetter() {
+ val prop = PropertySpec.builder("foo", String::class)
+ .mutable()
+ .getter(
+ FunSpec.getterBuilder()
+ .addModifiers(PUBLIC)
+ .addStatement("return %S", "_foo")
+ .build(),
+ )
+ .setter(
+ FunSpec.setterBuilder()
+ .addModifiers(PUBLIC)
+ .addParameter("value", String::class)
+ .build(),
+ )
+ .build()
+
+ assertThat(prop.toString()).isEqualTo(
+ """
+ |var foo: kotlin.String
+ | public get() = "_foo"
+ | public set(`value`) {
+ | }
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun inlineSingleAccessorVal() {
+ val prop = PropertySpec.builder("foo", String::class)
+ .getter(
+ FunSpec.getterBuilder()
+ .addModifiers(KModifier.INLINE)
+ .addStatement("return %S", "foo")
+ .build(),
+ )
+ .build()
+
+ assertThat(prop.toString()).isEqualTo(
+ """
+ |inline val foo: kotlin.String
+ | get() = "foo"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun inlineSingleAccessorVar() {
+ val prop = PropertySpec.builder("foo", String::class)
+ .mutable()
+ .getter(
+ FunSpec.getterBuilder()
+ .addModifiers(KModifier.INLINE)
+ .addStatement("return %S", "foo")
+ .build(),
+ )
+ .build()
+
+ assertThat(prop.toString()).isEqualTo(
+ """
+ |var foo: kotlin.String
+ | inline get() = "foo"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun inlineBothAccessors() {
+ val prop = PropertySpec.builder("foo", String::class.asTypeName())
+ .mutable()
+ .getter(
+ FunSpec.getterBuilder()
+ .addModifiers(KModifier.INLINE)
+ .addStatement("return %S", "foo")
+ .build(),
+ )
+ .setter(
+ FunSpec.setterBuilder()
+ .addModifiers(KModifier.INLINE)
+ .addParameter("value", String::class)
+ .build(),
+ )
+ .build()
+
+ assertThat(prop.toString()).isEqualTo(
+ """
+ |inline var foo: kotlin.String
+ | get() = "foo"
+ | set(`value`) {
+ | }
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun inlineForbiddenOnProperty() {
+ assertThrows<IllegalArgumentException> {
+ PropertySpec.builder("foo", String::class)
+ .addModifiers(KModifier.INLINE)
+ .build()
+ }.hasMessageThat().isEqualTo(
+ "KotlinPoet doesn't allow setting the inline modifier on " +
+ "properties. You should mark either the getter, the setter, or both inline.",
+ )
+ }
+
+ @Test fun equalsAndHashCode() {
+ val type = Int::class
+ var a = PropertySpec.builder("foo", type).build()
+ var b = PropertySpec.builder("foo", type).build()
+ assertThat(a == b).isTrue()
+ assertThat(a.hashCode()).isEqualTo(b.hashCode())
+ a = PropertySpec.builder("FOO", type, KModifier.PUBLIC, KModifier.LATEINIT).build()
+ b = PropertySpec.builder("FOO", type, KModifier.PUBLIC, KModifier.LATEINIT).build()
+ assertThat(a == b).isTrue()
+ assertThat(a.hashCode()).isEqualTo(b.hashCode())
+ }
+
+ @Test fun escapeKeywordInPropertyName() {
+ val prop = PropertySpec.builder("object", String::class)
+ .build()
+ assertThat(prop.toString()).isEqualTo(
+ """
+ |val `object`: kotlin.String
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun escapeKeywordInVariableName() {
+ val prop = PropertySpec.builder("object", String::class)
+ .mutable()
+ .build()
+ assertThat(prop.toString()).isEqualTo(
+ """
+ |var `object`: kotlin.String
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun externalTopLevel() {
+ val prop = PropertySpec.builder("foo", String::class)
+ .addModifiers(KModifier.EXTERNAL)
+ .build()
+
+ assertThat(prop.toString()).isEqualTo(
+ """
+ |external val foo: kotlin.String
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun escapePunctuationInPropertyName() {
+ val prop = PropertySpec.builder("with-hyphen", String::class)
+ .build()
+
+ assertThat(prop.toString()).isEqualTo(
+ """
+ |val `with-hyphen`: kotlin.String
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun generalBuilderEqualityTest() {
+ val originatingElement = FakeElement()
+ val prop = PropertySpec.builder("tacos", Int::class)
+ .mutable()
+ .addAnnotation(ClassName("com.squareup.kotlinpoet", "Vegan"))
+ .addKdoc("Can make it vegan!")
+ .addModifiers(KModifier.PUBLIC)
+ .addTypeVariable(TypeVariableName("T"))
+ .delegate("Delegates.notNull()")
+ .receiver(Int::class)
+ .getter(
+ FunSpec.getterBuilder()
+ .addModifiers(KModifier.INLINE)
+ .addStatement("return %S", 42)
+ .build(),
+ )
+ .setter(
+ FunSpec.setterBuilder()
+ .addModifiers(KModifier.INLINE)
+ .addParameter("value", Int::class)
+ .build(),
+ )
+ .addOriginatingElement(originatingElement)
+ .build()
+
+ val newProp = prop.toBuilder().build()
+ assertThat(newProp).isEqualTo(prop)
+ assertThat(newProp.originatingElements).containsExactly(originatingElement)
+ }
+
+ @Test fun modifyModifiers() {
+ val builder = PropertySpec
+ .builder("word", String::class)
+ .addModifiers(PRIVATE)
+
+ builder.modifiers.clear()
+ builder.modifiers.add(KModifier.INTERNAL)
+
+ assertThat(builder.build().modifiers).containsExactly(KModifier.INTERNAL)
+ }
+
+ @Test fun modifyAnnotations() {
+ val builder = PropertySpec
+ .builder("word", String::class)
+ .addAnnotation(
+ AnnotationSpec.builder(JvmName::class.asClassName())
+ .addMember("name = %S", "jvmWord")
+ .build(),
+ )
+
+ val javaWord = AnnotationSpec.builder(JvmName::class.asClassName())
+ .addMember("name = %S", "javaWord")
+ .build()
+ builder.annotations.clear()
+ builder.annotations.add(javaWord)
+
+ assertThat(builder.build().annotations).containsExactly(javaWord)
+ }
+
+ // https://github.com/square/kotlinpoet/issues/437
+ @Test fun typeVariable() {
+ val t = TypeVariableName("T", Any::class)
+ val prop = PropertySpec.builder("someFunction", t, PRIVATE)
+ .addTypeVariable(t)
+ .receiver(KClass::class.asClassName().parameterizedBy(t))
+ .getter(
+ FunSpec.getterBuilder()
+ .addModifiers(KModifier.INLINE)
+ .addStatement("return stuff as %T", t)
+ .build(),
+ )
+ .build()
+ assertThat(prop.toString()).isEqualTo(
+ """
+ |private inline val <T : kotlin.Any> kotlin.reflect.KClass<T>.someFunction: T
+ | get() = stuff as T
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun typeVariablesWithWhere() {
+ val t = TypeVariableName("T", Serializable::class, Cloneable::class)
+ val r = TypeVariableName("R", Any::class)
+ val function = Function::class.asClassName().parameterizedBy(t, r)
+ val prop = PropertySpec.builder("property", String::class, PRIVATE)
+ .receiver(function)
+ .addTypeVariables(listOf(t, r))
+ .getter(
+ FunSpec.getterBuilder()
+ .addStatement("return %S", "")
+ .build(),
+ )
+ .build()
+ assertThat(prop.toString()).isEqualTo(
+ """
+ |private val <T, R : kotlin.Any> java.util.function.Function<T, R>.`property`: kotlin.String where T : java.io.Serializable, T : kotlin.Cloneable
+ | get() = ""
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun reifiedTypeVariable() {
+ val t = TypeVariableName("T").copy(reified = true)
+ val prop = PropertySpec.builder("someFunction", t, PRIVATE)
+ .addTypeVariable(t)
+ .receiver(KClass::class.asClassName().parameterizedBy(t))
+ .getter(
+ FunSpec.getterBuilder()
+ .addModifiers(KModifier.INLINE)
+ .addStatement("return stuff as %T", t)
+ .build(),
+ )
+ .build()
+ assertThat(prop.toString()).isEqualTo(
+ """
+ |private inline val <reified T> kotlin.reflect.KClass<T>.someFunction: T
+ | get() = stuff as T
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun reifiedTypeVariableNotAllowedWhenNoAccessors() {
+ assertThrows<IllegalArgumentException> {
+ PropertySpec.builder("property", String::class)
+ .addTypeVariable(TypeVariableName("T").copy(reified = true))
+ .build()
+ }.hasMessageThat().isEqualTo(
+ "only type parameters of properties with inline getters and/or setters can be reified!",
+ )
+ }
+
+ @Test fun reifiedTypeVariableNotAllowedWhenGetterNotInline() {
+ assertThrows<IllegalArgumentException> {
+ PropertySpec.builder("property", String::class)
+ .addTypeVariable(TypeVariableName("T").copy(reified = true))
+ .getter(
+ FunSpec.getterBuilder()
+ .addStatement("return %S", "")
+ .build(),
+ )
+ .build()
+ }.hasMessageThat().isEqualTo(
+ "only type parameters of properties with inline getters and/or setters can be reified!",
+ )
+ }
+
+ @Test fun reifiedTypeVariableNotAllowedWhenSetterNotInline() {
+ assertThrows<IllegalArgumentException> {
+ PropertySpec.builder("property", String::class.asTypeName())
+ .mutable()
+ .addTypeVariable(TypeVariableName("T").copy(reified = true))
+ .setter(
+ FunSpec.setterBuilder()
+ .addParameter("value", String::class)
+ .addStatement("println()")
+ .build(),
+ )
+ .build()
+ }.hasMessageThat().isEqualTo(
+ "only type parameters of properties with inline getters and/or setters can be reified!",
+ )
+ }
+
+ @Test fun reifiedTypeVariableNotAllowedWhenOnlySetterIsInline() {
+ assertThrows<IllegalArgumentException> {
+ PropertySpec.builder("property", String::class.asTypeName())
+ .mutable()
+ .addTypeVariable(TypeVariableName("T").copy(reified = true))
+ .getter(
+ FunSpec.getterBuilder()
+ .addStatement("return %S", "")
+ .build(),
+ )
+ .setter(
+ FunSpec.setterBuilder()
+ .addModifiers(KModifier.INLINE)
+ .addParameter("value", String::class)
+ .addStatement("println()")
+ .build(),
+ )
+ .build()
+ }.hasMessageThat().isEqualTo(
+ "only type parameters of properties with inline getters and/or setters can be reified!",
+ )
+ }
+
+ @Test fun setterNotAllowedWhenPropertyIsNotMutable() {
+ assertThrows<IllegalArgumentException> {
+ PropertySpec.builder("property", String::class.asTypeName())
+ .setter(
+ FunSpec.setterBuilder()
+ .addModifiers(KModifier.INLINE)
+ .addParameter("value", String::class)
+ .addStatement("println()")
+ .build(),
+ )
+ .build()
+ }.hasMessageThat().isEqualTo("only a mutable property can have a setter")
+ }
+
+ // https://github.com/square/kotlinpoet/issues/462
+ @Test fun codeBlockInitializer() {
+ val param = ParameterSpec.builder("arg", ANY).build()
+ val initializer = CodeBlock.builder()
+ .beginControlFlow("{ %L ->", param)
+ .addStatement("println(\"arg=\$%N\")", param)
+ .endControlFlow()
+ .build()
+ val lambdaTypeName = ClassName.bestGuess("com.example.SomeTypeAlias")
+ val property = PropertySpec.builder("property", lambdaTypeName)
+ .initializer(initializer)
+ .build()
+ assertThat(property.toString()).isEqualTo(
+ """
+ |val `property`: com.example.SomeTypeAlias = { arg: kotlin.Any ->
+ | println("arg=${'$'}arg")
+ |}
+ |
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun doublePropertyInitialization() {
+ val codeBlockInitializer = PropertySpec.builder("listA", String::class)
+ .initializer(CodeBlock.builder().add("foo").build())
+ .initializer(CodeBlock.builder().add("bar").build())
+ .build()
+
+ assertThat(CodeBlock.of("bar")).isEqualTo(codeBlockInitializer.initializer)
+
+ val formatInitializer = PropertySpec.builder("listA", String::class)
+ .initializer("foo")
+ .initializer("bar")
+ .build()
+
+ assertThat(CodeBlock.of("bar")).isEqualTo(formatInitializer.initializer)
+ }
+
+ @Test fun propertyKdocWithoutLinebreak() {
+ val property = PropertySpec.builder("topping", String::class)
+ .addKdoc("The topping you want on your pizza")
+ .build()
+ assertThat(property.toString()).isEqualTo(
+ """
+ |/**
+ | * The topping you want on your pizza
+ | */
+ |val topping: kotlin.String
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun propertyKdocWithLinebreak() {
+ val property = PropertySpec.builder("topping", String::class)
+ .addKdoc("The topping you want on your pizza\n")
+ .build()
+ assertThat(property.toString()).isEqualTo(
+ """
+ |/**
+ | * The topping you want on your pizza
+ | */
+ |val topping: kotlin.String
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun getterKdoc() {
+ val property = PropertySpec.builder("amount", Int::class)
+ .initializer("4")
+ .getter(
+ FunSpec.getterBuilder()
+ .addKdoc("Simple multiplier")
+ .addStatement("return %L * 5", "field")
+ .build(),
+ )
+ .build()
+
+ assertThat(property.toString()).isEqualTo(
+ """
+ |val amount: kotlin.Int = 4
+ | /**
+ | * Simple multiplier
+ | */
+ | get() = field * 5
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun constProperty() {
+ val text = "This is a long string with a newline\nin the middle."
+ val spec = FileSpec.builder("testsrc", "Test")
+ .addProperty(
+ PropertySpec.builder("FOO", String::class, KModifier.CONST)
+ .initializer("%S", text)
+ .build(),
+ )
+ .build()
+ assertThat(spec.toString()).isEqualTo(
+ """
+ |package testsrc
+ |
+ |import kotlin.String
+ |
+ |public const val FOO: String = "This is a long string with a newline\nin the middle."
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun annotatedLambdaType() {
+ val annotation = AnnotationSpec.builder(ClassName("com.squareup.tacos", "Annotation")).build()
+ val type = LambdaTypeName.get(returnType = UNIT).copy(annotations = listOf(annotation))
+ val spec = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addProperty(PropertySpec.builder("foo", type).build())
+ .build()
+ assertThat(spec.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public val foo: @Annotation () -> Unit
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/1002
+ @Test fun visibilityOmittedOnAccessors() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addProperty(
+ PropertySpec.builder("foo", String::class, PRIVATE)
+ .mutable()
+ .getter(
+ FunSpec.getterBuilder()
+ .addStatement("return %S", "foo")
+ .build(),
+ )
+ .setter(
+ FunSpec.setterBuilder()
+ .addParameter("foo", String::class)
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ //language=kotlin
+ """
+ package com.squareup.tacos
+
+ import kotlin.String
+
+ private var foo: String
+ get() = "foo"
+ set(foo) {
+ }
+
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun varWithContextReceiverWithoutCustomAccessors() {
+ val mutablePropertySpecBuilder = {
+ PropertySpec.builder("foo", STRING)
+ .mutable()
+ .contextReceivers(INT)
+ }
+
+ assertThrows<IllegalArgumentException> {
+ mutablePropertySpecBuilder()
+ .getter(
+ FunSpec.getterBuilder()
+ .build(),
+ )
+ .build()
+ }.hasMessageThat()
+ .isEqualTo("mutable properties with context receivers require a $SETTER")
+
+ assertThrows<IllegalArgumentException> {
+ mutablePropertySpecBuilder()
+ .setter(
+ FunSpec.setterBuilder()
+ .build(),
+ )
+ .build()
+ }.hasMessageThat()
+ .isEqualTo("properties with context receivers require a $GETTER")
+ }
+
+ @Test fun valWithContextReceiverWithoutGetter() {
+ assertThrows<IllegalArgumentException> {
+ PropertySpec.builder("foo", STRING)
+ .mutable(false)
+ .contextReceivers(INT)
+ .build()
+ }.hasMessageThat()
+ .isEqualTo("properties with context receivers require a $GETTER")
+ }
+
+ @Test fun varWithContextReceiver() {
+ val propertySpec = PropertySpec.builder("foo", INT)
+ .mutable()
+ .contextReceivers(STRING)
+ .getter(
+ FunSpec.getterBuilder()
+ .addStatement("return \"\"")
+ .build(),
+ )
+ .setter(
+ FunSpec.setterBuilder()
+ .addParameter(
+ ParameterSpec.builder("value", STRING)
+ .build(),
+ )
+ .addStatement("")
+ .build(),
+ )
+ .build()
+
+ assertThat(propertySpec.toString()).isEqualTo(
+ """
+ |context(kotlin.String)
+ |var foo: kotlin.Int
+ | get() = ""
+ | set(`value`) {
+ |
+ | }
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun valWithContextReceiver() {
+ val propertySpec = PropertySpec.builder("foo", INT)
+ .mutable(false)
+ .contextReceivers(STRING)
+ .getter(
+ FunSpec.getterBuilder()
+ .addStatement("return length")
+ .build(),
+ )
+ .build()
+
+ assertThat(propertySpec.toString()).isEqualTo(
+ """
+ |context(kotlin.String)
+ |val foo: kotlin.Int
+ | get() = length
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @OptIn(DelicateKotlinPoetApi::class)
+ @Test
+ fun annotatedValWithContextReceiver() {
+ val propertySpec = PropertySpec.builder("foo", INT)
+ .mutable(false)
+ .addAnnotation(AnnotationSpec.get(TestAnnotation()))
+ .contextReceivers(STRING)
+ .getter(
+ FunSpec.getterBuilder()
+ .addStatement("return length")
+ .build(),
+ )
+ .build()
+
+ assertThat(propertySpec.toString()).isEqualTo(
+ """
+ |context(kotlin.String)
+ |@com.squareup.kotlinpoet.PropertySpecTest.TestAnnotation
+ |val foo: kotlin.Int
+ | get() = length
+ |
+ """.trimMargin(),
+ )
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/StringsTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/StringsTest.kt
new file mode 100644
index 00000000..49bec351
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/StringsTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2018 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class StringsTest {
+ @Test fun singleLineStringWithDollarSymbols() {
+ val stringWithTemplate = "$" + "annoyingUser" + " is annoying."
+ val funSpec = FunSpec.builder("getString")
+ .addStatement("return %S", stringWithTemplate)
+ .build()
+ assertThat(funSpec.toString())
+ .isEqualTo("public fun getString() = \"\${\'\$\'}annoyingUser is annoying.\"\n")
+ }
+
+ @Test fun multilineStringWithDollarSymbols() {
+ val stringWithTemplate = "Some string\n" + "$" + "annoyingUser" + " is annoying."
+ val funSpec = FunSpec.builder("getString")
+ .addStatement("return %S", stringWithTemplate)
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ "public fun getString() = \"\"\"\n" +
+ "|Some string\n" +
+ "|\${\'\$\'}annoyingUser is annoying.\n" +
+ "\"\"\".trimMargin()\n",
+ )
+ }
+
+ @Test fun singleLineStringTemplate() {
+ val stringWithTemplate = "$" + "annoyingUser" + " is annoying."
+ val funSpec = FunSpec.builder("getString")
+ .addStatement("return %P", stringWithTemplate)
+ .build()
+ assertThat(funSpec.toString())
+ .isEqualTo("public fun getString() = \"\"\"\$annoyingUser is annoying.\"\"\"\n")
+ }
+
+ @Test fun multilineStringTemplate() {
+ val stringWithTemplate = "Some string\n" + "$" + "annoyingUser" + " is annoying."
+ val funSpec = FunSpec.builder("getString")
+ .addStatement("return %P", stringWithTemplate)
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ "public fun getString() = \"\"\"\n" +
+ "|Some string\n" +
+ "|\$annoyingUser is annoying.\n" +
+ "\"\"\".trimMargin()\n",
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/572
+ @Test fun templateStringWithStringLiteralReference() {
+ val string = "SELECT * FROM socialFeedItem WHERE message IS NOT NULL AND userId \${ if (userId == null) \"IS\" else \"=\" } ?1 ORDER BY datetime(creation_time) DESC"
+ val funSpec = FunSpec.builder("getString")
+ .addStatement("return %P", string)
+ .build()
+ assertThat(funSpec.toString())
+ .isEqualTo("public fun getString() = \"\"\"SELECT * FROM socialFeedItem WHERE message IS NOT NULL AND userId \${ if (userId == null) \"IS\" else \"=\" } ?1 ORDER BY datetime(creation_time) DESC\"\"\"\n")
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TaggableTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TaggableTest.kt
new file mode 100644
index 00000000..504798b6
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TaggableTest.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.KModifier.CROSSINLINE
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class TaggableTest(val builder: Taggable.Builder<*>) {
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun data() = arrayOf(
+ AnnotationSpec.builder(JvmStatic::class),
+ FileSpec.builder("test", "Test"),
+ FunSpec.builder("test"),
+ ParameterSpec.builder("test", String::class.asClassName()),
+ PropertySpec.builder("test", String::class.asClassName()),
+ TypeAliasSpec.builder("Test", String::class.asClassName()),
+ TypeSpec.classBuilder("Test"),
+ )
+ }
+
+ @Before fun setUp() {
+ builder.tags.clear()
+ }
+
+ @Test fun builderShouldMakeDefensiveCopy() {
+ builder.tag(String::class, "test")
+ val taggable = builder.buildTaggable()
+ builder.tags.remove(String::class)
+ assertThat(taggable.tag<String>()).isEqualTo("test")
+ }
+
+ @Test fun missingShouldBeNull() {
+ val taggable = builder.buildTaggable()
+ assertThat(taggable.tag<Int>()).isNull()
+ }
+
+ @Test fun kclassParamFlow() {
+ builder.tag(String::class, "test")
+ val taggable = builder.buildTaggable()
+ assertThat(taggable.tag(String::class)).isEqualTo("test")
+ }
+
+ @Test fun javaClassParamFlow() {
+ builder.tag(String::class.java, "test")
+ val taggable = builder.buildTaggable()
+ assertThat(taggable.tag(String::class.java)).isEqualTo("test")
+ }
+
+ @Test fun kclassInJavaClassOut() {
+ builder.tag(String::class, "test")
+ val taggable = builder.buildTaggable()
+ assertThat(taggable.tag(String::class.java)).isEqualTo("test")
+ }
+
+ @Test fun javaClassInkClassOut() {
+ builder.tag(String::class.java, "test")
+ val taggable = builder.buildTaggable()
+ assertThat(taggable.tag(String::class)).isEqualTo("test")
+ }
+
+ private fun Taggable.Builder<*>.buildTaggable(): Taggable {
+ // Apply blocks test inline builder tag functions don't break the chain. Result is discarded
+ return when (this) {
+ is AnnotationSpec.Builder -> build().apply {
+ toBuilder()
+ .tag(1)
+ .addMember(CodeBlock.of(""))
+ .build()
+ }
+ is FileSpec.Builder -> build().apply {
+ toBuilder()
+ .tag(1)
+ .addFileComment("Test")
+ .build()
+ }
+ is FunSpec.Builder -> build().apply {
+ toBuilder()
+ .tag(1)
+ .returns(String::class)
+ .build()
+ }
+ is ParameterSpec.Builder -> build().apply {
+ toBuilder()
+ .tag(1)
+ .addModifiers(CROSSINLINE)
+ .build()
+ }
+ is PropertySpec.Builder -> build().apply {
+ toBuilder()
+ .tag(1)
+ .initializer(CodeBlock.of(""))
+ .build()
+ }
+ is TypeAliasSpec.Builder -> build().apply {
+ toBuilder()
+ .tag(1)
+ .addKdoc(CodeBlock.of(""))
+ .build()
+ }
+ is TypeSpec.Builder -> build().apply {
+ toBuilder()
+ .tag(1)
+ .addKdoc(CodeBlock.of(""))
+ .build()
+ }
+ else -> TODO("Unsupported type ${this::class.simpleName}")
+ }
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TestFiler.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TestFiler.kt
new file mode 100644
index 00000000..bb24d564
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TestFiler.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import java.io.OutputStream
+import java.nio.file.FileSystem
+import java.nio.file.Files
+import java.nio.file.Path
+import javax.annotation.processing.Filer
+import javax.lang.model.element.AnnotationMirror
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.ElementVisitor
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.Name
+import javax.lang.model.type.TypeMirror
+import javax.tools.FileObject
+import javax.tools.JavaFileManager
+import javax.tools.JavaFileObject
+import javax.tools.SimpleJavaFileObject
+
+internal class TestFiler(
+ fileSystem: FileSystem,
+ private val fileSystemRoot: Path,
+) : Filer {
+
+ internal inner class Source(private val path: Path) :
+ SimpleJavaFileObject(path.toUri(), JavaFileObject.Kind.SOURCE) {
+ override fun openOutputStream(): OutputStream {
+ val parent = path.parent
+ if (!Files.exists(parent)) fileSystemProvider.createDirectory(parent)
+ return fileSystemProvider.newOutputStream(path)
+ }
+ }
+
+ private val separator = fileSystem.separator
+ private val fileSystemProvider = fileSystem.provider()
+ private val originatingElementsMap = mutableMapOf<Path, List<Element>>()
+
+ fun getOriginatingElements(path: Path) = originatingElementsMap[path] ?: throw NullPointerException("Could not find $path")
+
+ override fun createSourceFile(
+ name: CharSequence,
+ vararg originatingElements: Element,
+ ): JavaFileObject {
+ val relative = name.toString().replace(".", separator) + ".kt" // Assumes well-formed.
+ val path = fileSystemRoot.resolve(relative)
+ originatingElementsMap[path] = originatingElements.toList()
+ return Source(path)
+ }
+
+ override fun createClassFile(name: CharSequence, vararg originatingElements: Element) =
+ throw UnsupportedOperationException("Not implemented.")
+
+ override fun createResource(
+ location: JavaFileManager.Location,
+ pkg: CharSequence,
+ relativeName: CharSequence,
+ vararg originatingElements: Element,
+ ): FileObject {
+ val relative = pkg.toString().replace(".", separator) + separator + relativeName
+ val path = fileSystemRoot.resolve(relative)
+ originatingElementsMap[path] = originatingElements.toList()
+ return Source(path)
+ }
+
+ override fun getResource(
+ location: JavaFileManager.Location,
+ pkg: CharSequence,
+ relativeName: CharSequence,
+ ) = throw UnsupportedOperationException("Not implemented.")
+}
+
+internal class FakeElement : Element {
+
+ override fun getModifiers(): MutableSet<Modifier> {
+ TODO()
+ }
+
+ override fun getSimpleName(): Name {
+ TODO()
+ }
+
+ override fun getKind(): ElementKind {
+ TODO()
+ }
+
+ override fun asType(): TypeMirror {
+ TODO()
+ }
+
+ override fun getEnclosingElement(): Element {
+ TODO()
+ }
+
+ override fun <R : Any?, P : Any?> accept(v: ElementVisitor<R, P>?, p: P): R {
+ TODO()
+ }
+
+ override fun <A : Annotation?> getAnnotationsByType(annotationType: Class<A>?): Array<A> {
+ TODO()
+ }
+
+ override fun <A : Annotation?> getAnnotation(annotationType: Class<A>?): A {
+ TODO()
+ }
+
+ override fun getAnnotationMirrors(): MutableList<out AnnotationMirror> {
+ TODO()
+ }
+
+ override fun getEnclosedElements(): MutableList<out Element> {
+ TODO()
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeAliasSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeAliasSpecTest.kt
new file mode 100644
index 00000000..f5c7eb22
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeAliasSpecTest.kt
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2017 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import java.util.concurrent.atomic.AtomicReference
+import kotlin.annotation.AnnotationRetention.RUNTIME
+import kotlin.annotation.AnnotationTarget.TYPEALIAS
+import kotlin.test.Test
+
+class TypeAliasSpecTest {
+
+ @Test fun simpleTypeAlias() {
+ val typeAliasSpec = TypeAliasSpec
+ .builder("Word", String::class)
+ .build()
+
+ assertThat(typeAliasSpec.toString()).isEqualTo(
+ """
+ |public typealias Word = kotlin.String
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun typeVariable() {
+ val v = TypeVariableName("V")
+ val typeAliasSpec = TypeAliasSpec
+ .builder("Word", List::class.asClassName().parameterizedBy(v))
+ .addTypeVariable(v)
+ .build()
+
+ assertThat(typeAliasSpec.toString()).isEqualTo(
+ """
+ |public typealias Word<V> = kotlin.collections.List<V>
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun publicVisibility() {
+ val typeAliasSpec = TypeAliasSpec
+ .builder("Word", String::class)
+ .addModifiers(KModifier.PUBLIC)
+ .build()
+
+ assertThat(typeAliasSpec.toString()).isEqualTo(
+ """
+ |public typealias Word = kotlin.String
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun internalVisibility() {
+ val typeAliasSpec = TypeAliasSpec
+ .builder("Word", String::class)
+ .addModifiers(KModifier.INTERNAL)
+ .build()
+
+ assertThat(typeAliasSpec.toString()).isEqualTo(
+ """
+ |internal typealias Word = kotlin.String
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun privateVisibility() {
+ val typeAliasSpec = TypeAliasSpec
+ .builder("Word", String::class)
+ .addModifiers(KModifier.PRIVATE)
+ .build()
+
+ assertThat(typeAliasSpec.toString()).isEqualTo(
+ """
+ |private typealias Word = kotlin.String
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun implTypeAlias() {
+ val typeName = AtomicReference::class.asClassName().parameterizedBy(TypeVariableName("V"))
+ val typeAliasSpec = TypeAliasSpec
+ .builder("AtomicRef", typeName)
+ .addModifiers(KModifier.ACTUAL)
+ .addTypeVariable(TypeVariableName("V"))
+ .build()
+
+ assertThat(typeAliasSpec.toString()).isEqualTo(
+ """
+ |public actual typealias AtomicRef<V> = java.util.concurrent.atomic.AtomicReference<V>
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun kdoc() {
+ val typeAliasSpec = TypeAliasSpec
+ .builder("Word", String::class)
+ .addKdoc("Word is just a type alias for [String](%T).\n", String::class)
+ .build()
+
+ assertThat(typeAliasSpec.toString()).isEqualTo(
+ """
+ |/**
+ | * Word is just a type alias for [String](kotlin.String).
+ | */
+ |public typealias Word = kotlin.String
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun annotations() {
+ val typeAliasSpec = TypeAliasSpec
+ .builder("Word", String::class)
+ .addAnnotation(
+ AnnotationSpec.builder(TypeAliasAnnotation::class.asClassName())
+ .addMember("value = %S", "words!")
+ .build(),
+ )
+ .build()
+
+ assertThat(typeAliasSpec.toString()).isEqualTo(
+ """
+ |@com.squareup.kotlinpoet.TypeAliasAnnotation(value = "words!")
+ |public typealias Word = kotlin.String
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun kdocWithoutNewLine() {
+ val typeAliasSpec = TypeAliasSpec
+ .builder("Word", String::class)
+ .addKdoc("Word is just a type alias for [String](%T).", String::class)
+ .build()
+
+ assertThat(typeAliasSpec.toString()).isEqualTo(
+ """
+ |/**
+ | * Word is just a type alias for [String](kotlin.String).
+ | */
+ |public typealias Word = kotlin.String
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun equalsAndHashCode() {
+ val a = TypeAliasSpec.builder("Word", String::class).addModifiers(KModifier.PUBLIC).build()
+ val b = TypeAliasSpec.builder("Word", String::class).addModifiers(KModifier.PUBLIC).build()
+
+ assertThat(a == b).isTrue()
+ assertThat(a.hashCode()).isEqualTo(b.hashCode())
+ }
+
+ @Test fun generalBuilderEqualityTest() {
+ val typeParam = TypeVariableName("V")
+ val typeAliasSpec = TypeAliasSpec
+ .builder("Bio", Pair::class.parameterizedBy(String::class, String::class))
+ .addKdoc("First nand Last Name.\n")
+ .addModifiers(KModifier.PUBLIC)
+ .addTypeVariable(typeParam)
+ .build()
+ assertThat(typeAliasSpec.toBuilder().build()).isEqualTo(typeAliasSpec)
+ }
+
+ @Test fun modifyModifiers() {
+ val builder = TypeAliasSpec
+ .builder("Word", String::class)
+ .addModifiers(KModifier.PRIVATE)
+
+ builder.modifiers.clear()
+ builder.modifiers.add(KModifier.INTERNAL)
+
+ assertThat(builder.build().modifiers).containsExactly(KModifier.INTERNAL)
+ }
+
+ @Test fun modifyTypeVariableNames() {
+ val builder = TypeAliasSpec
+ .builder("Word", String::class)
+ .addTypeVariable(TypeVariableName("V"))
+
+ val tVar = TypeVariableName("T")
+ builder.typeVariables.clear()
+ builder.typeVariables.add(tVar)
+
+ assertThat(builder.build().typeVariables).containsExactly(tVar)
+ }
+
+ @Test fun modifyAnnotations() {
+ val builder = TypeAliasSpec
+ .builder("Word", String::class)
+ .addAnnotation(
+ AnnotationSpec.builder(TypeAliasAnnotation::class.asClassName())
+ .addMember("value = %S", "value1")
+ .build(),
+ )
+
+ val javaWord = AnnotationSpec.builder(TypeAliasAnnotation::class.asClassName())
+ .addMember("value = %S", "value2")
+ .build()
+ builder.annotations.clear()
+ builder.annotations.add(javaWord)
+
+ assertThat(builder.build().annotations).containsExactly(javaWord)
+ }
+
+ @Test fun nameEscaping() {
+ val typeAlias = TypeAliasSpec.builder("fun", String::class).build()
+ assertThat(typeAlias.toString()).isEqualTo(
+ """
+ |public typealias `fun` = kotlin.String
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun annotatedLambdaType() {
+ val annotation = AnnotationSpec.builder(ClassName("", "Annotation")).build()
+ val type = LambdaTypeName.get(returnType = UNIT).copy(annotations = listOf(annotation))
+ val spec = TypeAliasSpec.builder("lambda", type).build()
+ assertThat(spec.toString()).isEqualTo(
+ """
+ |public typealias lambda = @Annotation () -> kotlin.Unit
+ |
+ """.trimMargin(),
+ )
+ }
+}
+
+@Retention(RUNTIME)
+@Target(TYPEALIAS)
+annotation class TypeAliasAnnotation(val value: String)
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeNameKotlinTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeNameKotlinTest.kt
new file mode 100644
index 00000000..3c16242a
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeNameKotlinTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class TypeNameKotlinTest {
+
+ @Test
+ fun typeNameOf_simple() {
+ val type = typeNameOf<TypeNameKotlinTest>()
+ assertThat(type.toString()).isEqualTo("com.squareup.kotlinpoet.TypeNameKotlinTest")
+ }
+
+ @Test
+ fun typeNameOf_simple_intrinsic() {
+ val type = typeNameOf<String>()
+ assertThat(type.toString()).isEqualTo("kotlin.String")
+ }
+
+ @Test
+ fun typeNameOf_array_primitive() {
+ val type = typeNameOf<IntArray>()
+ assertThat(type.toString()).isEqualTo("kotlin.IntArray")
+ }
+
+ @Test
+ fun typeNameOf_array_parameterized() {
+ val type = typeNameOf<Array<String>>()
+ assertThat(type.toString()).isEqualTo("kotlin.Array<kotlin.String>")
+ }
+
+ @Test
+ fun typeNameOf_nullable() {
+ val type = typeNameOf<String?>()
+ assertThat(type.toString()).isEqualTo("kotlin.String?")
+ }
+
+ @Test
+ fun typeNameOf_generic() {
+ val type = typeNameOf<List<String>>()
+ assertThat(type.toString()).isEqualTo("kotlin.collections.List<kotlin.String>")
+ }
+
+ @Test
+ fun typeNameOf_generic_wildcard_out() {
+ val type = typeNameOf<GenericType<out String>>()
+ assertThat(type.toString()).isEqualTo("com.squareup.kotlinpoet.TypeNameKotlinTest.GenericType<out kotlin.String>")
+ }
+
+ @Test
+ fun typeNameOf_generic_wildcard_in() {
+ val type = typeNameOf<GenericType<in String>>()
+ assertThat(type.toString()).isEqualTo("com.squareup.kotlinpoet.TypeNameKotlinTest.GenericType<in kotlin.String>")
+ }
+
+ @Test
+ fun typeNameOf_complex() {
+ val type = typeNameOf<Map<String, List<Map<*, GenericType<in Set<Array<GenericType<out String>?>>>>>>>()
+ assertThat(type.toString()).isEqualTo("kotlin.collections.Map<kotlin.String, kotlin.collections.List<kotlin.collections.Map<*, com.squareup.kotlinpoet.TypeNameKotlinTest.GenericType<in kotlin.collections.Set<kotlin.Array<com.squareup.kotlinpoet.TypeNameKotlinTest.GenericType<out kotlin.String>?>>>>>>")
+ }
+
+ @Suppress("unused")
+ class GenericType<T>
+
+ @Test
+ fun tag() {
+ val type = typeNameOf<String>().copy(tags = mapOf(String::class to "Test"))
+ assertThat(type.tag<String>()).isEqualTo("Test")
+ }
+
+ @Test
+ fun existingTagsShouldBePreserved() {
+ val type = typeNameOf<String>().copy(tags = mapOf(String::class to "Test"))
+ val copied = type.copy(nullable = true)
+ assertThat(copied.tag<String>()).isEqualTo("Test")
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeNameTest.java b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeNameTest.java
new file mode 100644
index 00000000..70019f9d
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeNameTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import org.junit.Test;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+
+public class TypeNameTest {
+
+ protected <E extends Enum<E>> E generic(E[] values) {
+ return values[0];
+ }
+
+ protected static class TestGeneric<T> {
+ class Inner {}
+
+ class InnerGeneric<T2> {}
+
+ static class NestedNonGeneric {}
+ }
+
+ protected static TestGeneric<String>.Inner testGenericStringInner() {
+ return null;
+ }
+
+ protected static TestGeneric<Integer>.Inner testGenericIntInner() {
+ return null;
+ }
+
+ protected static TestGeneric<Short>.InnerGeneric<Long> testGenericInnerLong() {
+ return null;
+ }
+
+ protected static TestGeneric<Short>.InnerGeneric<Integer> testGenericInnerInt() {
+ return null;
+ }
+
+ protected static TestGeneric.NestedNonGeneric testNestedNonGeneric() {
+ return null;
+ }
+
+ @Test public void genericType() throws Exception {
+ Method recursiveEnum = getClass().getDeclaredMethod("generic", Enum[].class);
+ TypeNames.get(recursiveEnum.getReturnType());
+ TypeNames.get(recursiveEnum.getGenericReturnType());
+ TypeName genericTypeName = TypeNames.get(recursiveEnum.getParameterTypes()[0]);
+ TypeNames.get(recursiveEnum.getGenericParameterTypes()[0]);
+
+ // Make sure the generic argument is present
+ assertThat(genericTypeName.toString()).contains("Enum");
+ }
+
+ @Test public void innerClassInGenericType() throws Exception {
+ Method genericStringInner = getClass().getDeclaredMethod("testGenericStringInner");
+ TypeNames.get(genericStringInner.getReturnType());
+ TypeName genericTypeName = TypeNames.get(genericStringInner.getGenericReturnType());
+ assertNotEquals(TypeNames.get(genericStringInner.getGenericReturnType()),
+ TypeNames.get(getClass().getDeclaredMethod("testGenericIntInner").getGenericReturnType()));
+
+ // Make sure the generic argument is present
+ assertThat(genericTypeName.toString()).isEqualTo(
+ TestGeneric.class.getCanonicalName() + "<java.lang.String>.Inner");
+ }
+
+ @Test public void innerGenericInGenericType() throws Exception {
+ Method genericStringInner = getClass().getDeclaredMethod("testGenericInnerLong");
+ TypeNames.get(genericStringInner.getReturnType());
+ TypeName genericTypeName = TypeNames.get(genericStringInner.getGenericReturnType());
+ assertNotEquals(TypeNames.get(genericStringInner.getGenericReturnType()),
+ TypeNames.get(getClass().getDeclaredMethod("testGenericInnerInt").getGenericReturnType()));
+
+ // Make sure the generic argument is present
+ assertThat(genericTypeName.toString()).isEqualTo(
+ TestGeneric.class.getCanonicalName() + "<java.lang.Short>.InnerGeneric<java.lang.Long>");
+ }
+
+ @Test public void innerStaticInGenericType() throws Exception {
+ Method staticInGeneric = getClass().getDeclaredMethod("testNestedNonGeneric");
+ TypeNames.get(staticInGeneric.getReturnType());
+ TypeName typeName = TypeNames.get(staticInGeneric.getGenericReturnType());
+
+ // Make sure there are no generic arguments
+ assertThat(typeName.toString()).isEqualTo(
+ TestGeneric.class.getCanonicalName() + ".NestedNonGeneric");
+ }
+
+ @Test public void equalsAndHashCodePrimitive() {
+ assertEqualsHashCodeAndToString(TypeNames.BOOLEAN, TypeNames.BOOLEAN);
+ assertEqualsHashCodeAndToString(TypeNames.BYTE, TypeNames.BYTE);
+ assertEqualsHashCodeAndToString(TypeNames.CHAR, TypeNames.CHAR);
+ assertEqualsHashCodeAndToString(TypeNames.DOUBLE, TypeNames.DOUBLE);
+ assertEqualsHashCodeAndToString(TypeNames.FLOAT, TypeNames.FLOAT);
+ assertEqualsHashCodeAndToString(TypeNames.INT, TypeNames.INT);
+ assertEqualsHashCodeAndToString(TypeNames.LONG, TypeNames.LONG);
+ assertEqualsHashCodeAndToString(TypeNames.SHORT, TypeNames.SHORT);
+ assertEqualsHashCodeAndToString(TypeNames.UNIT, TypeNames.UNIT);
+ }
+
+ @Test public void equalsAndHashCodeClassName() {
+ assertEqualsHashCodeAndToString(ClassNames.get(Object.class), ClassNames.get(Object.class));
+ assertEqualsHashCodeAndToString(TypeNames.get(Object.class), ClassNames.get(Object.class));
+ assertEqualsHashCodeAndToString(ClassName.bestGuess("java.lang.Object"),
+ ClassNames.get(Object.class));
+ }
+
+ @Test public void equalsAndHashCodeParameterizedTypeName() {
+ assertEqualsHashCodeAndToString(ParameterizedTypeName.get(List.class, Object.class),
+ ParameterizedTypeName.get(List.class, Object.class));
+ assertEqualsHashCodeAndToString(ParameterizedTypeName.get(Set.class, UUID.class),
+ ParameterizedTypeName.get(Set.class, UUID.class));
+ assertNotEquals(ClassNames.get(List.class), ParameterizedTypeName.get(List.class,
+ String.class));
+ }
+
+ @Test public void equalsAndHashCodeTypeVariableName() {
+ assertEqualsHashCodeAndToString(TypeVariableName.get("A"),
+ TypeVariableName.get("A"));
+ TypeVariableName typeVar1 = TypeVariableName.get("T", Comparator.class, Serializable.class);
+ TypeVariableName typeVar2 = TypeVariableName.get("T", Comparator.class, Serializable.class);
+ assertEqualsHashCodeAndToString(typeVar1, typeVar2);
+ }
+
+ @Test public void equalsAndHashCodeWildcardTypeName() {
+ assertEqualsHashCodeAndToString(WildcardTypeName.producerOf(Object.class),
+ WildcardTypeName.producerOf(Object.class));
+ assertEqualsHashCodeAndToString(WildcardTypeName.producerOf(Serializable.class),
+ WildcardTypeName.producerOf(Serializable.class));
+ assertEqualsHashCodeAndToString(WildcardTypeName.consumerOf(String.class),
+ WildcardTypeName.consumerOf(String.class));
+ }
+
+ private void assertEqualsHashCodeAndToString(TypeName a, TypeName b) {
+ assertEquals(a.toString(), b.toString());
+ assertThat(a.equals(b)).isTrue();
+ assertThat(a.hashCode()).isEqualTo(b.hashCode());
+ assertFalse(a.equals(null));
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeSpecTest.kt
new file mode 100644
index 00000000..cf6c3e29
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeSpecTest.kt
@@ -0,0 +1,5364 @@
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.collect.ImmutableMap
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.compile.CompilationRule
+import com.squareup.kotlinpoet.KModifier.ABSTRACT
+import com.squareup.kotlinpoet.KModifier.DATA
+import com.squareup.kotlinpoet.KModifier.IN
+import com.squareup.kotlinpoet.KModifier.INNER
+import com.squareup.kotlinpoet.KModifier.INTERNAL
+import com.squareup.kotlinpoet.KModifier.PRIVATE
+import com.squareup.kotlinpoet.KModifier.PUBLIC
+import com.squareup.kotlinpoet.KModifier.VARARG
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.jvm.throws
+import java.io.IOException
+import java.io.Serializable
+import java.lang.Deprecated
+import java.math.BigDecimal
+import java.util.AbstractSet
+import java.util.Collections
+import java.util.Comparator
+import java.util.EventListener
+import java.util.Locale
+import java.util.Random
+import java.util.concurrent.Callable
+import java.util.function.Consumer
+import java.util.logging.Logger
+import javax.lang.model.element.TypeElement
+import kotlin.reflect.KClass
+import kotlin.reflect.KFunction
+import kotlin.test.Ignore
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.fail
+import org.junit.Rule
+
+@OptIn(ExperimentalKotlinPoetApi::class)
+class TypeSpecTest {
+ private val tacosPackage = "com.squareup.tacos"
+
+ @Rule @JvmField
+ val compilation = CompilationRule()
+
+ private fun getElement(`class`: Class<*>): TypeElement {
+ return compilation.elements.getTypeElement(`class`.canonicalName)
+ }
+
+ private fun getElement(`class`: KClass<*>): TypeElement {
+ return getElement(`class`.java)
+ }
+
+ @Test fun basic() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("toString")
+ .addModifiers(KModifier.PUBLIC, KModifier.FINAL, KModifier.OVERRIDE)
+ .returns(String::class)
+ .addStatement("return %S", "taco")
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public class Taco {
+ | public final override fun toString(): String = "taco"
+ |}
+ |
+ """.trimMargin(),
+ )
+ assertEquals(1906837485, taco.hashCode().toLong()) // Update expected number if source changes.
+ }
+
+ @Test fun interestingTypes() {
+ val listOfAny = List::class.asClassName().parameterizedBy(STAR)
+ val listOfExtends = List::class.asClassName()
+ .parameterizedBy(WildcardTypeName.producerOf(Serializable::class))
+ val listOfSuper = List::class.asClassName()
+ .parameterizedBy(WildcardTypeName.consumerOf(String::class))
+ val taco = TypeSpec.classBuilder("Taco")
+ .addProperty("star", listOfAny)
+ .addProperty("outSerializable", listOfExtends)
+ .addProperty("inString", listOfSuper)
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.io.Serializable
+ |import kotlin.String
+ |import kotlin.collections.List
+ |
+ |public class Taco {
+ | public val star: List<*>
+ |
+ | public val outSerializable: List<out Serializable>
+ |
+ | public val inString: List<in String>
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun anonymousInnerClass() {
+ val foo = ClassName(tacosPackage, "Foo")
+ val bar = ClassName(tacosPackage, "Bar")
+ val thingThang = ClassName(tacosPackage, "Thing", "Thang")
+ val thingThangOfFooBar = thingThang.parameterizedBy(foo, bar)
+ val thung = ClassName(tacosPackage, "Thung")
+ val simpleThung = ClassName(tacosPackage, "SimpleThung")
+ val thungOfSuperBar = thung.parameterizedBy(WildcardTypeName.consumerOf(bar))
+ val thungOfSuperFoo = thung.parameterizedBy(WildcardTypeName.consumerOf(foo))
+ val simpleThungOfBar = simpleThung.parameterizedBy(bar)
+
+ val thungParameter = ParameterSpec.builder("thung", thungOfSuperFoo)
+ .build()
+ val aSimpleThung = TypeSpec.anonymousClassBuilder()
+ .superclass(simpleThungOfBar)
+ .addSuperclassConstructorParameter("%N", thungParameter)
+ .addFunction(
+ FunSpec.builder("doSomething")
+ .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+ .addParameter("bar", bar)
+ .addCode("/* code snippets */\n")
+ .build(),
+ )
+ .build()
+ val aThingThang = TypeSpec.anonymousClassBuilder()
+ .superclass(thingThangOfFooBar)
+ .addFunction(
+ FunSpec.builder("call")
+ .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+ .returns(thungOfSuperBar)
+ .addParameter(thungParameter)
+ .addStatement("return %L", aSimpleThung)
+ .build(),
+ )
+ .build()
+ val taco = TypeSpec.classBuilder("Taco")
+ .addProperty(
+ PropertySpec.builder("NAME", thingThangOfFooBar)
+ .initializer("%L", aThingThang)
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public class Taco {
+ | public val NAME: Thing.Thang<Foo, Bar> = object : Thing.Thang<Foo, Bar>() {
+ | public override fun call(thung: Thung<in Foo>): Thung<in Bar> = object : SimpleThung<Bar>(thung)
+ | {
+ | public override fun doSomething(bar: Bar): Unit {
+ | /* code snippets */
+ | }
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun anonymousClassWithSuperClassConstructorCall() {
+ val superclass = ArrayList::class.parameterizedBy(String::class)
+ val anonymousClass = TypeSpec.anonymousClassBuilder()
+ .addSuperclassConstructorParameter("%L", "4")
+ .superclass(superclass)
+ .build()
+ val taco = TypeSpec.classBuilder("Taco")
+ .addProperty(
+ PropertySpec.builder("names", superclass)
+ .initializer("%L", anonymousClass)
+ .build(),
+ ).build()
+
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.util.ArrayList
+ |import kotlin.String
+ |
+ |public class Taco {
+ | public val names: ArrayList<String> = object : ArrayList<String>(4) {
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/315
+ @Test fun anonymousClassWithMultipleSuperTypes() {
+ val superclass = ClassName("com.squareup.wire", "Message")
+ val anonymousClass = TypeSpec.anonymousClassBuilder()
+ .superclass(superclass)
+ .addSuperinterface(Runnable::class)
+ .addFunction(
+ FunSpec.builder("run")
+ .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+ .addCode("/* code snippets */\n")
+ .build(),
+ ).build()
+ val taco = TypeSpec.classBuilder("Taco")
+ .addProperty(
+ PropertySpec.builder("NAME", Runnable::class)
+ .initializer("%L", anonymousClass)
+ .build(),
+ ).build()
+
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.wire.Message
+ |import java.lang.Runnable
+ |import kotlin.Unit
+ |
+ |public class Taco {
+ | public val NAME: Runnable = object : Message(), Runnable {
+ | public override fun run(): Unit {
+ | /* code snippets */
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun anonymousClassWithoutSuperType() {
+ val anonymousClass = TypeSpec.anonymousClassBuilder().build()
+ val taco = TypeSpec.classBuilder("Taco")
+ .addProperty(
+ PropertySpec.builder("NAME", Any::class)
+ .initializer("%L", anonymousClass)
+ .build(),
+ ).build()
+
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Any
+ |
+ |public class Taco {
+ | public val NAME: Any = object {
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun annotatedParameters() {
+ val service = TypeSpec.classBuilder("Foo")
+ .addFunction(
+ FunSpec.constructorBuilder()
+ .addModifiers(KModifier.PUBLIC)
+ .addParameter("id", Long::class)
+ .addParameter(
+ ParameterSpec.builder("one", String::class)
+ .addAnnotation(ClassName(tacosPackage, "Ping"))
+ .build(),
+ )
+ .addParameter(
+ ParameterSpec.builder("two", String::class)
+ .addAnnotation(ClassName(tacosPackage, "Ping"))
+ .build(),
+ )
+ .addParameter(
+ ParameterSpec.builder("three", String::class)
+ .addAnnotation(
+ AnnotationSpec.builder(ClassName(tacosPackage, "Pong"))
+ .addMember("%S", "pong")
+ .build(),
+ )
+ .build(),
+ )
+ .addParameter(
+ ParameterSpec.builder("four", String::class)
+ .addAnnotation(ClassName(tacosPackage, "Ping"))
+ .build(),
+ )
+ .addCode("/* code snippets */\n")
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(service)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Long
+ |import kotlin.String
+ |
+ |public class Foo {
+ | public constructor(
+ | id: Long,
+ | @Ping one: String,
+ | @Ping two: String,
+ | @Pong("pong") three: String,
+ | @Ping four: String,
+ | ) {
+ | /* code snippets */
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ /**
+ * We had a bug where annotations were preventing us from doing the right thing when resolving
+ * imports. https://github.com/square/javapoet/issues/422
+ */
+ @Test fun annotationsAndJavaLangTypes() {
+ val freeRange = ClassName("javax.annotation", "FreeRange")
+ val taco = TypeSpec.classBuilder("EthicalTaco")
+ .addProperty(
+ "meat",
+ String::class.asClassName()
+ .copy(annotations = listOf(AnnotationSpec.builder(freeRange).build())),
+ )
+ .build()
+
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import javax.`annotation`.FreeRange
+ |import kotlin.String
+ |
+ |public class EthicalTaco {
+ | public val meat: @FreeRange String
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun retrofitStyleInterface() {
+ val observable = ClassName(tacosPackage, "Observable")
+ val fooBar = ClassName(tacosPackage, "FooBar")
+ val thing = ClassName(tacosPackage, "Thing")
+ val things = ClassName(tacosPackage, "Things")
+ val map = Map::class.asClassName()
+ val string = String::class.asClassName()
+ val headers = ClassName(tacosPackage, "Headers")
+ val post = ClassName(tacosPackage, "POST")
+ val body = ClassName(tacosPackage, "Body")
+ val queryMap = ClassName(tacosPackage, "QueryMap")
+ val header = ClassName(tacosPackage, "Header")
+ val service = TypeSpec.interfaceBuilder("Service")
+ .addFunction(
+ FunSpec.builder("fooBar")
+ .addModifiers(KModifier.PUBLIC, KModifier.ABSTRACT)
+ .addAnnotation(
+ AnnotationSpec.builder(headers)
+ .addMember("%S", "Accept: application/json")
+ .addMember("%S", "User-Agent: foobar")
+ .build(),
+ )
+ .addAnnotation(
+ AnnotationSpec.builder(post)
+ .addMember("%S", "/foo/bar")
+ .build(),
+ )
+ .returns(observable.parameterizedBy(fooBar))
+ .addParameter(
+ ParameterSpec.builder("things", things.parameterizedBy(thing))
+ .addAnnotation(body)
+ .build(),
+ )
+ .addParameter(
+ ParameterSpec.builder("query", map.parameterizedBy(string, string))
+ .addAnnotation(
+ AnnotationSpec.builder(queryMap)
+ .addMember("encodeValues = %L", "false")
+ .build(),
+ )
+ .build(),
+ )
+ .addParameter(
+ ParameterSpec.builder("authorization", string)
+ .addAnnotation(
+ AnnotationSpec.builder(header)
+ .addMember("%S", "Authorization")
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(service)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.collections.Map
+ |
+ |public interface Service {
+ | @Headers(
+ | "Accept: application/json",
+ | "User-Agent: foobar",
+ | )
+ | @POST("/foo/bar")
+ | public fun fooBar(
+ | @Body things: Things<Thing>,
+ | @QueryMap(encodeValues = false) query: Map<String, String>,
+ | @Header("Authorization") authorization: String,
+ | ): Observable<FooBar>
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun annotatedProperty() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addProperty(
+ PropertySpec.builder("thing", String::class, KModifier.PRIVATE)
+ .addAnnotation(
+ AnnotationSpec.builder(ClassName(tacosPackage, "JsonAdapter"))
+ .addMember("%T::class", ClassName(tacosPackage, "Foo"))
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public class Taco {
+ | @JsonAdapter(Foo::class)
+ | private val thing: String
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun annotatedPropertyUseSiteTarget() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addProperty(
+ PropertySpec.builder("thing", String::class, KModifier.PRIVATE)
+ .addAnnotation(
+ AnnotationSpec.builder(ClassName(tacosPackage, "JsonAdapter"))
+ .addMember("%T::class", ClassName(tacosPackage, "Foo"))
+ .useSiteTarget(AnnotationSpec.UseSiteTarget.FIELD)
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public class Taco {
+ | @field:JsonAdapter(Foo::class)
+ | private val thing: String
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun annotatedClass() {
+ val someType = ClassName(tacosPackage, "SomeType")
+ val taco = TypeSpec.classBuilder("Foo")
+ .addAnnotation(
+ AnnotationSpec.builder(ClassName(tacosPackage, "Something"))
+ .addMember("%T.%N", someType, "PROPERTY")
+ .addMember("%L", 12)
+ .addMember("%S", "goodbye")
+ .build(),
+ )
+ .addModifiers(KModifier.PUBLIC)
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |@Something(
+ | SomeType.PROPERTY,
+ | 12,
+ | "goodbye",
+ |)
+ |public class Foo
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun enumWithSubclassing() {
+ val roshambo = TypeSpec.enumBuilder("Roshambo")
+ .addModifiers(KModifier.PUBLIC)
+ .addEnumConstant(
+ "ROCK",
+ TypeSpec.anonymousClassBuilder()
+ .addKdoc("Avalanche!\n")
+ .build(),
+ )
+ .addEnumConstant(
+ "PAPER",
+ TypeSpec.anonymousClassBuilder()
+ .addSuperclassConstructorParameter("%S", "flat")
+ .addFunction(
+ FunSpec.builder("toString")
+ .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE, KModifier.OVERRIDE)
+ .returns(String::class)
+ .addCode("return %S\n", "paper airplane!")
+ .build(),
+ )
+ .build(),
+ )
+ .addEnumConstant(
+ "SCISSORS",
+ TypeSpec.anonymousClassBuilder()
+ .addSuperclassConstructorParameter("%S", "peace sign")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("handPosition", String::class, KModifier.PRIVATE)
+ .initializer("handPosition")
+ .build(),
+ )
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("handPosition", String::class)
+ .build(),
+ )
+ .addFunction(
+ FunSpec.constructorBuilder()
+ .addCode("this(%S)\n", "fist")
+ .build(),
+ )
+ .build()
+ assertThat(toString(roshambo)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public enum class Roshambo(
+ | private val handPosition: String,
+ |) {
+ | /**
+ | * Avalanche!
+ | */
+ | ROCK,
+ | PAPER("flat") {
+ | public override fun toString(): String = "paper airplane!"
+ | },
+ | SCISSORS("peace sign"),
+ | ;
+ |
+ | public constructor() {
+ | this("fist")
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ /** https://github.com/square/javapoet/issues/193 */
+ @Test fun enumsMayDefineAbstractFunctions() {
+ val roshambo = TypeSpec.enumBuilder("Tortilla")
+ .addModifiers(KModifier.PUBLIC)
+ .addEnumConstant(
+ "CORN",
+ TypeSpec.anonymousClassBuilder()
+ .addFunction(
+ FunSpec.builder("fold")
+ .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+ .build(),
+ )
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("fold")
+ .addModifiers(KModifier.PUBLIC, KModifier.ABSTRACT)
+ .build(),
+ )
+ .build()
+ assertThat(toString(roshambo)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public enum class Tortilla {
+ | CORN {
+ | public override fun fold(): Unit {
+ | }
+ | },
+ | ;
+ |
+ | public abstract fun fold(): Unit
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun enumsMayHavePrivateConstructorVals() {
+ val enum = TypeSpec.enumBuilder("MyEnum")
+ .primaryConstructor(
+ FunSpec.constructorBuilder().addParameter("number", Int::class).build(),
+ )
+ .addProperty(
+ PropertySpec.builder("number", Int::class)
+ .addModifiers(PRIVATE)
+ .initializer("number")
+ .build(),
+ )
+ .build()
+ assertThat(toString(enum)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |
+ |public enum class MyEnum(
+ | private val number: Int,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun classesMayHavePrivateConstructorPropertiesInTheirPrimaryConstructors() {
+ val myClass = TypeSpec.classBuilder("MyClass")
+ .primaryConstructor(
+ FunSpec.constructorBuilder().addParameter("number", Int::class).build(),
+ )
+ .addProperty(
+ PropertySpec.builder("number", Int::class)
+ .initializer("number")
+ .addModifiers(PRIVATE)
+ .build(),
+ )
+ .build()
+ assertThat(toString(myClass)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |
+ |public class MyClass(
+ | private val number: Int,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun sealedClassesMayDefineAbstractMembers() {
+ val sealedClass = TypeSpec.classBuilder("Sealed")
+ .addModifiers(KModifier.SEALED)
+ .addProperty(PropertySpec.builder("name", String::class).addModifiers(ABSTRACT).build())
+ .addFunction(FunSpec.builder("fold").addModifiers(KModifier.PUBLIC, KModifier.ABSTRACT).build())
+ .build()
+ assertThat(toString(sealedClass)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.Unit
+ |
+ |public sealed class Sealed {
+ | public abstract val name: String
+ |
+ | public abstract fun fold(): Unit
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun classesMayHaveVarargConstructorProperties() {
+ val variable = TypeSpec.classBuilder("Variable")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter(ParameterSpec.builder("name", String::class, VARARG).build())
+ .build(),
+ )
+ .addProperty(PropertySpec.builder("name", String::class).initializer("name").build())
+ .build()
+ assertThat(toString(variable)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public class Variable(
+ | public vararg val name: String,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ /** https://github.com/square/kotlinpoet/issues/942 */
+ @Test fun noConstructorPropertiesWithCustomGetter() {
+ val taco = TypeSpec.classBuilder("ObservantTaco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter(ParameterSpec.builder("contents", String::class).build())
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("contents", String::class).initializer("contents")
+ .getter(FunSpec.getterBuilder().addCode("println(%S)\nreturn field", "contents observed!").build())
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public class ObservantTaco(
+ | contents: String,
+ |) {
+ | public val contents: String = contents
+ | get() {
+ | println("contents observed!")
+ | return field
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun noConstructorPropertiesWithCustomSetter() {
+ val taco = TypeSpec.classBuilder("ObservantTaco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter(ParameterSpec.builder("contents", String::class).build())
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("contents", String::class).initializer("contents")
+ .mutable()
+ .setter(
+ FunSpec.setterBuilder()
+ .addParameter("value", String::class)
+ .addCode("println(%S)\nfield = value", "contents changed!").build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public class ObservantTaco(
+ | contents: String,
+ |) {
+ | public var contents: String = contents
+ | set(`value`) {
+ | println("contents changed!")
+ | field = value
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun onlyEnumsMayHaveEnumConstants() {
+ assertThrows<IllegalStateException> {
+ TypeSpec.classBuilder("Roshambo")
+ .addEnumConstant("ROCK")
+ .build()
+ }
+ }
+
+ /** https://github.com/square/kotlinpoet/issues/621 */
+ @Test fun enumWithMembersButNoConstansts() {
+ val roshambo = TypeSpec.enumBuilder("RenderPassCreate")
+ .addType(TypeSpec.companionObjectBuilder().build())
+ .build()
+ assertThat(toString(roshambo)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |public enum class RenderPassCreate {
+ | ;
+ | public companion object
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun enumWithMembersButNoConstructorCall() {
+ val roshambo = TypeSpec.enumBuilder("Roshambo")
+ .addEnumConstant(
+ "SPOCK",
+ TypeSpec.anonymousClassBuilder()
+ .addFunction(
+ FunSpec.builder("toString")
+ .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+ .returns(String::class)
+ .addStatement("return %S", "west side")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(toString(roshambo)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public enum class Roshambo {
+ | SPOCK {
+ | public override fun toString(): String = "west side"
+ | },
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ /** https://github.com/square/javapoet/issues/253 */
+ @Test fun enumWithAnnotatedValues() {
+ val roshambo = TypeSpec.enumBuilder("Roshambo")
+ .addModifiers(KModifier.PUBLIC)
+ .addEnumConstant(
+ "ROCK",
+ TypeSpec.anonymousClassBuilder()
+ .addAnnotation(java.lang.Deprecated::class)
+ .build(),
+ )
+ .addEnumConstant("PAPER")
+ .addEnumConstant("SCISSORS")
+ .build()
+ assertThat(toString(roshambo)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.lang.Deprecated
+ |
+ |public enum class Roshambo {
+ | @Deprecated
+ | ROCK,
+ | PAPER,
+ | SCISSORS,
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun funThrows() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addModifiers(KModifier.ABSTRACT)
+ .addFunction(
+ FunSpec.builder("throwOne")
+ .throws(IOException::class)
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("throwTwo")
+ .throws(IOException::class.asClassName(), ClassName(tacosPackage, "SourCreamException"))
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("abstractThrow")
+ .addModifiers(KModifier.ABSTRACT)
+ .throws(IOException::class)
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("nativeThrow")
+ .addModifiers(KModifier.EXTERNAL)
+ .throws(IOException::class)
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.io.IOException
+ |import kotlin.Unit
+ |import kotlin.jvm.Throws
+ |
+ |public abstract class Taco {
+ | @Throws(IOException::class)
+ | public fun throwOne(): Unit {
+ | }
+ |
+ | @Throws(
+ | IOException::class,
+ | SourCreamException::class,
+ | )
+ | public fun throwTwo(): Unit {
+ | }
+ |
+ | @Throws(IOException::class)
+ | public abstract fun abstractThrow(): Unit
+ |
+ | @Throws(IOException::class)
+ | public external fun nativeThrow(): Unit
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun typeVariables() {
+ val t = TypeVariableName("T")
+ val p = TypeVariableName("P", Number::class)
+ val location = ClassName(tacosPackage, "Location")
+ val typeSpec = TypeSpec.classBuilder("Location")
+ .addTypeVariable(t)
+ .addTypeVariable(p)
+ .addSuperinterface(Comparable::class.asClassName().parameterizedBy(p))
+ .addProperty("label", t)
+ .addProperty("x", p)
+ .addProperty("y", p)
+ .addFunction(
+ FunSpec.builder("compareTo")
+ .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+ .returns(Int::class)
+ .addParameter("p", p)
+ .addStatement("return 0")
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("of")
+ .addModifiers(KModifier.PUBLIC)
+ .addTypeVariable(t)
+ .addTypeVariable(p)
+ .returns(location.parameterizedBy(t, p))
+ .addParameter("label", t)
+ .addParameter("x", p)
+ .addParameter("y", p)
+ .addStatement("throw %T(%S)", UnsupportedOperationException::class, "TODO")
+ .build(),
+ )
+ .build()
+ assertThat(toString(typeSpec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.lang.UnsupportedOperationException
+ |import kotlin.Comparable
+ |import kotlin.Int
+ |import kotlin.Number
+ |
+ |public class Location<T, P : Number> : Comparable<P> {
+ | public val label: T
+ |
+ | public val x: P
+ |
+ | public val y: P
+ |
+ | public override fun compareTo(p: P): Int = 0
+ |
+ | public fun <T, P : Number> of(
+ | label: T,
+ | x: P,
+ | y: P,
+ | ): Location<T, P> = throw UnsupportedOperationException("TODO")
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun typeVariableWithBounds() {
+ val a = AnnotationSpec.builder(ClassName("com.squareup.tacos", "A")).build()
+ val p = TypeVariableName("P", Number::class)
+ val q = TypeVariableName("Q", Number::class).copy(annotations = listOf(a)) as TypeVariableName
+ val typeSpec = TypeSpec.classBuilder("Location")
+ .addTypeVariable(p.copy(bounds = p.bounds + listOf(Comparable::class.asTypeName())))
+ .addTypeVariable(q.copy(bounds = q.bounds + listOf(Comparable::class.asTypeName())))
+ .addProperty("x", p)
+ .addProperty("y", q)
+ .primaryConstructor(FunSpec.constructorBuilder().build())
+ .superclass(Number::class)
+ .addSuperinterface(Comparable::class)
+ .build()
+ assertThat(toString(typeSpec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Comparable
+ |import kotlin.Number
+ |
+ |public class Location<P, Q>() : Number(), Comparable where P : Number, P : Comparable, Q : Number, Q
+ | : Comparable {
+ | public val x: P
+ |
+ | public val y: @A Q
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun classImplementsExtends() {
+ val taco = ClassName(tacosPackage, "Taco")
+ val food = ClassName("com.squareup.tacos", "Food")
+ val typeSpec = TypeSpec.classBuilder("Taco")
+ .addModifiers(KModifier.ABSTRACT)
+ .superclass(AbstractSet::class.asClassName().parameterizedBy(food))
+ .addSuperinterface(Serializable::class)
+ .addSuperinterface(Comparable::class.asClassName().parameterizedBy(taco))
+ .build()
+ assertThat(toString(typeSpec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.io.Serializable
+ |import java.util.AbstractSet
+ |import kotlin.Comparable
+ |
+ |public abstract class Taco : AbstractSet<Food>(), Serializable, Comparable<Taco>
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun classImplementsExtendsSameName() {
+ val javapoetTaco = ClassName(tacosPackage, "Taco")
+ val tacoBellTaco = ClassName("com.taco.bell", "Taco")
+ val fishTaco = ClassName("org.fish.taco", "Taco")
+ val typeSpec = TypeSpec.classBuilder("Taco")
+ .superclass(fishTaco)
+ .addSuperinterface(Comparable::class.asClassName().parameterizedBy(javapoetTaco))
+ .addSuperinterface(tacoBellTaco)
+ .build()
+ assertThat(toString(typeSpec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Comparable
+ |
+ |public class Taco : org.fish.taco.Taco(), Comparable<Taco>, com.taco.bell.Taco
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun classImplementsInnerClass() {
+ val outer = ClassName(tacosPackage, "Outer")
+ val inner = outer.nestedClass("Inner")
+ val callable = Callable::class.asClassName()
+ val typeSpec = TypeSpec.classBuilder("Outer")
+ .superclass(callable.parameterizedBy(inner))
+ .addType(
+ TypeSpec.classBuilder("Inner")
+ .addModifiers(KModifier.INNER)
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(typeSpec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.util.concurrent.Callable
+ |
+ |public class Outer : Callable<Outer.Inner>() {
+ | public inner class Inner
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun enumImplements() {
+ val typeSpec = TypeSpec.enumBuilder("Food")
+ .addSuperinterface(Serializable::class)
+ .addSuperinterface(Cloneable::class)
+ .addEnumConstant("LEAN_GROUND_BEEF")
+ .addEnumConstant("SHREDDED_CHEESE")
+ .build()
+ assertThat(toString(typeSpec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.io.Serializable
+ |import kotlin.Cloneable
+ |
+ |public enum class Food : Serializable, Cloneable {
+ | LEAN_GROUND_BEEF,
+ | SHREDDED_CHEESE,
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun enumWithConstructorsAndKeywords() {
+ val primaryConstructor = FunSpec.constructorBuilder()
+ .addParameter("value", Int::class)
+ .build()
+ val typeSpec = TypeSpec.enumBuilder("Sort")
+ .primaryConstructor(primaryConstructor)
+ .addEnumConstant(
+ "open",
+ TypeSpec.anonymousClassBuilder()
+ .addSuperclassConstructorParameter("%L", 0)
+ .build(),
+ )
+ .addEnumConstant(
+ "closed",
+ TypeSpec.anonymousClassBuilder()
+ .addSuperclassConstructorParameter("%L", 1)
+ .build(),
+ )
+ .build()
+ assertThat(toString(typeSpec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |
+ |public enum class Sort(
+ | `value`: Int,
+ |) {
+ | `open`(0),
+ | closed(1),
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun interfaceExtends() {
+ val taco = ClassName(tacosPackage, "Taco")
+ val typeSpec = TypeSpec.interfaceBuilder("Taco")
+ .addSuperinterface(Serializable::class)
+ .addSuperinterface(Comparable::class.asClassName().parameterizedBy(taco))
+ .build()
+ assertThat(toString(typeSpec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.io.Serializable
+ |import kotlin.Comparable
+ |
+ |public interface Taco : Serializable, Comparable<Taco>
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun funInterface() {
+ val taco = ClassName(tacosPackage, "Taco")
+ val typeSpec = TypeSpec.funInterfaceBuilder(taco)
+ .addFunction(
+ FunSpec.builder("sam")
+ .addModifiers(ABSTRACT)
+ .build(),
+ )
+ .addFunction(FunSpec.builder("notSam").build())
+ .build()
+ assertThat(typeSpec.isFunctionalInterface).isTrue()
+ assertThat(toString(typeSpec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public fun interface Taco {
+ | public fun sam(): Unit
+ |
+ | public fun notSam(): Unit {
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun funInterface_empty_shouldError() {
+ assertThrows<IllegalStateException> {
+ TypeSpec.funInterfaceBuilder("Taco")
+ .build()
+ }.hasMessageThat()
+ .contains("Functional interfaces must have exactly one abstract function. Contained 0")
+ }
+
+ @Test fun funInterface_multipleAbstract_shouldError() {
+ assertThrows<IllegalStateException> {
+ TypeSpec.funInterfaceBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("fun1")
+ .addModifiers(ABSTRACT)
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("fun2")
+ .addModifiers(ABSTRACT)
+ .build(),
+ )
+ .build()
+ }.hasMessageThat()
+ .contains("Functional interfaces must have exactly one abstract function. Contained 2")
+ }
+
+ @Test fun nestedClasses() {
+ val taco = ClassName(tacosPackage, "Combo", "Taco")
+ val topping = ClassName(tacosPackage, "Combo", "Taco", "Topping")
+ val chips = ClassName(tacosPackage, "Combo", "Chips")
+ val sauce = ClassName(tacosPackage, "Combo", "Sauce")
+ val typeSpec = TypeSpec.classBuilder("Combo")
+ .addProperty("taco", taco)
+ .addProperty("chips", chips)
+ .addType(
+ TypeSpec.classBuilder(taco.simpleName)
+ .addProperty("toppings", List::class.asClassName().parameterizedBy(topping))
+ .addProperty("sauce", sauce)
+ .addType(
+ TypeSpec.enumBuilder(topping.simpleName)
+ .addEnumConstant("SHREDDED_CHEESE")
+ .addEnumConstant("LEAN_GROUND_BEEF")
+ .build(),
+ )
+ .build(),
+ )
+ .addType(
+ TypeSpec.classBuilder(chips.simpleName)
+ .addProperty("topping", topping)
+ .addProperty("dippingSauce", sauce)
+ .build(),
+ )
+ .addType(
+ TypeSpec.enumBuilder(sauce.simpleName)
+ .addEnumConstant("SOUR_CREAM")
+ .addEnumConstant("SALSA")
+ .addEnumConstant("QUESO")
+ .addEnumConstant("MILD")
+ .addEnumConstant("FIRE")
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(typeSpec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.collections.List
+ |
+ |public class Combo {
+ | public val taco: Taco
+ |
+ | public val chips: Chips
+ |
+ | public class Taco {
+ | public val toppings: List<Topping>
+ |
+ | public val sauce: Sauce
+ |
+ | public enum class Topping {
+ | SHREDDED_CHEESE,
+ | LEAN_GROUND_BEEF,
+ | }
+ | }
+ |
+ | public class Chips {
+ | public val topping: Taco.Topping
+ |
+ | public val dippingSauce: Sauce
+ | }
+ |
+ | public enum class Sauce {
+ | SOUR_CREAM,
+ | SALSA,
+ | QUESO,
+ | MILD,
+ | FIRE,
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun annotation() {
+ val annotation = TypeSpec.annotationBuilder("MyAnnotation")
+ .addModifiers(KModifier.PUBLIC)
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter(
+ ParameterSpec.builder("test", Int::class)
+ .build(),
+ )
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("test", Int::class)
+ .initializer("test")
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(annotation)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |
+ |public annotation class MyAnnotation(
+ | public val test: Int,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun annotationWithNestedTypes() {
+ val annotationName = ClassName(tacosPackage, "TacoDelivery")
+ val kindName = annotationName.nestedClass("Kind")
+ val annotation = TypeSpec.annotationBuilder(annotationName)
+ .addModifiers(PUBLIC)
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter(
+ ParameterSpec.builder("kind", kindName)
+ .build(),
+ )
+ .addParameter(
+ ParameterSpec.builder("quantity", Int::class)
+ .defaultValue("QUANTITY_DEFAULT")
+ .build(),
+ )
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("kind", kindName)
+ .initializer("kind")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("quantity", Int::class)
+ .initializer("quantity")
+ .build(),
+ )
+ .addType(
+ TypeSpec.enumBuilder("Kind")
+ .addEnumConstant("SOFT")
+ .addEnumConstant("HARD")
+ .build(),
+ )
+ .addType(
+ TypeSpec.companionObjectBuilder()
+ .addProperty(
+ PropertySpec
+ .builder("QUANTITY_DEFAULT", Int::class, KModifier.CONST)
+ .initializer("%L", 10_000)
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(annotation)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |
+ |public annotation class TacoDelivery(
+ | public val kind: Kind,
+ | public val quantity: Int = QUANTITY_DEFAULT,
+ |) {
+ | public enum class Kind {
+ | SOFT,
+ | HARD,
+ | }
+ |
+ | public companion object {
+ | public const val QUANTITY_DEFAULT: Int = 10_000
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Ignore @Test
+ fun innerAnnotationInAnnotationDeclaration() {
+ val bar = TypeSpec.annotationBuilder("Bar")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter(
+ ParameterSpec.builder("value", java.lang.Deprecated::class)
+ .build(),
+ )
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("value", java.lang.Deprecated::class)
+ .initializer("value")
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(bar)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.lang.Deprecated
+ |
+ |annotation class Bar() {
+ | fun value(): Deprecated default @Deprecated
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun interfaceWithProperties() {
+ val taco = TypeSpec.interfaceBuilder("Taco")
+ .addProperty("v", Int::class)
+ .build()
+
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |
+ |public interface Taco {
+ | public val v: Int
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun expectClass() {
+ val classA = TypeSpec.expectClassBuilder("ClassA")
+ .addFunction(
+ FunSpec.builder("test")
+ .build(),
+ )
+ .build()
+
+ assertThat(classA.toString()).isEqualTo(
+ """
+ |public expect class ClassA {
+ | public fun test(): kotlin.Unit
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun nestedExpectCompanionObjectWithFunction() {
+ val classA = TypeSpec.expectClassBuilder("ClassA")
+ .addType(
+ TypeSpec.companionObjectBuilder()
+ .addFunction(
+ FunSpec.builder("test")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+
+ assertThat(classA.toString()).isEqualTo(
+ """
+ |public expect class ClassA {
+ | public companion object {
+ | public fun test(): kotlin.Unit
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun nestedExpectClassWithFunction() {
+ val classA = TypeSpec.expectClassBuilder("ClassA")
+ .addType(
+ TypeSpec.classBuilder("ClassB")
+ .addFunction(
+ FunSpec.builder("test")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+
+ assertThat(classA.toString()).isEqualTo(
+ """
+ |public expect class ClassA {
+ | public class ClassB {
+ | public fun test(): kotlin.Unit
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun deeplyNestedExpectClassWithFunction() {
+ val classA = TypeSpec.expectClassBuilder("ClassA")
+ .addType(
+ TypeSpec.classBuilder("ClassB")
+ .addType(
+ TypeSpec.classBuilder("ClassC")
+ .addFunction(
+ FunSpec.builder("test")
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+
+ assertThat(classA.toString()).isEqualTo(
+ """
+ |public expect class ClassA {
+ | public class ClassB {
+ | public class ClassC {
+ | public fun test(): kotlin.Unit
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun veryDeeplyNestedExpectClassWithFunction() {
+ val classA = TypeSpec.expectClassBuilder("ClassA")
+ .addType(
+ TypeSpec.classBuilder("ClassB")
+ .addType(
+ TypeSpec.classBuilder("ClassC")
+ .addType(
+ TypeSpec.classBuilder("ClassD")
+ .addFunction(
+ FunSpec.builder("test")
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+
+ assertThat(classA.toString()).isEqualTo(
+ """
+ |public expect class ClassA {
+ | public class ClassB {
+ | public class ClassC {
+ | public class ClassD {
+ | public fun test(): kotlin.Unit
+ | }
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun deeplyNestedExpectClassWithConstructor() {
+ val classA = TypeSpec.expectClassBuilder("ClassA")
+ .addType(
+ TypeSpec.classBuilder("ClassB")
+ .addType(
+ TypeSpec.classBuilder("ClassC")
+ .addFunction(
+ FunSpec.constructorBuilder()
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+
+ assertThat(classA.toString()).isEqualTo(
+ """
+ |public expect class ClassA {
+ | public class ClassB {
+ | public class ClassC {
+ | public constructor()
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun veryDeeplyNestedExpectClassWithConstructor() {
+ val classA = TypeSpec.expectClassBuilder("ClassA")
+ .addType(
+ TypeSpec.classBuilder("ClassB")
+ .addType(
+ TypeSpec.classBuilder("ClassC")
+ .addType(
+ TypeSpec.classBuilder("ClassD")
+ .addFunction(
+ FunSpec.constructorBuilder()
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+
+ assertThat(classA.toString()).isEqualTo(
+ """
+ |public expect class ClassA {
+ | public class ClassB {
+ | public class ClassC {
+ | public class ClassD {
+ | public constructor()
+ | }
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun interfaceWithMethods() {
+ val taco = TypeSpec.interfaceBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("aMethod")
+ .addModifiers(KModifier.ABSTRACT)
+ .build(),
+ )
+ .addFunction(FunSpec.builder("aDefaultMethod").build())
+ .addFunction(
+ FunSpec.builder("aPrivateMethod")
+ .addModifiers(KModifier.PRIVATE)
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public interface Taco {
+ | public fun aMethod(): Unit
+ |
+ | public fun aDefaultMethod(): Unit {
+ | }
+ |
+ | private fun aPrivateMethod(): Unit {
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun referencedAndDeclaredSimpleNamesConflict() {
+ val internalTop = PropertySpec.builder(
+ "internalTop",
+ ClassName(tacosPackage, "Top"),
+ ).build()
+ val internalBottom = PropertySpec.builder(
+ "internalBottom",
+ ClassName(tacosPackage, "Top", "Middle", "Bottom"),
+ ).build()
+ val externalTop = PropertySpec.builder(
+ "externalTop",
+ ClassName(donutsPackage, "Top"),
+ ).build()
+ val externalBottom = PropertySpec.builder(
+ "externalBottom",
+ ClassName(donutsPackage, "Bottom"),
+ ).build()
+ val top = TypeSpec.classBuilder("Top")
+ .addProperty(internalTop)
+ .addProperty(internalBottom)
+ .addProperty(externalTop)
+ .addProperty(externalBottom)
+ .addType(
+ TypeSpec.classBuilder("Middle")
+ .addProperty(internalTop)
+ .addProperty(internalBottom)
+ .addProperty(externalTop)
+ .addProperty(externalBottom)
+ .addType(
+ TypeSpec.classBuilder("Bottom")
+ .addProperty(internalTop)
+ .addProperty(internalBottom)
+ .addProperty(externalTop)
+ .addProperty(externalBottom)
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(toString(top)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.donuts.Bottom
+ |
+ |public class Top {
+ | public val internalTop: Top
+ |
+ | public val internalBottom: Middle.Bottom
+ |
+ | public val externalTop: com.squareup.donuts.Top
+ |
+ | public val externalBottom: Bottom
+ |
+ | public class Middle {
+ | public val internalTop: Top
+ |
+ | public val internalBottom: Bottom
+ |
+ | public val externalTop: com.squareup.donuts.Top
+ |
+ | public val externalBottom: com.squareup.donuts.Bottom
+ |
+ | public class Bottom {
+ | public val internalTop: Top
+ |
+ | public val internalBottom: Bottom
+ |
+ | public val externalTop: com.squareup.donuts.Top
+ |
+ | public val externalBottom: com.squareup.donuts.Bottom
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun simpleNamesConflictInThisAndOtherPackage() {
+ val internalOther = PropertySpec.builder(
+ "internalOther",
+ ClassName(tacosPackage, "Other"),
+ ).build()
+ val externalOther = PropertySpec.builder(
+ "externalOther",
+ ClassName(donutsPackage, "Other"),
+ ).build()
+ val gen = TypeSpec.classBuilder("Gen")
+ .addProperty(internalOther)
+ .addProperty(externalOther)
+ .build()
+ assertThat(toString(gen)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |public class Gen {
+ | public val internalOther: Other
+ |
+ | public val externalOther: com.squareup.donuts.Other
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun intersectionType() {
+ val typeVariable = TypeVariableName("T", Comparator::class, Serializable::class)
+ val taco = TypeSpec.classBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("getComparator")
+ .addTypeVariable(typeVariable)
+ .returns(typeVariable)
+ .addStatement("return null")
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.io.Serializable
+ |import java.util.Comparator
+ |
+ |public class Taco {
+ | public fun <T> getComparator(): T where T : Comparator, T : Serializable = null
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun primitiveArrayType() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addProperty("ints", IntArray::class)
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.IntArray
+ |
+ |public class Taco {
+ | public val ints: IntArray
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun kdoc() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addKdoc("A hard or soft tortilla, loosely folded and filled with whatever\n")
+ .addKdoc("[random][%T] tex-mex stuff we could find in the pantry\n", Random::class)
+ .addKdoc(CodeBlock.of("and some [%T] cheese.\n", String::class))
+ .addProperty(
+ PropertySpec.builder("soft", Boolean::class)
+ .addKdoc("True for a soft flour tortilla; false for a crunchy corn tortilla.\n")
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("refold")
+ .addKdoc(
+ "Folds the back of this taco to reduce sauce leakage.\n" +
+ "\n" +
+ "For [%T#KOREAN], the front may also be folded.\n",
+ Locale::class,
+ )
+ .addParameter("locale", Locale::class)
+ .build(),
+ )
+ .build()
+ // Mentioning a type in KDoc will not cause an import to be added (java.util.Random here), but
+ // the short name will be used if it's already imported (java.util.Locale here).
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.util.Locale
+ |import kotlin.Boolean
+ |import kotlin.Unit
+ |
+ |/**
+ | * A hard or soft tortilla, loosely folded and filled with whatever
+ | * [random][java.util.Random] tex-mex stuff we could find in the pantry
+ | * and some [kotlin.String] cheese.
+ | */
+ |public class Taco {
+ | /**
+ | * True for a soft flour tortilla; false for a crunchy corn tortilla.
+ | */
+ | public val soft: Boolean
+ |
+ | /**
+ | * Folds the back of this taco to reduce sauce leakage.
+ | *
+ | * For [Locale#KOREAN], the front may also be folded.
+ | */
+ | public fun refold(locale: Locale): Unit {
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun kdocWithParameters() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addKdoc("A hard or soft tortilla, loosely folded and filled with whatever\n")
+ .addKdoc("[random][%T] tex-mex stuff we could find in the pantry\n", Random::class)
+ .addKdoc(CodeBlock.of("and some [%T] cheese.\n", String::class))
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter(
+ ParameterSpec.builder("temperature", Double::class)
+ .addKdoc(
+ CodeBlock.of(
+ "%L",
+ """
+ |Taco temperature. Can be as cold as the famous ice tacos from
+ |the Andes, or hot with lava-like cheese from the depths of
+ |the Ninth Circle.
+ |
+ """.trimMargin(),
+ ),
+ )
+ .build(),
+ )
+ .addParameter("soft", Boolean::class)
+ .addParameter(
+ ParameterSpec.builder("mild", Boolean::class)
+ .addKdoc(CodeBlock.of("%L", "Whether the taco is mild (ew) or crunchy (ye).\n"))
+ .build(),
+ )
+ .addParameter("nodoc", Int::class)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("soft", Boolean::class)
+ .addKdoc("True for a soft flour tortilla; false for a crunchy corn tortilla.\n")
+ .initializer("soft")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("mild", Boolean::class)
+ .addKdoc("No one likes mild tacos.")
+ .initializer("mild")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("nodoc", Int::class, KModifier.PRIVATE)
+ .initializer("nodoc")
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Boolean
+ |import kotlin.Double
+ |import kotlin.Int
+ |
+ |/**
+ | * A hard or soft tortilla, loosely folded and filled with whatever
+ | * [random][java.util.Random] tex-mex stuff we could find in the pantry
+ | * and some [kotlin.String] cheese.
+ | *
+ | * @param mild Whether the taco is mild (ew) or crunchy (ye).
+ | */
+ |public class Taco(
+ | /**
+ | * Taco temperature. Can be as cold as the famous ice tacos from
+ | * the Andes, or hot with lava-like cheese from the depths of
+ | * the Ninth Circle.
+ | */
+ | temperature: Double,
+ | /**
+ | * True for a soft flour tortilla; false for a crunchy corn tortilla.
+ | */
+ | public val soft: Boolean,
+ | /**
+ | * No one likes mild tacos.
+ | */
+ | public val mild: Boolean,
+ | private val nodoc: Int,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun annotationsInAnnotations() {
+ val beef = ClassName(tacosPackage, "Beef")
+ val chicken = ClassName(tacosPackage, "Chicken")
+ val option = ClassName(tacosPackage, "Option")
+ val mealDeal = ClassName(tacosPackage, "MealDeal")
+ val menu = TypeSpec.classBuilder("Menu")
+ .addAnnotation(
+ AnnotationSpec.builder(mealDeal)
+ .addMember("%L = %L", "price", 500)
+ .addMember(
+ "%L = [%L, %L]",
+ "options",
+ AnnotationSpec.builder(option)
+ .addMember("%S", "taco")
+ .addMember("%T::class", beef)
+ .build(),
+ AnnotationSpec.builder(option)
+ .addMember("%S", "quesadilla")
+ .addMember("%T::class", chicken)
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(toString(menu)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |@MealDeal(
+ | price = 500,
+ | options = [Option("taco", Beef::class), Option("quesadilla", Chicken::class)],
+ |)
+ |public class Menu
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun varargs() {
+ val taqueria = TypeSpec.classBuilder("Taqueria")
+ .addFunction(
+ FunSpec.builder("prepare")
+ .addParameter("workers", Int::class)
+ .addParameter("jobs", Runnable::class.asClassName(), VARARG)
+ .build(),
+ )
+ .build()
+ assertThat(toString(taqueria)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.lang.Runnable
+ |import kotlin.Int
+ |import kotlin.Unit
+ |
+ |public class Taqueria {
+ | public fun prepare(workers: Int, vararg jobs: Runnable): Unit {
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun varargsNotLast() {
+ val taqueria = TypeSpec.classBuilder("Taqueria")
+ .addFunction(
+ FunSpec.builder("prepare")
+ .addParameter("workers", Int::class)
+ .addParameter("jobs", Runnable::class.asClassName(), VARARG)
+ .addParameter("start", Boolean::class.asClassName())
+ .build(),
+ )
+ .build()
+ assertThat(toString(taqueria)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.lang.Runnable
+ |import kotlin.Boolean
+ |import kotlin.Int
+ |import kotlin.Unit
+ |
+ |public class Taqueria {
+ | public fun prepare(
+ | workers: Int,
+ | vararg jobs: Runnable,
+ | start: Boolean,
+ | ): Unit {
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun codeBlocks() {
+ val ifBlock = CodeBlock.builder()
+ .beginControlFlow("if (a != b)")
+ .addStatement("return i")
+ .endControlFlow()
+ .build()
+ val funBody = CodeBlock.builder()
+ .addStatement("val size = %T.min(listA.size, listB.size)", Math::class)
+ .beginControlFlow("for (i in 0 until size)")
+ .addStatement("val %N = %N[i]", "a", "listA")
+ .addStatement("val %N = %N[i]", "b", "listB")
+ .add("%L", ifBlock)
+ .endControlFlow()
+ .addStatement("return size")
+ .build()
+ val propertyBlock = CodeBlock.builder()
+ .add("%T.<%T, %T>builder()", ImmutableMap::class, String::class, String::class)
+ .add("\n.add(%S, %S)", '\'', "&#39;")
+ .add("\n.add(%S, %S)", '&', "&amp;")
+ .add("\n.add(%S, %S)", '<', "&lt;")
+ .add("\n.add(%S, %S)", '>', "&gt;")
+ .add("\n.build()")
+ .build()
+ val escapeHtml = PropertySpec.builder(
+ "ESCAPE_HTML",
+ Map::class.parameterizedBy(String::class, String::class),
+ )
+ .addModifiers(KModifier.PRIVATE)
+ .initializer(propertyBlock)
+ .build()
+ val util = TypeSpec.classBuilder("Util")
+ .addProperty(escapeHtml)
+ .addFunction(
+ FunSpec.builder("commonPrefixLength")
+ .returns(Int::class)
+ .addParameter("listA", List::class.parameterizedBy(String::class))
+ .addParameter("listB", List::class.parameterizedBy(String::class))
+ .addCode(funBody)
+ .build(),
+ )
+ .build()
+ assertThat(toString(util)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.google.common.collect.ImmutableMap
+ |import java.lang.Math
+ |import kotlin.Int
+ |import kotlin.String
+ |import kotlin.collections.List
+ |import kotlin.collections.Map
+ |
+ |public class Util {
+ | private val ESCAPE_HTML: Map<String, String> = ImmutableMap.<String, String>builder()
+ | .add("'", "&#39;")
+ | .add("&", "&amp;")
+ | .add("<", "&lt;")
+ | .add(">", "&gt;")
+ | .build()
+ |
+ | public fun commonPrefixLength(listA: List<String>, listB: List<String>): Int {
+ | val size = Math.min(listA.size, listB.size)
+ | for (i in 0 until size) {
+ | val a = listA[i]
+ | val b = listB[i]
+ | if (a != b) {
+ | return i
+ | }
+ | }
+ | return size
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun indexedElseIf() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("choices")
+ .beginControlFlow("if (%1L != null || %1L == %2L)", "taco", "otherTaco")
+ .addStatement("%T.out.println(%S)", System::class, "only one taco? NOO!")
+ .nextControlFlow("else if (%1L.%3L && %2L.%3L)", "taco", "otherTaco", "isSupreme()")
+ .addStatement("%T.out.println(%S)", System::class, "taco heaven")
+ .endControlFlow()
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.lang.System
+ |import kotlin.Unit
+ |
+ |public class Taco {
+ | public fun choices(): Unit {
+ | if (taco != null || taco == otherTaco) {
+ | System.out.println("only one taco? NOO!")
+ | } else if (taco.isSupreme() && otherTaco.isSupreme()) {
+ | System.out.println("taco heaven")
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun elseIf() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("choices")
+ .beginControlFlow("if (5 < 4) ")
+ .addStatement("%T.out.println(%S)", System::class, "wat")
+ .nextControlFlow("else if (5 < 6)")
+ .addStatement("%T.out.println(%S)", System::class, "hello")
+ .endControlFlow()
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.lang.System
+ |import kotlin.Unit
+ |
+ |public class Taco {
+ | public fun choices(): Unit {
+ | if (5 < 4) {
+ | System.out.println("wat")
+ | } else if (5 < 6) {
+ | System.out.println("hello")
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun inlineIndent() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("inlineIndent")
+ .addCode("if (3 < 4) {\n⇥%T.out.println(%S);\n⇤}\n", System::class, "hello")
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.lang.System
+ |import kotlin.Unit
+ |
+ |public class Taco {
+ | public fun inlineIndent(): Unit {
+ | if (3 < 4) {
+ | System.out.println("hello");
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun defaultModifiersForMemberInterfacesAndEnums() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addType(
+ TypeSpec.classBuilder("Meat")
+ .build(),
+ )
+ .addType(
+ TypeSpec.interfaceBuilder("Tortilla")
+ .build(),
+ )
+ .addType(
+ TypeSpec.enumBuilder("Topping")
+ .addEnumConstant("SALSA")
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |public class Taco {
+ | public class Meat
+ |
+ | public interface Tortilla
+ |
+ | public enum class Topping {
+ | SALSA,
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun membersOrdering() {
+ // Hand out names in reverse-alphabetical order to defend against unexpected sorting.
+ val taco = TypeSpec.classBuilder("Members")
+ .addType(TypeSpec.classBuilder("Z").build())
+ .addType(TypeSpec.classBuilder("Y").build())
+ .addProperty("W", String::class)
+ .addProperty("U", String::class)
+ .addFunction(FunSpec.builder("T").build())
+ .addFunction(FunSpec.builder("S").build())
+ .addFunction(FunSpec.builder("R").build())
+ .addFunction(FunSpec.builder("Q").build())
+ .addFunction(
+ FunSpec.constructorBuilder()
+ .addParameter("p", Int::class)
+ .build(),
+ )
+ .addFunction(
+ FunSpec.constructorBuilder()
+ .addParameter("o", Long::class)
+ .build(),
+ )
+ .build()
+ // Static properties, instance properties, constructors, functions, classes.
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |import kotlin.Long
+ |import kotlin.String
+ |import kotlin.Unit
+ |
+ |public class Members {
+ | public val W: String
+ |
+ | public val U: String
+ |
+ | public constructor(p: Int)
+ |
+ | public constructor(o: Long)
+ |
+ | public fun T(): Unit {
+ | }
+ |
+ | public fun S(): Unit {
+ | }
+ |
+ | public fun R(): Unit {
+ | }
+ |
+ | public fun Q(): Unit {
+ | }
+ |
+ | public class Z
+ |
+ | public class Y
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun nativeFunctions() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("nativeInt")
+ .addModifiers(KModifier.EXTERNAL)
+ .returns(Int::class)
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |
+ |public class Taco {
+ | public external fun nativeInt(): Int
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun nullStringLiteral() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addProperty(
+ PropertySpec.builder("NULL", String::class)
+ .initializer("%S", null)
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public class Taco {
+ | public val NULL: String = null
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun annotationToString() {
+ val annotation = AnnotationSpec.builder(SuppressWarnings::class)
+ .addMember("%S", "unused")
+ .build()
+ assertThat(annotation.toString()).isEqualTo("@java.lang.SuppressWarnings(\"unused\")")
+ }
+
+ @Test fun codeBlockToString() {
+ val codeBlock = CodeBlock.builder()
+ .addStatement("%T %N = %S.substring(0, 3)", String::class, "s", "taco")
+ .build()
+ assertThat(codeBlock.toString()).isEqualTo("kotlin.String s = \"taco\".substring(0, 3)\n")
+ }
+
+ @Test fun propertyToString() {
+ val property = PropertySpec.builder("s", String::class)
+ .initializer("%S.substring(0, 3)", "taco")
+ .build()
+ assertThat(property.toString())
+ .isEqualTo("val s: kotlin.String = \"taco\".substring(0, 3)\n")
+ }
+
+ @Test fun functionToString() {
+ val funSpec = FunSpec.builder("toString")
+ .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+ .returns(String::class)
+ .addStatement("return %S", "taco")
+ .build()
+ assertThat(funSpec.toString())
+ .isEqualTo("public override fun toString(): kotlin.String = \"taco\"\n")
+ }
+
+ @Test fun constructorToString() {
+ val constructor = FunSpec.constructorBuilder()
+ .addModifiers(KModifier.PUBLIC)
+ .addParameter("taco", ClassName(tacosPackage, "Taco"))
+ .addStatement("this.%N = %N", "taco", "taco")
+ .build()
+ assertThat(constructor.toString()).isEqualTo(
+ "" +
+ "public constructor(taco: com.squareup.tacos.Taco) {\n" +
+ " this.taco = taco\n" +
+ "}\n",
+ )
+ }
+
+ @Test fun parameterToString() {
+ val parameter = ParameterSpec.builder("taco", ClassName(tacosPackage, "Taco"))
+ .addModifiers(KModifier.CROSSINLINE)
+ .addAnnotation(ClassName("javax.annotation", "Nullable"))
+ .build()
+ assertThat(parameter.toString())
+ .isEqualTo("@javax.`annotation`.Nullable crossinline taco: com.squareup.tacos.Taco")
+ }
+
+ @Test fun classToString() {
+ val type = TypeSpec.classBuilder("Taco")
+ .build()
+ assertThat(type.toString()).isEqualTo(
+ "" +
+ "public class Taco\n",
+ )
+ }
+
+ @Test fun anonymousClassToString() {
+ val type = TypeSpec.anonymousClassBuilder()
+ .addSuperinterface(Runnable::class)
+ .addFunction(
+ FunSpec.builder("run")
+ .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+ .build(),
+ )
+ .build()
+ assertThat(type.toString()).isEqualTo(
+ """
+ |object : java.lang.Runnable {
+ | public override fun run(): kotlin.Unit {
+ | }
+ |}
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun interfaceClassToString() {
+ val type = TypeSpec.interfaceBuilder("Taco")
+ .build()
+ assertThat(type.toString()).isEqualTo(
+ """
+ |public interface Taco
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun annotationDeclarationToString() {
+ val type = TypeSpec.annotationBuilder("Taco")
+ .build()
+ assertThat(type.toString()).isEqualTo(
+ """
+ |public annotation class Taco
+ |
+ """.trimMargin(),
+ )
+ }
+
+ private fun toString(typeSpec: TypeSpec): String {
+ return FileSpec.get(tacosPackage, typeSpec).toString()
+ }
+
+ @Test fun multilineStatement() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("toString")
+ .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+ .returns(String::class)
+ .addStatement(
+ "val result = %S\n+ %S\n+ %S\n+ %S\n+ %S",
+ "Taco(",
+ "beef,",
+ "lettuce,",
+ "cheese",
+ ")",
+ )
+ .addStatement("return result")
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public class Taco {
+ | public override fun toString(): String {
+ | val result = "Taco("
+ | + "beef,"
+ | + "lettuce,"
+ | + "cheese"
+ | + ")"
+ | return result
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun multilineStatementWithAnonymousClass() {
+ val stringComparator = Comparator::class.parameterizedBy(String::class)
+ val listOfString = List::class.parameterizedBy(String::class)
+ val prefixComparator = TypeSpec.anonymousClassBuilder()
+ .addSuperinterface(stringComparator)
+ .addFunction(
+ FunSpec.builder("compare")
+ .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+ .returns(Int::class)
+ .addParameter("a", String::class)
+ .addParameter("b", String::class)
+ .addComment("Prefix the strings and compare them")
+ .addStatement("return a.substring(0, length)\n" + ".compareTo(b.substring(0, length))")
+ .build(),
+ )
+ .build()
+ val taco = TypeSpec.classBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("comparePrefix")
+ .returns(stringComparator)
+ .addParameter("length", Int::class)
+ .addComment("Return a new comparator for the target length.")
+ .addStatement("return %L", prefixComparator)
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("sortPrefix")
+ .addParameter("list", listOfString)
+ .addParameter("length", Int::class)
+ .addStatement("%T.sort(\nlist,\n%L)", Collections::class, prefixComparator)
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.util.Collections
+ |import java.util.Comparator
+ |import kotlin.Int
+ |import kotlin.String
+ |import kotlin.Unit
+ |import kotlin.collections.List
+ |
+ |public class Taco {
+ | public fun comparePrefix(length: Int): Comparator<String> {
+ | // Return a new comparator for the target length.
+ | return object : Comparator<String> {
+ | public override fun compare(a: String, b: String): Int {
+ | // Prefix the strings and compare them
+ | return a.substring(0, length)
+ | .compareTo(b.substring(0, length))
+ | }
+ | }
+ | }
+ |
+ | public fun sortPrefix(list: List<String>, length: Int): Unit {
+ | Collections.sort(
+ | list,
+ | object : Comparator<String> {
+ | public override fun compare(a: String, b: String): Int {
+ | // Prefix the strings and compare them
+ | return a.substring(0, length)
+ | .compareTo(b.substring(0, length))
+ | }
+ | })
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun multilineStrings() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addProperty(
+ PropertySpec.builder("toppings", String::class)
+ .initializer("%S", "shell\nbeef\nlettuce\ncheese\n")
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public class Taco {
+ | public val toppings: String = ${"\"\"\""}
+ | |shell
+ | |beef
+ | |lettuce
+ | |cheese
+ | |${"\"\"\""}.trimMargin()
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun multipleAnnotationAddition() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addAnnotations(
+ listOf(
+ AnnotationSpec.builder(SuppressWarnings::class)
+ .addMember("%S", "unchecked")
+ .build(),
+ AnnotationSpec.builder(Deprecated::class).build(),
+ ),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.lang.Deprecated
+ |import java.lang.SuppressWarnings
+ |
+ |@SuppressWarnings("unchecked")
+ |@Deprecated
+ |public class Taco
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun multiplePropertyAddition() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addProperties(
+ listOf(
+ PropertySpec.builder("ANSWER", Int::class, KModifier.CONST).build(),
+ PropertySpec.builder("price", BigDecimal::class, PRIVATE).build(),
+ ),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.math.BigDecimal
+ |import kotlin.Int
+ |
+ |public class Taco {
+ | public const val ANSWER: Int
+ |
+ | private val price: BigDecimal
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun multipleFunctionAddition() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addFunctions(
+ listOf(
+ FunSpec.builder("getAnswer")
+ .addModifiers(PUBLIC)
+ .returns(Int::class)
+ .addStatement("return %L", 42)
+ .build(),
+ FunSpec.builder("getRandomQuantity")
+ .addModifiers(PUBLIC)
+ .returns(Int::class)
+ .addKdoc("chosen by fair dice roll ;)\n")
+ .addStatement("return %L", 4)
+ .build(),
+ ),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |
+ |public class Taco {
+ | public fun getAnswer(): Int = 42
+ |
+ | /**
+ | * chosen by fair dice roll ;)
+ | */
+ | public fun getRandomQuantity(): Int = 4
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun multipleSuperinterfaceAddition() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addSuperinterfaces(
+ listOf(
+ Serializable::class.asTypeName(),
+ EventListener::class.asTypeName(),
+ ),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.io.Serializable
+ |import java.util.EventListener
+ |
+ |public class Taco : Serializable, EventListener
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun multipleTypeVariableAddition() {
+ val location = TypeSpec.classBuilder("Location")
+ .addTypeVariables(
+ listOf(
+ TypeVariableName("T"),
+ TypeVariableName("P", Number::class),
+ ),
+ )
+ .build()
+ assertThat(toString(location)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Number
+ |
+ |public class Location<T, P : Number>
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun multipleTypeAddition() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addTypes(
+ listOf(
+ TypeSpec.classBuilder("Topping").build(),
+ TypeSpec.classBuilder("Sauce").build(),
+ ),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |public class Taco {
+ | public class Topping
+ |
+ | public class Sauce
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun tryCatch() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("addTopping")
+ .addParameter("topping", ClassName("com.squareup.tacos", "Topping"))
+ .beginControlFlow("try")
+ .addCode("/* do something tricky with the topping */\n")
+ .nextControlFlow(
+ "catch (e: %T)",
+ ClassName("com.squareup.tacos", "IllegalToppingException"),
+ )
+ .endControlFlow()
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public class Taco {
+ | public fun addTopping(topping: Topping): Unit {
+ | try {
+ | /* do something tricky with the topping */
+ | } catch (e: IllegalToppingException) {
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun ifElse() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("isDelicious")
+ .addParameter("count", INT)
+ .returns(BOOLEAN)
+ .beginControlFlow("if (count > 0)")
+ .addStatement("return true")
+ .nextControlFlow("else")
+ .addStatement("return false")
+ .endControlFlow()
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Boolean
+ |import kotlin.Int
+ |
+ |public class Taco {
+ | public fun isDelicious(count: Int): Boolean {
+ | if (count > 0) {
+ | return true
+ | } else {
+ | return false
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun whenReturn() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("toppingPrice")
+ .addParameter("topping", String::class)
+ .beginControlFlow("return when(topping)")
+ .addStatement("%S -> 1", "beef")
+ .addStatement("%S -> 2", "lettuce")
+ .addStatement("%S -> 3", "cheese")
+ .addStatement("else -> throw IllegalToppingException(topping)")
+ .endControlFlow()
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public class Taco {
+ | public fun toppingPrice(topping: String) = when(topping) {
+ | "beef" -> 1
+ | "lettuce" -> 2
+ | "cheese" -> 3
+ | else -> throw IllegalToppingException(topping)
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun literalFromAnything() {
+ val value = object : Any() {
+ override fun toString(): String {
+ return "foo"
+ }
+ }
+ assertThat(CodeBlock.of("%L", value).toString()).isEqualTo("foo")
+ }
+
+ @Test fun nameFromCharSequence() {
+ assertThat(CodeBlock.of("%N", "text").toString()).isEqualTo("text")
+ }
+
+ @Test fun nameFromProperty() {
+ val property = PropertySpec.builder("property", String::class).build()
+ assertThat(CodeBlock.of("%N", property).toString()).isEqualTo("`property`")
+ }
+
+ @Test fun nameFromParameter() {
+ val parameter = ParameterSpec.builder("parameter", String::class).build()
+ assertThat(CodeBlock.of("%N", parameter).toString()).isEqualTo("parameter")
+ }
+
+ @Test fun nameFromFunction() {
+ val funSpec = FunSpec.builder("method")
+ .addModifiers(KModifier.ABSTRACT)
+ .returns(String::class)
+ .build()
+ assertThat(CodeBlock.of("%N", funSpec).toString()).isEqualTo("method")
+ }
+
+ @Test fun nameFromType() {
+ val type = TypeSpec.classBuilder("Type").build()
+ assertThat(CodeBlock.of("%N", type).toString()).isEqualTo("Type")
+ }
+
+ @Test fun nameFromUnsupportedType() {
+ assertThrows<IllegalArgumentException> {
+ CodeBlock.builder().add("%N", String::class)
+ }.hasMessageThat().isEqualTo("expected name but was " + String::class)
+ }
+
+ @Test fun stringFromAnything() {
+ val value = object : Any() {
+ override fun toString(): String {
+ return "foo"
+ }
+ }
+ assertThat(CodeBlock.of("%S", value).toString()).isEqualTo("\"foo\"")
+ }
+
+ @Test fun stringFromNull() {
+ assertThat(CodeBlock.of("%S", null).toString()).isEqualTo("null")
+ }
+
+ @Test fun typeFromTypeName() {
+ val typeName = String::class.asTypeName()
+ assertThat(CodeBlock.of("%T", typeName).toString()).isEqualTo("kotlin.String")
+ }
+
+ @Test fun typeFromTypeMirror() {
+ val mirror = getElement(String::class).asType()
+ assertThat(CodeBlock.of("%T", mirror).toString()).isEqualTo("java.lang.String")
+ }
+
+ @Test fun typeFromTypeElement() {
+ val element = getElement(String::class)
+ assertThat(CodeBlock.of("%T", element).toString()).isEqualTo("java.lang.String")
+ }
+
+ @Test fun typeFromReflectType() {
+ assertThat(CodeBlock.of("%T", String::class).toString()).isEqualTo("kotlin.String")
+ }
+
+ @Test fun typeFromUnsupportedType() {
+ assertThrows<IllegalArgumentException> {
+ CodeBlock.builder().add("%T", "kotlin.String")
+ }.hasMessageThat().isEqualTo("expected type but was kotlin.String")
+ }
+
+ @Test fun tooFewArguments() {
+ assertThrows<IllegalArgumentException> {
+ CodeBlock.builder().add("%S")
+ }.hasMessageThat().isEqualTo("index 1 for '%S' not in range (received 0 arguments)")
+ }
+
+ @Test fun unusedArgumentsRelative() {
+ assertThrows<IllegalArgumentException> {
+ CodeBlock.builder().add("%L %L", "a", "b", "c")
+ }.hasMessageThat().isEqualTo("unused arguments: expected 2, received 3")
+ }
+
+ @Test fun unusedArgumentsIndexed() {
+ assertThrows<IllegalArgumentException> {
+ CodeBlock.builder().add("%1L %2L", "a", "b", "c")
+ }.hasMessageThat().isEqualTo("unused argument: %3")
+
+ assertThrows<IllegalArgumentException> {
+ CodeBlock.builder().add("%1L %1L %1L", "a", "b", "c")
+ }.hasMessageThat().isEqualTo("unused arguments: %2, %3")
+
+ assertThrows<IllegalArgumentException> {
+ CodeBlock.builder().add("%3L %1L %3L %1L %3L", "a", "b", "c", "d")
+ }.hasMessageThat().isEqualTo("unused arguments: %2, %4")
+ }
+
+ @Test fun superClassOnlyValidForClasses() {
+ assertThrows<IllegalStateException> {
+ TypeSpec.annotationBuilder("A").superclass(Any::class.asClassName())
+ }
+
+ assertThrows<IllegalStateException> {
+ TypeSpec.enumBuilder("E").superclass(Any::class.asClassName())
+ }
+
+ assertThrows<IllegalStateException> {
+ TypeSpec.interfaceBuilder("I").superclass(Any::class.asClassName())
+ }
+ }
+
+ @Test fun superClassConstructorParametersOnlyValidForClasses() {
+ assertThrows<IllegalStateException> {
+ TypeSpec.annotationBuilder("A").addSuperclassConstructorParameter("")
+ }
+
+ assertThrows<IllegalStateException> {
+ TypeSpec.enumBuilder("E").addSuperclassConstructorParameter("")
+ }
+
+ assertThrows<IllegalStateException> {
+ TypeSpec.interfaceBuilder("I").addSuperclassConstructorParameter("")
+ }
+ }
+
+ @Test fun anonymousClassesCannotHaveModifiersOrTypeVariable() {
+ assertThrows<IllegalStateException> {
+ TypeSpec.anonymousClassBuilder().addModifiers(PUBLIC)
+ }
+
+ assertThrows<IllegalStateException> {
+ TypeSpec.anonymousClassBuilder().addTypeVariable(TypeVariableName("T")).build()
+ }
+
+ assertThrows<IllegalStateException> {
+ TypeSpec.anonymousClassBuilder().addTypeVariables(listOf(TypeVariableName("T"))).build()
+ }
+ }
+
+ @Test fun invalidSuperClass() {
+ assertThrows<IllegalStateException> {
+ TypeSpec.classBuilder("foo")
+ .superclass(List::class)
+ .superclass(Map::class)
+ }
+ }
+
+ @Test fun staticCodeBlock() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addProperty("foo", String::class, KModifier.PRIVATE)
+ .addProperty(
+ PropertySpec.builder("FOO", String::class, KModifier.PRIVATE, KModifier.CONST)
+ .initializer("%S", "FOO")
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("toString")
+ .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+ .returns(String::class)
+ .addStatement("return FOO")
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public class Taco {
+ | private val foo: String
+ |
+ | private const val FOO: String = "FOO"
+ |
+ | public override fun toString(): String = FOO
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun initializerBlockInRightPlace() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addProperty("foo", String::class, KModifier.PRIVATE)
+ .addProperty(
+ PropertySpec.builder("FOO", String::class, KModifier.PRIVATE, KModifier.CONST)
+ .initializer("%S", "FOO")
+ .build(),
+ )
+ .addFunction(FunSpec.constructorBuilder().build())
+ .addFunction(
+ FunSpec.builder("toString")
+ .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+ .returns(String::class)
+ .addStatement("return FOO")
+ .build(),
+ )
+ .addInitializerBlock(
+ CodeBlock.builder()
+ .addStatement("foo = %S", "FOO")
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public class Taco {
+ | private val foo: String
+ |
+ | private const val FOO: String = "FOO"
+ |
+ | init {
+ | foo = "FOO"
+ | }
+ |
+ | public constructor()
+ |
+ | public override fun toString(): String = FOO
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun initializersToBuilder() {
+ // Tests if toBuilder() contains instance initializers
+ val taco = TypeSpec.classBuilder("Taco")
+ .addProperty(PropertySpec.builder("foo", String::class, KModifier.PRIVATE).build())
+ .addProperty(
+ PropertySpec.builder("FOO", String::class, KModifier.PRIVATE, KModifier.CONST)
+ .initializer("%S", "FOO")
+ .build(),
+ )
+ .addFunction(FunSpec.constructorBuilder().build())
+ .addFunction(
+ FunSpec.builder("toString")
+ .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+ .returns(String::class)
+ .addStatement("return FOO")
+ .build(),
+ )
+ .build()
+
+ val recreatedTaco = taco.toBuilder().build()
+ assertThat(toString(taco)).isEqualTo(toString(recreatedTaco))
+
+ val initializersAdded = taco.toBuilder()
+ .addInitializerBlock(
+ CodeBlock.builder()
+ .addStatement("foo = %S", "instanceFoo")
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(initializersAdded)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public class Taco {
+ | private val foo: String
+ |
+ | private const val FOO: String = "FOO"
+ |
+ | init {
+ | foo = "instanceFoo"
+ | }
+ |
+ | public constructor()
+ |
+ | public override fun toString(): String = FOO
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun generalToBuilderEqualityTest() {
+ val originatingElement = FakeElement()
+ val comprehensiveTaco = TypeSpec.classBuilder("Taco")
+ .addKdoc("SuperTaco")
+ .addAnnotation(SuppressWarnings::class)
+ .addModifiers(DATA)
+ .addTypeVariable(TypeVariableName.of("State", listOf(ANY), IN).copy(reified = true))
+ .addType(
+ TypeSpec.companionObjectBuilder()
+ .build(),
+ )
+ .addType(
+ TypeSpec.classBuilder("InnerTaco")
+ .addModifiers(INNER)
+ .build(),
+ )
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .build(),
+ )
+ .superclass(ClassName("texmexfood", "TortillaBased"))
+ .addSuperclassConstructorParameter("true")
+ .addProperty(
+ PropertySpec.builder("meat", ClassName("texmexfood", "Meat"))
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("fold")
+ .build(),
+ )
+ .addSuperinterface(ClassName("texmexfood", "Consumable"))
+ .addOriginatingElement(originatingElement)
+ .build()
+
+ val newTaco = comprehensiveTaco.toBuilder().build()
+ assertThat(newTaco).isEqualTo(comprehensiveTaco)
+ assertThat(newTaco.originatingElements).containsExactly(originatingElement)
+ }
+
+ @Test fun generalEnumToBuilderEqualityTest() {
+ val bestTexMexEnum = TypeSpec.enumBuilder("BestTexMex")
+ .addEnumConstant("TACO")
+ .addEnumConstant("BREAKFAST_TACO")
+ .build()
+
+ assertThat(bestTexMexEnum.toBuilder().build()).isEqualTo(bestTexMexEnum)
+ }
+
+ @Test fun generalInterfaceBuilderEqualityTest() {
+ val taco = TypeSpec.interfaceBuilder("Taco")
+ .addProperty("isVegan", Boolean::class)
+ .addSuperinterface(Runnable::class)
+ .build()
+ assertThat(taco.toBuilder().build()).isEqualTo(taco)
+ }
+
+ @Test fun generalAnnotationBuilderEqualityTest() {
+ val annotation = TypeSpec.annotationBuilder("MyAnnotation")
+ .addModifiers(KModifier.PUBLIC)
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter(
+ ParameterSpec.builder("test", Int::class)
+ .build(),
+ )
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("test", Int::class)
+ .initializer("test")
+ .build(),
+ )
+ .build()
+ assertThat(annotation.toBuilder().build()).isEqualTo(annotation)
+ }
+
+ @Test fun generalExpectClassBuilderEqualityTest() {
+ val expectSpec = TypeSpec.expectClassBuilder("AtmoicRef")
+ .addModifiers(KModifier.INTERNAL)
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("value", Int::class)
+ .build(),
+ )
+ .addProperty(PropertySpec.builder("value", Int::class).build())
+ .addFunction(
+ FunSpec.builder("get")
+ .returns(Int::class)
+ .build(),
+ )
+ .build()
+ assertThat(expectSpec.toBuilder().build()).isEqualTo(expectSpec)
+ }
+
+ @Test fun generalObjectBuilderEqualityTest() {
+ val objectSpec = TypeSpec.objectBuilder("MyObject")
+ .addModifiers(KModifier.PUBLIC)
+ .addProperty("tacos", Int::class)
+ .addInitializerBlock(CodeBlock.builder().build())
+ .addFunction(
+ FunSpec.builder("test")
+ .addModifiers(KModifier.PUBLIC)
+ .build(),
+ )
+ .build()
+ assertThat(objectSpec.toBuilder().build()).isEqualTo(objectSpec)
+ }
+
+ @Test fun generalAnonymousClassBuilderEqualityTest() {
+ val anonObjectSpec = TypeSpec.anonymousClassBuilder()
+ .addSuperinterface(Runnable::class)
+ .addFunction(
+ FunSpec.builder("run")
+ .addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
+ .build(),
+ )
+ .build()
+ assertThat(anonObjectSpec.toBuilder().build()).isEqualTo(anonObjectSpec)
+ }
+
+ @Test fun initializerBlockUnsupportedExceptionOnInterface() {
+ val interfaceBuilder = TypeSpec.interfaceBuilder("Taco")
+ assertThrows<IllegalStateException> {
+ interfaceBuilder.addInitializerBlock(CodeBlock.builder().build())
+ }
+ }
+
+ @Test fun initializerBlockUnsupportedExceptionOnAnnotation() {
+ val annotationBuilder = TypeSpec.annotationBuilder("Taco")
+ assertThrows<IllegalStateException> {
+ annotationBuilder.addInitializerBlock(CodeBlock.builder().build())
+ }
+ }
+
+ @Test fun equalsAndHashCode() {
+ var a = TypeSpec.interfaceBuilder("taco").build()
+ var b = TypeSpec.interfaceBuilder("taco").build()
+ assertThat(a == b).isTrue()
+ assertThat(a.hashCode()).isEqualTo(b.hashCode())
+ a = TypeSpec.classBuilder("taco").build()
+ b = TypeSpec.classBuilder("taco").build()
+ assertThat(a == b).isTrue()
+ assertThat(a.hashCode()).isEqualTo(b.hashCode())
+ a = TypeSpec.enumBuilder("taco").addEnumConstant("SALSA").build()
+ b = TypeSpec.enumBuilder("taco").addEnumConstant("SALSA").build()
+ assertThat(a == b).isTrue()
+ assertThat(a.hashCode()).isEqualTo(b.hashCode())
+ a = TypeSpec.annotationBuilder("taco").build()
+ b = TypeSpec.annotationBuilder("taco").build()
+ assertThat(a == b).isTrue()
+ assertThat(a.hashCode()).isEqualTo(b.hashCode())
+ }
+
+ @Test fun classNameFactories() {
+ val className = ClassName("com.example", "Example")
+ assertThat(TypeSpec.classBuilder(className).build().name).isEqualTo("Example")
+ assertThat(TypeSpec.interfaceBuilder(className).build().name).isEqualTo("Example")
+ assertThat(TypeSpec.enumBuilder(className).addEnumConstant("A").build().name).isEqualTo("Example")
+ assertThat(TypeSpec.annotationBuilder(className).build().name).isEqualTo("Example")
+ }
+
+ @Test fun objectType() {
+ val type = TypeSpec.objectBuilder("MyObject")
+ .addModifiers(KModifier.PUBLIC)
+ .addProperty("tacos", Int::class)
+ .addInitializerBlock(CodeBlock.builder().build())
+ .addFunction(
+ FunSpec.builder("test")
+ .addModifiers(KModifier.PUBLIC)
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(type)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |import kotlin.Unit
+ |
+ |public object MyObject {
+ | public val tacos: Int
+ |
+ | init {
+ | }
+ |
+ | public fun test(): Unit {
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun objectClassWithSupertype() {
+ val superclass = ClassName("com.squareup.wire", "Message")
+ val type = TypeSpec.objectBuilder("MyObject")
+ .addModifiers(KModifier.PUBLIC)
+ .superclass(superclass)
+ .addInitializerBlock(CodeBlock.builder().build())
+ .addFunction(
+ FunSpec.builder("test")
+ .addModifiers(KModifier.PUBLIC)
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(type)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.wire.Message
+ |import kotlin.Unit
+ |
+ |public object MyObject : Message() {
+ | init {
+ | }
+ |
+ | public fun test(): Unit {
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun companionObject() {
+ val companion = TypeSpec.companionObjectBuilder()
+ .addProperty(
+ PropertySpec.builder("tacos", Int::class)
+ .initializer("%L", 42)
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("test")
+ .addModifiers(KModifier.PUBLIC)
+ .build(),
+ )
+ .build()
+
+ val type = TypeSpec.classBuilder("MyClass")
+ .addModifiers(KModifier.PUBLIC)
+ .addType(companion)
+ .build()
+
+ assertThat(toString(type)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |import kotlin.Unit
+ |
+ |public class MyClass {
+ | public companion object {
+ | public val tacos: Int = 42
+ |
+ | public fun test(): Unit {
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun companionObjectWithInitializer() {
+ val companion = TypeSpec.companionObjectBuilder()
+ .addProperty(
+ PropertySpec.builder("tacos", Int::class)
+ .mutable()
+ .initializer("%L", 24)
+ .build(),
+ )
+ .addInitializerBlock(
+ CodeBlock.builder()
+ .addStatement("tacos = %L", 42)
+ .build(),
+ )
+ .build()
+
+ val type = TypeSpec.classBuilder("MyClass")
+ .addModifiers(KModifier.PUBLIC)
+ .addType(companion)
+ .build()
+
+ assertThat(toString(type)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |
+ |public class MyClass {
+ | public companion object {
+ | public var tacos: Int = 24
+ |
+ | init {
+ | tacos = 42
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun companionObjectWithName() {
+ val companion = TypeSpec.companionObjectBuilder("Factory")
+ .addFunction(FunSpec.builder("tacos").build())
+ .build()
+
+ val type = TypeSpec.classBuilder("MyClass")
+ .addType(companion)
+ .build()
+
+ assertThat(toString(type)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public class MyClass {
+ | public companion object Factory {
+ | public fun tacos(): Unit {
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun companionObjectOnInterface() {
+ val companion = TypeSpec.companionObjectBuilder()
+ .addFunction(
+ FunSpec.builder("test")
+ .addModifiers(KModifier.PUBLIC)
+ .build(),
+ )
+ .build()
+
+ val type = TypeSpec.interfaceBuilder("MyInterface")
+ .addModifiers(KModifier.PUBLIC)
+ .addType(companion)
+ .build()
+
+ assertThat(toString(type)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public interface MyInterface {
+ | public companion object {
+ | public fun test(): Unit {
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun companionObjectOnEnum() {
+ val companion = TypeSpec.companionObjectBuilder()
+ .addFunction(
+ FunSpec.builder("test")
+ .addModifiers(KModifier.PUBLIC)
+ .build(),
+ )
+ .build()
+
+ val enumBuilder = TypeSpec.enumBuilder("MyEnum")
+ .addEnumConstant("FOO")
+ .addEnumConstant("BAR")
+ .addModifiers(KModifier.PUBLIC)
+ .addType(companion)
+ .build()
+
+ assertThat(toString(enumBuilder)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public enum class MyEnum {
+ | FOO,
+ | BAR,
+ | ;
+ |
+ | public companion object {
+ | public fun test(): Unit {
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun companionObjectOnObjectNotAllowed() {
+ val companion = TypeSpec.companionObjectBuilder()
+ .addFunction(
+ FunSpec.builder("test")
+ .addModifiers(KModifier.PUBLIC)
+ .build(),
+ )
+ .build()
+
+ val objectBuilder = TypeSpec.objectBuilder("MyObject")
+ .addModifiers(KModifier.PUBLIC)
+ .addType(companion)
+
+ assertThrows<IllegalArgumentException> {
+ objectBuilder.build()
+ }
+ }
+
+ @Test fun companionObjectSuper() {
+ val superclass = ClassName("com.squareup.wire", "Message")
+ val companion = TypeSpec.companionObjectBuilder()
+ .superclass(superclass)
+ .addFunction(
+ FunSpec.builder("test")
+ .addModifiers(KModifier.PUBLIC)
+ .build(),
+ )
+ .build()
+
+ val type = TypeSpec.classBuilder("MyClass")
+ .addModifiers(KModifier.PUBLIC)
+ .addType(companion)
+ .build()
+
+ assertThat(toString(type)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.wire.Message
+ |import kotlin.Unit
+ |
+ |public class MyClass {
+ | public companion object : Message() {
+ | public fun test(): Unit {
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun propertyInPrimaryConstructor() {
+ val type = TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("a", Int::class)
+ .addParameter("b", String::class)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("a", Int::class)
+ .initializer("a")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("b", String::class)
+ .initializer("b")
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(type)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |import kotlin.String
+ |
+ |public class Taco(
+ | public val a: Int,
+ | public val b: String,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun propertyWithKdocInPrimaryConstructor() {
+ val type = TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("a", Int::class)
+ .addParameter("b", String::class)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("a", Int::class)
+ .initializer("a")
+ .addKdoc("KDoc\n")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("b", String::class)
+ .initializer("b")
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(type)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |import kotlin.String
+ |
+ |public class Taco(
+ | /**
+ | * KDoc
+ | */
+ | public val a: Int,
+ | public val b: String,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun annotatedConstructor() {
+ val injectAnnotation = ClassName("javax.inject", "Inject")
+ val taco = TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addAnnotation(AnnotationSpec.builder(injectAnnotation).build())
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import javax.inject.Inject
+ |
+ |public class Taco @Inject constructor()
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun internalConstructor() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addModifiers(INTERNAL)
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |public class Taco internal constructor()
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun annotatedInternalConstructor() {
+ val injectAnnotation = ClassName("javax.inject", "Inject")
+ val taco = TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addAnnotation(AnnotationSpec.builder(injectAnnotation).build())
+ .addModifiers(INTERNAL)
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import javax.inject.Inject
+ |
+ |public class Taco @Inject internal constructor()
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun multipleAnnotationsInternalConstructor() {
+ val injectAnnotation = ClassName("javax.inject", "Inject")
+ val namedAnnotation = ClassName("javax.inject", "Named")
+ val taco = TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addAnnotation(AnnotationSpec.builder(injectAnnotation).build())
+ .addAnnotation(AnnotationSpec.builder(namedAnnotation).build())
+ .addModifiers(INTERNAL)
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import javax.inject.Inject
+ |import javax.inject.Named
+ |
+ |public class Taco @Inject @Named internal constructor()
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun importNonNullableProperty() {
+ val type = String::class.asTypeName()
+ val taco = TypeSpec.classBuilder("Taco")
+ .addProperty(
+ PropertySpec.builder("taco", type.copy(nullable = false))
+ .initializer("%S", "taco")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("nullTaco", type.copy(nullable = true))
+ .initializer("null")
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |
+ |public class Taco {
+ | public val taco: String = "taco"
+ |
+ | public val nullTaco: String? = null
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun superclassConstructorParams() {
+ val taco = TypeSpec.classBuilder("Foo")
+ .superclass(ClassName(tacosPackage, "Bar"))
+ .addSuperclassConstructorParameter("%S", "foo")
+ .addSuperclassConstructorParameter(CodeBlock.of("%L", 42))
+ .build()
+
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |public class Foo : Bar("foo", 42)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun superclassConstructorParamsForbiddenForAnnotation() {
+ assertThrows<IllegalStateException> {
+ TypeSpec.annotationBuilder("Taco")
+ .addSuperclassConstructorParameter("%S", "foo")
+ }
+ }
+
+ @Test fun classExtendsNoPrimaryConstructor() {
+ val typeSpec = TypeSpec.classBuilder("IoException")
+ .superclass(Exception::class)
+ .addFunction(FunSpec.constructorBuilder().build())
+ .build()
+
+ assertThat(toString(typeSpec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.lang.Exception
+ |
+ |public class IoException : Exception {
+ | public constructor()
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun classExtendsNoPrimaryOrSecondaryConstructor() {
+ val typeSpec = TypeSpec.classBuilder("IoException")
+ .superclass(Exception::class)
+ .build()
+
+ assertThat(toString(typeSpec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.lang.Exception
+ |
+ |public class IoException : Exception()
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun classExtendsNoPrimaryConstructorButSuperclassParams() {
+ assertThrows<IllegalArgumentException> {
+ TypeSpec.classBuilder("IoException")
+ .superclass(Exception::class)
+ .addSuperclassConstructorParameter("%S", "hey")
+ .addFunction(FunSpec.constructorBuilder().build())
+ .build()
+ }.hasMessageThat().isEqualTo(
+ "types without a primary constructor cannot specify secondary constructors and superclass constructor parameters",
+ )
+ }
+
+ @Test fun constructorWithDefaultParamValue() {
+ val type = TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter(
+ ParameterSpec.builder("a", Int::class)
+ .defaultValue("1")
+ .build(),
+ )
+ .addParameter(
+ ParameterSpec
+ .builder("b", String::class.asTypeName().copy(nullable = true))
+ .defaultValue("null")
+ .build(),
+ )
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("a", Int::class)
+ .initializer("a")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("b", String::class.asTypeName().copy(nullable = true))
+ .initializer("b")
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(type)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |import kotlin.String
+ |
+ |public class Taco(
+ | public val a: Int = 1,
+ | public val b: String? = null,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun constructorDelegation() {
+ val type = TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("a", String::class.asTypeName().copy(nullable = true))
+ .addParameter("b", String::class.asTypeName().copy(nullable = true))
+ .addParameter("c", String::class.asTypeName().copy(nullable = true))
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("a", String::class.asTypeName().copy(nullable = true))
+ .initializer("a")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("b", String::class.asTypeName().copy(nullable = true))
+ .initializer("b")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("c", String::class.asTypeName().copy(nullable = true))
+ .initializer("c")
+ .build(),
+ )
+ .addFunction(
+ FunSpec.constructorBuilder()
+ .addParameter("map", Map::class.parameterizedBy(String::class, String::class))
+ .callThisConstructor(
+ CodeBlock.of("map[%S]", "a"),
+ CodeBlock.of("map[%S]", "b"),
+ CodeBlock.of("map[%S]", "c"),
+ )
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(type)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.collections.Map
+ |
+ |public class Taco(
+ | public val a: String?,
+ | public val b: String?,
+ | public val c: String?,
+ |) {
+ | public constructor(map: Map<String, String>) : this(map["a"], map["b"], map["c"])
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun internalFunForbiddenInInterface() {
+ val type = TypeSpec.interfaceBuilder("ITaco")
+
+ assertThrows<IllegalArgumentException> {
+ type.addFunction(
+ FunSpec.builder("eat")
+ .addModifiers(ABSTRACT, INTERNAL)
+ .build(),
+ )
+ .build()
+ }.hasMessageThat().isEqualTo("modifiers [ABSTRACT, INTERNAL] must contain none of [INTERNAL, PROTECTED]")
+
+ assertThrows<IllegalArgumentException> {
+ type.addFunctions(
+ listOf(
+ FunSpec.builder("eat")
+ .addModifiers(ABSTRACT, INTERNAL)
+ .build(),
+ ),
+ )
+ .build()
+ }.hasMessageThat().isEqualTo("modifiers [ABSTRACT, INTERNAL] must contain none of [INTERNAL, PROTECTED]")
+ }
+
+ @Test fun privateAbstractFunForbiddenInInterface() {
+ val type = TypeSpec.interfaceBuilder("ITaco")
+
+ assertThrows<IllegalArgumentException> {
+ type.addFunction(
+ FunSpec.builder("eat")
+ .addModifiers(ABSTRACT, PRIVATE)
+ .build(),
+ )
+ .build()
+ }.hasMessageThat().isEqualTo("modifiers [ABSTRACT, PRIVATE] must contain none or only one of [ABSTRACT, PRIVATE]")
+
+ assertThrows<IllegalArgumentException> {
+ type.addFunctions(
+ listOf(
+ FunSpec.builder("eat")
+ .addModifiers(ABSTRACT, PRIVATE)
+ .build(),
+ ),
+ )
+ .build()
+ }.hasMessageThat().isEqualTo("modifiers [ABSTRACT, PRIVATE] must contain none or only one of [ABSTRACT, PRIVATE]")
+ }
+
+ @Test fun internalFunForbiddenInAnnotation() {
+ val type = TypeSpec.annotationBuilder("Taco")
+
+ assertThrows<IllegalArgumentException> {
+ type.addFunction(
+ FunSpec.builder("eat")
+ .addModifiers(INTERNAL)
+ .build(),
+ )
+ .build()
+ }.hasMessageThat().isEqualTo("annotation class Taco.eat requires modifiers [PUBLIC, ABSTRACT]")
+
+ assertThrows<IllegalArgumentException> {
+ type.addFunctions(
+ listOf(
+ FunSpec.builder("eat")
+ .addModifiers(INTERNAL)
+ .build(),
+ ),
+ )
+ .build()
+ }.hasMessageThat().isEqualTo("annotation class Taco.eat requires modifiers [PUBLIC, ABSTRACT]")
+ }
+
+ @Test fun classHeaderFormatting() {
+ val typeSpec = TypeSpec.classBuilder("Person")
+ .addModifiers(DATA)
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("id", Int::class)
+ .addParameter("name", String::class)
+ .addParameter("surname", String::class)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("id", Int::class, KModifier.OVERRIDE)
+ .initializer("id")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("name", String::class, KModifier.OVERRIDE)
+ .initializer("name")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("surname", String::class, KModifier.OVERRIDE)
+ .initializer("surname")
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(typeSpec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |import kotlin.String
+ |
+ |public data class Person(
+ | public override val id: Int,
+ | public override val name: String,
+ | public override val surname: String,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test
+ fun classHeaderAnnotations() {
+ val idParameterSpec = ParameterSpec.builder("id", Int::class)
+ .addAnnotation(ClassName("com.squareup.kotlinpoet", "Id"))
+ .defaultValue("1")
+ .build()
+
+ val typeSpec = TypeSpec.classBuilder("Person")
+ .addModifiers(DATA)
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter(idParameterSpec)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("id", Int::class)
+ .addModifiers(PRIVATE)
+ .initializer("id")
+ .addAnnotation(ClassName("com.squareup.kotlinpoet", "OrderBy"))
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(typeSpec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import com.squareup.kotlinpoet.Id
+ |import com.squareup.kotlinpoet.OrderBy
+ |import kotlin.Int
+ |
+ |public data class Person(
+ | @OrderBy
+ | @Id
+ | private val id: Int = 1,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun literalPropertySpec() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("shell")
+ .addCode(
+ CodeBlock.of(
+ "%L",
+ PropertySpec.builder("taco1", String::class.asTypeName())
+ .initializer("%S", "Taco!").build(),
+ ),
+ )
+ .addCode(
+ CodeBlock.of(
+ "%L",
+ PropertySpec.builder("taco2", String::class.asTypeName().copy(nullable = true))
+ .initializer("null")
+ .build(),
+ ),
+ )
+ .addCode(
+ CodeBlock.of(
+ "%L",
+ PropertySpec.builder("taco3", String::class.asTypeName(), KModifier.LATEINIT)
+ .mutable()
+ .build(),
+ ),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.Unit
+ |
+ |public class Taco {
+ | public fun shell(): Unit {
+ | val taco1: String = "Taco!"
+ | val taco2: String? = null
+ | lateinit var taco3: String
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun basicDelegateTest() {
+ val type = TypeSpec.classBuilder("Guac")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("somethingElse", String::class)
+ .build(),
+ )
+ .addSuperinterface(
+ Consumer::class.parameterizedBy(String::class),
+ CodeBlock.of("({ println(it) })"),
+ )
+ .build()
+
+ val expect = """
+ |package com.squareup.tacos
+ |
+ |import java.util.function.Consumer
+ |import kotlin.String
+ |
+ |public class Guac(
+ | somethingElse: String,
+ |) : Consumer<String> by ({ println(it) })
+ |
+ """.trimMargin()
+
+ assertThat(toString(type)).isEqualTo(expect)
+ }
+
+ @Test fun testDelegateOnObject() {
+ val type = TypeSpec.objectBuilder("Guac")
+ .addSuperinterface(
+ Consumer::class.parameterizedBy(String::class),
+ CodeBlock.of("({ println(it) })"),
+ )
+ .build()
+
+ val expect = """
+ |package com.squareup.tacos
+ |
+ |import java.util.function.Consumer
+ |import kotlin.String
+ |
+ |public object Guac : Consumer<String> by ({ println(it) })
+ |
+ """.trimMargin()
+
+ assertThat(toString(type)).isEqualTo(expect)
+ }
+
+ @Test fun testMultipleDelegates() {
+ val type = TypeSpec.classBuilder("StringToInteger")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .build(),
+ )
+ .addSuperinterface(
+ Function::class.parameterizedBy(String::class, Int::class),
+ CodeBlock.of("Function ({ text -> text.toIntOrNull() ?: 0 })"),
+ )
+ .addSuperinterface(
+ Runnable::class,
+ CodeBlock.of("Runnable ({ %T.debug(\"Hello world\") })", Logger::class.asTypeName()),
+ )
+ .build()
+
+ val expect = """
+ |package com.squareup.tacos
+ |
+ |import java.lang.Runnable
+ |import java.util.logging.Logger
+ |import kotlin.Function
+ |import kotlin.Int
+ |import kotlin.String
+ |
+ |public class StringToInteger() : Function<String, Int> by Function ({ text -> text.toIntOrNull() ?:
+ | 0 }), Runnable by Runnable ({ Logger.debug("Hello world") })
+ |
+ """.trimMargin()
+
+ assertThat(toString(type)).isEqualTo(expect)
+ }
+
+ @Test fun testNoSuchParameterDelegate() {
+ assertThrows<IllegalArgumentException> {
+ TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("other", String::class)
+ .build(),
+ )
+ .addSuperinterface(KFunction::class, "notOther")
+ .build()
+ }.hasMessageThat().isEqualTo("no such constructor parameter 'notOther' to delegate to for type 'Taco'")
+ }
+
+ @Test fun failAddParamDelegateWhenNullConstructor() {
+ assertThrows<IllegalArgumentException> {
+ TypeSpec.classBuilder("Taco")
+ .addSuperinterface(Runnable::class, "etc")
+ .build()
+ }.hasMessageThat().isEqualTo("delegating to constructor parameter requires not-null constructor")
+ }
+
+ @Test fun testAddedDelegateByParamName() {
+ val type = TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("superString", Function::class)
+ .build(),
+ )
+ .addSuperinterface(Function::class, "superString")
+ .build()
+
+ assertThat(toString(type)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Function
+ |
+ |public class Taco(
+ | superString: Function,
+ |) : Function by superString
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun failOnAddExistingDelegateType() {
+ assertThrows<IllegalArgumentException> {
+ TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("superString", Function::class)
+ .build(),
+ )
+ .addSuperinterface(Function::class, CodeBlock.of("{ print(Hello) }"))
+ .addSuperinterface(Function::class, "superString")
+ .build()
+ fail()
+ }.hasMessageThat().isEqualTo(
+ "'Taco' can not delegate to kotlin.Function " +
+ "by superString with existing declaration by { print(Hello) }",
+ )
+ }
+
+ @Test fun testDelegateIfaceWithOtherParamTypeName() {
+ val entity = ClassName(tacosPackage, "Entity")
+ val entityBuilder = ClassName(tacosPackage, "EntityBuilder")
+ val type = TypeSpec.classBuilder("EntityBuilder")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter(
+ ParameterSpec.builder(
+ "argBuilder",
+ ClassName(tacosPackage, "Payload")
+ .parameterizedBy(entityBuilder, entity),
+ )
+ .defaultValue("Payload.create()")
+ .build(),
+ )
+ .build(),
+ )
+ .addSuperinterface(
+ ClassName(tacosPackage, "TypeBuilder")
+ .parameterizedBy(entityBuilder, entity),
+ "argBuilder",
+ )
+ .build()
+
+ assertThat(toString(type)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |public class EntityBuilder(
+ | argBuilder: Payload<EntityBuilder, Entity> = Payload.create(),
+ |) : TypeBuilder<EntityBuilder, Entity> by argBuilder
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun externalClassFunctionHasNoBody() {
+ val typeSpec = TypeSpec.classBuilder("Foo")
+ .addModifiers(KModifier.EXTERNAL)
+ .addFunction(FunSpec.builder("bar").addModifiers(KModifier.EXTERNAL).build())
+ .build()
+
+ assertThat(toString(typeSpec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public external class Foo {
+ | public fun bar(): Unit
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun externalInterfaceWithMembers() {
+ val typeSpec = TypeSpec.interfaceBuilder("Foo")
+ .addModifiers(KModifier.EXTERNAL)
+ .addProperty(PropertySpec.builder("baz", String::class).addModifiers(KModifier.EXTERNAL).build())
+ .addFunction(FunSpec.builder("bar").addModifiers(KModifier.EXTERNAL).build())
+ .build()
+
+ assertThat(toString(typeSpec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.Unit
+ |
+ |public external interface Foo {
+ | public val baz: String
+ |
+ | public fun bar(): Unit
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun externalObjectWithMembers() {
+ val typeSpec = TypeSpec.objectBuilder("Foo")
+ .addModifiers(KModifier.EXTERNAL)
+ .addProperty(PropertySpec.builder("baz", String::class).addModifiers(KModifier.EXTERNAL).build())
+ .addFunction(FunSpec.builder("bar").addModifiers(KModifier.EXTERNAL).build())
+ .build()
+
+ assertThat(toString(typeSpec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.Unit
+ |
+ |public external object Foo {
+ | public val baz: String
+ |
+ | public fun bar(): Unit
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun externalClassWithNestedTypes() {
+ val typeSpec = TypeSpec.classBuilder("Foo")
+ .addModifiers(KModifier.EXTERNAL)
+ .addType(
+ TypeSpec.classBuilder("Nested1")
+ .addModifiers(KModifier.EXTERNAL)
+ .addType(
+ TypeSpec.objectBuilder("Nested2")
+ .addModifiers(KModifier.EXTERNAL)
+ .addFunction(FunSpec.builder("bar").addModifiers(KModifier.EXTERNAL).build())
+ .build(),
+ )
+ .addFunction(FunSpec.builder("baz").addModifiers(KModifier.EXTERNAL).build())
+ .build(),
+ )
+ .addType(
+ TypeSpec.companionObjectBuilder()
+ .addModifiers(KModifier.EXTERNAL)
+ .addFunction(FunSpec.builder("qux").addModifiers(KModifier.EXTERNAL).build())
+ .build(),
+ )
+ .build()
+
+ assertThat(toString(typeSpec)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |
+ |public external class Foo {
+ | public class Nested1 {
+ | public fun baz(): Unit
+ |
+ | public object Nested2 {
+ | public fun bar(): Unit
+ | }
+ | }
+ |
+ | public companion object {
+ | public fun qux(): Unit
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun isEnum() {
+ val enum = TypeSpec.enumBuilder("Topping")
+ .addEnumConstant("CHEESE")
+ .build()
+ assertThat(enum.isEnum).isTrue()
+ }
+
+ @Test fun isAnnotation() {
+ val annotation = TypeSpec.annotationBuilder("Taco")
+ .build()
+ assertThat(annotation.isAnnotation).isTrue()
+ }
+
+ @Test fun escapePunctuationInTypeName() {
+ assertThat(TypeSpec.classBuilder("With-Hyphen").build().toString()).isEqualTo(
+ """
+ |public class `With-Hyphen`
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun multipleCompanionObjects() {
+ assertThrows<IllegalArgumentException> {
+ TypeSpec.classBuilder("Taco")
+ .addTypes(
+ listOf(
+ TypeSpec.companionObjectBuilder()
+ .build(),
+ TypeSpec.companionObjectBuilder()
+ .build(),
+ ),
+ )
+ .build()
+ }
+ }
+
+ @Test fun objectKindIsCompanion() {
+ val companionObject = TypeSpec.companionObjectBuilder()
+ .build()
+ assertThat(companionObject.isCompanion).isTrue()
+ }
+
+ @Test fun typeNamesCollision() {
+ val sqlTaco = ClassName("java.sql", "Taco")
+ val source = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .addModifiers(DATA)
+ .addProperty(
+ PropertySpec.builder("madeFreshDatabaseDate", sqlTaco)
+ .initializer("madeFreshDatabaseDate")
+ .build(),
+ )
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("madeFreshDatabaseDate", sqlTaco)
+ .addParameter("fooNt", INT)
+ .build(),
+ )
+ .addFunction(
+ FunSpec.constructorBuilder()
+ .addParameter("anotherTaco", ClassName("com.squareup.tacos", "Taco"))
+ .callThisConstructor(CodeBlock.of("%T.defaultInstance(), 0", sqlTaco))
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(source.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |
+ |public data class Taco(
+ | public val madeFreshDatabaseDate: java.sql.Taco,
+ | fooNt: Int,
+ |) {
+ | public constructor(anotherTaco: Taco) : this(java.sql.Taco.defaultInstance(), 0)
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun modifyAnnotations() {
+ val builder = TypeSpec.classBuilder("Taco")
+ .addAnnotation(
+ AnnotationSpec.builder(JvmName::class.asClassName())
+ .addMember("name = %S", "jvmWord")
+ .build(),
+ )
+
+ val javaWord = AnnotationSpec.builder(JvmName::class.asClassName())
+ .addMember("name = %S", "javaWord")
+ .build()
+ builder.annotationSpecs.clear()
+ builder.annotationSpecs.add(javaWord)
+
+ assertThat(builder.build().annotationSpecs).containsExactly(javaWord)
+ }
+
+ @Test fun modifyTypeVariableNames() {
+ val builder = TypeSpec.classBuilder("Taco")
+ .addTypeVariable(TypeVariableName("V"))
+
+ val tVar = TypeVariableName("T")
+ builder.typeVariables.clear()
+ builder.typeVariables.add(tVar)
+
+ assertThat(builder.build().typeVariables).containsExactly(tVar)
+ }
+
+ @Test fun modifyFunctions() {
+ val builder = TypeSpec.classBuilder("Taco")
+ .addFunction(FunSpec.builder("topping").build())
+
+ val seasoning = FunSpec.builder("seasoning").build()
+ builder.funSpecs.clear()
+ builder.funSpecs.add(seasoning)
+
+ assertThat(builder.build().funSpecs).containsExactly(seasoning)
+ }
+
+ @Test fun modifyTypeSpecs() {
+ val builder = TypeSpec.classBuilder("Taco")
+ .addType(TypeSpec.classBuilder("Topping").build())
+
+ val seasoning = TypeSpec.classBuilder("Seasoning").build()
+ builder.typeSpecs.clear()
+ builder.typeSpecs.add(seasoning)
+
+ assertThat(builder.build().typeSpecs).containsExactly(seasoning)
+ }
+
+ @Test fun modifySuperinterfaces() {
+ val builder = TypeSpec.classBuilder("Taco")
+ .addSuperinterface(List::class)
+
+ builder.superinterfaces.clear()
+ builder.superinterfaces[Set::class.asTypeName()] = CodeBlock.EMPTY
+
+ assertThat(builder.build().superinterfaces)
+ .containsExactlyEntriesIn(mapOf(Set::class.asTypeName() to CodeBlock.EMPTY))
+ }
+
+ @Test fun modifyProperties() {
+ val builder = TypeSpec.classBuilder("Taco")
+ .addProperty(PropertySpec.builder("topping", String::class.asClassName()).build())
+
+ val seasoning = PropertySpec.builder("seasoning", String::class.asClassName()).build()
+ builder.propertySpecs.clear()
+ builder.propertySpecs.add(seasoning)
+
+ assertThat(builder.build().propertySpecs).containsExactly(seasoning)
+ }
+
+ @Test fun modifyEnumConstants() {
+ val builder = TypeSpec.enumBuilder("Taco")
+ .addEnumConstant("TOPPING")
+
+ builder.enumConstants.clear()
+ builder.enumConstants["SEASONING"] = TypeSpec.anonymousClassBuilder().build()
+
+ assertThat(builder.build().enumConstants)
+ .containsExactlyEntriesIn(mapOf("SEASONING" to TypeSpec.anonymousClassBuilder().build()))
+ }
+
+ @Test fun modifySuperclassConstructorParams() {
+ val builder = TypeSpec.classBuilder("Taco")
+ .addSuperclassConstructorParameter(CodeBlock.of("seasoning = %S", "mild"))
+
+ val seasoning = CodeBlock.of("seasoning = %S", "spicy")
+ builder.superclassConstructorParameters.clear()
+ builder.superclassConstructorParameters.add(seasoning)
+
+ assertThat(builder.build().superclassConstructorParameters).containsExactly(seasoning)
+ }
+
+ // https://github.com/square/kotlinpoet/issues/565
+ @Test fun markerEnum() {
+ val spec = TypeSpec.enumBuilder("Topping")
+ .build()
+ assertThat(spec.toString()).isEqualTo(
+ """
+ |public enum class Topping
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/586
+ @Test fun classKdocWithoutTags() {
+ val typeSpec = TypeSpec.classBuilder("Foo")
+ .addKdoc("blah blah")
+ .build()
+ assertThat(typeSpec.toString()).isEqualTo(
+ """
+ |/**
+ | * blah blah
+ | */
+ |public class Foo
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun classWithPropertyKdoc() {
+ val typeSpec = TypeSpec.classBuilder("Foo")
+ .addProperty(
+ PropertySpec.builder("bar", String::class)
+ .addKdoc("The bar for your foo")
+ .build(),
+ )
+ .build()
+ assertThat(typeSpec.toString()).isEqualTo(
+ """
+ |public class Foo {
+ | /**
+ | * The bar for your foo
+ | */
+ | public val bar: kotlin.String
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/563
+ @Test fun kdocFormatting() {
+ val typeSpec = TypeSpec.classBuilder("MyType")
+ .addKdoc("This is a thing for stuff.")
+ .addProperty(
+ PropertySpec.builder("first", INT)
+ .initializer("first")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("second", INT)
+ .initializer("second")
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("third", INT)
+ .initializer("third")
+ .build(),
+ )
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addKdoc("Construct a thing!")
+ .addParameter(
+ ParameterSpec.builder("first", INT)
+ .addKdoc("the first thing")
+ .build(),
+ )
+ .addParameter(
+ ParameterSpec.builder("second", INT)
+ .addKdoc("the second thing")
+ .build(),
+ )
+ .addParameter(
+ ParameterSpec.builder("third", INT)
+ .addKdoc("the third thing")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(typeSpec.toString()).isEqualTo(
+ """
+ |/**
+ | * This is a thing for stuff.
+ | */
+ |public class MyType(
+ | /**
+ | * the first thing
+ | */
+ | public val first: kotlin.Int,
+ | /**
+ | * the second thing
+ | */
+ | public val second: kotlin.Int,
+ | /**
+ | * the third thing
+ | */
+ | public val third: kotlin.Int,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun primaryConstructorWithOneParameterKdocFormatting() {
+ val typeSpec = TypeSpec.classBuilder("MyType")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter(
+ ParameterSpec.builder("first", INT)
+ .addKdoc("the first thing")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(typeSpec.toString()).isEqualTo(
+ """
+ |public class MyType(
+ | /**
+ | * the first thing
+ | */
+ | first: kotlin.Int,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/594
+ @Test fun longComment() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("getAnswer")
+ .returns(Int::class)
+ .addComment(
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
+ "eiusmod tempor incididunt ut labore et dolore magna aliqua.",
+ )
+ .addStatement("return 42")
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |
+ |public class Taco {
+ | public fun getAnswer(): Int {
+ | // Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
+ | return 42
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun originatingElementsIncludesThoseOfNestedTypes() {
+ val outerElement = FakeElement()
+ val innerElement = FakeElement()
+ val outer = TypeSpec.classBuilder("Outer")
+ .addOriginatingElement(outerElement)
+ .addType(
+ TypeSpec.classBuilder("Inner")
+ .addOriginatingElement(innerElement)
+ .build(),
+ )
+ .build()
+ assertThat(outer.originatingElements).containsExactly(outerElement, innerElement)
+ }
+
+ // https://github.com/square/kotlinpoet/issues/698
+ @Test fun escapeEnumConstants() {
+ val enum = TypeSpec.enumBuilder("MyEnum")
+ .addEnumConstant("test test")
+ .addEnumConstant("0constants")
+ .build()
+ assertThat(enum.toString()).isEqualTo(
+ """
+ |public enum class MyEnum {
+ | `test test`,
+ | `0constants`,
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun initOrdering_first() {
+ val type = TypeSpec.classBuilder("MyClass")
+ .addInitializerBlock(CodeBlock.builder().build())
+ .addProperty("tacos", Int::class)
+ .build()
+
+ //language=kotlin
+ assertThat(toString(type)).isEqualTo(
+ """
+ package com.squareup.tacos
+
+ import kotlin.Int
+
+ public class MyClass {
+ init {
+ }
+
+ public val tacos: Int
+ }
+
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun initOrdering_middle() {
+ val type = TypeSpec.classBuilder("MyClass")
+ .addProperty("tacos1", Int::class)
+ .addInitializerBlock(CodeBlock.builder().build())
+ .addProperty("tacos2", Int::class)
+ .build()
+
+ //language=kotlin
+ assertThat(toString(type)).isEqualTo(
+ """
+ package com.squareup.tacos
+
+ import kotlin.Int
+
+ public class MyClass {
+ public val tacos1: Int
+
+ init {
+ }
+
+ public val tacos2: Int
+ }
+
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun initOrdering_last() {
+ val type = TypeSpec.classBuilder("MyClass")
+ .addProperty("tacos", Int::class)
+ .addInitializerBlock(CodeBlock.builder().build())
+ .build()
+
+ //language=kotlin
+ assertThat(toString(type)).isEqualTo(
+ """
+ package com.squareup.tacos
+
+ import kotlin.Int
+
+ public class MyClass {
+ public val tacos: Int
+
+ init {
+ }
+ }
+
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun initOrdering_constructorParamsExludedAfterIndex() {
+ val type = TypeSpec.classBuilder("MyClass")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("tacos1", Int::class)
+ .addParameter("tacos2", Int::class)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("tacos1", Int::class)
+ .initializer("tacos1")
+ .build(),
+ )
+ .addInitializerBlock(CodeBlock.builder().build())
+ .addProperty(
+ PropertySpec.builder("tacos2", Int::class)
+ .initializer("tacos2")
+ .build(),
+ )
+ .build()
+
+ //language=kotlin
+ assertThat(toString(type)).isEqualTo(
+ """
+ package com.squareup.tacos
+
+ import kotlin.Int
+
+ public class MyClass(
+ public val tacos1: Int,
+ tacos2: Int,
+ ) {
+ init {
+ }
+
+ public val tacos2: Int = tacos2
+ }
+
+ """.trimIndent(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/843
+ @Test fun kdocWithParametersWithoutClassKdoc() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter(
+ ParameterSpec.builder("mild", Boolean::class)
+ .addKdoc(CodeBlock.of("%L", "Whether the taco is mild (ew) or crunchy (ye).\n"))
+ .build(),
+ )
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("mild", Boolean::class)
+ .addKdoc("No one likes mild tacos.")
+ .initializer("mild")
+ .build(),
+ )
+ .build()
+ assertThat(toString(taco)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Boolean
+ |
+ |/**
+ | * @param mild Whether the taco is mild (ew) or crunchy (ye).
+ | */
+ |public class Taco(
+ | /**
+ | * No one likes mild tacos.
+ | */
+ | public val mild: Boolean,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/848
+ @Test fun escapeEnumConstantNames() {
+ val enum = TypeSpec
+ .enumBuilder("MyEnum")
+ .addEnumConstant("object")
+ .build()
+ assertThat(toString(enum)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |public enum class MyEnum {
+ | `object`,
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ // https://youtrack.jetbrains.com/issue/KT-52315
+ @Test fun escapeHeaderAndImplAsEnumConstantNames() {
+ val primaryConstructor = FunSpec.constructorBuilder()
+ .addParameter("int", Int::class)
+ .build()
+ val enum = TypeSpec
+ .enumBuilder("MyEnum")
+ .primaryConstructor(primaryConstructor)
+ .addEnumConstant(
+ "header",
+ TypeSpec.anonymousClassBuilder()
+ .addSuperclassConstructorParameter("%L", 1)
+ .build(),
+ )
+ .addEnumConstant(
+ "impl",
+ TypeSpec.anonymousClassBuilder()
+ .addSuperclassConstructorParameter("%L", 2)
+ .build(),
+ )
+ .build()
+ assertThat(toString(enum)).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |
+ |public enum class MyEnum(
+ | int: Int,
+ |) {
+ | `header`(1),
+ | `impl`(2),
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun escapeClassNames() {
+ val type = TypeSpec.classBuilder("fun").build()
+ assertThat(type.toString()).isEqualTo(
+ """
+ |public class `fun`
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun escapeInnerClassName() {
+ val tacoType = ClassName("com.squareup.tacos", "Taco", "object")
+ val funSpec = FunSpec.builder("printTaco")
+ .addParameter("taco", tacoType)
+ .addStatement("print(taco)")
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun printTaco(taco: com.squareup.tacos.Taco.`object`): kotlin.Unit {
+ | print(taco)
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun escapeAllowedCharacters() {
+ val typeSpec = TypeSpec.classBuilder("A\$B")
+ .build()
+ assertThat(typeSpec.toString()).isEqualTo("public class `A\$B`\n")
+ }
+
+ // https://github.com/square/kotlinpoet/issues/1011
+ @Test fun abstractInterfaceMembers() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.interfaceBuilder("Taco")
+ .addProperty("foo", String::class, ABSTRACT)
+ .addProperty(
+ PropertySpec.builder("fooWithDefault", String::class)
+ .initializer("%S", "defaultValue")
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("bar")
+ .addModifiers(ABSTRACT)
+ .returns(String::class)
+ .build(),
+ )
+ .addFunction(
+ FunSpec.builder("barWithDefault")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ // language=kotlin
+ assertThat(file.toString()).isEqualTo(
+ """
+ package com.squareup.tacos
+
+ import kotlin.String
+ import kotlin.Unit
+
+ public interface Taco {
+ public val foo: String
+
+ public val fooWithDefault: String = "defaultValue"
+
+ public fun bar(): String
+
+ public fun barWithDefault(): Unit {
+ }
+ }
+
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun emptyConstructorGenerated() {
+ val taco = TypeSpec.classBuilder("Taco")
+ .primaryConstructor(FunSpec.constructorBuilder().build())
+ .build()
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(taco)
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ package com.squareup.tacos
+
+ public class Taco()
+
+ """.trimIndent(),
+ )
+ }
+
+ // Regression test for https://github.com/square/kotlinpoet/issues/1176
+ @Test fun `templates in class delegation blocks should be imported too`() {
+ val taco = TypeSpec.classBuilder("TacoShim")
+ .addSuperinterface(
+ ClassName("test", "Taco"),
+ CodeBlock.of("%T", ClassName("test", "RealTaco")),
+ )
+ .build()
+ val file = FileSpec.builder("com.squareup.tacos", "Tacos")
+ .addType(taco)
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ package com.squareup.tacos
+
+ import test.RealTaco
+ import test.Taco
+
+ public class TacoShim : Taco by RealTaco
+
+ """.trimIndent(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/1183
+ @Test fun `forbidden enum constant names`() {
+ var exception = assertFailsWith<IllegalArgumentException> {
+ TypeSpec.enumBuilder("Topping")
+ .addEnumConstant("name")
+ }
+ assertThat(exception.message).isEqualTo(
+ "constant with name \"name\" conflicts with a supertype member with the same name",
+ )
+
+ @Suppress("RemoveExplicitTypeArguments")
+ exception = assertFailsWith<IllegalArgumentException> {
+ TypeSpec.enumBuilder("Topping")
+ .addEnumConstant("ordinal")
+ }
+ assertThat(exception.message).isEqualTo(
+ "constant with name \"ordinal\" conflicts with a supertype member with the same name",
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/1183
+ @Test fun `forbidden enum property names`() {
+ var exception = assertFailsWith<IllegalArgumentException> {
+ TypeSpec.enumBuilder("Topping")
+ .addProperty("name", String::class)
+ }
+ assertThat(exception.message).isEqualTo(
+ "name is a final supertype member and can't be redeclared or overridden",
+ )
+
+ @Suppress("RemoveExplicitTypeArguments")
+ exception = assertFailsWith<IllegalArgumentException> {
+ TypeSpec.enumBuilder("Topping")
+ .addProperty("ordinal", String::class)
+ }
+ assertThat(exception.message).isEqualTo(
+ "ordinal is a final supertype member and can't be redeclared or overridden",
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/1234
+ @Test fun `enum constants are resolved`() {
+ val file = FileSpec.builder("com.example", "test")
+ .addType(
+ TypeSpec.enumBuilder("Foo")
+ .addProperty(
+ PropertySpec.builder("rawValue", String::class)
+ .initializer("%S", "")
+ .build(),
+ )
+ .addEnumConstant("String")
+ .build(),
+ )
+ .build()
+
+ assertThat(file.toString()).isEqualTo(
+ """
+ package com.example
+
+ public enum class Foo {
+ String,
+ ;
+
+ public val rawValue: kotlin.String = ""
+ }
+
+ """.trimIndent(),
+ )
+ }
+
+ // https://github.com/square/kotlinpoet/issues/1035
+ @Test fun dataClassWithKeywordProperty() {
+ val parameter = ParameterSpec.builder("data", STRING).build()
+ val typeSpec = TypeSpec.classBuilder("Example")
+ .addModifiers(DATA)
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter(parameter)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder(parameter.name, STRING)
+ .initializer("%N", parameter)
+ .build(),
+ )
+ .build()
+ assertThat(typeSpec.toString()).isEqualTo(
+ """
+ public data class Example(
+ public val `data`: kotlin.String,
+ )
+
+ """.trimIndent(),
+ )
+ }
+
+ @Test fun contextReceiver() {
+ val typeSpec = TypeSpec.classBuilder("Example")
+ .contextReceivers(STRING)
+ .build()
+
+ assertThat(typeSpec.toString()).isEqualTo(
+ """
+ |context(kotlin.String)
+ |public class Example
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun contextReceiver_mustBeClass() {
+ val t = assertFailsWith<IllegalStateException> {
+ TypeSpec.interfaceBuilder("Example")
+ .contextReceivers(STRING)
+ }
+ assertThat(t).hasMessageThat().contains("contextReceivers can only be applied on simple classes")
+ }
+
+ companion object {
+ private const val donutsPackage = "com.squareup.donuts"
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeVariableNameTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeVariableNameTest.kt
new file mode 100644
index 00000000..bca1a7cb
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypeVariableNameTest.kt
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2017 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.TypeVariableName.Companion.NULLABLE_ANY_LIST
+import java.io.Serializable
+import kotlin.test.Test
+
+class TypeVariableNameTest {
+ @Test fun nullableAnyIsImplicitBound() {
+ val typeVariableName = TypeVariableName("T")
+ assertThat(typeVariableName.bounds).containsExactly(NULLABLE_ANY)
+ }
+
+ @Test fun oneTypeVariableNoBounds() {
+ val funSpec = FunSpec.builder("foo")
+ .addTypeVariable(TypeVariableName("T"))
+ .returns(TypeVariableName("T").copy(nullable = true))
+ .addStatement("return null")
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun <T> foo(): T? = null
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun twoTypeVariablesNoBounds() {
+ val funSpec = FunSpec.builder("foo")
+ .addTypeVariable(TypeVariableName("T"))
+ .addTypeVariable(TypeVariableName("U"))
+ .returns(TypeVariableName("T").copy(nullable = true))
+ .addStatement("return null")
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun <T, U> foo(): T? = null
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun oneTypeVariableOneBound() {
+ val funSpec = FunSpec.builder("foo")
+ .addTypeVariable(TypeVariableName("T", Serializable::class))
+ .returns(TypeVariableName("T").copy(nullable = true))
+ .addStatement("return null")
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun <T : java.io.Serializable> foo(): T? = null
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun twoTypeVariablesOneBoundEach() {
+ val funSpec = FunSpec.builder("foo")
+ .addTypeVariable(TypeVariableName("T", Serializable::class))
+ .addTypeVariable(TypeVariableName("U", Runnable::class))
+ .returns(TypeVariableName("T").copy(nullable = true))
+ .addStatement("return null")
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun <T : java.io.Serializable, U : java.lang.Runnable> foo(): T? = null
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun oneTypeVariableTwoBounds() {
+ val funSpec = FunSpec.builder("foo")
+ .addTypeVariable(TypeVariableName("T", Serializable::class, Runnable::class))
+ .returns(TypeVariableName("T").copy(nullable = true))
+ .addStatement("return null")
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public fun <T> foo(): T? where T : java.io.Serializable, T : java.lang.Runnable = null
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun twoTypeVariablesTwoBoundsEach() {
+ val funSpec = FunSpec.builder("foo")
+ .addTypeVariable(TypeVariableName("T", Serializable::class, Runnable::class))
+ .addTypeVariable(TypeVariableName("U", Comparator::class, Cloneable::class))
+ .returns(TypeVariableName("T").copy(nullable = true))
+ .addStatement("return null")
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ "public fun <T, U> foo(): " +
+ "T? where T : java.io.Serializable, T : java.lang.Runnable, " +
+ "U : java.util.Comparator, U : kotlin.Cloneable = null\n",
+ )
+ }
+
+ @Test fun threeTypeVariables() {
+ val funSpec = FunSpec.builder("foo")
+ .addTypeVariable(TypeVariableName("T", Serializable::class, Runnable::class))
+ .addTypeVariable(TypeVariableName("U", Cloneable::class))
+ .addTypeVariable(TypeVariableName("V"))
+ .returns(TypeVariableName("T").copy(nullable = true))
+ .addStatement("return null")
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ "public fun <T, U : kotlin.Cloneable, V> foo(): " +
+ "T? where T : java.io.Serializable, T : java.lang.Runnable = null\n",
+ )
+ }
+
+ @Test fun addingBoundsRemovesImplicitBound() {
+ val typeSpec = TypeSpec.classBuilder("Taco")
+ .addTypeVariable(TypeVariableName("T").copy(bounds = listOf(Number::class.asTypeName())))
+ .build()
+ assertThat(typeSpec.toString()).isEqualTo(
+ """
+ |public class Taco<T : kotlin.Number>
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun inVariance() {
+ val typeSpec = TypeSpec.classBuilder("Taco")
+ .addTypeVariable(TypeVariableName("E", Number::class, variance = KModifier.IN))
+ .build()
+ assertThat(typeSpec.toString()).isEqualTo(
+ """
+ |public class Taco<in E : kotlin.Number>
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun outVariance() {
+ val typeSpec = TypeSpec.classBuilder("Taco")
+ .addTypeVariable(TypeVariableName("E", Number::class, variance = KModifier.OUT))
+ .build()
+ assertThat(typeSpec.toString()).isEqualTo(
+ """
+ |public class Taco<out E : kotlin.Number>
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun invalidVariance() {
+ assertThrows<IllegalArgumentException> {
+ TypeVariableName("E", KModifier.FINAL)
+ }
+ }
+
+ @Test fun reified() {
+ val funSpec = FunSpec.builder("printMembers")
+ .addModifiers(KModifier.INLINE)
+ .addTypeVariable(TypeVariableName("T").copy(reified = true))
+ .addStatement("println(T::class.members)")
+ .build()
+ assertThat(funSpec.toString()).isEqualTo(
+ """
+ |public inline fun <reified T> printMembers(): kotlin.Unit {
+ | println(T::class.members)
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun anyBoundsIsLegal() {
+ val typeSpec = TypeSpec.classBuilder("Taco")
+ .addTypeVariable(TypeVariableName("E", ANY))
+ .build()
+ assertThat(typeSpec.toString()).isEqualTo(
+ """
+ |public class Taco<E : kotlin.Any>
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun filterOutNullableAnyBounds() {
+ val typeSpec = TypeSpec.classBuilder("Taco")
+ .addTypeVariable(TypeVariableName("E", NULLABLE_ANY))
+ .build()
+ assertThat(typeSpec.toString()).isEqualTo(
+ """
+ |public class Taco<E>
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun emptyBoundsShouldDefaultToAnyNullable() {
+ val typeVariable = TypeVariableName("E", bounds = *emptyArray<TypeName>())
+ val typeSpec = TypeSpec.classBuilder("Taco")
+ .addTypeVariable(typeVariable)
+ .build()
+ assertThat(typeVariable.bounds).isEqualTo(NULLABLE_ANY_LIST)
+ assertThat(typeSpec.toString()).isEqualTo(
+ """
+ |public class Taco<E>
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun noBoundsShouldDefaultToAnyNullable() {
+ val typeVariable = TypeVariableName("E")
+ val typeSpec = TypeSpec.classBuilder("Taco")
+ .addTypeVariable(typeVariable)
+ .build()
+ assertThat(typeVariable.bounds).isEqualTo(NULLABLE_ANY_LIST)
+ assertThat(typeSpec.toString()).isEqualTo(
+ """
+ |public class Taco<E>
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun genericClassNoBoundsShouldDefaultToAnyNullable() {
+ val typeVariable = TypeVariableName.get(GenericClass::class.java.typeParameters[0])
+ val typeSpec = TypeSpec.classBuilder("Taco")
+ .addTypeVariable(typeVariable)
+ .build()
+ assertThat(typeVariable.bounds).isEqualTo(NULLABLE_ANY_LIST)
+ assertThat(typeSpec.toString()).isEqualTo(
+ """
+ |public class Taco<T>
+ |
+ """.trimMargin(),
+ )
+ }
+
+ class GenericClass<T>
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypesEclipseTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypesEclipseTest.kt
new file mode 100644
index 00000000..a3ae52b5
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypesEclipseTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.base.Charsets.UTF_8
+import com.google.common.collect.ImmutableSet
+import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler
+import org.junit.Rule
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.JUnit4
+import org.junit.runners.model.Statement
+import java.util.Locale
+import java.util.concurrent.atomic.AtomicReference
+import javax.annotation.processing.AbstractProcessor
+import javax.annotation.processing.ProcessingEnvironment
+import javax.annotation.processing.Processor
+import javax.annotation.processing.RoundEnvironment
+import javax.lang.model.SourceVersion
+import javax.lang.model.element.TypeElement
+import javax.lang.model.util.Elements
+import javax.lang.model.util.Types
+import javax.tools.DiagnosticCollector
+import javax.tools.JavaFileObject
+import kotlin.test.Ignore
+
+@Ignore("Not clear this test is useful to retain in the Kotlin world")
+class TypesEclipseTest : AbstractTypesTest() {
+ /**
+ * A [JUnit4] [Rule] that executes tests such that a instances of [Elements] and [Types] are
+ * available during execution.
+ *
+ * To use this rule in a test, just add the following field:
+ *
+ * ```java
+ * public CompilationRule compilationRule = new CompilationRule();
+ * ```
+ *
+ * @author Gregory Kick
+ */
+ class CompilationRule : TestRule {
+ private var elements: Elements? = null
+ private var types: Types? = null
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return object : Statement() {
+ override fun evaluate() {
+ val thrown = AtomicReference<Throwable>()
+ val successful = compile(
+ listOf(object : AbstractProcessor() {
+ override fun getSupportedSourceVersion() = SourceVersion.latest()
+
+ override fun getSupportedAnnotationTypes() = setOf("*")
+
+ @Synchronized override fun init(processingEnv: ProcessingEnvironment) {
+ super.init(processingEnv)
+ elements = processingEnv.elementUtils
+ types = processingEnv.typeUtils
+ }
+
+ override fun process(
+ annotations: Set<TypeElement>,
+ roundEnv: RoundEnvironment
+ ): Boolean {
+ // just run the test on the last round after compilation is over
+ if (roundEnv.processingOver()) {
+ try {
+ base.evaluate()
+ } catch (e: Throwable) {
+ thrown.set(e)
+ }
+ }
+ return false
+ }
+ })
+ )
+ check(successful)
+ val t = thrown.get()
+ if (t != null) {
+ throw t
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the [Elements] instance associated with the current execution of the rule.
+ *
+ * @throws IllegalStateException if this method is invoked outside the execution of the rule.
+ */
+ fun getElements() = elements!!
+
+ /**
+ * Returns the [Types] instance associated with the current execution of the rule.
+ *
+ * @throws IllegalStateException if this method is invoked outside the execution of the rule.
+ */
+ fun getTypes() = types!!
+
+ private fun compile(processors: Iterable<Processor>): Boolean {
+ val compiler = EclipseCompiler()
+ val diagnosticCollector = DiagnosticCollector<JavaFileObject>()
+ val fileManager = compiler.getStandardFileManager(
+ diagnosticCollector, Locale.getDefault(), UTF_8
+ )
+ val task = compiler.getTask(
+ null,
+ fileManager,
+ diagnosticCollector,
+ ImmutableSet.of(),
+ ImmutableSet.of(TypesEclipseTest::class.java.canonicalName),
+ ImmutableSet.of()
+ )
+ task.setProcessors(processors)
+ return task.call()!!
+ }
+ }
+
+ @JvmField @Rule val compilation = CompilationRule()
+
+ override val elements: Elements
+ get() = compilation.getElements()
+
+ override val types: Types
+ get() = compilation.getTypes()
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypesTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypesTest.kt
new file mode 100644
index 00000000..43bdbb7c
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/TypesTest.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.testing.compile.CompilationRule
+import org.junit.Rule
+import javax.lang.model.util.Elements
+import javax.lang.model.util.Types
+import kotlin.test.Ignore
+
+@Ignore("Not clear this test is useful to retain in the Kotlin world")
+class TypesTest : AbstractTypesTest() {
+ @JvmField @Rule val compilation = CompilationRule()
+
+ override val elements: Elements
+ get() = compilation.elements
+
+ override val types: Types
+ get() = compilation.types
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/UtilTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/UtilTest.kt
new file mode 100644
index 00000000..de3650b6
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/UtilTest.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class UtilTest {
+ @Test fun characterLiteral() {
+ assertEquals("a", characterLiteralWithoutSingleQuotes('a'))
+ assertEquals("b", characterLiteralWithoutSingleQuotes('b'))
+ assertEquals("c", characterLiteralWithoutSingleQuotes('c'))
+ assertEquals("%", characterLiteralWithoutSingleQuotes('%'))
+ // common escapes
+ assertEquals("\\b", characterLiteralWithoutSingleQuotes('\b'))
+ assertEquals("\\t", characterLiteralWithoutSingleQuotes('\t'))
+ assertEquals("\\n", characterLiteralWithoutSingleQuotes('\n'))
+ assertEquals("\\u000c", characterLiteralWithoutSingleQuotes('\u000c'))
+ assertEquals("\\r", characterLiteralWithoutSingleQuotes('\r'))
+ assertEquals("\"", characterLiteralWithoutSingleQuotes('"'))
+ assertEquals("\\'", characterLiteralWithoutSingleQuotes('\''))
+ assertEquals("\\\\", characterLiteralWithoutSingleQuotes('\\'))
+ // octal escapes
+ assertEquals("\\u0000", characterLiteralWithoutSingleQuotes('\u0000'))
+ assertEquals("\\u0007", characterLiteralWithoutSingleQuotes('\u0007'))
+ assertEquals("?", characterLiteralWithoutSingleQuotes('\u003f'))
+ assertEquals("\\u007f", characterLiteralWithoutSingleQuotes('\u007f'))
+ assertEquals("¿", characterLiteralWithoutSingleQuotes('\u00bf'))
+ assertEquals("ÿ", characterLiteralWithoutSingleQuotes('\u00ff'))
+ // unicode escapes
+ assertEquals("\\u0000", characterLiteralWithoutSingleQuotes('\u0000'))
+ assertEquals("\\u0001", characterLiteralWithoutSingleQuotes('\u0001'))
+ assertEquals("\\u0002", characterLiteralWithoutSingleQuotes('\u0002'))
+ assertEquals("€", characterLiteralWithoutSingleQuotes('\u20AC'))
+ assertEquals("☃", characterLiteralWithoutSingleQuotes('\u2603'))
+ assertEquals("♠", characterLiteralWithoutSingleQuotes('\u2660'))
+ assertEquals("♣", characterLiteralWithoutSingleQuotes('\u2663'))
+ assertEquals("♥", characterLiteralWithoutSingleQuotes('\u2665'))
+ assertEquals("♦", characterLiteralWithoutSingleQuotes('\u2666'))
+ assertEquals("✵", characterLiteralWithoutSingleQuotes('\u2735'))
+ assertEquals("✺", characterLiteralWithoutSingleQuotes('\u273A'))
+ assertEquals("/", characterLiteralWithoutSingleQuotes('\uFF0F'))
+ }
+
+ @Test fun stringLiteral() {
+ stringLiteral("abc")
+ stringLiteral("♦♥♠♣")
+ stringLiteral("€\\t@\\t\${\'\$\'}", "€\t@\t$")
+ assertThat(stringLiteralWithQuotes("abc();\ndef();"))
+ .isEqualTo("\"\"\"\n|abc();\n|def();\n\"\"\".trimMargin()")
+ stringLiteral("This is \\\"quoted\\\"!", "This is \"quoted\"!")
+ stringLiteral("e^{i\\\\pi}+1=0", "e^{i\\pi}+1=0")
+ assertThat(stringLiteralWithQuotes("abc();\ndef();", isConstantContext = true))
+ .isEqualTo("\"abc();\\ndef();\"")
+ }
+
+ @Test fun legalIdentifiers() {
+ assertThat("foo".isIdentifier).isTrue()
+ assertThat("bAr1".isIdentifier).isTrue()
+ assertThat("1".isIdentifier).isFalse()
+ assertThat("♦♥♠♣".isIdentifier).isFalse()
+ assertThat("`♦♥♠♣`".isIdentifier).isTrue()
+ assertThat("` ♣ !`".isIdentifier).isTrue()
+ assertThat("€".isIdentifier).isFalse()
+ assertThat("`€`".isIdentifier).isTrue()
+ assertThat("`1`".isIdentifier).isTrue()
+ assertThat("```".isIdentifier).isFalse()
+ assertThat("``".isIdentifier).isFalse()
+ assertThat("\n".isIdentifier).isFalse()
+ assertThat("`\n`".isIdentifier).isFalse()
+ assertThat("\r".isIdentifier).isFalse()
+ assertThat("`\r`".isIdentifier).isFalse()
+ assertThat("when".isIdentifier).isTrue()
+ assertThat("fun".isIdentifier).isTrue()
+ assertThat("".isIdentifier).isFalse()
+ }
+
+ @Test fun escapeNonJavaIdentifiers() {
+ assertThat("8startWithNumber".escapeIfNecessary()).isEqualTo("`8startWithNumber`")
+ assertThat("with-hyphen".escapeIfNecessary()).isEqualTo("`with-hyphen`")
+ assertThat("with space".escapeIfNecessary()).isEqualTo("`with·space`")
+ assertThat("with_unicode_punctuation\u2026".escapeIfNecessary()).isEqualTo("`with_unicode_punctuation\u2026`")
+ }
+
+ @Test fun escapeSpaceInName() {
+ val generated = FileSpec.builder("a", "b")
+ .addFunction(
+ FunSpec.builder("foo").apply {
+ addParameter("aaa bbb", typeNameOf<(Int) -> String>())
+ val arg = mutableListOf<String>()
+ addStatement(
+ StringBuilder().apply {
+ repeat(10) {
+ append("%N($it) + ")
+ arg += "aaa bbb"
+ }
+ append("%N(100)")
+ arg += "aaa bbb"
+ }.toString(),
+ *arg.toTypedArray(),
+ )
+ }.build(),
+ )
+ .build()
+ .toString()
+
+ val expectedOutput = """
+ package a
+
+ import kotlin.Function1
+ import kotlin.Int
+ import kotlin.String
+ import kotlin.Unit
+
+ public fun foo(`aaa bbb`: Function1<Int, String>): Unit {
+ `aaa bbb`(0) + `aaa bbb`(1) + `aaa bbb`(2) + `aaa bbb`(3) + `aaa bbb`(4) + `aaa bbb`(5) +
+ `aaa bbb`(6) + `aaa bbb`(7) + `aaa bbb`(8) + `aaa bbb`(9) + `aaa bbb`(100)
+ }
+
+ """.trimIndent()
+
+ assertThat(generated).isEqualTo(expectedOutput)
+ }
+
+ @Test fun escapeMultipleTimes() {
+ assertThat("A-\$B".escapeIfNecessary()).isEqualTo("`A-\$B`")
+ }
+
+ @Test fun escapeEscaped() {
+ assertThat("`A`".escapeIfNecessary()).isEqualTo("`A`")
+ }
+
+ private fun stringLiteral(string: String) = stringLiteral(string, string)
+
+ private fun stringLiteral(expected: String, value: String) =
+ assertEquals("\"$expected\"", stringLiteralWithQuotes(value))
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ValueTypeSpecTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ValueTypeSpecTest.kt
new file mode 100644
index 00000000..e6efa3fa
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/ValueTypeSpecTest.kt
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2021 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.KModifier.INLINE
+import com.squareup.kotlinpoet.KModifier.PRIVATE
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class ValueTypeSpecTest(private val useValue: Boolean) {
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "value={0}")
+ fun data(): Collection<Array<Any>> {
+ return listOf(
+ arrayOf(true),
+ arrayOf(false),
+ )
+ }
+ }
+
+ private val modifier = if (useValue) KModifier.VALUE else INLINE
+ private val modifierString = modifier.keyword
+
+ private fun classBuilder() = if (useValue) {
+ TypeSpec.valueClassBuilder("Guacamole")
+ } else {
+ TypeSpec.classBuilder("Guacamole")
+ .addModifiers(modifier)
+ }
+
+ @Test fun validInlineClass() {
+ val guacamole = classBuilder()
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("avacado", String::class)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("avacado", String::class)
+ .initializer("avacado")
+ .build(),
+ )
+ .build()
+
+ assertThat(guacamole.toString()).isEqualTo(
+ """
+ |public $modifierString class Guacamole(
+ | public val avacado: kotlin.String,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun inlineClassWithInitBlock() {
+ val guacamole = classBuilder()
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("avacado", String::class)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("avacado", String::class)
+ .initializer("avacado")
+ .build(),
+ )
+ .addInitializerBlock(CodeBlock.EMPTY)
+ .build()
+
+ assertThat(guacamole.toString()).isEqualTo(
+ """
+ |public $modifierString class Guacamole(
+ | public val avacado: kotlin.String,
+ |) {
+ | init {
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ class InlineSuperClass
+
+ @Test fun inlineClassWithSuperClass() {
+ assertThrows<IllegalStateException> {
+ classBuilder()
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("avocado", String::class)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("avocado", String::class)
+ .initializer("avocado")
+ .build(),
+ )
+ .superclass(InlineSuperClass::class)
+ .build()
+ }.hasMessageThat().isEqualTo("value/inline classes cannot have super classes")
+ }
+
+ interface InlineSuperInterface
+
+ @Test fun inlineClassInheritsFromInterface() {
+ val guacamole = classBuilder()
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("avocado", String::class)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("avocado", String::class)
+ .initializer("avocado")
+ .build(),
+ )
+ .addSuperinterface(InlineSuperInterface::class)
+ .build()
+
+ assertThat(guacamole.toString()).isEqualTo(
+ """
+ |public $modifierString class Guacamole(
+ | public val avocado: kotlin.String,
+ |) : com.squareup.kotlinpoet.ValueTypeSpecTest.InlineSuperInterface
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun inlineClassWithoutBackingProperty() {
+ assertThrows<IllegalArgumentException> {
+ classBuilder()
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("avocado", String::class)
+ .build(),
+ )
+ .addProperty("garlic", String::class)
+ .build()
+ }.hasMessageThat().isEqualTo("value/inline classes must have a single read-only (val) property parameter.")
+ }
+
+ @Test fun inlineClassWithoutProperties() {
+ assertThrows<IllegalStateException> {
+ classBuilder()
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("avocado", String::class)
+ .build(),
+ )
+ .build()
+ }.hasMessageThat().isEqualTo("value/inline classes must have at least 1 property")
+ }
+
+ @Test fun inlineClassWithMutableProperties() {
+ assertThrows<IllegalStateException> {
+ classBuilder()
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("avocado", String::class)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("avocado", String::class)
+ .initializer("avocado")
+ .mutable()
+ .build(),
+ )
+ .build()
+ }.hasMessageThat().isEqualTo("value/inline classes must have a single read-only (val) property parameter.")
+ }
+
+ @Test
+ fun inlineClassWithPrivateConstructor() {
+ val guacamole = classBuilder()
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("avocado", String::class)
+ .addModifiers(PRIVATE)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("avocado", String::class)
+ .initializer("avocado")
+ .build(),
+ )
+ .build()
+
+ assertThat(guacamole.toString()).isEqualTo(
+ """
+ |public $modifierString class Guacamole private constructor(
+ | public val avocado: kotlin.String,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun inlineEnumClass() {
+ val guacamole = TypeSpec.enumBuilder("Foo")
+ .addModifiers(modifier)
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("x", Int::class)
+ .build(),
+ )
+ .addEnumConstant(
+ "A",
+ TypeSpec.anonymousClassBuilder()
+ .addSuperclassConstructorParameter("%L", 1)
+ .build(),
+ )
+ .addEnumConstant(
+ "B",
+ TypeSpec.anonymousClassBuilder()
+ .addSuperclassConstructorParameter("%L", 2)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("x", Int::class)
+ .initializer("x")
+ .build(),
+ )
+ .build()
+ assertThat(guacamole.toString()).isEqualTo(
+ """
+ |public enum $modifierString class Foo(
+ | public val x: kotlin.Int,
+ |) {
+ | A(1),
+ | B(2),
+ | ;
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+}
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/jvm/JvmAnnotationsTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/jvm/JvmAnnotationsTest.kt
new file mode 100644
index 00000000..f0b68a10
--- /dev/null
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/jvm/JvmAnnotationsTest.kt
@@ -0,0 +1,1215 @@
+/*
+ * Copyright (C) 2018 Square, Inc.
+ *
+ * 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 com.squareup.kotlinpoet.jvm
+
+import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.KModifier.DATA
+import com.squareup.kotlinpoet.ParameterSpec
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.STRING
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.asTypeName
+import com.squareup.kotlinpoet.assertThrows
+import java.io.IOException
+import kotlin.test.Test
+
+class JvmAnnotationsTest {
+
+ @Test fun jvmField() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .addProperty(
+ PropertySpec.builder("foo", String::class)
+ .jvmField()
+ .initializer("%S", "foo")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.JvmField
+ |
+ |public class Taco {
+ | @JvmField
+ | public val foo: String = "foo"
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmFieldConstructorParameter() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("foo", String::class)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("foo", String::class)
+ .jvmField()
+ .initializer("foo")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.JvmField
+ |
+ |public class Taco(
+ | @JvmField
+ | public val foo: String,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmStaticProperty() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .addType(
+ TypeSpec.companionObjectBuilder()
+ .addProperty(
+ PropertySpec.builder("foo", String::class)
+ .jvmStatic()
+ .initializer("%S", "foo")
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.JvmStatic
+ |
+ |public class Taco {
+ | public companion object {
+ | @JvmStatic
+ | public val foo: String = "foo"
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmStaticFunction() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .addType(
+ TypeSpec.companionObjectBuilder()
+ .addFunction(
+ FunSpec.builder("foo")
+ .jvmStatic()
+ .addStatement("return %S", "foo")
+ .returns(String::class)
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.JvmStatic
+ |
+ |public class Taco {
+ | public companion object {
+ | @JvmStatic
+ | public fun foo(): String = "foo"
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmStaticGetter() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .addType(
+ TypeSpec.companionObjectBuilder()
+ .addProperty(
+ PropertySpec.builder("foo", String::class)
+ .getter(
+ FunSpec.getterBuilder()
+ .jvmStatic()
+ .addStatement("return %S", "foo")
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.JvmStatic
+ |
+ |public class Taco {
+ | public companion object {
+ | public val foo: String
+ | @JvmStatic
+ | get() = "foo"
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmStaticSetter() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .addType(
+ TypeSpec.companionObjectBuilder()
+ .addProperty(
+ PropertySpec.builder("foo", String::class.asTypeName())
+ .mutable()
+ .setter(
+ FunSpec.setterBuilder()
+ .jvmStatic()
+ .addParameter("value", String::class)
+ .build(),
+ )
+ .initializer("%S", "foo")
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.JvmStatic
+ |
+ |public class Taco {
+ | public companion object {
+ | public var foo: String = "foo"
+ | @JvmStatic
+ | set(`value`) {
+ | }
+ | }
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmStaticForbiddenOnConstructor() {
+ assertThrows<IllegalStateException> {
+ FunSpec.constructorBuilder()
+ .jvmStatic()
+ }.hasMessageThat().isEqualTo("Can't apply @JvmStatic to a constructor!")
+ }
+
+ @Test fun throwsFunction() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addFunction(
+ FunSpec.builder("foo")
+ .throws(IOException::class, IllegalArgumentException::class)
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.io.IOException
+ |import java.lang.IllegalArgumentException
+ |import kotlin.Unit
+ |import kotlin.jvm.Throws
+ |
+ |@Throws(
+ | IOException::class,
+ | IllegalArgumentException::class,
+ |)
+ |public fun foo(): Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun throwsFunctionCustomException() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addFunction(
+ FunSpec.builder("foo")
+ .throws(ClassName("com.squareup.tacos", "IllegalTacoException"))
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |import kotlin.jvm.Throws
+ |
+ |@Throws(IllegalTacoException::class)
+ |public fun foo(): Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun throwsPrimaryConstructor() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .throws(IOException::class)
+ .addParameter("foo", String::class)
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.io.IOException
+ |import kotlin.String
+ |import kotlin.jvm.Throws
+ |
+ |public class Taco @Throws(IOException::class) constructor(
+ | foo: String,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun throwsGetter() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addProperty(
+ PropertySpec.builder("foo", String::class)
+ .getter(
+ FunSpec.getterBuilder()
+ .throws(IOException::class)
+ .addStatement("return %S", "foo")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.io.IOException
+ |import kotlin.String
+ |import kotlin.jvm.Throws
+ |
+ |public val foo: String
+ | @Throws(IOException::class)
+ | get() = "foo"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun throwsSetter() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addProperty(
+ PropertySpec.builder("foo", String::class)
+ .mutable()
+ .setter(
+ FunSpec.setterBuilder()
+ .throws(IOException::class)
+ .addParameter("value", String::class)
+ .addStatement("print(%S)", "foo")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import java.io.IOException
+ |import kotlin.String
+ |import kotlin.jvm.Throws
+ |
+ |public var foo: String
+ | @Throws(IOException::class)
+ | set(`value`) {
+ | print("foo")
+ | }
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmOverloadsFunction() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addFunction(
+ FunSpec.builder("foo")
+ .jvmOverloads()
+ .addParameter("bar", Int::class)
+ .addParameter(
+ ParameterSpec.builder("baz", String::class)
+ .defaultValue("%S", "baz")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |import kotlin.String
+ |import kotlin.Unit
+ |import kotlin.jvm.JvmOverloads
+ |
+ |@JvmOverloads
+ |public fun foo(bar: Int, baz: String = "baz"): Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmOverloadsPrimaryConstructor() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .jvmOverloads()
+ .addParameter("bar", Int::class)
+ .addParameter(
+ ParameterSpec.builder("baz", String::class)
+ .defaultValue("%S", "baz")
+ .build(),
+ )
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |import kotlin.String
+ |import kotlin.jvm.JvmOverloads
+ |
+ |public class Taco @JvmOverloads constructor(
+ | bar: Int,
+ | baz: String = "baz",
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmOverloadsOnGetterForbidden() {
+ assertThrows<IllegalStateException> {
+ FunSpec.getterBuilder()
+ .jvmOverloads()
+ }.hasMessageThat().isEqualTo("Can't apply @JvmOverloads to a getter!")
+ }
+
+ @Test fun jvmOverloadsOnSetterForbidden() {
+ assertThrows<IllegalStateException> {
+ FunSpec.setterBuilder()
+ .jvmOverloads()
+ }.hasMessageThat().isEqualTo("Can't apply @JvmOverloads to a setter!")
+ }
+
+ @Test fun jvmNameFile() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .jvmName("TacoUtils")
+ .addProperty(
+ PropertySpec.builder("foo", String::class)
+ .initializer("%S", "foo")
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |@file:JvmName("TacoUtils")
+ |
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.JvmName
+ |
+ |public val foo: String = "foo"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmNameFunction() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addFunction(
+ FunSpec.builder("foo")
+ .jvmName("getFoo")
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |import kotlin.jvm.JvmName
+ |
+ |@JvmName("getFoo")
+ |public fun foo(): Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmNameGetter() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addProperty(
+ PropertySpec.builder("foo", String::class)
+ .getter(
+ FunSpec.getterBuilder()
+ .jvmName("foo")
+ .addStatement("return %S", "foo")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.JvmName
+ |
+ |public val foo: String
+ | @JvmName("foo")
+ | get() = "foo"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmNameSetter() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addProperty(
+ PropertySpec.builder("foo", String::class.asTypeName())
+ .mutable()
+ .initializer("%S", "foo")
+ .setter(
+ FunSpec.setterBuilder()
+ .jvmName("foo")
+ .addParameter("value", String::class)
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.JvmName
+ |
+ |public var foo: String = "foo"
+ | @JvmName("foo")
+ | set(`value`) {
+ | }
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmNameForbiddenOnConstructor() {
+ assertThrows<IllegalStateException> {
+ FunSpec.constructorBuilder()
+ .jvmName("notAConstructor")
+ }.hasMessageThat().isEqualTo("Can't apply @JvmName to a constructor!")
+ }
+
+ @Test fun jvmMultifileClass() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .jvmMultifileClass()
+ .addProperty(
+ PropertySpec.builder("foo", String::class)
+ .initializer("%S", "foo")
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |@file:JvmMultifileClass
+ |
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.JvmMultifileClass
+ |
+ |public val foo: String = "foo"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmSuppressWildcardsClass() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .jvmSuppressWildcards()
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.jvm.JvmSuppressWildcards
+ |
+ |@JvmSuppressWildcards
+ |public class Taco
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmSuppressWildcardsFunction() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addFunction(
+ FunSpec.builder("foo")
+ .jvmSuppressWildcards(suppress = false)
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |import kotlin.jvm.JvmSuppressWildcards
+ |
+ |@JvmSuppressWildcards(suppress = false)
+ |public fun foo(): Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmSuppressWildcardsOnConstructorForbidden() {
+ assertThrows<IllegalStateException> {
+ FunSpec.constructorBuilder()
+ .jvmSuppressWildcards()
+ }.hasMessageThat().isEqualTo("Can't apply @JvmSuppressWildcards to a constructor!")
+ }
+
+ @Test fun jvmSuppressWildcardsOnGetterForbidden() {
+ assertThrows<IllegalStateException> {
+ FunSpec.getterBuilder()
+ .jvmSuppressWildcards()
+ }.hasMessageThat().isEqualTo("Can't apply @JvmSuppressWildcards to a getter!")
+ }
+
+ @Test fun jvmSuppressWildcardsOnSetterForbidden() {
+ assertThrows<IllegalStateException> {
+ FunSpec.setterBuilder()
+ .jvmSuppressWildcards()
+ }.hasMessageThat().isEqualTo("Can't apply @JvmSuppressWildcards to a setter!")
+ }
+
+ @Test fun jvmSuppressWildcardsProperty() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addProperty(
+ PropertySpec.builder("foo", String::class)
+ .jvmSuppressWildcards(suppress = false)
+ .initializer("%S", "foo")
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.JvmSuppressWildcards
+ |
+ |@JvmSuppressWildcards(suppress = false)
+ |public val foo: String = "foo"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmSuppressWildcardsType() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addFunction(
+ FunSpec.builder("foo")
+ .addParameter(
+ "a",
+ List::class.asClassName()
+ .parameterizedBy(Int::class.asTypeName().jvmSuppressWildcards()),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |import kotlin.Unit
+ |import kotlin.collections.List
+ |import kotlin.jvm.JvmSuppressWildcards
+ |
+ |public fun foo(a: List<@JvmSuppressWildcards Int>): Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmWildcardType() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addFunction(
+ FunSpec.builder("foo")
+ .addParameter(
+ "a",
+ List::class.asClassName()
+ .parameterizedBy(Int::class.asTypeName().jvmWildcard()),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Int
+ |import kotlin.Unit
+ |import kotlin.collections.List
+ |import kotlin.jvm.JvmWildcard
+ |
+ |public fun foo(a: List<@JvmWildcard Int>): Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun synchronizedFunction() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addFunction(
+ FunSpec.builder("foo")
+ .synchronized()
+ .addStatement("return %S", "foo")
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.jvm.Synchronized
+ |
+ |@Synchronized
+ |public fun foo() = "foo"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun synchronizedGetter() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addProperty(
+ PropertySpec.builder("foo", String::class)
+ .getter(
+ FunSpec.getterBuilder()
+ .synchronized()
+ .addStatement("return %S", "foo")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.Synchronized
+ |
+ |public val foo: String
+ | @Synchronized
+ | get() = "foo"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun synchronizedSetter() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addProperty(
+ PropertySpec.builder("foo", String::class.asTypeName())
+ .mutable()
+ .initializer("%S", "foo")
+ .setter(
+ FunSpec.setterBuilder()
+ .synchronized()
+ .addParameter("value", String::class)
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.Synchronized
+ |
+ |public var foo: String = "foo"
+ | @Synchronized
+ | set(`value`) {
+ | }
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun synchronizedOnConstructorForbidden() {
+ assertThrows<IllegalStateException> {
+ FunSpec.constructorBuilder()
+ .synchronized()
+ }.hasMessageThat().isEqualTo("Can't apply @Synchronized to a constructor!")
+ }
+
+ @Test fun transient() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .addProperty(
+ PropertySpec.builder("foo", String::class)
+ .transient()
+ .initializer("%S", "foo")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.Transient
+ |
+ |public class Taco {
+ | @Transient
+ | public val foo: String = "foo"
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun transientConstructorParameter() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("foo", String::class)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("foo", String::class)
+ .transient()
+ .initializer("foo")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.Transient
+ |
+ |public class Taco(
+ | @Transient
+ | public val foo: String,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun volatile() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .addProperty(
+ PropertySpec.builder("foo", String::class)
+ .volatile()
+ .initializer("%S", "foo")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.Volatile
+ |
+ |public class Taco {
+ | @Volatile
+ | public val foo: String = "foo"
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun volatileConstructorParameter() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("foo", String::class)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("foo", String::class)
+ .volatile()
+ .initializer("foo")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.Volatile
+ |
+ |public class Taco(
+ | @Volatile
+ | public val foo: String,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun strictfpFunction() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addFunction(
+ FunSpec.builder("foo")
+ .strictfp()
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.Unit
+ |import kotlin.jvm.Strictfp
+ |
+ |@Strictfp
+ |public fun foo(): Unit {
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun strictfpPrimaryConstructor() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .strictfp()
+ .addParameter("foo", String::class)
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.Strictfp
+ |
+ |public class Taco @Strictfp constructor(
+ | foo: String,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun strictfpGetter() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addProperty(
+ PropertySpec.builder("foo", String::class)
+ .getter(
+ FunSpec.getterBuilder()
+ .strictfp()
+ .addStatement("return %S", "foo")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.Strictfp
+ |
+ |public val foo: String
+ | @Strictfp
+ | get() = "foo"
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun strictfpSetter() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addProperty(
+ PropertySpec.builder("foo", String::class)
+ .mutable()
+ .setter(
+ FunSpec.setterBuilder()
+ .strictfp()
+ .addParameter("value", String::class)
+ .addStatement("print(%S)", "foo")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.Strictfp
+ |
+ |public var foo: String
+ | @Strictfp
+ | set(`value`) {
+ | print("foo")
+ | }
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmDefaultProperty() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.interfaceBuilder("Taco")
+ .addProperty(
+ PropertySpec.builder("foo", String::class)
+ .jvmDefault()
+ .initializer("%S", "foo")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.JvmDefault
+ |
+ |public interface Taco {
+ | @JvmDefault
+ | public val foo: String = "foo"
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmDefaultFunction() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.interfaceBuilder("Taco")
+ .addFunction(
+ FunSpec.builder("foo")
+ .jvmDefault()
+ .returns(String::class)
+ .addStatement("return %S", "foo")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.JvmDefault
+ |
+ |public interface Taco {
+ | @JvmDefault
+ | public fun foo(): String = "foo"
+ |}
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmInlineClass() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.valueClassBuilder("Taco")
+ .jvmInline()
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("value", STRING)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("value", STRING)
+ .initializer("value")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.JvmInline
+ |
+ |@JvmInline
+ |public value class Taco(
+ | public val `value`: String,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+
+ @Test fun jvmRecordClass() {
+ val file = FileSpec.builder("com.squareup.tacos", "Taco")
+ .addType(
+ TypeSpec.classBuilder("Taco")
+ .jvmRecord()
+ .addModifiers(DATA)
+ .primaryConstructor(
+ FunSpec.constructorBuilder()
+ .addParameter("value", STRING)
+ .build(),
+ )
+ .addProperty(
+ PropertySpec.builder("value", STRING)
+ .initializer("value")
+ .build(),
+ )
+ .build(),
+ )
+ .build()
+ assertThat(file.toString()).isEqualTo(
+ """
+ |package com.squareup.tacos
+ |
+ |import kotlin.String
+ |import kotlin.jvm.JvmRecord
+ |
+ |@JvmRecord
+ |public data class Taco(
+ | public val `value`: String,
+ |)
+ |
+ """.trimMargin(),
+ )
+ }
+}
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 00000000..74caf790
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,69 @@
+# pip install mkdocs mkdocs-material
+# mkdocs serve
+# mkdocs gh-deploy
+
+site_name: KotlinPoet
+repo_name: KotlinPoet
+repo_url: https://github.com/square/kotlinpoet
+site_description: "A Kotlin API for generating .kt source files"
+site_author: Square, Inc.
+remote_branch: gh-pages
+
+copyright: 'Copyright &copy; 2015 Square, Inc.'
+
+theme:
+ name: 'material'
+ logo: 'images/icon-square.png'
+ favicon: 'images/icon-square.png'
+ palette:
+ - media: '(prefers-color-scheme: light)'
+ scheme: default
+ primary: 'cyan'
+ accent: 'deep-purple'
+ toggle:
+ icon: material/weather-night
+ name: Switch to dark mode
+ - media: '(prefers-color-scheme: dark)'
+ scheme: slate
+ primary: 'black'
+ accent: 'blue-grey'
+ toggle:
+ icon: material/weather-sunny
+ name: Switch to light mode
+
+extra_css:
+ - 'css/app.css'
+
+markdown_extensions:
+ - smarty
+ - codehilite:
+ guess_lang: false
+ - footnotes
+ - meta
+ - toc:
+ permalink: true
+ - pymdownx.betterem:
+ smart_enable: all
+ - pymdownx.caret
+ - pymdownx.inlinehilite
+ - pymdownx.magiclink
+ - pymdownx.smartsymbols
+ - pymdownx.superfences
+ - pymdownx.emoji
+ - tables
+ - admonition
+
+nav:
+ - 'Overview':
+ - 'KotlinPoet': index.md
+ - 'Interop - JavaPoet': interop-javapoet.md
+ - 'Interop - kotlinx-metadata': interop-kotlinx-metadata.md
+ - 'Interop - KSP': interop-ksp.md
+ - 'API':
+ - 'kotlinpoet': 1.x/kotlinpoet/index.html
+ - 'interop-javapoet': 1.x/interop-javapoet/index.html
+ - 'interop-kotlinx-metadata': 1.x/interop-kotlinx-metadata/index.html
+ - 'interop-ksp': 1.x/interop-ksp/index.html
+ - 'Stack Overflow ⏏': https://stackoverflow.com/questions/tagged/kotlinpoet?sort=active
+ - 'Change Log': changelog.md
+ - 'Contributing': contributing.md
diff --git a/renovate.json b/renovate.json
new file mode 100644
index 00000000..7ebea239
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1,12 @@
+{
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+ "extends": [
+ "config:base"
+ ],
+ "packageRules": [
+ {
+ "matchManagers": ["pip_requirements"],
+ "automerge": true
+ }
+ ]
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 00000000..3e23cc20
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * 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.
+ */
+pluginManagement {
+ repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+include(
+ ":kotlinpoet",
+ ":interop:javapoet",
+ ":interop:kotlinx-metadata",
+ ":interop:ksp",
+ ":interop:ksp:test-processor",
+)